PEP 359: The "make" Statement

S

Steven Bethard

Ok, I finally have a PEP number. Here's the most updated version of the
"make" statement PEP. I'll be posting it shortly to python-dev.

Thanks again for the previous discussion and suggestions!


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


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>``.
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 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.

(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. 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.::

class C(object):
...
def get_x(self):
...
def set_x(self):
...
x = property(get_x, set_x, ...)

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::

class C(object):
...
make property x:
def get(self):
...
def set(self):
...

The definition of such a property callable could be as simple as::

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)

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"""

badger = 42

def spam():
...

And if Python acquires interfaces, given an appropriately defined
``interface`` callable, the make statement can support interface
creation through the syntax::

make interface C(...):
...

This would mean that interface systems like that of Zope would no
longer have to abuse the class syntax to create proper interface
instances.


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 [3]_.

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
===========

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
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?

**********

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 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::

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


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] Namespace-based property recipe
(http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/442418)

... [3] Make Statement patch
(http://ucsu.colorado.edu/~bethard/py/make_statement.patch)

... [4] Instances of create in the stdlib
(http://mail.python.org/pipermail/python-list/2006-April/335159.html)

... [5] Instances of create in Zope+Plone
(http://mail.python.org/pipermail/python-list/2006-April/335284.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:
 
N

Nicolas Fleury

Steven said:
Ok, I finally have a PEP number. Here's the most updated version of the
"make" statement PEP. I'll be posting it shortly to python-dev.

Thanks again for the previous discussion and suggestions!

I find it very interesting.

My only complaint is that it is limited to things that can be described
as a namespace, where the order of declaration is lost. This problem is
also true with metaclasses, but it is more acceptable since they are for
classes.

For example, it would be nice to use such a feature to define C
structures that are to be exported in a binary format without having to
use a hidden counter or other hacks. I have no idea of the
implementation implications, but it would be nice to have the capability
to override the namespace type to use something else than dict, so that
it can be filled with symbols one statement at a time.

The callable could have something like a __namespacetype__ that could be
use instead of dict. That type would have to implement __setitem__.

Regards,
Nicolas
 
F

Felipe Almeida Lessa

Em Qui, 2006-04-13 às 23:17 -0400, Nicolas Fleury escreveu:
The callable could have something like a __namespacetype__ that could be
use instead of dict. That type would have to implement __setitem__.

Or the namespace variable could be a list of tuples.
 
O

OKB (not okblacke)

Nicolas said:
I have no idea of the
implementation implications, but it would be nice to have the
capability to override the namespace type to use something else
than dict, so that it can be filled with symbols one statement at a
time.

I agree. I have sometimes wanted this ability for classes, so that
class definitions could map assignments onto class attributes in some
way besides blindly stuffing into a dict.

--
--OKB (not okblacke)
Brendan Barnwell
"Do not follow where the path may lead. Go, instead, where there is
no path, and leave a trail."
--author unknown
 
S

Steven Bethard

Nicolas said:
I find it very interesting.

My only complaint is that it is limited to things that can be described
as a namespace, where the order of declaration is lost. This problem is
also true with metaclasses, but it is more acceptable since they are for
classes.

Yep, this seems to be the biggest complaint. I'm waiting for write
access to the repository, but here's a clip from the upcoming update:

Open Issues
===========

....

Should users of the make statement be able to determine in which dict
object the code is executed? The make statement could look for a
``__make_dict__`` attribute and call it to allow things like::

make Element html:
make Element body:
make Element h1:
'''First heading text'''
make Element h1:
'''Second heading text'''

where a normal dict object would not suffice since order and repeated
names must be allowed. Assuming that the ``__make_dict__`` attribute
was called to get the dict in which the block should be executed, the
following code should make the above make statements work::

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.values = []
def __getitem__(self, name):
try:
return self._super.__getitem__(name)
except KeyError:
return globals()[name]
def __setitem__(self, name, value):
self._super.__setitem__(name, value)
if not name.startswith('__'):
self.values.append(value)

def __new__(cls, name, args, edict):
result = etree.ElementTree.Element(name)
result.text = edict.pop('__doc__', None)
print edict.values
for element in edict.values:
result.append(element)
return result


STeVe
 
D

Duncan Booth

Steven said:
Should users of the make statement be able to determine in which dict
object the code is executed? The make statement could look for a
``__make_dict__`` attribute and call it to allow things like::

make Element html:
make Element body:
make Element h1:
'''First heading text'''
make Element h1:
'''Second heading text'''

I'm concerned about the magic implied here for __doc__:

make Element p:
'''This is '''
make Element strong:
'''not'''
'''going to work'''


There is another effect which should be considered here. If you allow
Element to create an object to be used as the namespace, then as well as
doing special tracking when values are set in the namespace it can also
pre-seed it with names which magically appear in scope within the make.

e.g.

make Element html:
make Element body:
make Element p:
text('But this ')
make Element strong:
text('could')
text(' be made to work')

or even:

make Element p:
text('This would ')
strong('also')
text(' work')

where text and strong are readable in the Element namespace (there doesn't
actually have to be any relationship between the names you can access and
the names you set).
 
S

Steven Bethard

Duncan said:
Steven said:
Should users of the make statement be able to determine in which dict
object the code is executed? The make statement could look for a
``__make_dict__`` attribute and call it to allow things like::

make Element html:
make Element body:
make Element h1:
'''First heading text'''
make Element h1:
'''Second heading text'''
[snip]
There is another effect which should be considered here. If you allow
Element to create an object to be used as the namespace, then as well as
doing special tracking when values are set in the namespace it can also
pre-seed it with names which magically appear in scope within the make.

e.g.

make Element html:
make Element body:
make Element p:
text('But this ')
make Element strong:
text('could')
text(' be made to work')

This is nice. I'll have to play around with it a bit to see how hard it
would be to make it work.

STeVe
 
S

Steven Bethard

Steven said:
Duncan said:
Steven said:
Should users of the make statement be able to determine in which dict
object the code is executed? The make statement could look for a
``__make_dict__`` attribute and call it to allow things like::

make Element html:
make Element body:
make Element h1:
'''First heading text'''
make Element h1:
'''Second heading text'''
[snip]
There is another effect which should be considered here. If you allow
Element to create an object to be used as the namespace, then as well
as doing special tracking when values are set in the namespace it can
also pre-seed it with names which magically appear in scope within the
make.

e.g.

make Element html:
make Element body:
make Element p:
text('But this ')
make Element strong:
text('could')
text(' be made to work')

This is nice. I'll have to play around with it a bit to see how hard it
would be to make it work.

Okay, I think it'll work[1]. I'm going to update this section to
something more like:


Open Issues
===========

....

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 like::

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')

assert etree.ElementTree.tostring(body) == '''\
<html>\
<body>\
before first h1\
<h1 style="first">first h1</h1>\
after first h1\
<h1 style="second">second h1</h1>\
after second h1\
</body>\
</html>\
'''

Assuming that the make statement calls the callable's
``__make_dict__`` to get the dict in which to execute the code, the
following should make the above make statements work::

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



[1] Here's the code I used to test it.
.... try:
.... make_dict = callable.__make_dict__
.... except AttributeError:
.... make_dict = dict
.... block_dict = make_dict()
.... exec block_string in block_dict
.... return callable(name, args, block_dict)
........ 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
........ text('before first h1')
.... h1 = make(Element, 'h1', (), textwrap.dedent('''
.... attrib(style='first')
.... text('first h1')
.... tail('after first h1')
.... '''))
.... h1 = make(Element, 'h1', (), textwrap.dedent('''
.... attrib(style='second')
.... text('second h1')
.... tail('after second h1')
.... '''))
.... """)).... <body>\
.... before first h1\
.... <h1 style="first">first h1</h1>\
.... after first h1\
.... <h1 style="second">second h1</h1>\
.... after second h1\
.... </body>\
.... '''


STeVe
 
F

Felipe Almeida Lessa

Em Sex, 2006-04-14 às 09:31 -0600, Steven Bethard escreveu:
[1] Here's the code I used to test it.
... try:
... make_dict = callable.__make_dict__
... except AttributeError:
... make_dict = dict
... block_dict = make_dict()
... exec block_string in block_dict
... return callable(name, args, block_dict)
...

I think it would be nice not to put those ">>>" and "..." to make copy
and paste easier. Okay, I know we can do "".join(line[4:] for line in
text), but that's just my humble opinion.
 
N

Nicolas Fleury

Felipe said:
Em Sex, 2006-04-14 às 09:31 -0600, Steven Bethard escreveu:
[1] Here's the code I used to test it.
def make(callable, name, args, block_string):
... try:
... make_dict = callable.__make_dict__
... except AttributeError:
... make_dict = dict
... block_dict = make_dict()
... exec block_string in block_dict
... return callable(name, args, block_dict)
...

I think it would be nice not to put those ">>>" and "..." to make copy
and paste easier. Okay, I know we can do "".join(line[4:] for line in
text), but that's just my humble opinion.

Note that there's a "special paste" in wxPython PyShell, with which you
can copy/paste these lines.

Nicolas
 
R

Rob Williscroft

Steven Bethard wrote in
in comp.lang.python:
Open Issues
===========

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
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?

I don't know wether this has been suggested or not, but
what about def:

def namespace ns:
x = 1

def type blah(object):
pass

def property x:
def get():
return ns.x


Rob.
 
T

Tim Hochberg

Steven said:
Steven said:
Duncan said:
Steven Bethard wrote:


Should users of the make statement be able to determine in which dict
object the code is executed? The make statement could look for a
``__make_dict__`` attribute and call it to allow things like::

make Element html:
make Element body:
make Element h1:
'''First heading text'''
make Element h1:
'''Second heading text'''
[snip]

There is another effect which should be considered here. If you allow
Element to create an object to be used as the namespace, then as well
as doing special tracking when values are set in the namespace it can
also pre-seed it with names which magically appear in scope within the
make.

e.g.

make Element html:
make Element body:
make Element p:
text('But this ')
make Element strong:
text('could')
text(' be made to work')

This is nice. I'll have to play around with it a bit to see how hard it
would be to make it work.


Okay, I think it'll work[1]. I'm going to update this section to
something more like:


Open Issues
===========

...

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 like::

I think this PEP is going off the rails. It's primary virtue was that it
was a simpler, clearer way to write:

class Foo(args):
__metaclass__ = some_metaclass
#...

Once it starts calling secret magic methods behind the scenes it's
losing that virture. And for what? Supporting some use cases that have
reasonable solutions already?
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')


What's the virtue of this form? Is it the indentation? If so, I suspect
some relatively pretty solution could be manufactured using the 'with'
syntax. Does anyone really write html 'by hand' in this manner anyway? I
think this approach may start looking a lot less appealing when you are
generating the HTML from some other data. Then you have loops inside the
body. Somehow you have to clean up all the leftover loop variables so
they don't end up in your document. However, that would require some
surgery and increased complexity in Element. It all looks like it would
be messy and perhaps useless in real life.

Regards,

-tim


assert etree.ElementTree.tostring(body) == '''\
<html>\
<body>\
before first h1\
<h1 style="first">first h1</h1>\
after first h1\
<h1 style="second">second h1</h1>\
after second h1\
</body>\
</html>\
'''

Assuming that the make statement calls the callable's
``__make_dict__`` to get the dict in which to execute the code, the
following should make the above make statements work::

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



[1] Here's the code I used to test it.
... try:
... make_dict = callable.__make_dict__
... except AttributeError:
... make_dict = dict
... block_dict = make_dict()
... exec block_string in block_dict
... return callable(name, args, block_dict)
...... 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
...... text('before first h1')
... h1 = make(Element, 'h1', (), textwrap.dedent('''
... attrib(style='first')
... text('first h1')
... tail('after first h1')
... '''))
... h1 = make(Element, 'h1', (), textwrap.dedent('''
... attrib(style='second')
... text('second h1')
... tail('after second h1')
... '''))
... """))... <body>\
... before first h1\
... <h1 style="first">first h1</h1>\
... after first h1\
... <h1 style="second">second h1</h1>\
... after second h1\
... </body>\
... '''


