Update to PEP 487, exactly as posted to python-dev Wed, Jul 13, 2016.
Except for one typo fix.
This commit is contained in:
parent
14eedbf0fe
commit
2493d89091
279
pep-0487.txt
279
pep-0487.txt
|
@ -8,7 +8,7 @@ Type: Standards Track
|
|||
Content-Type: text/x-rst
|
||||
Created: 27-Feb-2015
|
||||
Python-Version: 3.6
|
||||
Post-History: 27-Feb-2015, 5-Feb-2016
|
||||
Post-History: 27-Feb-2015, 5-Feb-2016, 24-Jun-2016, 2-Jul-2016, 13-Jul-2016
|
||||
Replaces: 422
|
||||
|
||||
|
||||
|
@ -21,16 +21,11 @@ creating the potential for spurious metaclass conflicts.
|
|||
|
||||
This PEP proposes to instead support a wide range of customisation
|
||||
scenarios through a new ``__init_subclass__`` hook in the class body,
|
||||
a hook to initialize descriptors, and a way to keep the order in which
|
||||
attributes are defined.
|
||||
|
||||
Those hooks should at first be defined in a metaclass in the standard
|
||||
library, with the option that this metaclass eventually becomes the
|
||||
default ``type`` metaclass.
|
||||
and a hook to initialize attributes.
|
||||
|
||||
The new mechanism should be easier to understand and use than
|
||||
implementing a custom metaclass, and thus should provide a gentler
|
||||
introduction to the full power Python's metaclass machinery.
|
||||
introduction to the full power of Python's metaclass machinery.
|
||||
|
||||
|
||||
Background
|
||||
|
@ -58,41 +53,31 @@ of use cases falls into just three categories: some initialization code
|
|||
running after class creation, the initialization of descriptors and
|
||||
keeping the order in which class attributes were defined.
|
||||
|
||||
Those three use cases can easily be performed by just one metaclass. If
|
||||
this metaclass is put into the standard library, and all libraries that
|
||||
wish to customize class creation use this very metaclass, no combination
|
||||
of metaclasses is necessary anymore.
|
||||
The first two categories can easily be achieved by having simple hooks
|
||||
into the class creation:
|
||||
|
||||
The three use cases are achieved as follows:
|
||||
1. An ``__init_subclass__`` hook that initializes
|
||||
all subclasses of a given class.
|
||||
2. upon class creation, a ``__set_owner__`` hook is called on all the
|
||||
attribute (descriptors) defined in the class, and
|
||||
|
||||
1. The metaclass contains an ``__init_subclass__`` hook that initializes
|
||||
all subclasses of a given class,
|
||||
2. the metaclass calls an ``__init_descriptor__`` hook for all descriptors
|
||||
defined in the class, and
|
||||
3. an ``__attribute_order__`` tuple is left in the class in order to inspect
|
||||
the order in which attributes were defined.
|
||||
|
||||
For ease of use, a base class ``SubclassInit`` is defined, which uses said
|
||||
metaclass and contains an empty stub for the hook described for use case 1.
|
||||
The third category is the topic of another PEP 520.
|
||||
|
||||
As an example, the first use case looks as follows::
|
||||
|
||||
class SpamBase(SubclassInit):
|
||||
# this is implicitly a @classmethod
|
||||
def __init_subclass__(cls, **kwargs):
|
||||
# This is invoked after a subclass is created, but before
|
||||
# explicit decorators are called.
|
||||
# The usual super() mechanisms are used to correctly support
|
||||
# multiple inheritance.
|
||||
# **kwargs are the keyword arguments to the subclasses'
|
||||
# class creation statement
|
||||
super().__init_subclass__(cls, **kwargs)
|
||||
>>> class QuestBase:
|
||||
... # this is implicitly a @classmethod
|
||||
... def __init_subclass__(cls, swallow, **kwargs):
|
||||
... cls.swallow = swallow
|
||||
... super().__init_subclass__(**kwargs)
|
||||
|
||||
class Spam(SpamBase):
|
||||
pass
|
||||
# the new hook is called on Spam
|
||||
>>> class Quest(QuestBase, swallow="african"):
|
||||
... pass
|
||||
|
||||
The base class ``SubclassInit`` contains an empty ``__init_subclass__``
|
||||
>>> Quest.swallow
|
||||
'african'
|
||||
|
||||
The base class ``object`` contains an empty ``__init_subclass__``
|
||||
method which serves as an endpoint for cooperative multiple inheritance.
|
||||
Note that this method has no keyword arguments, meaning that all
|
||||
methods which are more specialized have to process all keyword
|
||||
|
@ -104,8 +89,9 @@ similar mechanism has long been supported by `Zope's ExtensionClass`_),
|
|||
but the situation has changed sufficiently in recent years that
|
||||
the idea is worth reconsidering for inclusion.
|
||||
|
||||
The second part of the proposal adds an ``__init_descriptor__``
|
||||
initializer for descriptors. Descriptors are defined in the body of a
|
||||
The second part of the proposal adds an ``__set_owner__``
|
||||
initializer for class attributes, especially if they are descriptors.
|
||||
Descriptors are defined in the body of a
|
||||
class, but they do not know anything about that class, they do not
|
||||
even know the name they are accessed with. They do get to know their
|
||||
owner once ``__get__`` is called, but still they do not know their
|
||||
|
@ -118,26 +104,34 @@ the first part of the proposal, it makes sense to have one solution
|
|||
for this problem for everyone.
|
||||
|
||||
To give an example of its usage, imagine a descriptor representing weak
|
||||
referenced values (this is an insanely simplified, yet working example)::
|
||||
referenced values::
|
||||
|
||||
import weakref
|
||||
|
||||
class WeakAttribute:
|
||||
def __get__(self, instance, owner):
|
||||
return instance.__dict__[self.name]
|
||||
return instance.__dict__[self.name]()
|
||||
|
||||
def __set__(self, instance, value):
|
||||
instance.__dict__[self.name] = weakref.ref(value)
|
||||
|
||||
# this is the new initializer:
|
||||
def __init_descriptor__(self, owner, name):
|
||||
def __set_owner__(self, owner, name):
|
||||
self.name = name
|
||||
|
||||
The third part of the proposal is to leave a tuple called
|
||||
``__attribute_order__`` in the class that contains the order in which
|
||||
the attributes were defined. This is a very common usecase, many
|
||||
libraries use an ``OrderedDict`` to store this order. This is a very
|
||||
simple way to achieve the same goal.
|
||||
While this example looks very trivial, it should be noted that until
|
||||
now such an attribute cannot be defined without the use of a metaclass.
|
||||
And given that such a metaclass can make life very hard, this kind of
|
||||
attribute does not exist yet.
|
||||
|
||||
Initializing descriptors could simply be done in the
|
||||
``__init_subclass__`` hook. But this would mean that descriptors can
|
||||
only be used in classes that have the proper hook, the generic version
|
||||
like in the example would not work generally. One could also call
|
||||
``__set_owner__`` from whithin the base implementation of
|
||||
``object.__init_subclass__``. But given that it is a common mistake
|
||||
to forget to call ``super()``, it would happen too often that suddenly
|
||||
descriptors are not initialized.
|
||||
|
||||
|
||||
Key Benefits
|
||||
|
@ -176,76 +170,25 @@ it is recognised as a bug in the subclass rather than the library author
|
|||
breaching backwards compatibility guarantees.
|
||||
|
||||
|
||||
A path of introduction into Python
|
||||
==================================
|
||||
|
||||
Most of the benefits of this PEP can already be implemented using
|
||||
a simple metaclass. For the ``__init_subclass__`` hook this works
|
||||
all the way down to Python 2.7, while the attribute order needs Python 3.0
|
||||
to work. Such a class has been `uploaded to PyPI`_.
|
||||
|
||||
The only drawback of such a metaclass are the mentioned problems with
|
||||
metaclasses and multiple inheritance. Two classes using such a
|
||||
metaclass can only be combined, if they use exactly the same such
|
||||
metaclass. This fact calls for the inclusion of such a class into the
|
||||
standard library, let's call it ``SubclassMeta``, with the base class
|
||||
using it called ``SubclassInit``. Once all users use this standard
|
||||
library metaclass, classes from different packages can easily be
|
||||
combined.
|
||||
|
||||
But still such classes cannot be easily combined with other classes
|
||||
using other metaclasses. Authors of metaclasses should bear that in
|
||||
mind and inherit from the standard metaclass if it seems useful
|
||||
for users of the metaclass to add more functionality. Ultimately,
|
||||
if the need for combining with other metaclasses is strong enough,
|
||||
the proposed functionality may be introduced into Python's ``type``.
|
||||
|
||||
Those arguments strongly hint to the following procedure to include
|
||||
the proposed functionality into Python:
|
||||
|
||||
1. The metaclass implementing this proposal is put onto PyPI, so that
|
||||
it can be used and scrutinized.
|
||||
2. Once the code is properly mature, it can be added to the Python
|
||||
standard library. There should be a new module called
|
||||
``metaclass`` which collects tools for metaclass authors, as well
|
||||
as a documentation of the best practices of how to write
|
||||
metaclasses.
|
||||
3. If the need of combining this metaclass with other metaclasses is
|
||||
strong enough, it may be included into Python itself.
|
||||
|
||||
While the metaclass is still in the standard library and not in the
|
||||
language, it may still clash with other metaclasses. The most
|
||||
prominent metaclass in use is probably ABCMeta. It is also a
|
||||
particularly good example for the need of combining metaclasses. For
|
||||
users who want to define an ABC with subclass initialization, we should
|
||||
support a ``ABCSubclassInit`` class, or let ABCMeta inherit from this
|
||||
PEP's metaclass.
|
||||
|
||||
Extensions written in C or C++ also often define their own metaclass.
|
||||
It would be very useful if those could also inherit from the metaclass
|
||||
defined here, but this is probably not possible.
|
||||
|
||||
New Ways of Using Classes
|
||||
=========================
|
||||
|
||||
This proposal has many usecases like the following. In the examples,
|
||||
we still inherit from the ``SubclassInit`` base class. This would
|
||||
become unnecessary once this PEP is included in Python directly.
|
||||
|
||||
Subclass registration
|
||||
---------------------
|
||||
|
||||
Especially when writing a plugin system, one likes to register new
|
||||
subclasses of a plugin baseclass. This can be done as follows::
|
||||
|
||||
class PluginBase(SubclassInit):
|
||||
class PluginBase(Object):
|
||||
subclasses = []
|
||||
|
||||
def __init_subclass__(cls, **kwargs):
|
||||
super().__init_subclass__(**kwargs)
|
||||
cls.subclasses.append(cls)
|
||||
|
||||
One should note that this also works nicely as a mixin class.
|
||||
In this example, ``PluginBase.subclasses`` will contain a plain list of all
|
||||
subclasses in the entire inheritance tree. One should note that this also
|
||||
works nicely as a mixin class.
|
||||
|
||||
Trait descriptors
|
||||
-----------------
|
||||
|
@ -256,19 +199,90 @@ of a metaclass to work. This is how this would look like with this
|
|||
PEP::
|
||||
|
||||
class Trait:
|
||||
def __init__(self, minimum, maximum):
|
||||
self.minimum = minimum
|
||||
self.maximum = maximum
|
||||
|
||||
def __get__(self, instance, owner):
|
||||
return instance.__dict__[self.key]
|
||||
|
||||
def __set__(self, instance, value):
|
||||
instance.__dict__[self.key] = value
|
||||
if self.minimum < value < self.maximum:
|
||||
instance.__dict__[self.key] = value
|
||||
else:
|
||||
raise ValueError("value not in range")
|
||||
|
||||
def __init_descriptor__(self, owner, name):
|
||||
def __set_owner__(self, owner, name):
|
||||
self.key = name
|
||||
|
||||
class Int(Trait):
|
||||
def __set__(self, instance, value):
|
||||
# some boundary check code here
|
||||
super().__set__(instance, value)
|
||||
Implementation Details
|
||||
======================
|
||||
|
||||
For those who prefer reading Python over english, the following is a Python
|
||||
equivalent of the C API changes proposed in this PEP, where the new ``object``
|
||||
and ``type`` defined here inherit from the usual ones::
|
||||
|
||||
import types
|
||||
|
||||
class type(type):
|
||||
def __new__(cls, *args, **kwargs):
|
||||
if len(args) == 1:
|
||||
return super().__new__(cls, args[0])
|
||||
name, bases, ns = args
|
||||
init = ns.get('__init_subclass__')
|
||||
if isinstance(init, types.FunctionType):
|
||||
ns['__init_subclass__'] = classmethod(init)
|
||||
self = super().__new__(cls, name, bases, ns)
|
||||
for k, v in self.__dict__.items():
|
||||
func = getattr(v, '__set_owner__', None)
|
||||
if func is not None:
|
||||
func(self, k)
|
||||
super(self, self).__init_subclass__(**kwargs)
|
||||
return self
|
||||
|
||||
def __init__(self, name, bases, ns, **kwargs):
|
||||
super().__init__(name, bases, ns)
|
||||
|
||||
class object:
|
||||
@classmethod
|
||||
def __init_subclass__(cls):
|
||||
pass
|
||||
|
||||
class object(object, metaclass=type):
|
||||
pass
|
||||
|
||||
In this code, first the ``__set_owner__`` are called on the descriptors, and
|
||||
then the ``__init_subclass__``. This means that subclass initializers already
|
||||
see the fully initialized descriptors. This way, ``__init_subclass__`` users
|
||||
can fix all descriptors again if this is needed.
|
||||
|
||||
Another option would have been to call ``__set_owner__`` in the base
|
||||
implementation of ``object.__init_subclass__``. This way it would be possible
|
||||
event to prevent ``__set_owner__`` from being called. Most of the times,
|
||||
however, such a prevention would be accidental, as it often happens that a call
|
||||
to ``super()`` is forgotten.
|
||||
|
||||
Another small change should be noted here: in the current implementation of
|
||||
CPython, ``type.__init__`` explicitly forbids the use of keyword arguments,
|
||||
while ``type.__new__`` allows for its attributes to be shipped as keyword
|
||||
arguments. This is weirdly incoherent, and thus the above code forbids that.
|
||||
While it would be possible to retain the current behavior, it would be better
|
||||
if this was fixed, as it is probably not used at all: the only use case would
|
||||
be that at metaclass calls its ``super().__new__`` with *name*, *bases* and
|
||||
*dict* (yes, *dict*, not *namespace* or *ns* as mostly used with modern
|
||||
metaclasses) as keyword arguments. This should not be done.
|
||||
|
||||
As a second change, the new ``type.__init__`` just ignores keyword
|
||||
arguments. Currently, it insists that no keyword arguments are given. This
|
||||
leads to a (wanted) error if one gives keyword arguments to a class declaration
|
||||
if the metaclass does not process them. Metaclass authors that do want to
|
||||
accept keyword arguments must filter them out by overriding ``__init___``.
|
||||
|
||||
In the new code, it is not ``__init__`` that complains about keyword arguments,
|
||||
but ``__init_subclass__``, whose default implementation takes no arguments. In
|
||||
a classical inheritance scheme using the method resolution order, each
|
||||
``__init_subclass__`` may take out it's keyword arguments until none are left,
|
||||
which is checked by the default implementation of ``__init_subclass__``.
|
||||
|
||||
|
||||
Rejected Design Options
|
||||
|
@ -295,6 +309,11 @@ The original proposal also made major changes in the class
|
|||
initialization process, rendering it impossible to back-port the
|
||||
proposal to older Python versions.
|
||||
|
||||
More importantly, having a pure Python implementation allows us to
|
||||
take two preliminary steps before before we actually change the
|
||||
interpreter, giving us the chance to iron out all possible wrinkles
|
||||
in the API.
|
||||
|
||||
|
||||
Other variants of calling the hook
|
||||
----------------------------------
|
||||
|
@ -320,32 +339,6 @@ decorator is singularly incomprehensible (particularly since PEP 3115
|
|||
documents it as an ordinary method, and the current documentation doesn't
|
||||
explicitly say anything one way or the other).
|
||||
|
||||
|
||||
Defining arbitrary namespaces
|
||||
-----------------------------
|
||||
|
||||
PEP 422 defined a generic way to add arbitrary namespaces for class
|
||||
definitions. This approach is much more flexible than just leaving
|
||||
the definition order in a tuple. The ``__prepare__`` method in a metaclass
|
||||
supports exactly this behavior. But given that effectively
|
||||
the only use cases that could be found out in the wild were the
|
||||
``OrderedDict`` way of determining the attribute order, it seemed
|
||||
reasonable to only support this special case.
|
||||
|
||||
The metaclass described in this PEP has been designed to be very simple
|
||||
such that it could be reasonably made the default metaclass. This was
|
||||
especially important when designing the attribute order functionality:
|
||||
This was a highly demanded feature and has been enabled through the
|
||||
``__prepare__`` method of metaclasses. This method can be abused in
|
||||
very weird ways, making it hard to correctly maintain this feature in
|
||||
CPython. This is why it has been proposed to deprecated this feature,
|
||||
and instead use ``OrderedDict`` as the standard namespace, supporting
|
||||
the most important feature while dropping most of the complexity. But
|
||||
this would have meant that ``OrderedDict`` becomes a language builtin
|
||||
like dict and set, and not just a standard library class. The choice
|
||||
of the ``__attribute_order__`` tuple is a much simpler solution to the
|
||||
problem.
|
||||
|
||||
A more ``__new__``-like hook
|
||||
----------------------------
|
||||
|
||||
|
@ -354,29 +347,29 @@ In PEP 422 the hook worked more like the ``__new__`` method than the
|
|||
modifying one. This allows a bit more flexibility, but at the cost
|
||||
of much harder implementation and undesired side effects.
|
||||
|
||||
Adding a class attribute with the attribute order
|
||||
-------------------------------------------------
|
||||
|
||||
This got its own PEP 520.
|
||||
|
||||
|
||||
History
|
||||
=======
|
||||
|
||||
This used to be a competing proposal to PEP 422 by Nick Coughlan and
|
||||
Daniel Urban. It shares both most of the PEP text and proposed code, but
|
||||
has major differences in how to achieve its goals. In the meantime, PEP 422
|
||||
has been withdrawn favouring this approach.
|
||||
This used to be a competing proposal to PEP 422 by Nick Coghlan and Daniel
|
||||
Urban. PEP 422 intended to achieve the same goals as this PEP, but with a
|
||||
different way of implementation. In the meantime, PEP 422 has been withdrawn
|
||||
favouring this approach.
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
.. _published code:
|
||||
http://mail.python.org/pipermail/python-dev/2012-June/119878.html
|
||||
|
||||
.. _more than 10 years ago:
|
||||
http://mail.python.org/pipermail/python-dev/2001-November/018651.html
|
||||
|
||||
.. _Zope's ExtensionClass:
|
||||
http://docs.zope.org/zope_secrets/extensionclass.html
|
||||
|
||||
.. _uploaded to PyPI:
|
||||
https://pypi.python.org/pypi/metaclass
|
||||
|
||||
Copyright
|
||||
=========
|
||||
|
@ -384,7 +377,7 @@ Copyright
|
|||
This document has been placed in the public domain.
|
||||
|
||||
|
||||
|
||||
|
||||
..
|
||||
Local Variables:
|
||||
mode: indented-text
|
||||
|
|
Loading…
Reference in New Issue