diff --git a/pep-0359.txt b/pep-0359.txt index f2e9a7950..6d10971c2 100644 --- a/pep-0359.txt +++ b/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:: = ("", , ) where ```` is the dict created by executing ````. +This is mostly syntactic sugar for:: + + class : + __metaclass__ = + + +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 @@ 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 - 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 @@ 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 : @@ -139,25 +304,39 @@ where ```` is the dict created by executing ````. The ```` 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:: metaclasses could be supported by using the metaclass as the callable -in a make statement:: +in a make-statement:: make : @@ -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 ========= diff --git a/pep-3002.txt b/pep-3002.txt index 72d3cf46c..08740622c 100644 --- a/pep-3002.txt +++ b/pep-3002.txt @@ -6,8 +6,8 @@ Author: Steven Bethard 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