17.7 reflect-metadata and Emitting Type Information

Now, let’s get into the weird and wonderful world of making decorators actually useful. You see, the basic decorators we just covered are like getting a fancy new power tool… without the battery. They let you see that a class or method is being decorated, but they don’t give you the runtime information about that class or method to do anything truly intelligent. You know target is a constructor function, but what are the names and types of its properties? Good luck.

17.6 The New ES2023 Decorator Standard vs Legacy Decorators

Alright, let’s settle this. For years, you’ve been using decorators in TypeScript or Babel, and I’ve been right there with you, grumbling about the absurdly complex incantations needed to make them work. That was the legacy decorator proposal, a weird, unofficial draft that got stuck in committee purgatory for so long that everyone just started using it anyway. It was the house guest who overstayed their welcome by about seven years.

17.5 Parameter Decorators and Dependency Injection

Now, let’s talk about the black sheep of the decorator family: parameter decorators. You’ve probably seen them lurking in Angular or other DI-heavy frameworks, looking cryptic and a bit magical. Their entire job is to let you attach metadata to a function’s parameters. That’s it. They don’t do anything on their own. They just whisper, “Hey, when you’re constructing this class later, you should probably pass 'database' for this first parameter, okay?” They are the ultimate backseat drivers of the JavaScript world.

17.4 Property and Accessor Decorators

Right, decorators. You’ve probably heard the hype, seen the @ symbols littering frameworks, and wondered if it’s just magic glitter someone sprinkled on JavaScript. It’s not. It’s a structured, powerful, and frankly, a bit awkward way to metaprogram your classes. We’re going to focus on the ones that finally made it into the official spec: property and accessor decorators. Forget the old, weird, wildly-incompatible-between-Babel-and-TypeScript versions. ES2023 is the real deal, and it’s about time.

17.3 Method Decorators: Intercepting Method Calls

Alright, let’s get our hands dirty with method decorators. Forget the dry theory—you want to know what these things do. At their core, a method decorator is a function that gets to wrap another function, specifically a method on a class. It can intercept the call, mess with the inputs, mess with the output, or even replace the entire method. It’s like having a universal middleware system for your class methods, and it’s incredibly powerful for things like logging, validation, or access control without cluttering your actual business logic.

17.2 Class Decorators: Wrapping and Replacing Constructors

Alright, let’s get our hands dirty with the two main flavors of class decorators: wrapping and replacing. This is where the magic happens, and also where you can spectacularly blow your own foot off if you’re not careful. I’m here to make sure you keep all your toes. The core idea is simple: a class decorator receives the entire class constructor as its target and can either return a new constructor (wrapping or replacing) or mutate the original one. We’re going to focus on the non-mutating, return-a-new-constructor approach because it’s cleaner, more powerful, and frankly, less likely to cause weird side-effects that make you question your life choices.

17.1 The Decorator Proposal: Stage History and experimentalDecorators

Alright, let’s talk about the weird, liminal space decorators have occupied for the last decade. If you’ve dabbled in TypeScript or modern frameworks, you’ve used them. @Component, @observable, you name it. But here’s the kicker: for years, the decorators you were using were a complete fiction. Well, not a complete fiction. They were based on a very real, very old Stage 2 proposal from… checks notes… 2014. A proposal that was essentially a draft scribbled on a napkin, which the TC39 committee then spent the next several years completely rewriting. This left us all in a state of Schrödinger’s decorator: both standardized and experimental at the same time.

23.8 Standard Library Decorators: @staticmethod, @classmethod, @property, @lru_cache

The Python standard library provides a suite of decorators that are fundamental to writing clean, efficient, and idiomatic object-oriented code. These decorators modify the behavior of methods, transforming them into specialized constructs like static methods, class methods, properties, and cached functions. Understanding their distinct purposes and the underlying mechanics is crucial for effective class design. @staticmethod The @staticmethod decorator is used to define a method that does not operate on an instance or the class itself. It is essentially a function that resides inside a class’s namespace for organizational purposes. A static method receives no implicit first argument; it is passed neither the instance (self) nor the class (cls). This makes it ideal for utility functions that are logically related to the class but do not need to access or modify any class-specific or instance-specific state.

