Adjust the redrafted PEP 422 to match reality

This commit is contained in:
Nick Coghlan 2013-03-05 22:22:20 +10:00
parent 685514f87a
commit cc2b99b5ad
1 changed files with 78 additions and 29 deletions

View File

@ -27,16 +27,6 @@ The new mechanism is also much 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.
.. note::
This PEP, in particular the use of __prepare__ to share a single
namespace amongst multiple class objects, highlights a possible issue
with the attribute lookup caching: when the underlying mapping is updated
by other means, the attribute lookup cache is not invalidated correctly.
Since the optimisation provided by that cache is highly desirable,
some of the ideas in this PEP may need to be declared as officially
unsupported (since the observed behaviour is rather odd when the
caches get out of sync).
Background
==========
@ -153,16 +143,31 @@ the idea is worth reconsidering.
However, the introduction of the metaclass ``__prepare__`` method in PEP
3115 allows a further enhancement that was not possible in Python 2: this
PEP also proposes that ``type.__prepare__`` be updated to accept a
``namespace`` keyword-only argument. If present, the value provided as the
``namespace`` argument will be returned from ``type.__prepare__`` instead of
a freshly created dictionary instance. For example, the following will use
PEP also proposes that ``type.__prepare__`` be updated to accept a factory
function as a ``namespace`` keyword-only argument. If present, the value
provided as the ``namespace`` argument will be called without arguments
to create the result of ``type.__prepare__`` instead of using a freshly
created dictionary instance. For example, the following will use
the ordered dictionary created in the header as the class namespace::
class OrderedExample(namespace=collections.OrderedDict()):
class OrderedExample(namespace=collections.OrderedDict):
def __init_class__(cls):
# cls.__dict__ is still a read-only proxy to the class namespace,
# but the underlying storage is the OrderedDict instance
# but the underlying storage is an OrderedDict instance
.. note::
This PEP, along with the existing ability to use __prepare__ to share a
single namespace amongst multiple class objects, highlights a possible
issue with the attribute lookup caching: when the underlying mapping is
updated by other means, the attribute lookup cache is not invalidated
correctly (this is a key part of the reason class ``__dict__`` attributes
produce a read-only view of the underlying storage).
Since the optimisation provided by that cache is highly desirable,
the use of a preexisting namespace as the class namespace may need to
be declared as officially unsupported (since the observed behaviour is
rather strange when the caches get out of sync).
Key Benefits
@ -175,7 +180,8 @@ Easier use of custom namespaces for a class
Currently, to use a different type (such as ``collections.OrderedDict``) for
a class namespace, or to use a pre-populated namespace, it is necessary to
write and use a custom metaclass. With this PEP, using a custom namespace
becomes as simple as specifying it in the class header.
becomes as simple as specifying an appropriate factory function in the
class header.
Easier inheritance of definition time behaviour
@ -245,20 +251,19 @@ including some aspects of the object models of both Javascript and Ruby.
All of the examples below are actually possible today through the use of a
custom metaclass::
if 1:
class CustomNamespace(type):
@classmethod
def __prepare__(meta, name, bases, *, namespace=None, **kwds):
parent_namespace = super().__prepare__(name, bases, **kwds)
return namespace if namespace is not None else parent_namespace
return namespace() if namespace is not None else parent_namespace
def __new__(meta, name, bases, ns, *, namespace=None, **kwds):
return super().__new__(meta, name, bases, ns, **kwds)
def __init__(cls, name, bases, ns, *, namespace=None, **kwds):
return super().__init__(name, bases, ns, **kwds)
The advantage of implementing the new keyword directly in
``type.__prepare__`` is that the *only* persistent effect is
``type.__prepare__`` is that the *only* persistent effect is then
the change in the underlying storage of the class attributes. The metaclass
of the class remains unchanged, eliminating many of the drawbacks
typically associated with these kinds of customisations.
@ -269,7 +274,7 @@ Order preserving classes
::
class OrderedClass(namespace=collections.OrderedDict()):
class OrderedClass(namespace=collections.OrderedDict):
a = 1
b = 2
c = 3
@ -281,7 +286,7 @@ Prepopulated namespaces
::
seed_data = dict(a=1, b=2, c=3)
class PrepopulatedClass(namespace=seed_data.copy()):
class PrepopulatedClass(namespace=seed_data.copy):
pass
@ -290,21 +295,54 @@ Cloning a prototype class
::
class NewClass(namespace=Prototype.__dict__.copy()):
class NewClass(namespace=Prototype.__dict__.copy):
pass
Defining an extensible class
----------------------------
Extending a class
-----------------
.. note:: Just because the PEP makes it *possible* to do this relatively,
cleanly doesn't mean anyone *should* do this!
::
class Extensible:
namespace = locals()
from collections import MutableMapping
class ExtendingClass(namespace=Extensible.namespace):
# The MutableMapping + dict combination should give something that
# generally behaves correctly as a mapping, while still being accepted
# as a class namespace
class ClassNamespace(MutableMapping, dict):
def __init__(self, cls):
self._cls = cls
def __len__(self):
return len(dir(self._cls))
def __iter__(self):
for attr in dir(self._cls):
yield attr
def __contains__(self, attr):
return hasattr(self._cls, attr)
def __getitem__(self, attr):
return getattr(self._cls, attr)
def __setitem__(self, attr, value):
setattr(self._cls, attr, value)
def __delitem__(self, attr):
delattr(self._cls, attr)
def extend(cls):
return lambda: ClassNamespace(cls)
class Example:
pass
class ExtendedExample(namespace=extend(Example)):
a = 1
b = 2
c = 3
>>> Example.a, Example.b, Example.c
(1, 2, 3)
Rejected Design Options
=======================
@ -335,6 +373,17 @@ documents it as an ordinary method, and the current documentation doesn't
explicitly say anything one way or the other).
Passing in the namespace directly rather than a factory function
----------------------------------------------------------------
At one point, this PEP proposed that the class namespace be passed
directly as a keyword argument, rather than passing a factory function.
However, this encourages an unsupported behaviour (that is, passing the
same namespace to multiple classes, or retaining direct write access
to a mapping used as a class namespace), so the API was switched to
the factory function version.
Reference Implementation
========================