Update 422 based on python-dev feedback
This commit is contained in:
parent
59de0218af
commit
fa7af10a99
99
pep-0422.txt
99
pep-0422.txt
|
@ -44,7 +44,7 @@ the metaclass hint that may be provided as part of the class definition.
|
|||
While in many cases these two meanings end up referring to one and the same
|
||||
object, there are two situations where that is not the case:
|
||||
|
||||
* If the metaclass hint refers to an instance of ``type``, then it is
|
||||
* If the metaclass hint refers to a subclass of ``type``, then it is
|
||||
considered as a candidate metaclass along with the metaclasses of all of
|
||||
the parents of the class being defined. If a more appropriate metaclass is
|
||||
found amongst the candidates, then it will be used instead of the one
|
||||
|
@ -72,6 +72,16 @@ attribute, and the class creation process would extract that value from the
|
|||
class namespace to use as the metaclass hint. There is `published code`_ that
|
||||
makes use of this feature.
|
||||
|
||||
Another new feature in Python 3 is the zero-argument form of the ``super()``
|
||||
builtin, introduced by PEP 3135. This feature uses an implicit ``__class__``
|
||||
reference to the class being defined to replace the "by name" references
|
||||
required in Python 2. Just as code invoked during execution of a Python 2
|
||||
metaclass could not call methods that referenced the class by name (as the
|
||||
name had not yet been bound in the containing scope), similarly, Python 3
|
||||
metaclasses cannot call methods that rely on the implicit ``__class__``
|
||||
reference (as it is not populated until after the metaclass has returned
|
||||
control to the class creation machiner).
|
||||
|
||||
|
||||
Proposal
|
||||
========
|
||||
|
@ -90,10 +100,10 @@ following criteria:
|
|||
4. Can be added to an existing base class without a significant risk of
|
||||
introducing backwards compatibility problems
|
||||
|
||||
One mechanism that would achieve this goal is to add a new class
|
||||
One mechanism that can achieve this goal is to add a new class
|
||||
initialisation hook, modelled directly on the existing instance
|
||||
initialisation hook. However, the signature would be constrained to ensure
|
||||
that correctly supporting multiple inheritance is kept as simple as possible.
|
||||
initialisation hook, but with the signature constrained to match that
|
||||
of an ordinary class decorator.
|
||||
|
||||
Specifically, it is proposed that class definitions be able to provide a
|
||||
class initialisation hook as follows::
|
||||
|
@ -110,51 +120,57 @@ class initialisation hook as follows::
|
|||
If present on the created object, this new hook will be called by the class
|
||||
creation machinery *after* the ``__class__`` reference has been initialised.
|
||||
For ``types.new_class()``, it will be called as the last step before
|
||||
returning the created class object. Calling the hook automatically from
|
||||
``type.__init__`` unfortunately doesn't work, as it would mean the
|
||||
``__init_class__`` method would be unable to call any methods that relied
|
||||
on the ``__class__`` reference (or used the zero-argument form of
|
||||
``super()``).
|
||||
returning the created class object.
|
||||
|
||||
If a metaclass wishes to block class initialisation for some reason, it
|
||||
must arrange for ``cls.__init_class__`` to trigger ``AttributeError``.
|
||||
|
||||
This general proposal is not a new idea (it was first suggested `more than
|
||||
10 years ago`_), but I believe the situation has changed sufficiently in
|
||||
that time that the idea is worth reconsidering.
|
||||
This general proposal is not a new idea (it was first suggested for
|
||||
inclusion in the language definition `more than 10 years ago`_, and a
|
||||
similar mechanism has long been supported by `Zope's ExtensionClass`_),
|
||||
but I believe the situation has changed sufficiently in recent years that
|
||||
the idea is worth reconsidering.
|
||||
|
||||
|
||||
Key Benefits
|
||||
============
|
||||
|
||||
|
||||
Replaces dynamic setting of ``__metaclass__``
|
||||
---------------------------------------------
|
||||
Replaces many use cases for dynamic setting of ``__metaclass__``
|
||||
-----------------------------------------------------------------
|
||||
|
||||
For use cases that didn't involve completely replacing the defined class,
|
||||
For use cases that don't involve completely replacing the defined class,
|
||||
Python 2 code that dynamically set ``__metaclass__`` can now dynamically
|
||||
set ``__init_class__`` instead. For more advanced use cases, introduction of
|
||||
an explicit metaclass will still be necessary in order to support Python 3.
|
||||
an explicit metaclass (possibly made available as a required base class) will
|
||||
still be necessary in order to support Python 3.
|
||||
|
||||
|
||||
Easier inheritance of definition time behaviour
|
||||
-----------------------------------------------
|
||||
|
||||
Understanding Python's metaclass system requires a deep understanding of
|
||||
Understanding Python's metaclasses requires a deep understanding of
|
||||
the type system and the class construction process. This is legitimately
|
||||
seen as confusing, due to the need to keep multiple moving parts (the code,
|
||||
seen as challenging, due to the need to keep multiple moving parts (the code,
|
||||
the metaclass hint, the actual metaclass, the class object, instances of the
|
||||
class object) clearly distinct in your mind.
|
||||
class object) clearly distinct in your mind. Even when you know the rules,
|
||||
it's still easy to make a mistake if you're not being extremely careful.
|
||||
An earlier version of this PEP actually included such a mistake: it
|
||||
stated "instance of type" for a constraint that is actually "subclass of
|
||||
type".
|
||||
|
||||
Understanding the proposed class initialisation hook requires understanding
|
||||
decorators and ordinary method inheritance, which is a much simpler prospect.
|
||||
Understanding the proposed class initialisation hook only requires
|
||||
understanding decorators and ordinary method inheritance, which isn't
|
||||
quite as daunting a task. The new hook provides a more gradual path
|
||||
towards understanding all of the phases involved in the class definition
|
||||
process.
|
||||
|
||||
|
||||
Reduced chance of metaclass conflicts
|
||||
-------------------------------------
|
||||
|
||||
One of the big issues that makes library authors reluctant to use metaclasses
|
||||
(even when it would be appropriate) is the risk of metaclass conflicts.
|
||||
(even when they would be appropriate) is the risk of metaclass conflicts.
|
||||
These occur whenever two unrelated metaclasses are used by the desired
|
||||
parents of a class definition. This risk also makes it very difficult to
|
||||
*add* a metaclass to a class that has previously been published without one.
|
||||
|
@ -164,12 +180,12 @@ a similar level of risk to adding an ``__init__`` method: technically, there
|
|||
is a risk of breaking poorly implemented subclasses, but when that occurs,
|
||||
it is recognised as a bug in the subclass rather than the library author
|
||||
breaching backwards compatibility guarantees. In fact, due to the constrained
|
||||
signature, the risk in this case is actually even lower than in the case of
|
||||
``__init__``.
|
||||
signature of ``__init_class__``, the risk in this case is actually even
|
||||
lower than in the case of ``__init__``.
|
||||
|
||||
|
||||
Integrates cleanly with PEP 3135
|
||||
--------------------------------
|
||||
Integrates cleanly with \PEP 3135
|
||||
---------------------------------
|
||||
|
||||
Unlike code that runs as part of the metaclass, code that runs as part of
|
||||
the new hook will be able to freely invoke class methods that rely on the
|
||||
|
@ -280,6 +296,35 @@ just go away, as that kind of thing is taken care of through the use of an
|
|||
ordinary class method invocation.
|
||||
|
||||
|
||||
Automatic metaclass derivation
|
||||
------------------------------
|
||||
|
||||
When no appropriate metaclass is found, it's theoretically possible to
|
||||
automatically derive a metaclass for a new type based on the metaclass hint
|
||||
and the metaclasses of the bases.
|
||||
|
||||
While adding such a mechanism would reduce the risk of spurious metaclass
|
||||
conflicts, it would do nothing to improve integration with PEP 3135, would
|
||||
not help with porting Python 2 code that set ``__metaclass__`` dynamically
|
||||
and would not provide a more straightforward inherited mechanism for invoking
|
||||
additional operations after the class invocation is complete.
|
||||
|
||||
In addition, there would still be a risk of metaclass conflicts in cases
|
||||
where the base metaclasses were not written with multiple inheritance in
|
||||
mind. In such situations, there's a chance of introducing latent defects
|
||||
if one or more metaclasses are not invoked correctly.
|
||||
|
||||
|
||||
Calling the new hook from ``type.__init__``
|
||||
-------------------------------------------
|
||||
|
||||
Calling the new hook automatically from ``type.__init__``, would achieve most
|
||||
of the goals of this PEP. However, using that approach would mean that
|
||||
``__init_class__`` implementations would be unable to call any methods that
|
||||
relied on the ``__class__`` reference (or used the zero-argument form of
|
||||
``super()``), and could not make use of those features themselves.
|
||||
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
|
@ -289,6 +334,8 @@ References
|
|||
.. _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
|
||||
|
||||
Copyright
|
||||
=========
|
||||
|
|
Loading…
Reference in New Issue