23.7 Decorating Classes

Decorators provide a powerful mechanism to modify or enhance class behavior without resorting to inheritance. When applied to classes, decorators receive the class object itself as their argument, allowing them to inspect, modify, or even completely replace the original class definition. This approach is particularly valuable for implementing cross-cutting concerns like logging, validation, registration, and data transformation across multiple classes. Basic Class Decoration Syntax A class decorator is a function that takes a class and returns a modified class or a new class. The syntax mirrors function decoration, using the @ symbol immediately before the class definition.

23.6 Class-Based Decorators

While function-based decorators are common, class-based decorators offer a more structured and powerful approach, particularly for stateful decorators or those requiring complex configuration. A class becomes a decorator by making its instances callable, typically by implementing the __call__ method. This method is invoked whenever the decorated function is called, allowing the class to intercept, modify, or replace the call. Implementing a Basic Class-Based Decorator The fundamental mechanism is to have the class’s __init__ method accept and store the function to be decorated. The __call__ method then wraps the original function, executing code before and after its invocation.

23.5 Parametrized Decorators: Decorators That Accept Arguments

Parametrized decorators elevate the concept of decorators from simple function wrappers to powerful, reusable factories of decorator logic. While a standard decorator applies a fixed transformation, a parametrized decorator accepts arguments that customize the behavior of the transformation it applies. This is achieved by structuring the decorator as a function that returns a decorator. The key to understanding this pattern lies in the three nested layers of function definitions: The outermost function accepts the decorator’s own parameters (e.g., n=2). The middle function acts as the standard decorator, accepting the target function to be decorated. The innermost function is the actual wrapper that replaces the original function, implementing the customized logic using the parameters from the outermost scope. The Three-Layer Structure This structure might seem complex at first, but it arises naturally from Python’s scoping rules and execution model. When the interpreter encounters @decorator_factory(arg), it immediately calls decorator_factory(arg). This call must return a function that is itself a decorator—a function that takes a function and returns a wrapper. This is precisely the role of the middle function.

23.4 Stacking Multiple Decorators: Order of Application

When multiple decorators are applied to a single function, they are not executed simultaneously but rather in a specific, nested order. This process is often visualized as building an onion, where each decorator adds a new layer around the original function. The order of application is crucial because it directly dictates the runtime behavior of the decorated function. Decorators are applied from the bottom up, meaning the decorator closest to the def keyword is applied first, and the one farthest away is applied last. However, when the decorated function is called, the execution of these layers happens in the reverse order: from the outermost layer inward to the core function, and then back outward.

23.3 Preserving Metadata with functools.wraps

When you create a decorator in Python, you are essentially creating a function that wraps another function. While this is powerful, it introduces a significant problem: the original function’s identity is lost. The wrapper function created by the decorator replaces the original function object. This means crucial metadata—such as the function’s name (__name__), its docstring (__doc__), and its module (__module__)—are overwritten with the wrapper’s metadata. This loss of information breaks introspection tools and can make debugging and logging exceptionally difficult.

23.2 Writing a Simple Decorator from Scratch

At its core, a decorator is a higher-order function—a function that takes another function as an argument and returns a new function, usually with enhanced or altered behavior. The decorator syntax @decorator is merely syntactic sugar that applies this function transformation in a declarative and readable way, directly above the function definition. This pattern is a powerful application of Python’s first-class functions and closures, allowing you to modify the behavior of functions or methods without permanently modifying their source code.

23.1 The Decorator Syntax: @ and What It Expands To

At its core, the @decorator syntax is a powerful form of syntactic sugar—a feature that makes code easier to read and write without adding new functionality to the language. It provides a clean, declarative way to modify or extend the behavior of a function or class immediately after its definition. To truly master decorators, one must understand what this syntax expands into, as this reveals the underlying mechanics and unlocks the ability to write more advanced decorators.

— joke —

...