2012-08-15 26 views
22
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 

我认为后者似乎很自然。我确信这有一个很好的理由。它是什么?为什么`getattr`不支持连续的属性检索?

+4

现在这样做:'SETATTR(一, 'b.c',2)'。 getattr(a,'b.c')'现在应该返回什么?如果在'b'之前没有'c',怎么办?你可以在属性名称中使用'.',所以你不能指望'getattr'能够像这样遍历对象。 – 2012-08-15 19:27:18

回答

30

您不能在getattr函数中放置句点,因为getattr是对象的直接字典查找。

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

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

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

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

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

OP已经意识到......我想他是在问为什么。不是一个无理由的问题operator.attrgetter('a.b')(obj)_will_ resolve dotted notation – 2012-08-15 19:28:09

+0

感谢您指出。澄清我的回应,希望更好地解释我的意思。 – 2012-08-15 20:28:28

+0

这是一个非常好的和简单的解决一个令人不安的问题。下面的答案并不简单。这个答案不尝试去实现Python已经做了的事情,这使得它很好。 – Bobort 2016-10-20 14:55:00

6

因为getattr不能这样工作。 getattr使用给定名称(第二个参数)获取给定对象(第一个参数)的属性。所以,你的代码:

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

表示:访问 “b.c” 由 “一”引用对象的属性。显然你的对象没有叫做“b.c”的属性。

得到 “C” 的属性,你必须使用两个getattr电话:

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

让我们来解开它更好的理解:

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

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

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

4

我认为你的困惑是由于直点符号(例如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不能像你期望的那样工作。

+1

天哪,我看到这发生在一个混淆的代码比赛... – 2012-08-21 03:48:40

37

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'> 
+3

这应该是接受的答案imho,从塔那的答案是旁边没用。 – sleepycal 2014-03-12 16:15:58

+0

我都很好的回答,这样就完成了工作。但是,这个答案并没有给OP提供什么他正在寻找的原因 - 它不起作用的原因。 – 2014-03-12 16:55:24

+0

接受的答案不能解决原始问题。假设你只给了字符串“b.c”,这是解决问题的唯一答案。 – 2014-05-02 14:16:17

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 

调用函数中的一个函数。如果假设你希望ABCD,你可以调用通过a.multi_getattr('bcd')来完成。这将推广操作而不用担心字符串中的点操作的数量。

2

我认为实现你想要的最直接的方法是使用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' 
相关问题