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 提出的观点。