Python 属性中允许使用特殊字符的原因
Reason for allowing Special Characters in Python Attributes
我有点意外地发现您可以使用 setattr
为对象设置 'illegal' 属性。我所说的非法是指具有无法使用具有传统 .
运算符引用的 __getattr__
接口检索的名称的属性。它们只能通过 getattr
方法检索。
这对我来说似乎相当令人惊讶,我想知道这是否有原因,或者它是否只是被忽略了等等。因为存在一个用于检索属性的运算符,以及一个标准实现setattribute
接口,我希望它只允许实际可以正常检索的属性名称。而且,如果您有一些奇怪的理由想要具有无效名称的属性,则必须为它们实现自己的接口。
只有我一个人对这种行为感到惊讶吗?
class Foo:
"stores attrs"
foo = Foo()
setattr(foo, "bar.baz", "this can't be reached")
dir(foo)
这个 returns 既奇怪又有点误导的东西:
[...'__weakref__', 'bar.baz']
如果我想以 'standard' 的方式访问 foo.bar.baz,我不能。无法检索它很有意义,但设置它的能力令人惊讶。
foo.bar.baz
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Foo' object has no attribute 'bar'
是否简单地假设,如果您必须使用 setattr
设置变量,您将通过 getattr
引用它?因为在运行时,这可能并不总是正确的,尤其是对于 Python 的交互式解释器、反射等。默认情况下允许这样做似乎仍然很奇怪。
编辑:一个(非常粗略的)例子,我希望看到 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)
这将不允许我在我的属性名称中使用无效字符。显然,super()
不能用于基础对象 class,但这只是一个例子。
我认为您关于属性 必须 是 "identifiers" 的假设是不正确的。正如您所指出的,python 对象支持任意属性(不仅仅是标识符),因为对于大多数对象,属性存储在实例的 __dict__
中(这是一个 dict
,因此支持任意字符串键)。但是,为了完全拥有属性访问运算符,需要限制可以以这种方式访问的名称集,以允许生成可以解析它的语法。
Is it simply assumed that, if you have to use setattr to set the variable, you are going to reference it via getattr?
没有。我不认为这是假设的。我认为假设是,如果您使用 .
运算符引用属性,那么您就知道这些属性 是什么 。如果您有能力知道这些属性 是什么 ,那么您可能可以控制它们的名称。如果您可以控制它们的名称,那么您可以为它们命名解析器知道如何处理的名称 ;-).
我认为该语言的这一特性是该语言实现方式的意外副作用。
有几个问题表明该功能是一个副作用。
首先,来自"Zen of Python":
There should be one-- and preferably only one --obvious way to do it.
对我来说,访问属性的明显方法是使用 .
运算符。因此,我认为与运算符不兼容的名称是非法的,因为它们需要 "hacks" 才能使用它们。
其次,尽管我们可以在实例的 __dict__
中使用整数键(如 Mark Ransom 所指出的),但我认为 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
同样适用于 setattr()
内置函数。因此,我将内置函数视为一种引入的 walkarround 以促进动态属性访问(内置函数在 Python 0.9.1 中不存在)。
最后,以下代码(声明 __slots__
属性)失败了:
>>> class A(object):
... __slots__ = ['a.b']
...
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: __slots__ must be identifiers
这表明属性名称应该是标识符。
但是,由于我找不到允许的属性名称的任何正式语法,所以我也看到了@mgilson valid 提出的观点。
我有点意外地发现您可以使用 setattr
为对象设置 'illegal' 属性。我所说的非法是指具有无法使用具有传统 .
运算符引用的 __getattr__
接口检索的名称的属性。它们只能通过 getattr
方法检索。
这对我来说似乎相当令人惊讶,我想知道这是否有原因,或者它是否只是被忽略了等等。因为存在一个用于检索属性的运算符,以及一个标准实现setattribute
接口,我希望它只允许实际可以正常检索的属性名称。而且,如果您有一些奇怪的理由想要具有无效名称的属性,则必须为它们实现自己的接口。
只有我一个人对这种行为感到惊讶吗?
class Foo:
"stores attrs"
foo = Foo()
setattr(foo, "bar.baz", "this can't be reached")
dir(foo)
这个 returns 既奇怪又有点误导的东西:
[...'__weakref__', 'bar.baz']
如果我想以 'standard' 的方式访问 foo.bar.baz,我不能。无法检索它很有意义,但设置它的能力令人惊讶。
foo.bar.baz
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Foo' object has no attribute 'bar'
是否简单地假设,如果您必须使用 setattr
设置变量,您将通过 getattr
引用它?因为在运行时,这可能并不总是正确的,尤其是对于 Python 的交互式解释器、反射等。默认情况下允许这样做似乎仍然很奇怪。
编辑:一个(非常粗略的)例子,我希望看到 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)
这将不允许我在我的属性名称中使用无效字符。显然,super()
不能用于基础对象 class,但这只是一个例子。
我认为您关于属性 必须 是 "identifiers" 的假设是不正确的。正如您所指出的,python 对象支持任意属性(不仅仅是标识符),因为对于大多数对象,属性存储在实例的 __dict__
中(这是一个 dict
,因此支持任意字符串键)。但是,为了完全拥有属性访问运算符,需要限制可以以这种方式访问的名称集,以允许生成可以解析它的语法。
Is it simply assumed that, if you have to use setattr to set the variable, you are going to reference it via getattr?
没有。我不认为这是假设的。我认为假设是,如果您使用 .
运算符引用属性,那么您就知道这些属性 是什么 。如果您有能力知道这些属性 是什么 ,那么您可能可以控制它们的名称。如果您可以控制它们的名称,那么您可以为它们命名解析器知道如何处理的名称 ;-).
我认为该语言的这一特性是该语言实现方式的意外副作用。
有几个问题表明该功能是一个副作用。
首先,来自"Zen of Python":
There should be one-- and preferably only one --obvious way to do it.
对我来说,访问属性的明显方法是使用 .
运算符。因此,我认为与运算符不兼容的名称是非法的,因为它们需要 "hacks" 才能使用它们。
其次,尽管我们可以在实例的 __dict__
中使用整数键(如 Mark Ransom 所指出的),但我认为 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
同样适用于 setattr()
内置函数。因此,我将内置函数视为一种引入的 walkarround 以促进动态属性访问(内置函数在 Python 0.9.1 中不存在)。
最后,以下代码(声明 __slots__
属性)失败了:
>>> class A(object):
... __slots__ = ['a.b']
...
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: __slots__ must be identifiers
这表明属性名称应该是标识符。
但是,由于我找不到允许的属性名称的任何正式语法,所以我也看到了@mgilson valid 提出的观点。