[Python-checkins] peps: Adjust the redrafted PEP 422 to match reality

nick.coghlan python-checkins at python.org
Tue Mar 5 13:22:58 CET 2013


http://hg.python.org/peps/rev/bf60a9ca80a9
changeset:   4782:bf60a9ca80a9
user:        Nick Coghlan <ncoghlan at gmail.com>
date:        Tue Mar 05 22:22:20 2013 +1000
summary:
  Adjust the redrafted PEP 422 to match reality

files:
  pep-0422.txt |  107 ++++++++++++++++++++++++++++----------
  1 files changed, 78 insertions(+), 29 deletions(-)


diff --git a/pep-0422.txt b/pep-0422.txt
--- a/pep-0422.txt
+++ b/pep-0422.txt
@@ -27,16 +27,6 @@
 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 @@
 
 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 @@
 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 @@
 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 @@
 
 ::
 
-    class OrderedClass(namespace=collections.OrderedDict()):
+    class OrderedClass(namespace=collections.OrderedDict):
         a = 1
         b = 2
         c = 3
@@ -281,7 +286,7 @@
 ::
 
     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 @@
 
 ::
 
-    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 @@
 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
 ========================
 

-- 
Repository URL: http://hg.python.org/peps


More information about the Python-checkins mailing list