6

我有点意外地发现,您可以使用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'

是否只是假设,如果您必须使用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()不能在基 Object 类上使用,但这只是一个示例。

4

2 回答 2

5

我认为您认为属性必须是“标识符”的假设是不正确的。正如您所注意到的,python 对象支持任意属性(不仅仅是标识符),因为对于大多数对象,属性存储在实例中__dict__(它是 a dict,因此支持任意字符串键)。但是,为了完全拥有属性访问运算符,可以以这种方式访问​​的名称集需要被限制以允许生成可以解析它的语法。

是否只是假设,如果您必须使用 setattr 设置变量,您将通过 getattr 引用它?

不,我不认为这是假设的。我认为假设是,如果您使用.运算符引用属性,那么您知道这些属性什么。如果您有能力知道这些属性什么,那么您可能可以控制它们的名称。而且,如果您可以控制它们的名称,那么您可以将它们命名为解析器知道如何处理的名称;-)。

于 2016-07-08T19:16:02.630 回答
2

我认为该语言的该功能是该语言实现方式的意外副作用。

有几个问题表明该功能是副作用。

一、出自《Python之禅》:

应该有一种——最好只有一种——明显的方法来做到这一点。

对我来说,访问属性的明显方法是使用.运算符。因此,我认为与运营商不兼容的名称是非法的,因为它们需要“黑客”才能使用它们。

其次,尽管我们可以在实例中使用整数键__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 提出的观点有效。

于 2016-10-07T20:40:36.030 回答