PEP 363 – 动态属性访问语法
- 作者:
- Ben North <ben at redfrontdoor.org>
- 状态:
- 已拒绝
- 类型:
- 标准跟踪
- 创建日期:
- 2007年1月29日
- 发布历史:
- 2007年2月12日
摘要
目前可以通过“getattr”和“setattr”内置函数进行动态属性访问。本 PEP 建议一种新语法,使此类访问更加容易,例如允许编码人员编写
x.('foo_%d' % n) += 1
z = y.('foo_%d' % n).('bar_%s' % s)
而不是:
attr_name = 'foo_%d' % n
setattr(x, attr_name, getattr(x, attr_name) + 1)
z = getattr(getattr(y, 'foo_%d' % n), 'bar_%s' % s)
基本原理
字典访问和索引都有友好的调用语法:编码人员可以编写 x[12]
而不是 x.__getitem__(12)
。这还允许在增强赋值中使用下标元素,例如“x[12] += 1”。目前的提案也将这种易用性带到了动态属性访问中。
目前可以通过两种方式进行属性访问
- 当属性名称在编写代码时已知时,可以使用“.NAME”后缀,例如
x.foo = 42 y.bar += 100
- 当属性名称在运行时动态计算时,必须使用“getattr”和“setattr”内置函数
x = getattr(y, 'foo_%d' % n) setattr(z, 'bar_%s' % s, 99)
“getattr”内置函数还允许编码人员在对象没有给定名称的属性时指定一个默认返回值
x = getattr(y, 'foo_%d' % n, 0)
本 PEP 描述了一种用于动态属性访问的新语法——“x.(expr)”——摘要中给出了示例。
(新语法还可以为“get”情况提供默认值,例如
x = y.('foo_%d' % n, None)
这种 2 参数形式的动态属性访问不允许作为(增强或普通)赋值的目标。“讨论”部分包含关于 2 参数扩展的意见。
最后,新语法可以与“del”语句一起使用,例如
del x.(attr_name)
对现有代码的影响
提议的新语法目前无效,因此本提案不会改变任何现有良好格式程序的含义。
在 2.5 发行版的所有“*.py”文件中,大约有 600 处使用了“getattr”、“setattr”或“delattr”。它们细分如下(由于是部分手动检查得出的数字,可能存在一些误差)
c.300 uses of plain "getattr(x, attr_name)", which could be
replaced with the new syntax;
c.150 uses of the 3-argument form, i.e., with the default
value; these could be replaced with the 2-argument form
of the new syntax (the cases break down into c.125 cases
where the attribute name is a literal string, and c.25
where it's only known at run-time);
c.5 uses of the 2-argument form with a literal string
attribute name, which I think could be replaced with the
standard "x.attribute" syntax;
c.120 uses of setattr, of which 15 use getattr to find the
new value; all could be replaced with the new syntax,
the 15 where getattr is also involved would show a
particular increase in clarity;
c.5 uses which would have to stay as "getattr" because they
are calls of a variable named "getattr" whose default
value is the builtin "getattr";
c.5 uses of the 2-argument form, inside a try/except block
which catches AttributeError and uses a default value
instead; these could use 2-argument form of the new
syntax;
c.10 uses of "delattr", which could use the new syntax.
例如,这行
setattr(self, attr, change_root(self.root, getattr(self, attr)))
来自 Lib/distutils/command/install.py 可以重写为
self.(attr) = change_root(self.root, self.(attr))
这行
setattr(self, method_name, getattr(self.metadata, method_name))
来自 Lib/distutils/dist.py 可以重写为
self.(method_name) = self.metadata.(method_name)
性能影响
初步的 Pystone 测量结果尚无定论,但表明在打了补丁的版本中,Pystone 分数可能存在约 1% 的性能损失。一种建议是,这是因为 ceval.c 中较长的主循环会影响缓存行为,但这尚未得到证实。
另一方面,测量表明动态属性访问的速度提升了约 40-45%。
错误情况
只允许使用字符串作为属性名称,因此会产生以下错误
>>> x.(99) = 8
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: attribute name must be string, not 'int'
这由现有的 PyObject_GetAttr
函数处理。
草案实现
草案实现向 Grammar/Grammar 中的“trailer”子句添加了一个新的备选项,在 Python.asdl 中添加了一个新的 AST 类型“DynamicAttribute”,并伴随对 symtable.c、ast.c 和 compile.c 的更改,以及三个新的操作码(load/store/del),并伴随对 opcode.h 和 ceval.c 的更改。该补丁在核心代码中增加了约 180 行,在测试中增加了约 100 行。它可通过 Sourceforge 补丁 #1657573 [1] 获取。
邮件列表讨论
本 PEP 的初始草稿发布于 20070209 的 python-ideas [2],反应普遍积极。然后,PEP 于 20070212 发布到 python-dev [3],并引发了有趣的讨论。简要总结如下
起初,这个想法得到了相当(但并非一致)的支持,尽管语法的具体选择则反响不一。一些人认为“.”很容易被忽略,导致语法与方法/函数调用混淆。提出了一些替代语法
obj.(foo)
obj.[foo]
obj.{foo}
obj{foo}
obj.*foo
obj->foo
obj<-foo
obj@[foo]
obj.[[foo]]
其中“obj.[foo]”成为首选。在这次初步讨论中,2 参数形式普遍不受欢迎,因此将其从 PEP 中删除。
然后讨论退回到这个特定功能是否提供了足够的益处来证明新语法的合理性。除了要求编码人员熟悉新语法外,还存在向后兼容性问题——使用新语法的代码将无法在旧版 Python 上运行。
取而代之的是, Martin von Löwis 提出了一个名为“wrapper class”的新类,其规范/概念实现如下
class attrs:
def __init__(self, obj):
self.obj = obj
def __getitem__(self, name):
return getattr(self.obj, name)
def __setitem__(self, name, value):
return setattr(self.obj, name, value)
def __delitem__(self, name):
return delattr(self, name)
def __contains__(self, name):
return hasattr(self, name)
这被认为是解决原始问题的更清洁、更优雅的解决方案。(另一项建议是提供对象属性字典式访问的 mixin 类。)
最终决定,目前的 PEP 没有满足引入新语法的举证责任,这是从讨论一开始就有一些人提出的观点。Wrapper class 的想法被保留,作为未来 PEP 的一种可能性。
参考资料
版权
本文档已置于公共领域。
来源: https://github.com/python/peps/blob/main/peps/pep-0363.rst
最后修改: 2024-04-14 20:08:31 GMT