STeVe
 
S

Steven Bethard

Rob said:
Steven Bethard wrote in
in comp.lang.python:
Open Issues
===========

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
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?

I don't know wether this has been suggested or not, but
what about def:

def namespace ns:
x = 1

def type blah(object):
pass

def property x:
def get():
return ns.x

I think that's probably a bad idea because it would make people think
that the statement acts like a function definition, when it actually
acts like a class definition.

STeVe
 
S

Steven Bethard

Tim said:
Steven said:
Steven said:
Duncan Booth wrote:
make Element html:
make Element body:
make Element p:
text('But this ')
make Element strong:
text('could')
text(' be made to work')

This is nice. I'll have to play around with it a bit to see how hard
it would be to make it work.

Okay, I think it'll work[1]. I'm going to update this section to
something more like:


Open Issues
===========

...

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 like::

I think this PEP is going off the rails. It's primary virtue was that it
was a simpler, clearer way to write:

class Foo(args):
__metaclass__ = some_metaclass
#...

Once it starts calling secret magic methods behind the scenes it's
losing that virture. And for what? Supporting some use cases that have
reasonable solutions already?

That's why it's in the Open Issues section. I expect most of these open
issues to be resolved by rejection. (At least, that's my preferred
resolution.) But since they people have brought them up, I think they
need to be addressed as completely as possible.

