This PEP proposes a generalization of the class-declaration syntax,themake statement. The proposed syntax and semantics parallelthe 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 thestatement when something other than a class is being created. Ofcourse, other syntax for such a statement is possible, but it is hopedthat by keeping a strong parallel to the class statement, anunderstanding of how classes and metaclasses work will translate intoan understanding of how the make-statement works as well.
The PEP is based on a suggestion[1] from Michele Simionato on thepython-dev list.
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 theinstance methods of a property at a different level than otherinstance methods and requires fixed names for the property functions.
Class statements provide two nice facilities to Python:
Thus in a simple class statement like:
classC(object):x=1deffoo(self):return'bar'
the metaclass (type) gets called with something like:
C=type('C',(object,),{'x':1,'foo':<functionfooat...>})
The class statement is just syntactic sugar for the above assignmentstatement, but clearly a very useful sort of syntactic sugar. Itavoids not only the repetition ofC, but also simplifies thecreation of the dict by allowing it to be expressed as a series ofstatements.
Historically, type instances (a.k.a. class objects) have been theonly objects blessed with this sort of syntactic support. The makestatement aims to extend this support to other sorts of objects wheresuch syntax would also be useful.
Let’s say I have some attributes in a module that I access like:
mod.thematic_roletypemod.opinion_roletypemod.text_formatmod.html_format
and since “Namespaces are one honking great idea”, I’d like to be ableto access these attributes instead as:
mod.roletypes.thematicmod.roletypes.opinionmod.format.textmod.format.html
I currently have two main options:
roletypes andformatinto submodules, and move the attributes to the submodules.roletypes andformat classes, and move theattributes to the classes.The former is a fair chunk of refactoring work, and produces two tinymodules without much content. The latter keeps the attributes localto the module, but creates classes when there is no intention of evercreating instances of those classes.
In situations like this, it would be nice to simply be able to declarea “namespace” to hold the few attributes. With the new makestatement, I could introduce my new namespaces with something like:
makenamespaceroletypes:thematic=...opinion=...makenamespaceformat:text=...html=...
and keep my attributes local to the module without making classes thatare never intended to be instantiated. One definition of namespacethat would make this work is:
classnamespace(object):def__init__(self,name,args,kwargs):self.__dict__.update(kwargs)
Given this definition, at the end of the make-statements above,roletypes andformat would be namespace instances.
In GUI toolkits, objects like frames and panels are often associatedwith attributes and functions. With the make-statement, code thatlooks something like:
root=Tkinter.Tk()frame=Tkinter.Frame(root)frame.pack()defsay_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 itsdeclaration:
root=Tkinter.Tk()frame=Tkinter.Frame(root)frame.pack()makeTkinter.Buttonhi_there(frame):text="Hello"defcommand():print"hi there, everyone!"hi_there.pack(side=Tkinter.LEFT)root.mainloop()
Since descriptors are used to customize access to an attribute, it’soften useful to know the name of that attribute. Current Pythondoesn’t give an easy way to find this name and so a lot of customdescriptors, like Ian Bicking’s setonce descriptor[3], have to hackaround this somehow. With the make-statement, you could create asetonce attribute like:
classA(object):...makesetoncex:"A's x attribute"...
where thesetonce descriptor would be defined like:
classsetonce(object):def__init__(self,name,args,kwargs):self._name='_setonce_attr_%s'%nameself.__doc__=kwargs.pop('__doc__',None)def__get__(self,obj,type=None):ifobjisNone:returnselfreturngetattr(obj,self._name)def__set__(self,obj,value):try:getattr(obj,self._name)exceptAttributeError:setattr(obj,self._name,value)else:raiseAttributeError("Attribute already set")defset(self,obj,value):setattr(obj,self._name,value)def__delete__(self,obj):delattr(obj,self._name)
Note that unlike the original implementation, the private attributename is stable since it uses the name of the descriptor, and thereforeinstances of class A are pickleable.
Python’s property type takes three function arguments and a docstringargument which, though relevant only to the property, must be declaredbefore it and then passed as arguments to the property call, e.g.:
classC(object):...defget_x(self):...defset_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 declaringproperties easier. With the make-statement, the following syntaxcould be supported:
classC(object):...makeblock_propertyx:'''The x of the frobulation'''deffget(self):...deffset(self):...
with the following definition ofblock_property:
defblock_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)assertnotblock_dictreturnproperty(fget,fset,fdel,doc)
Guido[6] and others have occasionally suggested introducinginterfaces into python. Most suggestions have offered syntax alongthe lines of:
interfaceIFoo:"""Foo blah blah"""deffumble(name,count):"""docstring"""
but since there is currently no way in Python to declare an interfacein this manner, most implementations of Python interfaces use classobjects instead, e.g. Zope’s:
classIFoo(Interface):"""Foo blah blah"""deffumble(name,count):"""docstring"""
With the new make-statement, these interfaces could instead bedeclared as:
makeInterfaceIFoo:"""Foo blah blah"""deffumble(name,count):"""docstring"""
which makes the intent (that this is an interface, not a class) muchclearer.
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 tuplewill be assumed.
A patch is available implementing these semantics[7].
The make-statement introduces a new keyword,make. Thus in Python2.6, the make-statement will have to be enabled usingfrom__future__importmake_statement.
Does themake keyword break too much code? Originally, the makestatement used the keywordcreate (a suggestion due to AlyssaCoghlan). However, investigations into the standard library[8] andZope+Plone code[9] revealed thatcreate would break a lot morecode, somake was adopted as the keyword instead. However, thereare still a few instances wheremake would break code. Is there abetter keyword for the statement?
Some possible keywords and their counts in the standard library (plussome installed packages):
Currently, there are not many functions which have the signature(name,args,kwargs). That means that something like:
makedictparams:x=1y=2
is currently impossible because the dict constructor has a differentsignature. Does this sort of thing need to be supported? Onesuggestion, by Carl Banks, would be to add a__make__ magic methodthat if found would be called instead of__call__. For types,the__make__ method would be identical to__call__ and thusunnecessary, 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):returncls(**kwargs)
Of course, rather than adding another magic method, the dict typecould just grow a classmethod something likedict.fromblock thatcould be used like:
makedict.fromblockparams:x=1y=2
So the question is, will many types want to use the make-statement asan alternate constructor? And if so, does that alternate constructorneed to have the same name as the original constructor?
Should users of the make-statement be able to determine in which dictobject the code is executed? This would allow the make-statement tobe used in situations where a normal dict object would not suffice,e.g. if order and repeated names must be allowed. Allowing this sortof customization could allow XML to be written without repeatingelement names, and with nesting of make-statements corresponding tonesting of XML elements:
makeElementhtml:makeElementbody:text('before first h1')makeElementh1:attrib(style='first')text('first h1')tail('after first h1')makeElementh1:attrib(style='second')text('second h1')tail('after second h1')
If the make-statement tried to get the dict in which to execute itsblock by calling the callable’s__make_dict__ method, thefollowing code would allow the make-statement to be used as above:
classElement(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=Noneself.tail=Noneself.attrib={}def__getitem__(self,name):try:returnself._super.__getitem__(name)exceptKeyError:ifnamein['attrib','text','tail']:returngetattr(self,'set_%s'%name)else:returnglobals()[name]def__setitem__(self,name,value):self._super.__setitem__(name,value)self.elements.append(value)defset_attrib(self,**kwargs):self.attrib=kwargsdefset_text(self,text):self.text=textdefset_tail(self,text):self.tail=textdef__new__(cls,name,args,edict):get_element=etree.ElementTree.Elementresult=get_element(name,attrib=edict.attrib)result.text=edict.textresult.tail=edict.tailforelementinedict.elements:result.append(element)returnresult
Note, however, that the code to support this is somewhat fragile –it has to magically populate the namespace withattrib,textandtail, and it assumes that every name binding inside the makestatement body is creating an Element. As it stands, this code wouldbreak with the introduction of a simple for-loop to any one of themake-statement bodies, because the for-loop would bind a name to anon-Element object. This could be worked around by adding some sortof isinstance check or attribute examination, but this still resultsin a somewhat fragile solution.
It has also been pointed out that the with-statement can provideequivalent nesting with a much more explicit syntax:
withElement('html')ashtml:withElement('body')asbody:body.text='before first h1'withElement('h1',style='first')ash1:h1.text='first h1'h1.tail='after first h1'withElement('h1',style='second')ash1:h1.text='second h1'h1.tail='after second h1'
And if the repetition of the element names here is too much of a DRYviolation, it is also possible to eliminate all as-clauses except forthe first by adding a few methods to Element.[10]
So are there real use-cases for executing the block in a dict of adifferent type? And if so, should the make-statement be extended tosupport them?
It might be possible to remove the make keyword so that suchstatements would begin with the callable being called, e.g.:
namespacens:badger=42defspam():...interfaceC(...):...
However, almost all other Python statements begin with a keyword, andremoving the keyword would make it harder to look up this construct inthe documentation. Additionally, this would add some complexity inthe grammar and so far I (Steven Bethard) have not been able toimplement the feature without the keyword.
As a side-effect of its generality, the make-statement mostlyeliminates the need for the__metaclass__ attribute in classobjects. Thus in Python 3000, instead of:
class <name> <bases-tuple>: __metaclass__ = <metaclass> <block>
metaclasses could be supported by using the metaclass as the callablein a make-statement:
make<metaclass><name><bases-tuple>:<block>
Removing the__metaclass__ hook would simplify the BUILD_CLASSopcode a bit.
In the most extreme application of make-statements, the classstatement itself could be deprecated in favor ofmaketypestatements.
This document has been placed in the public domain.
Source:https://github.com/python/peps/blob/main/peps/pep-0359.rst
Last modified:2025-02-01 08:59:27 GMT