2011-07-18 38 views
9

我有接受字典或其他對象的方法以及從這些對象中獲取「字段」的名稱。如果對象是字典,則該方法使用__getitem__來檢索指定的密鑰,否則它使用getattr來檢索指定的屬性。這在網頁模板語言中很常見。例如,在一個Chameleon模板,你可能有:爲什麼Python沒有內置的混合getattr + __getitem__?

<p tal:content="foo.keyname">Stuff goes here</p> 

如果您在foo通過像{'keyname':'bar'}的字典,然後foo.keyname取了「鍵名」鍵,進入「酒吧」。如果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) 

的事情是,這是一個很常見的模式。我已經在很多模塊中看到過這種方法或者非常相似的方法。那麼,爲什麼在語言的核心,或者至少在其中一個核心模塊中不是這樣?如果沒有,那麼應該如何編寫?有沒有確定的方法?

+2

我強烈反對說:「事情是,這是一個很常見的模式」。顯式比顯式更好。屬性訪問完全可以通過鍵訪問... –

+1

是的,但它在頁面模板語言等特定環境中仍然很常見。我已經看到了它(並且需要實現它),足以希望將其重構爲核心模塊或通用模塊。 –

+2

沒有這樣的東西 - 可能是因爲Python開發人員不想強制使用錯誤的編程風格.... point ..如果你編寫不好的代碼,那麼你必須明確地寫出它....明確比隱含更好。 –

回答

15

我有點半讀你的問題,寫下面,然後重讀你的問題,並意識到我回答了一個微妙不同的問題。但我認爲下面實際上仍然提供了一種答案。如果你不這麼認爲,假設你已經提出了這個更一般的問題,我認爲這包括你的子問題:

「爲什麼Python不提供任何內置的方式來處理屬性和項目可以互換?「


我已經對這個問題給了一點點思考,我想答案很簡單。在創建容器類型時,區分屬性項目是非常重要的。任何相當發達的容器類型都會有許多屬性 - 通常並不總是方法 - 使它能夠以優雅的方式管理其內容。舉例來說,字典有itemsvalues,keys,iterkeys等等。這些屬性全部使用.表示法進行訪問。另一方面,項目使用[]表示法進行訪問。所以不會有碰撞。

當您使用.表示法啓用物品訪問時會發生什麼情況?突然間你有重疊名字空間。你現在如何處理碰撞?如果你繼承了一個字典並賦予它這個功能,那麼你不能使用像items這樣的鍵,否則你必須創建某種名稱空間層次結構。第一個選項創建了一個繁重,難以遵循且難以執行的規則。第二個選項創建了一個令人討厭的複雜度,但沒有完全解決碰撞問題,因爲您仍然必須有一個可選接口來指定是否需要items項或items屬性。

現在,對於某些非常原始的類型,這是可以接受的。例如,這可能就是爲什麼標準庫中有namedtuple。 (但是要注意,namedtuple受這些非常的問題,這可能是爲什麼它被作爲一個工廠函數實現的(防止繼承),並使用怪異,私有方法的名稱,如_asdict。)

這也是非常,非常,非常容易創建一個沒有(公共)屬性的object的子類,並在其上使用setattr。它甚至很容易覆蓋__getitem____setitem____delitem__調用__getattribute____setattr____delattr__,使項目的訪問剛剛成爲getattr()語法糖,setattr()等(雖然這是一個有點更值得懷疑,因爲它創造多少有些意外的行爲。)

但是,對於任何想要能夠擴展和繼承的發展良好的容器類,添加新的有用屬性,坦然地說,一個__getattr__ + __getitem__混合將是一個巨大的PITA。

4

你可以很容易地編寫自己的dict子類,它本來就是這樣表現的。最小實現,我喜歡叫一個屬性的「堆」,就像這樣:

class Pile(dict): 
    def __getattr__(self, key): 
     return self[key] 
    def __setattr__(self, key, value): 
     self[key] = value 

不幸的是,如果你需要能夠應對任何字典或傳遞給你的屬性載貨的對象,而不是從一開始就控制了對象,這無濟於事。

在您的情況下,我可能會使用非常類似於您所擁有的東西,除非將它分解爲函數,所以我不必一直重複。

+2

這就是事情;我並沒有真正控制傳入的內容,我想盡可能簡化包的用戶,最好使用與已經解決它的其他人相同的語義。 :-)雖然,感謝您的答案。對不起誰低估了它。 : - / –

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 

或者你可以很容易地定義自己的類型總是實際存儲到它的字典,但是允許的屬性設置和獲取外觀:

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中使用了ducktyping和所有其他類型的僞裝,並且類實際上只是字典,但是在某些時候,我們需要像字典或列表這樣的容器的不同功能,而不是我們希望從類中獲得的功能,並且它的方法解析順序,重寫等。

相關問題