10

我有接受字典或其他对象的方法以及从这些对象中获取的“字段”的名称。如果对象是 dict,则该方法用于__getitem__检索命名键,否则它用于getattr检索命名属性。这在 Web 模板语言中很常见。例如,在Chameleon模板中,您可能有:

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

如果您foo以 dict 之类的形式传入{'keyname':'bar'},则foo.keyname获取“keyname”键以获取“bar”。Iffoo是一个类的实例,例如:

class Foo(object):
    keyname = 'baz'

然后foo.keynamekeyname属性中获取值。Chameleon 本身实现了该功能(在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

我已经在自己的包中实现了它,例如:

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

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

4

3 回答 3

16

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

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


我已经对这个问题进行了一些思考,我认为答案很简单。创建容器类型时,区分属性非常重要。任何合理开发的容器类型都将具有许多属性——通常但不总是方法——使其能够以优雅的方式管理其内容。例如,一个 dict 有items, values, keys,iterkeys等等。这些属性都使用.符号访问。另一方面,使用[]符号访问项目。所以不可能有碰撞。

当您使用.符号启用项目访问时会发生什么?突然间你有了重叠的命名空间。你现在如何处理碰撞?如果您将 dict 子类化并为其提供此功能,则您不能像items规则那样使用键,或者您必须创建某种命名空间层次结构。第一个选项创建了一个繁重、难以遵循和难以执行的规则。第二个选项会产生烦人的复杂性,但不能完全解决冲突问题,因为您仍然必须有一个替代界面来指定您想要items项目还是items属性。

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

object创建没有(公共)属性的子类并在其上使用也非常、非常、非常容易setattr__getitem__覆盖,__setitem____delitem__调用__getattribute__, __setattr__and甚至非常容易__delattr__,因此项目访问只是成为 , 等的语法糖getattr()setattr()(尽管这有点可疑,因为它会产生一些意想不到的行为。)

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

于 2011-07-18T20:03:10.943 回答
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中有ducktyping和所有其他类型的伪装,无论如何,类实际上只是字典,但是在某些时候,我们希望从容器(如字典或列表)中获得与我们希望从类中获得不同的功能,它的方法解析顺序, 覆盖等。

于 2011-07-18T19:37:39.073 回答
4

您可以很容易地编写自己的dict子类,以这种方式运行。我喜欢称之为“一堆”属性的最小实现是这样的:

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

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

在您的情况下,我可能会使用与您所拥有的非常相似的东西,只是将其分解为一个函数,这样我就不必一直重复它。

于 2011-07-18T19:28:13.797 回答