[Python-Dev] Updated: PEP 359: The make statement

Brett Cannon brett at python.org
Tue Apr 18 20:30:24 CEST 2006


Definitely an intriguing idea!  I am +0 just because I don't know how
needed it is, but it is definitely cool.

As for your open issues, ditching __metaclass__ is fine if this goes
in, but I would keep 'class' around as simplified syntactic sugar for
the common case.

-Brett

On 4/18/06, Steven Bethard <steven.bethard at gmail.com> wrote:
> I've updated PEP 359 with a bunch of the recent suggestions.  The
> patch is available at:
>     http://bugs.python.org/1472459
> and I've pasted the full text below.
>
> I've tried to be more explicit about the goals -- the make statement
> is mostly syntactic sugar for::
>
>    class <name> <tuple>:
>        __metaclass__ = <callable>
>        <block>
>
> so that you don't have to lie to your readers when you're not actually
> creating a class.  I've also added some new examples and expanded the
> discussion of the old ones to give the statement some better
> motivation.  And I've expanded the Open Issues discussions to consider
> a few alternate keywords and to indicate some of the difficulties in
> allowing a ``__make_dict__`` attribute for customizing the dict in
> which the block is executed.
>
>
>
> PEP: 359
> Title: The "make" Statement
> Version: $Revision: 45366 $
> Last-Modified: $Date: 2006-04-13 07:36:24 -0600 (Thu, 13 Apr 2006) $
> Author: Steven Bethard <steven.bethard at gmail.com>
> Status: Draft
> Type: Standards Track
> Content-Type: text/x-rst
> Created: 05-Apr-2006
> Python-Version: 2.6
> Post-History: 05-Apr-2006, 06-Apr-2006, 13-Apr-2006
>
>
> Abstract
> ========
>
> This PEP proposes a generalization of the class-declaration syntax,
> the ``make`` statement.  The proposed syntax and semantics parallel
> the syntax for class definition, and so::
>
>    make <callable> <name> <tuple>:
>        <block>
>
> is translated into the assignment::
>
>    <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.
>
>
> Motivation
> ==========
>
> Class statements provide two nice facilities to Python:
>
>   (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.
>
> Thus in a simple class statement like::
>
>      class C(object):
>          x = 1
>          def foo(self):
>              return 'bar'
>
> the metaclass (``type``) gets called with something like::
>
>     C = type('C', (object,), {'x':1, 'foo':<function foo at ...>})
>
> The class statement is just syntactic sugar for the above assignment
> statement, but clearly a very useful sort of syntactic sugar.  It
> avoids not only the repetition of ``C``, but also simplifies the
> 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.  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):
>         ...
>         def get_x(self):
>             ...
>         def set_x(self):
>             ...
>         x = property(get_x, set_x, "the x of the frobulation")
>
> 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 block_property x:
>             '''The x of the frobulation'''
>             def fget(self):
>                 ...
>             def fset(self):
>                 ...
>
> with the following definition of ``block_property``::
>
>     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"""
>
> which makes the intent (that this is an interface, not a class) much
> clearer.
>
>
> Specification
> =============
>
> Python will translate a make-statement::
>
>     make <callable> <name> <tuple>:
>         <block>
>
> into the assignment::
>
>     <name> = <callable>("<name>", <tuple>, <namespace>)
>
> where ``<namespace>`` is the dict created by executing ``<block>``.
> The ``<tuple>`` expression is optional; if not present, an empty tuple
> will be assumed.
>
> 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
> __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 [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::
>
>     make dict params:
>         x = 1
>         y = 2
>
> 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 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)
>
> Of course, rather than adding another magic method, the dict type
> could just grow a classmethod something like ``dict.fromblock`` that
> could be used like::
>
>     make dict.fromblock params:
>         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
> ===================
>
> Remove the make keyword
> -------------------------
>
> It might be possible to remove the make keyword so that such
> statements would begin with the callable being called, e.g.::
>
>     namespace ns:
>         badger = 42
>         def spam():
>             ...
>
>     interface C(...):
>         ...
>
> However, almost all other Python statements begin with a keyword, and
> removing the keyword would make it harder to look up this construct in
> the documentation.  Additionally, this would add some complexity in
> the grammar and so far I (Steven Bethard) have not been able to
> implement the feature without the keyword.
>
>
> Removing __metaclass__ in Python 3000
> -------------------------------------
>
> 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::
>
>    class <name> <bases-tuple>:
>        __metaclass__ = <metaclass>
>        <block>
>
> metaclasses could be supported by using the metaclass as the callable
> in a make-statement::
>
>    make <metaclass> <name> <bases-tuple>:
>        <block>
>
> Removing the ``__metaclass__`` hook would simplify the BUILD_CLASS
> opcode a bit.
>
>
> Removing class statements in Python 3000
> ----------------------------------------
>
> In the most extreme application of make-statements, the class
> statement itself could be deprecated in favor of ``make type``
> statements.
>
>
> References
> ==========
>
> .. [1] Michele Simionato's original suggestion
>    (http://mail.python.org/pipermail/python-dev/2005-October/057435.html)
>
> .. [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)
>
> .. [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)
>
> .. [7] Instances of create in the stdlib
>    (http://mail.python.org/pipermail/python-list/2006-April/335159.html)
>
> .. [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
> =========
>
> This document has been placed in the public domain.
>
>
> ..
>    Local Variables:
>    mode: indented-text
>    indent-tabs-mode: nil
>    sentence-end-double-space: t
>    fill-column: 70
>    coding: utf-8
>    End:
> _______________________________________________
> Python-Dev mailing list
> Python-Dev at python.org
> http://mail.python.org/mailman/listinfo/python-dev
> Unsubscribe: http://mail.python.org/mailman/options/python-dev/brett%40python.org
>


More information about the Python-Dev mailing list