在 Python 属性中允许特殊字符的原因

Reason for allowing Special Characters in Python Attributes

提问人:Keozon 提问时间:7/9/2016 最后编辑:Keozon 更新时间:10/8/2016 访问量:2765

问:

我有点意外地发现,您可以使用 为对象设置“非法”属性。我所说的非法是指名称无法使用具有传统运算符引用的接口检索的属性。它们只能通过该方法检索。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 的交互式解释器、反射等。默认情况下允许这样做似乎仍然很奇怪。setattrgetattr

编辑:一个(非常粗略的)示例,我希望看到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()

蟒蛇 最不令人惊讶

评论

1赞 Charles 7/9/2016
您使用的是哪个版本的 Python?您也可以使用...foo.__dict__['bar.baz']
0赞 Keozon 7/9/2016
蟒蛇 3.5.0。感谢您的替代方法。这种方法比 getattr 有什么好处吗?还是只是偏好?
0赞 mgilson 7/9/2016
@Keozon -- 建议正常使用。例如,它适用于没有这样的物体很少见(因为通常不鼓励创造它们),但它们确实存在并且对某些目的有用。getattr__dict__

答:

5赞 mgilson 7/9/2016 #1

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

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

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

评论

1赞 Mark Ransom 7/9/2016
中的键根本不需要是字符串,如果需要,可以在同一字典中混合和匹配键类型。dict
0赞 mgilson 7/9/2016
@MarkRansom -- 对。我并不是想暗示它只能保持字符串。我只是说,如果它是一个字符串,a 可以保存它,并且确实可以防止将非字符串设置为属性。dictdictsetattr
0赞 Keozon 7/9/2016
@mgilson谢谢。你对我的假设是正确的,而且可能对原因是正确的。我挣扎的是,在我看来,使 python 成为如此出色的语言的原因是如何通过某些接口实现每个运算符的。由于这种观点,我倾向于认为运算符是利用接口的最优雅或“标准”的方式......因此,接口的“标准”实现应符合操作员的限制。我将不得不考虑这一点。
2赞 abukaj 10/8/2016 #2

我认为该语言的这一特性是该语言实现方式的意外副作用。

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

首先,来自“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_ATTRCALL_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提出的观点是有效的。