我有接受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.keyname
从keyname
属性中获取值。变色龙本身实现了这个功能(在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)
问题是,这是一种非常常见的模式。我已经在很多模块中看到过这种方法或者与它非常类似的方法。那么为什么在语言的核心,或者至少在一个核心模块中不是这样的东西呢?如果不这样做,是否有一种明确的方式可以应该写出来?
答案 0 :(得分:15)
我有点半读你的问题,写下面的内容,然后重新阅读你的问题并意识到我已经回答了一个微妙的不同问题。但我认为以下实际上仍然提供了一个答案。如果你不这么认为,请假装你曾经问过这个更普遍的问题,我认为这个问题包括你的问题:
“为什么Python不提供任何内置方式将属性和项目视为可互换的?”
我对这个问题给予了相当多的思考,我认为答案非常简单。创建容器类型时,区分属性和项非常重要。任何合理完善的容器类型都有许多属性 - 通常并不总是方法 - 使它能够以优雅的方式管理其内容。例如,dict有items
,values
,keys
,iterkeys
等等。这些属性都使用.
表示法访问。另一方面,使用[]
表示法访问项目。所以不会发生碰撞。
使用.
表示法启用项目访问时会发生什么?突然间你有重叠的命名空间。你现在如何处理碰撞?如果您对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
不幸的是,如果您需要能够处理传递给您的字典或带有属性的对象,而不是从一开始就控制对象,那么这将无济于事。
在你的情况下,我可能会使用与你所拥有的非常相似的东西,除非把它分解成一个函数,所以我不必一直重复它。