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
|
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:
|
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
|
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
|
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
|
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
|
class namespace to use as the metaclass hint. There is `published code`_ that
|
||||||
makes use of this feature.
|
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
|
Proposal
|
||||||
========
|
========
|
||||||
|
@ -90,10 +100,10 @@ following criteria:
|
||||||
4. Can be added to an existing base class without a significant risk of
|
4. Can be added to an existing base class without a significant risk of
|
||||||
introducing backwards compatibility problems
|
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, modelled directly on the existing instance
|
||||||
initialisation hook. However, the signature would be constrained to ensure
|
initialisation hook, but with the signature constrained to match that
|
||||||
that correctly supporting multiple inheritance is kept as simple as possible.
|
of an ordinary class decorator.
|
||||||
|
|
||||||
Specifically, it is proposed that class definitions be able to provide a
|
Specifically, it is proposed that class definitions be able to provide a
|
||||||
class initialisation hook as follows::
|
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
|
If present on the created object, this new hook will be called by the class
|
||||||
creation machinery *after* the ``__class__`` reference has been initialised.
|
creation machinery *after* the ``__class__`` reference has been initialised.
|
||||||
For ``types.new_class()``, it will be called as the last step before
|
For ``types.new_class()``, it will be called as the last step before
|
||||||
returning the created class object. Calling the hook automatically from
|
returning the created class object.
|
||||||
``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()``).
|
|
||||||
|
|
||||||
If a metaclass wishes to block class initialisation for some reason, it
|
If a metaclass wishes to block class initialisation for some reason, it
|
||||||
must arrange for ``cls.__init_class__`` to trigger ``AttributeError``.
|
must arrange for ``cls.__init_class__`` to trigger ``AttributeError``.
|
||||||
|
|
||||||
This general proposal is not a new idea (it was first suggested `more than
|
This general proposal is not a new idea (it was first suggested for
|
||||||
10 years ago`_), but I believe the situation has changed sufficiently in
|
inclusion in the language definition `more than 10 years ago`_, and a
|
||||||
that time that the idea is worth reconsidering.
|
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
|
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
|
Python 2 code that dynamically set ``__metaclass__`` can now dynamically
|
||||||
set ``__init_class__`` instead. For more advanced use cases, introduction of
|
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
|
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
|
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
|
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
|
Understanding the proposed class initialisation hook only requires
|
||||||
decorators and ordinary method inheritance, which is a much simpler prospect.
|
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
|
Reduced chance of metaclass conflicts
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
|
|
||||||
One of the big issues that makes library authors reluctant to use metaclasses
|
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
|
These occur whenever two unrelated metaclasses are used by the desired
|
||||||
parents of a class definition. This risk also makes it very difficult to
|
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.
|
*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,
|
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
|
it is recognised as a bug in the subclass rather than the library author
|
||||||
breaching backwards compatibility guarantees. In fact, due to the constrained
|
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
|
signature of ``__init_class__``, the risk in this case is actually even
|
||||||
``__init__``.
|
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
|
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
|
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.
|
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
|
References
|
||||||
==========
|
==========
|
||||||
|
|
||||||
|
@ -289,6 +334,8 @@ References
|
||||||
.. _more than 10 years ago:
|
.. _more than 10 years ago:
|
||||||
http://mail.python.org/pipermail/python-dev/2001-November/018651.html
|
http://mail.python.org/pipermail/python-dev/2001-November/018651.html
|
||||||
|
|
||||||
|
.. _Zope's ExtensionClass:
|
||||||
|
http://docs.zope.org/zope_secrets/extensionclass.html
|
||||||
|
|
||||||
Copyright
|
Copyright
|
||||||
=========
|
=========
|
||||||
|
|
Loading…
Reference in New Issue