But I think you make a good point that this particular case can be just
as easily done using a with-statement (I think). I'll add that to this
part of the PEP (once I'm sure it works).

STeVe
 
S

Steven Bethard

Steven said:
Tim said:
Steven said:
Steven Bethard wrote:
Duncan Booth wrote:
make Element html:
make Element body:
make Element p:
text('But this ')
make Element strong:
text('could')
text(' be made to work')

This is nice. I'll have to play around with it a bit to see how
hard it would be to make it work.

Okay, I think it'll work[1]. I'm going to update this section to
something more like:


Open Issues
===========

...

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 like::

I think this PEP is going off the rails. It's primary virtue was that
it was a simpler, clearer way to write:

class Foo(args):
__metaclass__ = some_metaclass
#...

Once it starts calling secret magic methods behind the scenes it's
losing that virture. And for what? Supporting some use cases that have
reasonable solutions already?

That's why it's in the Open Issues section. I expect most of these open
issues to be resolved by rejection. (At least, that's my preferred
resolution.) But since they people have brought them up, I think they
need to be addressed as completely as possible.

But I think you make a good point that this particular case can be just
as easily done using a with-statement (I think). I'll add that to this
part of the PEP (once I'm sure it works).

Hmm... Actually, while the with-statement can probably help out with
the nesting, it doesn't help out with the DRY; you still have to repeat
the element name (once for the call to Element, and once as the name
that Element object is bound to).

STeVe
 
T

Tim Hochberg

Steven said:
Steven said:
Tim said:
Steven Bethard wrote:

Steven Bethard wrote:

Duncan Booth wrote:

make Element html:
make Element body:
make Element p:
text('But this ')
make Element strong:
text('could')
text(' be made to work')

This is nice. I'll have to play around with it a bit to see how
hard it would be to make it work.

Okay, I think it'll work[1]. I'm going to update this section to
something more like:


Open Issues
===========

...

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 like::

I think this PEP is going off the rails. It's primary virtue was that
it was a simpler, clearer way to write:

class Foo(args):
__metaclass__ = some_metaclass
#...

Once it starts calling secret magic methods behind the scenes it's
losing that virture. And for what? Supporting some use cases that have
reasonable solutions already?

That's why it's in the Open Issues section. I expect most of these open
issues to be resolved by rejection. (At least, that's my preferred
resolution.) But since they people have brought them up, I think they
need to be addressed as completely as possible.

But I think you make a good point that this particular case can be just
as easily done using a with-statement (I think). I'll add that to this
part of the PEP (once I'm sure it works).


Hmm... Actually, while the with-statement can probably help out with
the nesting, it doesn't help out with the DRY; you still have to repeat
the element name (once for the call to Element, and once as the name
that Element object is bound to).


I don't think that's correct. I think that with a suitably designed
HtmlDocument object, the following should be possible:

with HtmlDocument("Title") as doc:
with doc.element("body"):
doc.text("before first h1")
with doc.element("h1", style="first"):
doc.text("first h1")
# I don't understand the point of tail, but you could do that too
doc.text("after first h1")
with doc.element("h1", style="second"):
doc.text("second h1")
doc.text("after second h1")

That seems reasonably DRY compliant. Doc would simply stack and unstack
the attributes on the way in and out of the with blocks. This arguably
maps better to the underlying HTML as well.

I'd like to reiterate my point that, as far as I can tell, the make
statement won't actually work for creating HTML in all but the most
trivial of cases. Consider:

with HtmlDocument("Title Here") as doc:
with doc.element("body"):
with doc.element("h1"):
doc.text("An ordered list")
with doc.element("ol"): # Ordered list
for value in some_list:
doc.listitem(value)

If I try to translate this to make syntax, I get into trouble. I end up
with 'value' getting set as an element over and over again. Not at all
what I want! I suppose you could filter your expression so that only
things that subtype some marker class get added, but that's piling
weirdness on top of confusion; and good sign that the whole idea is
better off abandoned.

-tim
 
M

Michele Simionato

Tim Hochberg ha scritto:
I think this PEP is going off the rails. It's primary virtue was that it
was a simpler, clearer way to write:

class Foo(args):
__metaclass__ = some_metaclass
#...

Once it starts calling secret magic methods behind the scenes it's
losing that virture. And for what? Supporting some use cases that have
reasonable solutions already?

FWIW, this is my feeling too. Let's keep it simple, please!

Michele Simionato
 
T

Tim Hochberg

Tim said:
Steven said:
Steven said:
Tim Hochberg wrote:

Steven Bethard wrote:

Steven Bethard wrote:

Duncan Booth wrote:

make Element html:
make Element body:
make Element p:
text('But this ')
make Element strong:
text('could')
text(' be made to work')


This is nice. I'll have to play around with it a bit to see how
hard it would be to make it work.


Okay, I think it'll work[1]. I'm going to update this section to
something more like:


Open Issues
===========

...

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 like::


I think this PEP is going off the rails. It's primary virtue was
that it was a simpler, clearer way to write:

class Foo(args):
__metaclass__ = some_metaclass
#...

Once it starts calling secret magic methods behind the scenes it's
losing that virture. And for what? Supporting some use cases that
have reasonable solutions already?


That's why it's in the Open Issues section. I expect most of these
open issues to be resolved by rejection. (At least, that's my
preferred resolution.) But since they people have brought them up,
I think they need to be addressed as completely as possible.

But I think you make a good point that this particular case can be
just as easily done using a with-statement (I think). I'll add that
to this part of the PEP (once I'm sure it works).



Hmm... Actually, while the with-statement can probably help out with
the nesting, it doesn't help out with the DRY; you still have to
repeat the element name (once for the call to Element, and once as
the name that Element object is bound to).



I don't think that's correct. I think that with a suitably designed
HtmlDocument object, the following should be possible:

with HtmlDocument("Title") as doc:
with doc.element("body"):
doc.text("before first h1")
with doc.element("h1", style="first"):
doc.text("first h1")
# I don't understand the point of tail, but you could do that too
doc.text("after first h1")
with doc.element("h1", style="second"):
doc.text("second h1")
doc.text("after second h1")
Here's code to do this. It would be probably be better to use elment
tree or some such instead of pushing out the HTML directly, but this
should get the idea across (testing using 2.5a1):



class HtmlTag(object):
def __init__(self, document, type, attribs):
self.document = document
self.type = type
self.attribs = attribs
def __context__(self):
return self
def _format_attribs(self):
if not self.attribs:
return ''
return ' ' + ' '.join('%s="%s"' % (k,v) for
k, v in self.attribs.items())
def __enter__(self):
self.document.entities.append('<%s%s>' % (self.type,
self._format_attribs()))
return self
def __exit__(self, type, value, traceback):
self.document.entities.append('</%s>' % self.type)
def add(self):
self.document.entities.append('<%s%s/>' % (self.type,
self._format_attribs()))

class HtmlDocument(object):
def __init__(self):
self.entities = []
def tag(self, type, **atribs):
return HtmlTag(self, type, atribs)
def text(self, value):
self.entities.append(value)
def tostring(self):
return ''.join(self.entities)
def __context__(self):
return self
def __enter__(self):
self.entities.append('<html>')
return self
def __exit__(self, type, value, traceback):
if not (type is value is traceback is None):
raise type(value)
self.entities.append('</html>')

And here are some simple examples:

with HtmlDocument() as doc:
with doc.tag("body"):
doc.text("before first h1")
with doc.tag("h1", style="first"):
doc.text("first h1")
doc.text("after first h1")
with doc.tag("h1", style="second"):
doc.text("second h1")
doc.text("after second h1")

expected = '''\
<html>\
<body>\
before first h1\
<h1 style="first">first h1</h1>\
after first h1\
<h1 style="second">second h1</h1>\
after second h1\
</body>\
</html>\
'''
print doc.tostring() == expected

some_list = ["foo", "bar", 'bazz', "froodle"]

with HtmlDocument() as doc:
with doc.tag("body"):
with doc.tag("h1"):
doc.text("An ordered list")
with doc.tag("ol"):
for value in some_list:
with doc.tag('li'):
doc.text(value)

print doc.tostring()




Regards,

-tim
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,582
Members
45,060
Latest member
BuyKetozenseACV

Latest Threads

Top