584 lines
18 KiB
ReStructuredText
584 lines
18 KiB
ReStructuredText
PEP: 359
|
||
Title: The "make" Statement
|
||
Version: $Revision$
|
||
Last-Modified: $Date$
|
||
Author: Steven Bethard <steven.bethard@gmail.com>
|
||
Status: Withdrawn
|
||
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.
|
||
|
||
|
||
Withdrawal Notice
|
||
=================
|
||
|
||
This PEP was withdrawn at Guido's request [2]_. Guido didn't like it,
|
||
and in particular didn't like how the property use-case puts the
|
||
instance methods of a property at a different level than other
|
||
instance methods and requires fixed names for the property functions.
|
||
|
||
|
||
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 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 [3]_, 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 [4]_ and others [5]_
|
||
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 [6]_ 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 [7]_.
|
||
|
||
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 Alyssa
|
||
Coghlan). However, investigations into the standard library [8]_ and
|
||
Zope+Plone code [9]_ 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
|
||
violation, it is also possible to eliminate all as-clauses except for
|
||
the first by adding a few methods to Element. [10]_
|
||
|
||
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
|
||
(https://mail.python.org/pipermail/python-dev/2005-October/057435.html)
|
||
|
||
.. [2] Guido requests withdrawal
|
||
(https://mail.python.org/pipermail/python-3000/2006-April/000936.html)
|
||
|
||
.. [3] Ian Bicking's setonce descriptor
|
||
(http://blog.ianbicking.org/easy-readonly-attributes.html)
|
||
|
||
.. [4] Guido ponders property syntax
|
||
(https://mail.python.org/pipermail/python-dev/2005-October/057404.html)
|
||
|
||
.. [5] Namespace-based property recipe
|
||
(http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/442418)
|
||
|
||
.. [6] Python interfaces
|
||
(http://www.artima.com/weblogs/viewpost.jsp?thread=86641)
|
||
|
||
.. [7] Make Statement patch
|
||
(http://ucsu.colorado.edu/~bethard/py/make_statement.patch)
|
||
|
||
.. [8] Instances of create in the stdlib
|
||
(https://mail.python.org/pipermail/python-list/2006-April/335159.html)
|
||
|
||
.. [9] Instances of create in Zope+Plone
|
||
(https://mail.python.org/pipermail/python-list/2006-April/335284.html)
|
||
|
||
.. [10] Eliminate as-clauses in with-statement XML
|
||
(https://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:
|