为什么Python没有内置混合getattr + __getitem__?

时间:2011-07-18 19:14:28

标签: python magic-methods getattr

我有接受dicts或其他对象的方法以及从这些对象中获取的“fields”的名称。如果对象是dict,则该方法使用__getitem__来检索命名密钥,否则它使用getattr来检索命名属性。这在网络模板语言中非常常见。例如,在Chameleon模板中,您可能有:

<p tal:content="foo.keyname">Stuff goes here</p>

如果您将foo作为{'keyname':'bar'}之类的字典传入,则foo.keyname会获取“keyname”键以获得“bar”。如果foo是类的实例,则为:

class Foo(object):
    keyname = 'baz'

然后foo.keynamekeyname属性中获取值。变色龙本身实现了这个功能(在chameleon.py26模块中),如下所示:

def lookup_attr(obj, key):
    try:
        return getattr(obj, key)
    except AttributeError as exc:
        try:
            get = obj.__getitem__
        except AttributeError:
            raise exc
        try:
            return get(key)
        except KeyError:
            raise exc

我已经在my own package中实现了它,如:

try:
    value = obj[attribute]
except (KeyError, TypeError):
    value = getattr(obj, attribute)

问题是,这是一种非常常见的模式。我已经在很多模块中看到过这种方法或者与它非常类似的方法。那么为什么在语言的核心,或者至少在一个核心模块中不是这样的东西呢?如果不这样做,是否有一种明确的方式可以应该写出来?

3 个答案:

答案 0 :(得分:15)

我有点半读你的问题,写下面的内容,然后重新阅读你的问题并意识到我已经回答了一个微妙的不同问题。但我认为以下实际上仍然提供了一个答案。如果你不这么认为,请假装你曾经问过这个更普遍的问题,我认为这个问题包括你的问题:

“为什么Python不提供任何内置方式将属性和项目视为可互换的?”


我对这个问题给予了相当多的思考,我认为答案非常简单。创建容器类型时,区分属性非常重要。任何合理完善的容器类型都有许多属性 - 通常并不总是方法 - 使它能够以优雅的方式管理其内容。例如,dict有itemsvalueskeysiterkeys等等。这些属性都使用.表示法访问。另一方面,使用[]表示法访问项目。所以不会发生碰撞。

使用.表示法启用项目访问时会发生什么?突然间你有重叠的命名空间。你现在如何处理碰撞?如果您对dict进行子类化并为其提供此功能,则要么不能使用items之类的键作为规则,要么必须创建某种命名空间层次结构。第一个选项会创建一个繁琐,难以遵循且难以执行的规则。第二个选项会产生令人讨厌的复杂性,而无法完全解决碰撞问题,因为您仍需要使用备用接口来指定是否需要items项或items属性。

现在,对于某些非常原始的类型,这是可以接受的。这可能就是为什么标准库中有namedtuple的原因。 (但请注意namedtuple受这些问题的影响,这可能是为什么它被实现为工厂函数(阻止继承)并使用奇怪的私有方法名称,如_asdict。)

创建一个没有(公共)属性的object子类并在其上使用setattr也非常非常容易。覆盖__getitem____setitem____delitem__来调用__getattribute____setattr____delattr__甚至非常容易,因此项目访问权限就变为getattr()setattr()等的语法糖(虽然这有点令人质疑,因为它会产生一些意想不到的行为。)

但是对于任何一种发展良好的容器类,你希望能够扩展和继承,添加新的有用属性,坦率地说,__getattr__ + __getitem__混合体将是一个巨大的PITA。

答案 1 :(得分:5)

python标准库中最接近的是namedtuple(),http://docs.python.org/dev/library/collections.html#collections.namedtuple

Foo = namedtuple('Foo', ['key', 'attribute'])
foo = Foo(5, attribute=13)
print foo[1]
print foo.key

或者您可以轻松定义自己的类型,该类型始终存储在其dict中,但允许属性设置和获取的外观:

class MyDict(dict):
    def __getattr__(self, attr):
        return self[attr]
    def __setattr__(self, attr, value):
        self[attr] = value

d = MyDict()

d.a = 3
d[3] = 'a'
print(d['a']) # 3
print(d[3]) # 'a'
print(d['b']) # Returns a keyerror

但是不要d.3,因为这是一个语法错误。当然,制作这样的混合存储类型有更复杂的方法,在网上搜索很多例子。

就如何检查两者而言,变色龙的方式看起来很彻底。当谈到'为什么没有办法在标准库中同时做'时,这是因为歧义是不好的。是的,我们在python中有类型和所有其他类型的伪装,并且类无论如何都只是字典,但是在某些时候我们希望从类似dict或list的容器中获得不同的功能,而不是我们想要的类,使用它的方法解析顺序,压倒等等。

答案 2 :(得分:4)

您可以非常轻松地编写自己的dict子类,这种子类本身就是这样的。一个最小的实现,我喜欢称之为“堆”属性,就像这样:

class Pile(dict):
    def __getattr__(self, key):
        return self[key]
    def __setattr__(self, key, value):
        self[key] = value

不幸的是,如果您需要能够处理传递给您的字典或带有属性的对象,而不是从一开始就控制对象,那么这将无济于事。

在你的情况下,我可能会使用与你所拥有的非常相似的东西,除非把它分解成一个函数,所以我不必一直重复它。