[Python-checkins] r45552 - peps/trunk/pep-0359.txt peps/trunk/pep-3002.txt

david.goodger python-checkins at python.org
Wed Apr 19 01:25:48 CEST 2006


Author: david.goodger
Date: Wed Apr 19 01:25:47 2006
New Revision: 45552

Modified:
   peps/trunk/pep-0359.txt
   peps/trunk/pep-3002.txt
Log:
updates from Steven Bethard

Modified: peps/trunk/pep-0359.txt
==============================================================================
--- peps/trunk/pep-0359.txt	(original)
+++ peps/trunk/pep-0359.txt	Wed Apr 19 01:25:47 2006
@@ -8,7 +8,7 @@
 Content-Type: text/x-rst
 Created: 05-Apr-2006
 Python-Version: 2.6
-Post-History: 05-Apr-2006, 06-Apr-2006
+Post-History: 05-Apr-2006, 06-Apr-2006, 13-Apr-2006
 
 
 Abstract
@@ -26,6 +26,19 @@
    <name> = <callable>("<name>", <tuple>, <namespace>)
 
 where ``<namespace>`` is the dict created by executing ``<block>``.
+This is mostly syntactic sugar for::
+
+   class <name> <tuple>:
+       __metaclass__ = <callable>
+       <block>
+
+and is intended to help more clearly express the intent of the
+statement when something other than a class is being created.  Of
+course, other syntax for such a statement is possible, but it is hoped
+that by keeping a strong parallel to the class statement, an
+understanding of how classes and metaclasses work will translate into
+an understanding of how the make-statement works as well.
+
 The PEP is based on a suggestion [1]_ from Michele Simionato on the
 python-dev list.
 
@@ -35,12 +48,11 @@
 
 Class statements provide two nice facilities to Python:
 
-  (1) They are the standard Python means of creating a namespace.  All
-      statements within a class body are executed, and the resulting
-      local name bindings are passed as a dict to the metaclass.
+(1) They execute a block of statements and provide the resulting
+    bindings as a dict to the metaclass.
 
-  (2) They encourage DRY (don't repeat yourself) by allowing the class
-      being created to know the name it is being assigned.
+(2) They encourage DRY (don't repeat yourself) by allowing the class
+    being created to know the name it is being assigned.
 
 Thus in a simple class statement like::
 
@@ -59,13 +71,153 @@
 creation of the dict by allowing it to be expressed as a series of
 statements.
 
-Historically, type instances (a.k.a. class objects) have been the only
-objects blessed with this sort of syntactic support.  But other sorts
-of objects could benefit from such support.  For example, property
-objects take three function arguments, but because the property type
-cannot be passed a namespace, these functions, though relevant only to
-the property, must be declared before it and then passed as arguments
-to the property call, e.g.::
+Historically, type instances (a.k.a. class objects) have been the
+only objects blessed with this sort of syntactic support.  The make
+statement aims to extend this support to other sorts of objects where
+such syntax would also be useful.
+
+
+Example: simple namespaces
+--------------------------
+
+Let's say I have some attributes in a module that I access like::
+
+    mod.thematic_roletype
+    mod.opinion_roletype
+
+    mod.text_format
+    mod.html_format
+
+and since "Namespaces are one honking great idea", I'd like to be able
+to access these attributes instead as::
+
+    mod.roletypes.thematic
+    mod.roletypes.opinion
+    
+    mod.format.text
+    mod.format.html
+
+I currently have two main options:
+
+(1) Turn the module into a package, turn ``roletypes`` and ``format``
+    into submodules, and move the attributes to the submodules.
+
+(2) Create ``roletypes`` and ``format`` classes, and move the
+    attributes to the classes.
+
+The former is a fair chunk of refactoring work, and produces two tiny
+modules without much content.  The latter keeps the attributes local
+to the module, but creates classes when there is no intention of ever
+creating instances of those classes.
+
+In situations like this, it would be nice to simply be able to declare
+a "namespace" to hold the few attributes.  With the new make
+statement, I could introduce my new namespaces with something like::
+
+    make namespace roletypes:
+        thematic = ...
+        opinion = ...
+        
+    make namespace format:
+        text = ...
+        html = ...
+
+and keep my attributes local to the module without making classes that
+are never intended to be instantiated.  One definition of namespace
+that would make this work is::
+
+    class namespace(object):
+        def __init__(self, name, args, kwargs):
+            self.__dict__.update(kwargs)
+
+Given this definition, at the end of the make-statements above,
+``roletypes`` and ``format`` would be namespace instances.
+
+
+Example: GUI objects
+--------------------
+
+In GUI toolkits, objects like frames and panels are often associated
+with attributes and functions.  With the make-statement, code that
+looks something like::
+
+    root = Tkinter.Tk()
+    frame = Tkinter.Frame(root)
+    frame.pack()
+    def say_hi():
+        print "hi there, everyone!"
+    hi_there = Tkinter.Button(frame, text="Hello", command=say_hi)
+    hi_there.pack(side=Tkinter.LEFT)
+    root.mainloop()
+    
+could be rewritten to group the the Button's function with its
+declaration::
+
+    root = Tkinter.Tk()
+    frame = Tkinter.Frame(root)
+    frame.pack()
+    make Tkinter.Button hi_there(frame):
+        text = "Hello"
+        def command():
+            print "hi there, everyone!"
+    hi_there.pack(side=Tkinter.LEFT)
+    root.mainloop()
+
+
+Example: custom descriptors
+---------------------------
+
+Since descriptors are used to customize access to an attribute, it's
+often useful to know the name of that attribute.  Current Python
+doesn't give an easy way to find this name and so a lot of custom
+descriptors, like Ian Bicking's setonce descriptor [2]_, have to hack
+around this somehow.  With the make-statement, you could create a
+``setonce`` attribute like::
+
+    class A(object):
+        ...
+        make setonce x:
+            "A's x attribute"
+        ...
+
+where the ``setonce`` descriptor would be defined like::
+
+    class setonce(object):
+    
+        def __init__(self, name, args, kwargs):
+            self._name = '_setonce_attr_%s' % name
+            self.__doc__ = kwargs.pop('__doc__', None)
+    
+        def __get__(self, obj, type=None):
+            if obj is None:
+                return self
+            return getattr(obj, self._name)
+    
+        def __set__(self, obj, value):
+            try:
+                getattr(obj, self._name)
+            except AttributeError:
+                setattr(obj, self._name, value)
+            else:
+                raise AttributeError("Attribute already set")
+    
+        def set(self, obj, value):
+            setattr(obj, self._name, value)
+    
+        def __delete__(self, obj):
+            delattr(obj, self._name)
+
+Note that unlike the original implementation, the private attribute
+name is stable since it uses the name of the descriptor, and therefore
+instances of class A are pickleable.
+
+
+Example: property namespaces
+----------------------------
+
+Python's property type takes three function arguments and a docstring
+argument which, though relevant only to the property, must be declared
+before it and then passed as arguments to the property call, e.g.::
 
     class C(object):
         ...
@@ -73,60 +225,73 @@
             ...
         def set_x(self):
             ...
-        x = property(get_x, set_x, ...)
+        x = property(get_x, set_x, "the x of the frobulation")
 
-There have been a few recipes [2]_ trying to work around this
-behavior, but with the new make statement (and an appropriate
-definition of property), the getter and setter functions can be
-defined in the property's namespace like::
+This issue has been brought up before, and Guido [3]_ and others [4]_
+have briefly mused over alternate property syntaxes to make declaring
+properties easier.  With the make-statement, the following syntax
+could be supported::
 
     class C(object):
         ...
-        make property x:
-            def get(self):
+        make block_property x:
+            '''The x of the frobulation'''
+            def fget(self):
                 ...
-            def set(self):
+            def fset(self):
                 ...
 
-The definition of such a property callable could be as simple as::
-
-    def property(name, args, namespace):
-        fget = namespace.get('get')
-        fset = namespace.get('set')
-        fdel = namespace.get('delete')
-        doc = namespace.get('__doc__')
-        return __builtin__.property(fget, fset, fdel, doc)
-
-Of course, properties are only one of the many possible uses of the
-make statement.  The make statement is useful in essentially any
-situation where a name is associated with a namespace.  So, for
-example, namespaces could be created as simply as::
-
-    make namespace ns:
-        """This creates a namespace named ns with a badger attribute
-        and a spam function"""
-
-        badger = 42
-
-        def spam():
-            ...
+with the following definition of ``block_property``::
 
-And if Python acquires interfaces, given an appropriately defined
-``interface`` callable, the make statement can support interface
-creation through the syntax::
-
-    make interface C(...):
-        ...
+    def block_property(name, args, block_dict):
+        fget = block_dict.pop('fget', None)
+        fset = block_dict.pop('fset', None)
+        fdel = block_dict.pop('fdel', None)
+        doc = block_dict.pop('__doc__', None)
+        assert not block_dict
+        return property(fget, fset, fdel, doc)
+
+
+Example: interfaces
+-------------------
+
+Guido [5]_ and others have occasionally suggested introducing
+interfaces into python.  Most suggestions have offered syntax along
+the lines of::
+
+    interface IFoo:
+        """Foo blah blah"""
+
+        def fumble(name, count):
+            """docstring"""
+
+but since there is currently no way in Python to declare an interface
+in this manner, most implementations of Python interfaces use class
+objects instead, e.g. Zope's::
+
+    class IFoo(Interface):
+        """Foo blah blah"""
+  
+        def fumble(name, count):
+            """docstring"""
+
+With the new make-statement, these interfaces could instead be
+declared as::
+
+    make Interface IFoo:
+        """Foo blah blah"""
+  
+        def fumble(name, count):
+            """docstring"""
 
-This would mean that interface systems like that of Zope would no
-longer have to abuse the class syntax to create proper interface
-instances.
+which makes the intent (that this is an interface, not a class) much
+clearer.
 
 
 Specification
 =============
 
-Python will translate a make statement::
+Python will translate a make-statement::
 
     make <callable> <name> <tuple>:
         <block>
@@ -139,25 +304,39 @@
 The ``<tuple>`` expression is optional; if not present, an empty tuple
 will be assumed.
 
-A patch is available implementing these semantics [3]_.
+A patch is available implementing these semantics [6]_.
 
-The make statement introduces a new keyword, ``make``.  Thus in Python
-2.6, the make statement will have to be enabled using ``from
+The make-statement introduces a new keyword, ``make``.  Thus in Python
+2.6, the make-statement will have to be enabled using ``from
 __future__ import make_statement``.
 
 
 Open Issues
 ===========
 
+Keyword
+-------
+
 Does the ``make`` keyword break too much code?  Originally, the make
 statement used the keyword ``create`` (a suggestion due to Nick
-Coghlan).  However, investigations into the standard library [4]_ and
-Zope+Plone code [5]_ revealed that ``create`` would break a lot more
+Coghlan).  However, investigations into the standard library [7]_ and
+Zope+Plone code [8]_ revealed that ``create`` would break a lot more
 code, so ``make`` was adopted as the keyword instead.  However, there
 are still a few instances where ``make`` would break code.  Is there a
 better keyword for the statement?
 
-**********
+Some possible keywords and their counts in the standard library (plus
+some installed packages):
+
+* make - 2 (both in tests)
+* create - 19 (including existing function in imaplib)
+* build - 83 (including existing class in distutils.command.build)
+* construct - 0
+* produce - 0
+
+
+The make-statement as an alternate constructor
+----------------------------------------------
 
 Currently, there are not many functions which have the signature
 ``(name, args, kwargs)``.  That means that something like::
@@ -169,10 +348,10 @@
 is currently impossible because the dict constructor has a different
 signature.  Does this sort of thing need to be supported?  One
 suggestion, by Carl Banks, would be to add a ``__make__`` magic method
-that would be called before ``__call__``.  For types, the ``__make__``
-method would be identical to ``__call__`` (and thus unnecessary), but
-dicts could support the make statement by defining a ``__make__``
-method on the dict type that looks something like::
+that if found would be called instead of ``__call__``.  For types,
+the ``__make__`` method would be identical to ``__call__`` and thus
+unnecessary, but dicts could support the make-statement by defining a
+``__make__`` method on the dict type that looks something like::
 
     def __make__(cls, name, args, kwargs):
         return cls(**kwargs)
@@ -185,6 +364,112 @@
         x = 1
         y = 2
 
+So the question is, will many types want to use the make-statement as
+an alternate constructor?  And if so, does that alternate constructor
+need to have the same name as the original constructor?
+
+
+Customizing the dict in which the block is executed
+---------------------------------------------------
+
+Should users of the make-statement be able to determine in which dict
+object the code is executed?  This would allow the make-statement to
+be used in situations where a normal dict object would not suffice,
+e.g. if order and repeated names must be allowed.  Allowing this sort
+of customization could allow XML to be written without repeating
+element names, and with nesting of make-statements corresponding to
+nesting of XML elements::
+
+    make Element html:
+        make Element body:
+            text('before first h1')
+            make Element h1:
+                attrib(style='first')
+                text('first h1')
+                tail('after first h1')
+            make Element h1:
+                attrib(style='second')
+                text('second h1')
+                tail('after second h1')
+
+If the make-statement tried to get the dict in which to execute its
+block by calling the callable's ``__make_dict__`` method, the
+following code would allow the make-statement to be used as above::
+
+    class Element(object):
+    
+        class __make_dict__(dict):
+        
+            def __init__(self, *args, **kwargs):
+                self._super = super(Element.__make_dict__, self)
+                self._super.__init__(*args, **kwargs)
+                self.elements = []
+                self.text = None
+                self.tail = None
+                self.attrib = {}
+                
+            def __getitem__(self, name):
+                try:
+                    return self._super.__getitem__(name)
+                except KeyError:
+                    if name in ['attrib', 'text', 'tail']:
+                        return getattr(self, 'set_%s' % name)
+                    else:
+                        return globals()[name]
+                
+            def __setitem__(self, name, value):
+                self._super.__setitem__(name, value)
+                self.elements.append(value)
+                
+            def set_attrib(self, **kwargs):
+                self.attrib = kwargs
+                
+            def set_text(self, text):
+                self.text = text
+                
+            def set_tail(self, text):
+                self.tail = text
+        
+        def __new__(cls, name, args, edict):
+            get_element = etree.ElementTree.Element
+            result = get_element(name, attrib=edict.attrib)
+            result.text = edict.text
+            result.tail = edict.tail
+            for element in edict.elements:
+                result.append(element)
+            return result
+
+Note, however, that the code to support this is somewhat fragile --
+it has to magically populate the namespace with ``attrib``, ``text``
+and ``tail``, and it assumes that every name binding inside the make
+statement body is creating an Element.  As it stands, this code would
+break with the introduction of a simple for-loop to any one of the
+make-statement bodies, because the for-loop would bind a name to a
+non-Element object.  This could be worked around by adding some sort
+of isinstance check or attribute examination, but this still results
+in a somewhat fragile solution.
+
+It has also been pointed out that the with-statement can provide
+equivalent nesting with a much more explicit syntax::
+
+    with Element('html') as html:
+        with Element('body') as body:
+            body.text = 'before first h1'
+            with Element('h1', style='first') as h1:
+                h1.text = 'first h1'
+                h1.tail = 'after first h1'
+            with Element('h1', style='second') as h1:
+                h1.text = 'second h1'
+                h1.tail = 'after second h1'
+    
+And if the repetition of the element names here is too much of a DRY
+violoation, it is also possible to eliminate all as-clauses except for
+the first by adding a few methods to Element. [9]_
+
+So are there real use-cases for executing the block in a dict of a
+different type?  And if so, should the make-statement be extended to
+support them?
+
 
 Optional Extensions
 ===================
@@ -213,7 +498,7 @@
 Removing __metaclass__ in Python 3000
 -------------------------------------
 
-As a side-effect of its generality, the make statement mostly
+As a side-effect of its generality, the make-statement mostly
 eliminates the need for the ``__metaclass__`` attribute in class
 objects.  Thus in Python 3000, instead of::
 
@@ -222,7 +507,7 @@
        <block>
 
 metaclasses could be supported by using the metaclass as the callable
-in a make statement::
+in a make-statement::
 
    make <metaclass> <name> <bases-tuple>:
        <block>
@@ -234,7 +519,7 @@
 Removing class statements in Python 3000
 ----------------------------------------
 
-In the most extreme application of make statements, the class
+In the most extreme application of make-statements, the class
 statement itself could be deprecated in favor of ``make type``
 statements.
 
@@ -245,18 +530,30 @@
 .. [1] Michele Simionato's original suggestion
    (http://mail.python.org/pipermail/python-dev/2005-October/057435.html)
 
-.. [2] Namespace-based property recipe
+.. [2] Ian Bicking's setonce descriptor
+   (http://blog.ianbicking.org/easy-readonly-attributes.html)
+
+.. [3] Guido ponders property syntax
+   (http://mail.python.org/pipermail/python-dev/2005-October/057404.html)
+
+.. [4] Namespace-based property recipe
    (http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/442418)
 
-.. [3] Make Statement patch
+.. [5] Python interfaces
+   (http://www.artima.com/weblogs/viewpost.jsp?thread=86641)
+
+.. [6] Make Statement patch
    (http://ucsu.colorado.edu/~bethard/py/make_statement.patch)
 
-.. [4] Instances of create in the stdlib
+.. [7] Instances of create in the stdlib
    (http://mail.python.org/pipermail/python-list/2006-April/335159.html)
 
-.. [5] Instances of create in Zope+Plone
+.. [8] Instances of create in Zope+Plone
    (http://mail.python.org/pipermail/python-list/2006-April/335284.html)
 
+.. [9] Eliminate as-clauses in with-statement XML
+   (http://mail.python.org/pipermail/python-list/2006-April/336774.html)
+
 
 Copyright
 =========

Modified: peps/trunk/pep-3002.txt
==============================================================================
--- peps/trunk/pep-3002.txt	(original)
+++ peps/trunk/pep-3002.txt	Wed Apr 19 01:25:47 2006
@@ -6,8 +6,8 @@
 Status: Draft
 Type: Process
 Content-Type: text/x-rst
-Created: 03-Mar-2006
-Post-History: 03-Mar-2006
+Created: 27-Mar-2006
+Post-History: 27-Mar-2006, 13-Apr-2006
 
 
 Abstract


More information about the Python-checkins mailing list