reSTify PEP 363 (#322)
This commit is contained in:
parent
e889fa82a5
commit
7f68262609
325
pep-0363.txt
325
pep-0363.txt
|
@ -5,247 +5,256 @@ Last-Modified: $Date$
|
||||||
Author: Ben North <ben@redfrontdoor.org>
|
Author: Ben North <ben@redfrontdoor.org>
|
||||||
Status: Rejected
|
Status: Rejected
|
||||||
Type: Standards Track
|
Type: Standards Track
|
||||||
Content-Type: text/plain
|
Content-Type: text/x-rst
|
||||||
Created: 29-Jan-2007
|
Created: 29-Jan-2007
|
||||||
Post-History: 12-Feb-2007
|
Post-History: 12-Feb-2007
|
||||||
|
|
||||||
|
|
||||||
Abstract
|
Abstract
|
||||||
|
========
|
||||||
|
|
||||||
Dynamic attribute access is currently possible using the "getattr"
|
Dynamic attribute access is currently possible using the "getattr"
|
||||||
and "setattr" builtins. The present PEP suggests a new syntax to
|
and "setattr" builtins. The present PEP suggests a new syntax to
|
||||||
make such access easier, allowing the coder for example to write
|
make such access easier, allowing the coder for example to write::
|
||||||
|
|
||||||
x.('foo_%d' % n) += 1
|
x.('foo_%d' % n) += 1
|
||||||
|
|
||||||
z = y.('foo_%d' % n).('bar_%s' % s)
|
z = y.('foo_%d' % n).('bar_%s' % s)
|
||||||
|
|
||||||
instead of
|
instead of::
|
||||||
|
|
||||||
attr_name = 'foo_%d' % n
|
attr_name = 'foo_%d' % n
|
||||||
setattr(x, attr_name, getattr(x, attr_name) + 1)
|
setattr(x, attr_name, getattr(x, attr_name) + 1)
|
||||||
|
|
||||||
z = getattr(getattr(y, 'foo_%d' % n), 'bar_%s' % s)
|
z = getattr(getattr(y, 'foo_%d' % n), 'bar_%s' % s)
|
||||||
|
|
||||||
|
|
||||||
Rationale
|
Rationale
|
||||||
|
=========
|
||||||
|
|
||||||
Dictionary access and indexing both have a friendly invocation
|
Dictionary access and indexing both have a friendly invocation
|
||||||
syntax: instead of x.__getitem__(12) the coder can write x[12].
|
syntax: instead of ``x.__getitem__(12)`` the coder can write ``x[12]``.
|
||||||
This also allows the use of subscripted elements in an augmented
|
This also allows the use of subscripted elements in an augmented
|
||||||
assignment, as in "x[12] += 1". The present proposal brings this
|
assignment, as in "x[12] += 1". The present proposal brings this
|
||||||
ease-of-use to dynamic attribute access too.
|
ease-of-use to dynamic attribute access too.
|
||||||
|
|
||||||
Attribute access is currently possible in two ways:
|
Attribute access is currently possible in two ways:
|
||||||
|
|
||||||
* When the attribute name is known at code-writing time, the
|
* When the attribute name is known at code-writing time, the
|
||||||
".NAME" trailer can be used, as in
|
".NAME" trailer can be used, as in::
|
||||||
|
|
||||||
x.foo = 42
|
x.foo = 42
|
||||||
y.bar += 100
|
y.bar += 100
|
||||||
|
|
||||||
* When the attribute name is computed dynamically at run-time, the
|
* When the attribute name is computed dynamically at run-time, the
|
||||||
"getattr" and "setattr" builtins must be used:
|
"getattr" and "setattr" builtins must be used::
|
||||||
|
|
||||||
x = getattr(y, 'foo_%d' % n)
|
x = getattr(y, 'foo_%d' % n)
|
||||||
setattr(z, 'bar_%s' % s, 99)
|
setattr(z, 'bar_%s' % s, 99)
|
||||||
|
|
||||||
The "getattr" builtin also allows the coder to specify a default
|
The "getattr" builtin also allows the coder to specify a default
|
||||||
value to be returned in the event that the object does not have
|
value to be returned in the event that the object does not have
|
||||||
an attribute of the given name:
|
an attribute of the given name::
|
||||||
|
|
||||||
x = getattr(y, 'foo_%d' % n, 0)
|
x = getattr(y, 'foo_%d' % n, 0)
|
||||||
|
|
||||||
This PEP describes a new syntax for dynamic attribute access ---
|
This PEP describes a new syntax for dynamic attribute access ---
|
||||||
"x.(expr)" --- with examples given in the Abstract above.
|
"x.(expr)" --- with examples given in the Abstract above.
|
||||||
|
|
||||||
(The new syntax could also allow the provision of a default value in
|
(The new syntax could also allow the provision of a default value in
|
||||||
the "get" case, as in:
|
the "get" case, as in::
|
||||||
|
|
||||||
x = y.('foo_%d' % n, None)
|
x = y.('foo_%d' % n, None)
|
||||||
|
|
||||||
This 2-argument form of dynamic attribute access would not be
|
This 2-argument form of dynamic attribute access would not be
|
||||||
permitted as the target of an (augmented or normal) assignment. The
|
permitted as the target of an (augmented or normal) assignment. The
|
||||||
"Discussion" section below includes opinions specifically on the
|
"Discussion" section below includes opinions specifically on the
|
||||||
2-argument extension.)
|
2-argument extension.)
|
||||||
|
|
||||||
Finally, the new syntax can be used with the "del" statement, as in
|
Finally, the new syntax can be used with the "del" statement, as in::
|
||||||
|
|
||||||
del x.(attr_name)
|
del x.(attr_name)
|
||||||
|
|
||||||
|
|
||||||
Impact On Existing Code
|
Impact On Existing Code
|
||||||
|
=======================
|
||||||
|
|
||||||
The proposed new syntax is not currently valid, so no existing
|
The proposed new syntax is not currently valid, so no existing
|
||||||
well-formed programs have their meaning altered by this proposal.
|
well-formed programs have their meaning altered by this proposal.
|
||||||
|
|
||||||
Across all "*.py" files in the 2.5 distribution, there are around
|
Across all "\*.py" files in the 2.5 distribution, there are around
|
||||||
600 uses of "getattr", "setattr" or "delattr". They break down as
|
600 uses of "getattr", "setattr" or "delattr". They break down as
|
||||||
follows (figures have some room for error because they were
|
follows (figures have some room for error because they were
|
||||||
arrived at by partially-manual inspection):
|
arrived at by partially-manual inspection)::
|
||||||
|
|
||||||
c.300 uses of plain "getattr(x, attr_name)", which could be
|
c.300 uses of plain "getattr(x, attr_name)", which could be
|
||||||
replaced with the new syntax;
|
replaced with the new syntax;
|
||||||
|
|
||||||
c.150 uses of the 3-argument form, i.e., with the default
|
c.150 uses of the 3-argument form, i.e., with the default
|
||||||
value; these could be replaced with the 2-argument form
|
value; these could be replaced with the 2-argument form
|
||||||
of the new syntax (the cases break down into c.125 cases
|
of the new syntax (the cases break down into c.125 cases
|
||||||
where the attribute name is a literal string, and c.25
|
where the attribute name is a literal string, and c.25
|
||||||
where it's only known at run-time);
|
where it's only known at run-time);
|
||||||
|
|
||||||
c.5 uses of the 2-argument form with a literal string
|
c.5 uses of the 2-argument form with a literal string
|
||||||
attribute name, which I think could be replaced with the
|
attribute name, which I think could be replaced with the
|
||||||
standard "x.attribute" syntax;
|
standard "x.attribute" syntax;
|
||||||
|
|
||||||
c.120 uses of setattr, of which 15 use getattr to find the
|
c.120 uses of setattr, of which 15 use getattr to find the
|
||||||
new value; all could be replaced with the new syntax,
|
new value; all could be replaced with the new syntax,
|
||||||
the 15 where getattr is also involved would show a
|
the 15 where getattr is also involved would show a
|
||||||
particular increase in clarity;
|
particular increase in clarity;
|
||||||
|
|
||||||
c.5 uses which would have to stay as "getattr" because they
|
c.5 uses which would have to stay as "getattr" because they
|
||||||
are calls of a variable named "getattr" whose default
|
are calls of a variable named "getattr" whose default
|
||||||
value is the builtin "getattr";
|
value is the builtin "getattr";
|
||||||
|
|
||||||
c.5 uses of the 2-argument form, inside a try/except block
|
c.5 uses of the 2-argument form, inside a try/except block
|
||||||
which catches AttributeError and uses a default value
|
which catches AttributeError and uses a default value
|
||||||
instead; these could use 2-argument form of the new
|
instead; these could use 2-argument form of the new
|
||||||
syntax;
|
syntax;
|
||||||
|
|
||||||
c.10 uses of "delattr", which could use the new syntax.
|
c.10 uses of "delattr", which could use the new syntax.
|
||||||
|
|
||||||
As examples, the line
|
As examples, the line::
|
||||||
|
|
||||||
setattr(self, attr, change_root(self.root, getattr(self, attr)))
|
setattr(self, attr, change_root(self.root, getattr(self, attr)))
|
||||||
|
|
||||||
from Lib/distutils/command/install.py could be rewritten
|
from Lib/distutils/command/install.py could be rewritten::
|
||||||
|
|
||||||
self.(attr) = change_root(self.root, self.(attr))
|
self.(attr) = change_root(self.root, self.(attr))
|
||||||
|
|
||||||
and the line
|
and the line::
|
||||||
|
|
||||||
setattr(self, method_name, getattr(self.metadata, method_name))
|
setattr(self, method_name, getattr(self.metadata, method_name))
|
||||||
|
|
||||||
from Lib/distutils/dist.py could be rewritten
|
from Lib/distutils/dist.py could be rewritten::
|
||||||
|
|
||||||
self.(method_name) = self.metadata.(method_name)
|
self.(method_name) = self.metadata.(method_name)
|
||||||
|
|
||||||
|
|
||||||
Performance Impact
|
Performance Impact
|
||||||
|
==================
|
||||||
|
|
||||||
Initial pystone measurements are inconclusive, but suggest there may
|
Initial pystone measurements are inconclusive, but suggest there may
|
||||||
be a performance penalty of around 1% in the pystones score with the
|
be a performance penalty of around 1% in the pystones score with the
|
||||||
patched version. One suggestion is that this is because the longer
|
patched version. One suggestion is that this is because the longer
|
||||||
main loop in ceval.c hurts the cache behaviour, but this has not
|
main loop in ceval.c hurts the cache behaviour, but this has not
|
||||||
been confirmed.
|
been confirmed.
|
||||||
|
|
||||||
On the other hand, measurements suggest a speed-up of around 40--45%
|
On the other hand, measurements suggest a speed-up of around 40--45%
|
||||||
for dynamic attribute access.
|
for dynamic attribute access.
|
||||||
|
|
||||||
|
|
||||||
Error Cases
|
Error Cases
|
||||||
|
===========
|
||||||
|
|
||||||
Only strings are permitted as attribute names, so for instance the
|
Only strings are permitted as attribute names, so for instance the
|
||||||
following error is produced:
|
following error is produced::
|
||||||
|
|
||||||
>>> x.(99) = 8
|
>>> x.(99) = 8
|
||||||
Traceback (most recent call last):
|
Traceback (most recent call last):
|
||||||
File "<stdin>", line 1, in <module>
|
File "<stdin>", line 1, in <module>
|
||||||
TypeError: attribute name must be string, not 'int'
|
TypeError: attribute name must be string, not 'int'
|
||||||
|
|
||||||
This is handled by the existing PyObject_GetAttr function.
|
This is handled by the existing ``PyObject_GetAttr`` function.
|
||||||
|
|
||||||
|
|
||||||
Draft Implementation
|
Draft Implementation
|
||||||
|
====================
|
||||||
|
|
||||||
A draft implementation adds a new alternative to the "trailer"
|
A draft implementation adds a new alternative to the "trailer"
|
||||||
clause in Grammar/Grammar; a new AST type, "DynamicAttribute" in
|
clause in Grammar/Grammar; a new AST type, "DynamicAttribute" in
|
||||||
Python.asdl, with accompanying changes to symtable.c, ast.c, and
|
Python.asdl, with accompanying changes to symtable.c, ast.c, and
|
||||||
compile.c, and three new opcodes (load/store/del) with
|
compile.c, and three new opcodes (load/store/del) with
|
||||||
accompanying changes to opcode.h and ceval.c. The patch consists
|
accompanying changes to opcode.h and ceval.c. The patch consists
|
||||||
of c.180 additional lines in the core code, and c.100 additional
|
of c.180 additional lines in the core code, and c.100 additional
|
||||||
lines of tests. It is available as sourceforge patch #1657573 [1].
|
lines of tests. It is available as sourceforge patch #1657573 [1]_.
|
||||||
|
|
||||||
|
|
||||||
Mailing Lists Discussion
|
Mailing Lists Discussion
|
||||||
|
========================
|
||||||
|
|
||||||
Initial posting of this PEP in draft form was to python-ideas on
|
Initial posting of this PEP in draft form was to python-ideas on
|
||||||
20070209 [2], and the response was generally positive. The PEP was
|
20070209 [2]_, and the response was generally positive. The PEP was
|
||||||
then posted to python-dev on 20070212 [3], and an interesting
|
then posted to python-dev on 20070212 [3]_, and an interesting
|
||||||
discussion ensued. A brief summary:
|
discussion ensued. A brief summary:
|
||||||
|
|
||||||
Initially, there was reasonable (but not unanimous) support for the
|
Initially, there was reasonable (but not unanimous) support for the
|
||||||
idea, although the precise choice of syntax had a more mixed
|
idea, although the precise choice of syntax had a more mixed
|
||||||
reception. Several people thought the "." would be too easily
|
reception. Several people thought the "." would be too easily
|
||||||
overlooked, with the result that the syntax could be confused with a
|
overlooked, with the result that the syntax could be confused with a
|
||||||
method/function call. A few alternative syntaxes were suggested:
|
method/function call. A few alternative syntaxes were suggested::
|
||||||
|
|
||||||
obj.(foo)
|
obj.(foo)
|
||||||
obj.[foo]
|
obj.[foo]
|
||||||
obj.{foo}
|
obj.{foo}
|
||||||
obj{foo}
|
obj{foo}
|
||||||
obj.*foo
|
obj.*foo
|
||||||
obj->foo
|
obj->foo
|
||||||
obj<-foo
|
obj<-foo
|
||||||
obj@[foo]
|
obj@[foo]
|
||||||
obj.[[foo]]
|
obj.[[foo]]
|
||||||
|
|
||||||
with "obj.[foo]" emerging as the preferred one. In this initial
|
with "obj.[foo]" emerging as the preferred one. In this initial
|
||||||
discussion, the two-argument form was universally disliked, so it
|
discussion, the two-argument form was universally disliked, so it
|
||||||
was to be taken out of the PEP.
|
was to be taken out of the PEP.
|
||||||
|
|
||||||
Discussion then took a step back to whether this particular feature
|
Discussion then took a step back to whether this particular feature
|
||||||
provided enough benefit to justify new syntax. As well as requiring
|
provided enough benefit to justify new syntax. As well as requiring
|
||||||
coders to become familiar with the new syntax, there would also be
|
coders to become familiar with the new syntax, there would also be
|
||||||
the problem of backward compatibility --- code using the new syntax
|
the problem of backward compatibility --- code using the new syntax
|
||||||
would not run on older pythons.
|
would not run on older pythons.
|
||||||
|
|
||||||
Instead of new syntax, a new "wrapper class" was proposed, with the
|
Instead of new syntax, a new "wrapper class" was proposed, with the
|
||||||
following specification / conceptual implementation suggested by
|
following specification / conceptual implementation suggested by
|
||||||
Martin von Loewis:
|
Martin von Löwis::
|
||||||
|
|
||||||
class attrs:
|
class attrs:
|
||||||
def __init__(self, obj):
|
def __init__(self, obj):
|
||||||
self.obj = obj
|
self.obj = obj
|
||||||
def __getitem__(self, name):
|
def __getitem__(self, name):
|
||||||
return getattr(self.obj, name)
|
return getattr(self.obj, name)
|
||||||
def __setitem__(self, name, value):
|
def __setitem__(self, name, value):
|
||||||
return setattr(self.obj, name, value)
|
return setattr(self.obj, name, value)
|
||||||
def __delitem__(self, name):
|
def __delitem__(self, name):
|
||||||
return delattr(self, name)
|
return delattr(self, name)
|
||||||
def __contains__(self, name):
|
def __contains__(self, name):
|
||||||
return hasattr(self, name)
|
return hasattr(self, name)
|
||||||
|
|
||||||
This was considered a cleaner and more elegant solution to the
|
This was considered a cleaner and more elegant solution to the
|
||||||
original problem. (Another suggestion was a mixin class providing
|
original problem. (Another suggestion was a mixin class providing
|
||||||
dictionary-style access to an object's attributes.)
|
dictionary-style access to an object's attributes.)
|
||||||
|
|
||||||
The decision was made that the present PEP did not meet the burden
|
The decision was made that the present PEP did not meet the burden
|
||||||
of proof for the introduction of new syntax, a view which had been
|
of proof for the introduction of new syntax, a view which had been
|
||||||
put forward by some from the beginning of the discussion. The
|
put forward by some from the beginning of the discussion. The
|
||||||
wrapper class idea was left open as a possibility for a future PEP.
|
wrapper class idea was left open as a possibility for a future PEP.
|
||||||
|
|
||||||
|
|
||||||
References
|
References
|
||||||
|
==========
|
||||||
|
|
||||||
[1] Sourceforge patch #1657573
|
.. [1] Sourceforge patch #1657573
|
||||||
http://sourceforge.net/tracker/index.php?func=detail&aid=1657573&group_id=5470&atid=305470
|
http://sourceforge.net/tracker/index.php?func=detail&aid=1657573&group_id=5470&atid=305470
|
||||||
|
|
||||||
[2] https://mail.python.org/pipermail/python-ideas/2007-February/000210.html
|
.. [2] https://mail.python.org/pipermail/python-ideas/2007-February/000210.html
|
||||||
and following posts
|
and following posts
|
||||||
|
|
||||||
[3] https://mail.python.org/pipermail/python-dev/2007-February/070939.html
|
.. [3] https://mail.python.org/pipermail/python-dev/2007-February/070939.html
|
||||||
and following posts
|
and following posts
|
||||||
|
|
||||||
|
|
||||||
Copyright
|
Copyright
|
||||||
|
=========
|
||||||
|
|
||||||
This document has been placed in the public domain.
|
This document has been placed in the public domain.
|
||||||
|
|
||||||
|
..
|
||||||
Local Variables:
|
Local Variables:
|
||||||
mode: indented-text
|
mode: indented-text
|
||||||
indent-tabs-mode: nil
|
indent-tabs-mode: nil
|
||||||
sentence-end-double-space: t
|
sentence-end-double-space: t
|
||||||
fill-column: 70
|
fill-column: 70
|
||||||
coding: utf-8
|
coding: utf-8
|
||||||
End:
|
End:
|
||||||
|
|
Loading…
Reference in New Issue