2013-04-26 69 views
24

我班有一個快譯通,例如:Python:如何實現__getattr __()?

class MyClass(object): 
    def __init__(self): 
     self.data = {'a': 'v1', 'b': 'v2'} 

然後我想用字典的鍵與MyClass的實例來訪問字典,例如:

ob = MyClass() 
v = ob.a # Here I expect ob.a returns 'v1' 

我知道這應該可以實現由__getattr__,但我是Python的新手,我不知道如何實現它。

+5

理想的情況下,一點都沒有。 ;-) – delnan 2013-04-26 13:28:18

+0

你可能不想在實踐中這樣做。如果您的數據屬於字典,請使用字典;如果你的數據屬於一個對象,使用一個對象。無論如何,['namedtuple'](http://docs.python.org/3.3/library/collections.html#namedtuple-factory-function-for-tuples-with-named-fields),其工作方式類似於輕量級的對象,可能會做你想做的。 – 2013-04-26 13:30:50

+0

請記住'__getattr__'僅用於缺少屬性查找。 – Kos 2013-04-26 13:37:04

回答

43
class MyClass(object): 

    def __init__(self): 
     self.data = {'a': 'v1', 'b': 'v2'} 

    def __getattr__(self, attr): 
     return self.data[attr] 

>>> ob = MyClass() 
>>> v = ob.a 
>>> v 
'v1' 

實施__setattr__時,雖然要小心,你將需要進行一些修改:

class MyClass(object): 

    def __init__(self): 
     # prevents infinite recursion from self.data = {'a': 'v1', 'b': 'v2'} 
     # as now we have __setattr__, which will call __getattr__ when the line 
     # self.data[k] tries to access self.data, won't find it in the instance 
     # dictionary and return self.data[k] will in turn call __getattr__ 
     # for the same reason and so on.... so we manually set data initially 
     super(MyClass, self).__setattr__('data', {'a': 'v1', 'b': 'v2'}) 

    def __setattr__(self, k, v): 
     self.data[k] = v 

    def __getattr__(self, k): 
     # we don't need a special call to super here because getattr is only 
     # called when an attribute is NOT found in the instance's dictionary 
     try: 
      return self.data[k] 
     except KeyError: 
      raise AttributeError 

>>> ob = MyClass() 
>>> ob.c = 1 
>>> ob.c 
1 

如果您不需要設置屬性只是使用一個namedtuple 例如。

>>> from collections import namedtuple 
>>> MyClass = namedtuple("MyClass", ["a", "b"]) 
>>> ob = MyClass(a=1, b=2) 
>>> ob.a 
1 

如果你想在默認參數,你可以只寫一個包裝類周圍:

class MyClass(namedtuple("MyClass", ["a", "b"])): 

    def __new__(cls, a="v1", b="v2"): 
     return super(MyClass, cls).__new__(cls, a, b) 

或者它看起來就像一個功能更好:

def MyClass(a="v1", b="v2", cls=namedtuple("MyClass", ["a", "b"])): 
    return cls(a, b) 

>>> ob = MyClass() 
>>> ob.a 
'v1' 
+0

如果我只實現\ __ getattr__,是** ob.a **只讀? – TieDad 2013-04-26 13:33:34

+0

@EvanLi是的,如果你的意思是'ob.data ['a']'。你仍然可以設置'ob.a = 1',但是這會設置'ob .__ dict __ ['a']'(實例的字典,不是你的!)。那麼當你訪問'ob.a'時,它不會調用'__getattr__',因爲'__getattr__'在實例中已經存在的屬性時被繞過 – jamylak 2013-04-26 13:35:23

+0

有沒有辦法阻止* ob.a = 1 *?也許要實現\ __ setattr__並引發異常? – TieDad 2013-04-26 13:39:05

3
class A(object): 
    def __init__(self): 
    self.data = {'a': 'v1', 'b': 'v2'} 
    def __getattr__(self, attr): 
    try: 
     return self.data[attr] 
    except: 
     return "not found" 


>>>a = A() 
>>>print a.a 
v1 
>>>print a.c 
not found 
+0

在這種情況下引發異常可能會更好,而不是返回'None'。無論如何,你的代碼可以縮短爲'return self.data.get(attr)' – jamylak 2013-04-26 13:36:03

+1

如果找不到密鑰,你可以指定一個默認值。 '.get(attr,「not found」)' – 2013-04-26 13:39:52

+0

@limelights「找不到」+1 – 2013-04-26 14:03:57

3

因此我喜歡這樣做。

我從某個地方拿過它,但我不記得在哪裏。

class A(dict): 
    def __init__(self, *a, **k): 
     super(A, self).__init__(*a, **k) 
     self.__dict__ = self 

這使得對象的__dict__一樣的本身,使屬性和項目的訪問映射到相同的字典:

a = A() 
a['a'] = 2 
a.b = 5 
print a.a, a['b'] # prints 2 5 
+0

我認爲這是由Alex Martelli所做的,但我可能是錯的,我在這裏找到它http://stackoverflow.com/a/14620633我相信它的名字'AttrDict' – jamylak 2013-04-26 14:03:21

+0

@jamylak它是更古老。我剛剛找到了3年前最後更改的https://github.com/brickZA/jsobject,並從http://www.jiaaro.com/making-python-objects-that-act-like-javascrip/中借用它。他們使用名稱'JsObject'。 – glglgl 2013-11-15 08:22:35

1

我想通了一個擴展@ glglgl的回答,處理嵌套字典和詞典內部列出了在原詞典:

class d(dict): 
    def __init__(self, *a, **k): 
     super(d, self).__init__(*a, **k) 
     self.__dict__ = self 
     for k in self.__dict__: 
      if isinstance(self.__dict__[k], dict): 
       self.__dict__[k] = d(self.__dict__[k]) 
      elif isinstance(self.__dict__[k], list): 
       for i in range(len(self.__dict__[k])): 
        if isinstance(self.__dict__[k][i], dict): 
         self.__dict__[k][i] = d(self.__dict__[k][i]) 
0

您可以初始化類字典通過構造函數:

def __init__(self,**data): 

,並調用它,如下所示:

f = MyClass(**{'a': 'v1', 'b': 'v2'}) 

所有實例的屬性被訪問(讀)在__setattr__,需要利用其母公司(超)方法是宣佈,只一次:

super().__setattr__('NewVarName1', InitialValue) 

或者

super().__setattr__('data', dict()) 

此之後,它們可以被訪問或以通常的方式分配給:

self.x = 1 

self.data = data 

屬性和實例屬性不被訪問在__setattr__,可以以通常的方式被聲明

重寫的__setattr__方法現在必須在其自身內部調用父方法,以便聲明新變量:

super().__setattr__(key,value) 

一個完整的類將如下所示:

class MyClass(object): 
    def __init__(self, **data): 
     # The variable self.data is used by method __setattr__ 
     # inside this class, so we will need to declare it 
     # using the parent __setattr__ method: 
     super().__setattr__('data', dict()) 
     self.data = data    
     # These declarations will jump to 
     # super().__setattr__('data', dict()) 
     # inside method __setattr__ of this class: 
     self.x = 1 
     self.y = 2 

    def __getattr__(self, name): 
    # This will callback will never be called for instance variables 
    # that have beed declared before being accessed. 
     if name in self.data: 
      # Return a valid dictionary item: 
      return self.data[name] 
     else: 
      # So when an instance variable is being accessed, and 
      # it has not been declared before, nor is it contained 
      # in dictionary 'data', an attribute exception needs to 
      # be raised. 
      raise AttributeError 

    def __setattr__(self, key, value): 
     if key in self.data: 
      # Assign valid dictionary items here: 
      self.data[key] = value 
     else: 
      # Assign anything else as an instance attribute: 
      super().__setattr__(key,value) 

測試:

f = MyClass(**{'a': 'v1', 'b': 'v2'}) 
print("f.a = ", f.a) 
print("f.b = ", f.b) 
print("f.data = ", f.data) 
f.a = 'c' 
f.d = 'e' 
print("f.a = ", f.a) 
print("f.b = ", f.b) 
print("f.data = ", f.data) 
print("f.d = ", f.d) 
print("f.x = ", f.x) 
print("f.y = ", f.y) 
# Should raise attributed Error 
print("f.g = ", f.g) 

輸出:

f.a = v1 
f.b = v2 
f.data = {'a': 'v1', 'b': 'v2'} 
f.a = c 
f.b = v2 
f.data = {'a': 'c', 'b': 'v2'} 
f.d = e 
f.x = 1 
f.y = 2 
Traceback (most recent call last): 
    File "MyClass.py", line 49, in <module> 
    print("f.g = ", f.g) 
    File "MyClass.py", line 25, in __getattr__ 
    raise AttributeError 
AttributeError 
0

我想,這是實現冷卻器

class MyClass(object): 
    def __init__(self): 
     self.data = {'a': 'v1', 'b': 'v2'} 
    def __getattr__(self,key): 
     return self.data.get(key,None) 
3

晚會晚了,但發現了兩個非常好的資源,可以更好地解釋這一點(恕我直言)。

http://western-skies.blogspot.com.br/2008/02/complete-example-of-getattr-in-python.html中所述,您應該使用self.__dict__來訪問__getattr__中的字段,以避免無限遞歸。所提供的例子是:

def __getattr__(self, attrName): 
    if not self.__dict__.has_key(attrName): 
    value = self.fetchAttr(attrName) # computes the value 
    self.__dict__[attrName] = value 
    return self.__dict__[attrName] 

注意:在第二行(上圖),一個更Python方法是(has_key顯然在Python 3甚至移除):

if attrName not in self.__dict__: 

另一個資源(http://farmdev.com/src/secrets/magicmethod/#introducing-getattr)解釋說__getattr__只有在該對象中沒有找到該屬性時纔會被調用,並且hasattr總是返回True如果存在的實現。它提供了下面的例子,來證明:

class Test(object): 
    def __init__(self): 
     self.a = 'a' 
     self.b = 'b' 

    def __getattr__(self, name): 
     return 123456 

t = Test() 
print 'object variables: %r' % t.__dict__.keys() 
#=> object variables: ['a', 'b'] 
print t.a 
#=> a 
print t.b 
#=> b 
print t.c 
#=> 123456 
print getattr(t, 'd') 
#=> 123456 
print hasattr(t, 'x') 
#=> True