updates from Steven Bethard
This commit is contained in:
parent
6967cb362c
commit
54ca43d444
423
pep-0359.txt
423
pep-0359.txt
|
@ -8,7 +8,7 @@ Type: Standards Track
|
|||
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 @@ 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.
|
||||
|
||||
|
@ -35,11 +48,10 @@ Motivation
|
|||
|
||||
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
|
||||
(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 @@ 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. 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 @@ to the property call, e.g.::
|
|||
...
|
||||
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::
|
||||
with the following definition of ``block_property``::
|
||||
|
||||
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)
|
||||
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)
|
||||
|
||||
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"""
|
||||
Example: interfaces
|
||||
-------------------
|
||||
|
||||
badger = 42
|
||||
Guido [5]_ and others have occasionally suggested introducing
|
||||
interfaces into python. Most suggestions have offered syntax along
|
||||
the lines of::
|
||||
|
||||
def spam():
|
||||
...
|
||||
interface IFoo:
|
||||
"""Foo blah blah"""
|
||||
|
||||
And if Python acquires interfaces, given an appropriately defined
|
||||
``interface`` callable, the make statement can support interface
|
||||
creation through the syntax::
|
||||
def fumble(name, count):
|
||||
"""docstring"""
|
||||
|
||||
make interface C(...):
|
||||
...
|
||||
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::
|
||||
|
||||
This would mean that interface systems like that of Zope would no
|
||||
longer have to abuse the class syntax to create proper interface
|
||||
instances.
|
||||
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::
|
||||
Python will translate a make-statement::
|
||||
|
||||
make <callable> <name> <tuple>:
|
||||
<block>
|
||||
|
@ -139,25 +304,39 @@ 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 [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 @@ Currently, there are not many functions which have the signature
|
|||
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 @@ could be used like::
|
|||
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 @@ implement the feature without the keyword.
|
|||
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 @@ objects. Thus in Python 3000, instead of::
|
|||
<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 @@ opcode a bit.
|
|||
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 @@ References
|
|||
.. [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
|
||||
=========
|
||||
|
|
|
@ -6,8 +6,8 @@ Author: Steven Bethard <steven.bethard@gmail.com>
|
|||
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
|
||||
|
|
Loading…
Reference in New Issue