PEP: 422 Title: Dynamic class decorators Version: $Revision$ Last-Modified: $Date$ Author: Nick Coghlan Status: Draft Type: Standards Track Content-Type: text/x-rst Created: 5-Jun-2012 Post-History: 5-Jun-2012 Abstract ======== Classes currently support two mechanisms for modification of the class at definition time: metaclasses and lexical decorators. Metaclasses can be awkward and challenging to use correctly in conjunction with multiple inheritance and lexical decorators don't interact with class inheritance at all. This PEP proposes a new mechanism for dynamic class decoration that interacts more cleanly with class inheritance mechanisms. Specification ============= This PEP proposes that a new step be added to the class creation process, after the metaclass invocation to construct the class instance and before the application of lexical decorators. This step will walk the class MRO in reverse order, looking for ``__decorators__`` entries in each class dictionary. These entries are expected to be iterables that are also walked in reverse order to retrieve class decorators that are automatically applied to the class being defined:: for entry in reversed(cls.mro()): decorators = entry.__dict__.get("__decorators__", ()) for deco in reversed(decorators): cls = deco(cls) This step in the class creation process will be an implicit part of the class statement and also part of the behaviour of ``types.new_class()``. Rationale ========= When decorator support was added to classes, the lexical decoration syntax was copied directly from function decorators:: @decorator class Example: # Subclasses will not be decorated automatically pass This mechanism works well, so long as it is considered acceptable that the decorator is *not* applied automatically to any subclasses. If it is desired that the behaviour be inherited, it is currently necessary to make the step up to defining a `custom metaclass`_:: class DynamicDecorators(type): """Metaclass for dynamic decorator support Creates the class normally, then runs through the MRO looking for __decorators__ attributes and applying the contained decorators to the newly created class """ def __new__(meta, name, bases, ns): cls = super(DynamicDecorators, meta).__new__(meta, name, bases, ns) for entry in reversed(cls.mro()): decorators = entry.__dict__.get("__decorators__", ()) for deco in reversed(decorators): cls = deco(cls) return cls class Example(metaclass=DynamicDecorators): # Subclasses *will* be decorated automatically __decorators__ = [decorator] The main potential problem with this approach, is that it can place significant constraints on the type heirarchy, as it requires that all metaclasses used be well behaved with respect to multiple inheritance. By making dynamic decorators an inherent part of the class creation process, many current use cases of metaclasses may be replaced with dynamic decorators instead, greatly reducing the likelihood of metaclass conflicts, as well as being substantially easier to write correctly in the first place. Design Discussion ================= Allowing metaclasses to override the dynamic decoration process --------------------------------------------------------------- This PEP does not provide a mechanism that allows metaclasses to override the dynamic decoration process. If this feature is deemed desirable in the future, then it can be added by moving the functionality described in this PEP into a new method on the metaclass (for example, ``__decorate__``), with ``type`` providing a suitable default implementation that matches the behaviour described here. This PEP chose the simplicity of the current approach, as lexical decorators are currently outside the scope of metaclass control, so it seems reasonable to pursue the simpler strategy in the absence of a solid use case for making this behaviour configurable. Iterating over decorator entries in reverse order ------------------------------------------------- This order was chosen to match the layout of lexical decorators when converted to ordinary function calls. Just as the following are equivalent:: @deco2 @deco1 class C: pass class C: pass C = deco2(deco1(C)) So too will the following be roughly equivalent (aside from inheritance):: class C: __decorators__ = [deco2, deco1] class C: pass C = deco2(deco1(C)) Iterating over the MRO in reverse order --------------------------------------- The order of iteration over the MRO for decorator application was chosen to match the order of actual call *evaluation* when using ``super`` to invoke parent class implementations: the first method to run to completion is that closest to the base of the class hierarchy. References ========== .. _custom metaclass: https://bitbucket.org/ncoghlan/misc/src/default/pep422.py Copyright ========= This document has been placed in the public domain. .. Local Variables: mode: indented-text indent-tabs-mode: nil sentence-end-double-space: t fill-column: 70 coding: utf-8 End: