Update PEP 487 (#53)
* Update PEP 487 This adds some changes proposed by Guido. * removed outdated comment * rename __set_owner__ to __set_name__
This commit is contained in:
parent
2493d89091
commit
14c5ca31b6
50
pep-0487.txt
50
pep-0487.txt
|
@ -58,15 +58,15 @@ into the class creation:
|
||||||
|
|
||||||
1. An ``__init_subclass__`` hook that initializes
|
1. An ``__init_subclass__`` hook that initializes
|
||||||
all subclasses of a given class.
|
all subclasses of a given class.
|
||||||
2. upon class creation, a ``__set_owner__`` hook is called on all the
|
2. upon class creation, a ``__set_name__`` hook is called on all the
|
||||||
attribute (descriptors) defined in the class, and
|
attribute (descriptors) defined in the class, and
|
||||||
|
|
||||||
The third category is the topic of another PEP 520.
|
The third category is the topic of another PEP, PEP 520.
|
||||||
|
|
||||||
As an example, the first use case looks as follows::
|
As an example, the first use case looks as follows::
|
||||||
|
|
||||||
>>> class QuestBase:
|
>>> class QuestBase:
|
||||||
... # this is implicitly a @classmethod
|
... # this is implicitly a @classmethod (see below for motivation)
|
||||||
... def __init_subclass__(cls, swallow, **kwargs):
|
... def __init_subclass__(cls, swallow, **kwargs):
|
||||||
... cls.swallow = swallow
|
... cls.swallow = swallow
|
||||||
... super().__init_subclass__(**kwargs)
|
... super().__init_subclass__(**kwargs)
|
||||||
|
@ -89,7 +89,7 @@ similar mechanism has long been supported by `Zope's ExtensionClass`_),
|
||||||
but the situation has changed sufficiently in recent years that
|
but the situation has changed sufficiently in recent years that
|
||||||
the idea is worth reconsidering for inclusion.
|
the idea is worth reconsidering for inclusion.
|
||||||
|
|
||||||
The second part of the proposal adds an ``__set_owner__``
|
The second part of the proposal adds an ``__set_name__``
|
||||||
initializer for class attributes, especially if they are descriptors.
|
initializer for class attributes, especially if they are descriptors.
|
||||||
Descriptors are defined in the body of a
|
Descriptors are defined in the body of a
|
||||||
class, but they do not know anything about that class, they do not
|
class, but they do not know anything about that class, they do not
|
||||||
|
@ -116,9 +116,23 @@ referenced values::
|
||||||
instance.__dict__[self.name] = weakref.ref(value)
|
instance.__dict__[self.name] = weakref.ref(value)
|
||||||
|
|
||||||
# this is the new initializer:
|
# this is the new initializer:
|
||||||
def __set_owner__(self, owner, name):
|
def __set_name__(self, owner, name):
|
||||||
self.name = name
|
self.name = name
|
||||||
|
|
||||||
|
Such a ``WeakAttribute`` may, for example, be used in a tree structure
|
||||||
|
where one wants to avoid cyclic references via the parent::
|
||||||
|
|
||||||
|
class TreeNode:
|
||||||
|
parent = WeakAttribute()
|
||||||
|
|
||||||
|
def __init__(self, parent):
|
||||||
|
self.parent = parent
|
||||||
|
|
||||||
|
Note that the ``parent`` attribute is used like a normal attribute,
|
||||||
|
yet the tree contains no cyclic references and can thus be easily
|
||||||
|
garbage collected when out of use. The ``parent`` attribute magically
|
||||||
|
becomes ``None`` once the parent ceases existing.
|
||||||
|
|
||||||
While this example looks very trivial, it should be noted that until
|
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.
|
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
|
And given that such a metaclass can make life very hard, this kind of
|
||||||
|
@ -128,7 +142,7 @@ Initializing descriptors could simply be done in the
|
||||||
``__init_subclass__`` hook. But this would mean that descriptors can
|
``__init_subclass__`` hook. But this would mean that descriptors can
|
||||||
only be used in classes that have the proper hook, the generic version
|
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
|
like in the example would not work generally. One could also call
|
||||||
``__set_owner__`` from whithin the base implementation of
|
``__set_name__`` from whithin the base implementation of
|
||||||
``object.__init_subclass__``. But given that it is a common mistake
|
``object.__init_subclass__``. But given that it is a common mistake
|
||||||
to forget to call ``super()``, it would happen too often that suddenly
|
to forget to call ``super()``, it would happen too often that suddenly
|
||||||
descriptors are not initialized.
|
descriptors are not initialized.
|
||||||
|
@ -179,7 +193,7 @@ Subclass registration
|
||||||
Especially when writing a plugin system, one likes to register new
|
Especially when writing a plugin system, one likes to register new
|
||||||
subclasses of a plugin baseclass. This can be done as follows::
|
subclasses of a plugin baseclass. This can be done as follows::
|
||||||
|
|
||||||
class PluginBase(Object):
|
class PluginBase:
|
||||||
subclasses = []
|
subclasses = []
|
||||||
|
|
||||||
def __init_subclass__(cls, **kwargs):
|
def __init_subclass__(cls, **kwargs):
|
||||||
|
@ -212,7 +226,7 @@ PEP::
|
||||||
else:
|
else:
|
||||||
raise ValueError("value not in range")
|
raise ValueError("value not in range")
|
||||||
|
|
||||||
def __set_owner__(self, owner, name):
|
def __set_name__(self, owner, name):
|
||||||
self.key = name
|
self.key = name
|
||||||
|
|
||||||
Implementation Details
|
Implementation Details
|
||||||
|
@ -234,7 +248,7 @@ and ``type`` defined here inherit from the usual ones::
|
||||||
ns['__init_subclass__'] = classmethod(init)
|
ns['__init_subclass__'] = classmethod(init)
|
||||||
self = super().__new__(cls, name, bases, ns)
|
self = super().__new__(cls, name, bases, ns)
|
||||||
for k, v in self.__dict__.items():
|
for k, v in self.__dict__.items():
|
||||||
func = getattr(v, '__set_owner__', None)
|
func = getattr(v, '__set_name__', None)
|
||||||
if func is not None:
|
if func is not None:
|
||||||
func(self, k)
|
func(self, k)
|
||||||
super(self, self).__init_subclass__(**kwargs)
|
super(self, self).__init_subclass__(**kwargs)
|
||||||
|
@ -251,14 +265,14 @@ and ``type`` defined here inherit from the usual ones::
|
||||||
class object(object, metaclass=type):
|
class object(object, metaclass=type):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
In this code, first the ``__set_owner__`` are called on the descriptors, and
|
In this code, first the ``__set_name__`` are called on the descriptors, and
|
||||||
then the ``__init_subclass__``. This means that subclass initializers already
|
then the ``__init_subclass__``. This means that subclass initializers already
|
||||||
see the fully initialized descriptors. This way, ``__init_subclass__`` users
|
see the fully initialized descriptors. This way, ``__init_subclass__`` users
|
||||||
can fix all descriptors again if this is needed.
|
can fix all descriptors again if this is needed.
|
||||||
|
|
||||||
Another option would have been to call ``__set_owner__`` in the base
|
Another option would have been to call ``__set_name__`` in the base
|
||||||
implementation of ``object.__init_subclass__``. This way it would be possible
|
implementation of ``object.__init_subclass__``. This way it would be possible
|
||||||
event to prevent ``__set_owner__`` from being called. Most of the times,
|
even to prevent ``__set_name__`` from being called. Most of the times,
|
||||||
however, such a prevention would be accidental, as it often happens that a call
|
however, such a prevention would be accidental, as it often happens that a call
|
||||||
to ``super()`` is forgotten.
|
to ``super()`` is forgotten.
|
||||||
|
|
||||||
|
@ -309,14 +323,9 @@ The original proposal also made major changes in the class
|
||||||
initialization process, rendering it impossible to back-port the
|
initialization process, rendering it impossible to back-port the
|
||||||
proposal to older Python versions.
|
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 hooks
|
||||||
Other variants of calling the hook
|
-----------------------------------
|
||||||
----------------------------------
|
|
||||||
|
|
||||||
Other names for the hook were presented, namely ``__decorate__`` or
|
Other names for the hook were presented, namely ``__decorate__`` or
|
||||||
``__autodecorate__``. This proposal opts for ``__init_subclass__`` as
|
``__autodecorate__``. This proposal opts for ``__init_subclass__`` as
|
||||||
|
@ -324,6 +333,9 @@ it is very close to the ``__init__`` method, just for the subclass,
|
||||||
while it is not very close to decorators, as it does not return the
|
while it is not very close to decorators, as it does not return the
|
||||||
class.
|
class.
|
||||||
|
|
||||||
|
For the ``__set_name__`` hook other names have been proposed as well,
|
||||||
|
``__set_owner__``, ``__set_ownership__`` and ``__init_descriptor__``.
|
||||||
|
|
||||||
|
|
||||||
Requiring an explicit decorator on ``__init_subclass__``
|
Requiring an explicit decorator on ``__init_subclass__``
|
||||||
--------------------------------------------------------
|
--------------------------------------------------------
|
||||||
|
|
Loading…
Reference in New Issue