python-peps/pep-0422.txt

169 lines
5.2 KiB
Plaintext
Raw Normal View History

2012-06-05 08:09:20 -04:00
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: