46
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

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

4

7 回答 7

93

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

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

这样做的唯一方法getattr是嵌套调用:

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

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

import operator
operator.attrgetter("b.c")(a)
于 2012-08-15T19:23:59.277 回答
49

Python 的内置reduce函数可以实现您正在寻找的功能。这是一个简单的小辅助函数,可以完成工作:

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'>
于 2013-01-14T18:37:20.133 回答
8

我认为您的困惑源于直点表示法 (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

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

另一面是 whilefoo.b.c等价于foo.__dict__['b'].__dict__['c'],getattr(foo, 'b.c')等价于foo.__dict__['b.c']。这就是为什么getattr不能按预期工作的原因。

于 2012-08-15T19:50:47.353 回答
6

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

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

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

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

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

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

b = getattr(a, "b")
c = getattr(b, "c")
于 2012-08-15T19:26:09.117 回答
6

我认为实现你想要的最直接的方法是使用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'
于 2016-08-23T09:46:52.473 回答
1

您可以通过拆分点运算符并为每个点运算符执行 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

如果假设你想调用 abcd,你可以通过 a.multi_getattr('bcd') 来实现。这将概括操作而不用担心字符串中点操作的计数。

于 2016-05-16T11:05:50.563 回答
0

应该返回getattr('a.b', {'a': None}, 'default-value'}什么?它应该提高AttributeError还是返回'default-value'?这就是为什么如果引入复杂的键getattr会使其难以使用。

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

于 2012-08-15T19:34:06.920 回答