为什么`getattr`不支持连续的属性检索?

时间:2012-08-15 19:22:21

标签: python

class A(): pass

a = A()
b = A()

a.b = b
b.c = 1

a.b     # this is b
getattr(a, "b") # so is this

a.b.c   # this is 1   
getattr(a, "b.c") # this raises an AttributeError

我假设后者似乎很自然。我确信这是有充分理由的。它是什么?

7 个答案:

答案 0 :(得分:49)

你不能在getattr函数中放一个句点,因为getattr就像访问对象的字典查找一样(但由于子类化和其他Python实现细节,它比这更复杂一点)。

如果在a上使用'dir'函数,您将看到与对象属性对应的字典键。在这种情况下,字符串“b.c”在字典键集合中不是

使用getattr执行此操作的唯一方法是嵌套调用:

getattr(getattr(a, "b"), "c")

幸运的是,标准库有更好的解决方案!

import operator
operator.attrgetter("b.c")(a)

答案 1 :(得分:42)

Python's built-in reduce function启用您正在寻找的功能。这是一个简单的小助手功能,可以完成工作:

class NoDefaultProvided(object):
    pass

def getattrd(obj, name, default=NoDefaultProvided):
    """
    Same as getattr(), but allows dot notation lookup
    Discussed in:
    http://stackoverflow.com/questions/11975781
    """

    try:
        return reduce(getattr, name.split("."), obj)
    except AttributeError, e:
        if default != NoDefaultProvided:
            return default
        raise

测试证明;

>>> getattrd(int, 'a')
AttributeError: type object 'int' has no attribute 'a'

>>> getattr(int, 'a')
AttributeError: type object 'int' has no attribute 'a'

>>> getattrd(int, 'a', None)
None

>>> getattr(int, 'a', None)
None

>>> getattrd(int, 'a', None)
None

>>> getattrd(int, '__class__.__name__')
type

>>> getattrd(int, '__class__')
<type 'type'>

答案 2 :(得分:7)

我认为你的困惑源于直点符号(ex a.b.c)访问与getattr()相同的参数这一事实,但解析逻辑是不同的。虽然它们本质上都是对象__dict__属性的关键,但getattr()并不受点可访问属性的更严格要求的约束。例如

setattr(foo, 'Big fat ugly string.  But you can hash it.', 2)

有效,因为该字符串只是foo.__dict__中的哈希键,但是

foo.Big fat ugly string.  But you can hash it. = 2

foo.'Big fat ugly string.  But you can hash it.' = 2

是语法错误,因为现在你要求解释器将这些东西解析为原始代码,但这不起作用。

另一方面,虽然foo.b.c相当于foo.__dict__['b'].__dict__['c'],但getattr(foo, 'b.c')相当于foo.__dict__['b.c']。这就是getattr无法正常工作的原因。

答案 3 :(得分:6)

因为getattr不起作用。 getattr获取具有给定名称(第二个参数)的给定对象(第一个参数)的属性。所以你的代码:

getattr(a, "b.c") # this raises an AttributeError

表示:访问“a”引用的对象的“b.c”属性。显然,您的对象没有名为“b.c”的属性。

要获得“c”属性,您必须使用两个getattr来电:

getattr(getattr(a, "b"), "c")

让我们打开它以便更好地理解:

b = getattr(a, "b")
c = getattr(b, "c")

答案 4 :(得分:3)

我认为实现目标最直接的方法是使用operator.attrgetter

>>> import operator
>>> class B():
...   c = 'foo'
... 
>>> class A():
...   b = B()
... 
>>> a = A()
>>> operator.attrgetter('b.c')(a)
'foo'

如果该属性不存在,那么您将获得AttributeError

>>> operator.attrgetter('b.d')(a)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: B instance has no attribute 'd'

答案 5 :(得分:0)

应该返回getattr('a.b', {'a': None}, 'default-value'}的内容?它应该引发AttributeError还是只返回'default-value'?这就是为什么在getattr中引入复杂密钥会使其使用起来模糊不清的原因。

因此,将getattr(..)函数视为对象属性字典的get方法更为自然。

答案 6 :(得分:0)

您可以通过拆分点运算符并为每个点运算符执行getattr()来调用多个getattr而不调用函数内的函数

def multi_getattr(self,obj, attr, default = None):
          attributes = attr.split(".")
          for i in attributes:
              try:
                  obj = getattr(obj, i)
              except AttributeError:
                  if default:
                      return default
                  else:
                      raise
          return obj

如果您想调用a.b.c.d,可以通过a.multi_getattr('b.c.d')来完成。这将概括操作而不用担心字符串中的点操作计数。