提问人:Keozon 提问时间:7/9/2016 最后编辑:Keozon 更新时间:10/8/2016 访问量:2765
在 Python 属性中允许特殊字符的原因
Reason for allowing Special Characters in Python Attributes
问:
我有点意外地发现,您可以使用 为对象设置“非法”属性。我所说的非法是指名称无法使用具有传统运算符引用的接口检索的属性。它们只能通过该方法检索。setattr
__getattr__
.
getattr
对我来说,这似乎相当令人惊讶,我想知道这是否是有原因的,或者它是否只是被忽视了,等等。由于存在用于检索属性的运算符和接口的标准实现,因此我希望它只允许实际可以正常检索的属性名称。而且,如果你有一些奇怪的理由想要具有无效名称的属性,你将不得不为它们实现自己的接口。setattribute
只有我对这种行为感到惊讶吗?
class Foo:
"stores attrs"
foo = Foo()
setattr(foo, "bar.baz", "this can't be reached")
dir(foo)
这返回了一些既奇怪又有点误导的内容:[...'__weakref__', 'bar.baz']
如果我想以“标准”方式访问 foo.bar.baz,我不能。无法检索它是完全有道理的,但设置它的能力令人惊讶。
foo.bar.baz
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Foo' object has no attribute 'bar'
是否简单地假设,如果您必须使用来设置变量,您将通过 ?因为在运行时,这可能并不总是正确的,尤其是对于 Python 的交互式解释器、反射等。默认情况下允许这样做似乎仍然很奇怪。setattr
getattr
编辑:一个(非常粗略的)示例,我希望看到setattr的默认实现:
import re
class Safe:
"stores attrs"
def __setattr__(self, attr, value):
if not re.match(r"^\w[\w\d\-]+$", attr):
raise AttributeError("Invalid characters in attribute name")
else:
super().__setattr__(attr, value)
这将不允许我在属性名称中使用无效字符。显然,不能在 Object 基类上使用,但这只是一个例子。super()
答:
我认为你关于属性必须是“标识符”的假设是不正确的。正如你所指出的,python 对象支持任意属性(不仅仅是标识符),因为对于大多数对象,属性存储在实例的(这是一个,因此支持任意字符串键)。但是,为了拥有属性访问运算符,需要限制可以以这种方式访问的名称集,以允许生成可以解析它的语法。__dict__
dict
是否简单地假设,如果您必须使用 setattr 来设置变量,您将通过 getattr 引用它?
不。我不认为这是假设的。我认为假设是,如果您使用运算符引用属性,那么您就知道这些属性是什么。如果你有能力知道这些属性是什么,那么你可能可以控制它们的名称。如果你可以控制它们的名称,那么你可以给它们命名一些解析器知道如何处理的东西;-)。.
评论
dict
dict
dict
setattr
我认为该语言的这一特性是该语言实现方式的意外副作用。
有几个问题表明该功能是副作用。
首先,来自“Python的禅”:
应该有一种——最好只有一种——显而易见的方法来做到这一点。
对我来说,访问属性的明显方法是使用运算符。因此,我认为与运营商不兼容的名称是非法的,因为它们需要“黑客”才能使用它们。.
其次,尽管我们可以在实例中使用整数键(正如 Mark Ransom 所指出的那样),但我不认为这是一个有效的属性名称。特别是它破坏了对象行为:__dict__
int
>>> a.__dict__[12] = 42
>>> dir(a)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unorderable types: int() < str()
第三,Python 文档对运算符和内置等价性的说法并不完全正确。区别在于生成的字节码。前者编译为字节码,而后者 - 编译为:.
getattr()
LOAD_ATTR
CALL_FUNCTION
>>> dis.dis(lambda x: x.a)
1 0 LOAD_FAST 0 (x)
3 LOAD_ATTR 0 (a)
6 RETURN_VALUE
>>> dis.dis(lambda x: getattr(x, 'a'))
1 0 LOAD_GLOBAL 0 (getattr)
3 LOAD_FAST 0 (x)
6 LOAD_CONST 1 ('a')
9 CALL_FUNCTION 2 (2 positional, 0 keyword pair)
12 RETURN_VALUE
这同样适用于内置。因此,我认为内置函数是一种为促进动态属性访问而引入的 walkarround(Python 0.9.1 中没有内置函数)。setattr()
最后,以下代码(声明属性)失败:__slots__
>>> class A(object):
... __slots__ = ['a.b']
...
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: __slots__ must be identifiers
这表明属性名称应该是标识符。
但是,由于我找不到允许的属性名称的任何正式语法,因此我也看到@mgilson提出的观点是有效的。
评论
foo.__dict__['bar.baz']
getattr
__dict__