Add PEP 422: Dynamic Class Decorators
This commit is contained in:
parent
a51d891e28
commit
ea2123adbb
|
@ -0,0 +1,168 @@
|
||||||
|
PEP: 422
|
||||||
|
Title: Dynamic class decorators
|
||||||
|
Version: $Revision$
|
||||||
|
Last-Modified: $Date$
|
||||||
|
Author: Nick Coghlan <ncoghlan@gmail.com>
|
||||||
|
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:
|
||||||
|
|
Loading…
Reference in New Issue