C#
PY

C# to Python

10 lessons

Progress0%
1Introduction2Type Systems3Functions4Collections5Object-Oriented Programming6LINQ to Comprehensions7Async Programming8Ecosystem9Decorators and Metaprogramming10Error Handling
All Mirror Courses
C#
PY
Decorators and Metaprogramming
MirrorLesson 9 of 10
Lesson 9

Decorators and Metaprogramming

Python decorators vs C# attributes — cross-cutting concerns and AOP

Introduction

In this lesson, you'll learn about decorators and metaprogramming in Python. Coming from C#, you already have a foundation for understanding this concept. We'll build on that knowledge while highlighting the key differences.

Mirror Card
C#
From C#:

In C#, you're familiar with python decorators vs c# attributes — cross-cutting concerns and aop.

PY
In Python:

Python has its own approach to python decorators vs c# attributes — cross-cutting concerns and aop, which we'll explore step by step.

The Python Way

Let's see how Python handles this concept. Here's a typical example:

PY
Python Example
import functools
import time
import logging

logger = logging.getLogger(__name__)

# Simple decorator (= C# attribute that ALSO executes code)
def log(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        logger.info(f"Calling {func.__name__}")
        result = func(*args, **kwargs)
        logger.info(f"Done {func.__name__}")
        return result
    return wrapper

# Decorator with arguments
def retry(times=3, exceptions=(Exception,)):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            for attempt in range(times):
                try:
                    return func(*args, **kwargs)
                except exceptions as e:
                    if attempt == times - 1: raise
                    time.sleep(2 ** attempt)
        return wrapper
    return decorator

# Timing decorator
def timed(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        start = time.perf_counter()
        result = func(*args, **kwargs)
        elapsed = time.perf_counter() - start
        print(f"{func.__name__} took {elapsed:.3f}s")
        return result
    return wrapper

@log
@timed
@retry(times=3, exceptions=(IOError,))
def process_order(order):
    # decorators applied bottom-up: retry → timed → log
    fetch_inventory(order.id)

Comparing to C#

Here's how you might have written similar code in C#:

C#
C# (What you know)
// Attributes — metadata attached to types/methods
[Serializable]
[Obsolete("Use NewMethod instead")]
public class LegacyClass { }

// Custom attribute
[AttributeUsage(AttributeTargets.Method)]
public class LogAttribute : Attribute
{
    public string Level { get; }
    public LogAttribute(string level = "Info") => Level = level;
}

// Applied to method
[Log("Debug")]
public void ProcessOrder(Order o) { }

// AOP via middleware (ASP.NET Core)
app.Use(async (context, next) => {
    var sw = Stopwatch.StartNew();
    await next(context);
    Console.WriteLine($"Request took {sw.ElapsedMs}ms");
});

// Or via Scrutor / Castle DynamicProxy for method interception
// Most C# AOP requires external packages

[Required]
[StringLength(100, MinimumLength = 1)]
public string Name { get; set; }
Mirror Card
C#
From C#:

You may be used to different syntax or behavior.

PY
In Python:

Python decorators are functions that EXECUTE at call time; C# attributes are passive metadata

Mirror Card
C#
From C#:

You may be used to different syntax or behavior.

PY
In Python:

C# attributes are read via reflection; Python decorators wrap the function directly

Mirror Card
C#
From C#:

You may be used to different syntax or behavior.

PY
In Python:

Python decorators can add behavior (logging, retry, caching) without reflection

Mirror Card
C#
From C#:

You may be used to different syntax or behavior.

PY
In Python:

C# AOP requires middleware or DI proxy libraries; Python AOP is built-in via decorators

Mirror Card
C#
From C#:

You may be used to different syntax or behavior.

PY
In Python:

@functools.wraps preserves __name__ and __doc__ — always use it

Step-by-Step Breakdown

1. Decorator as Wrapper

A Python decorator wraps a function — it runs code before/after or instead of the original. C# attributes just tag metadata; behavior needs a framework to read them.

C#
C#
[Log("Debug")]
public void ProcessOrder(Order o) { }
PY
Python
@log
def process_order(o):
    ...
# @log actually wraps process_order
# process_order = log(process_order)

2. Decorator with Arguments

Decorators with args use three levels: factory → decorator → wrapper. The factory creates the decorator with the given config.

C#
C#
[Retry(3)]
public string Fetch() { }
PY
Python
def retry(times=3):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kw):
            for i in range(times):
                try: return func(*args, **kw)
                except: pass
        return wrapper
    return decorator

@retry(times=3)
def fetch(): ...

3. Stacking Decorators

Multiple decorators stack bottom-up. @a @b @c def f means f = a(b(c(f))). Order matters for wrappers.

C#
C#
[Log]
[Timed]
[Retry(3)]
public void Work() { }
PY
Python
@log      # outermost — runs first/last
@timed    # middle
@retry(3) # innermost — wraps work directly
def work():
    ...
# equivalent to: work = log(timed(retry(3)(work)))

4. Class Decorators

Decorators can also wrap classes. @dataclass, @singleton are common examples.

C#
C#
[Singleton]
public class Config { }
PY
Python
def singleton(cls):
    instances = {}
    @functools.wraps(cls)
    def wrapper(*a, **kw):
        if cls not in instances:
            instances[cls] = cls(*a, **kw)
        return instances[cls]
    return wrapper

@singleton
class Config: pass

Common Mistakes

When coming from C#, developers often make these mistakes:

  • Python decorators are functions that EXECUTE at call time; C# attributes are passive metadata
  • C# attributes are read via reflection; Python decorators wrap the function directly
  • Python decorators can add behavior (logging, retry, caching) without reflection
Common Pitfall
Don't assume Python works exactly like C#. While the concepts may be similar, the syntax and behavior can differ significantly.

Key Takeaways

  • Python decorators EXECUTE code — not just metadata like C# attributes
  • Three levels for arg decorators: factory(args) → decorator(func) → wrapper(*args)
  • Stack decorators bottom-up: @a @b def f → f = a(b(f))
  • functools.wraps preserves function metadata — always use in wrappers
Rule of Thumb
The best way to learn is by doing. Try rewriting some of your C# code in Python to practice these concepts.
PreviousNext