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 <[EMAIL PROTECTED]> 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 <[EMAIL PROTECTED]> > 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@python.org > http://mail.python.org/mailman/listinfo/python-dev > Unsubscribe: > http://mail.python.org/mailman/options/python-dev/brett%40python.org > _______________________________________________ Python-Dev mailing list Python-Dev@python.org http://mail.python.org/mailman/listinfo/python-dev Unsubscribe: http://mail.python.org/mailman/options/python-dev/archive%40mail-archive.com