[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