2012-08-15 58 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' 
相關問題