2013-04-26 16 views
24

La mia classe ha un dizionario, per esempio:Python: come implementare __getattr __()?

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

poi voglio usare il tasto del dict con MyClass esempio per accedere al dizionario, per esempio:

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

So che questo dovrebbe essere attuata di __getattr__, ma sono nuovo di Python, non so esattamente come implementarlo.

+5

Idealmente, non a tutti. ;-) – delnan

+0

Probabilmente non vorrai davvero farlo in pratica. Se i tuoi dati appartengono a un ditt, usa un ditt; se i tuoi dati appartengono a un oggetto, usa un oggetto. Comunque, ['namedtuple'] (http://docs.python.org/3.3/library/collections.html#namedtuple-factory-function-for-tuples-with-named-fields), che funziona in modo gentile oggetto leggero, potrebbe fare quello che vuoi. –

+0

Ricorda che '__getattr__' è usato solo per la ricerca degli attributi mancanti. – Kos

risposta

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' 

Fare attenzione in sede di attuazione __setattr__ però, è necessario fare alcune modifiche:

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 

Se non è necessario impostare gli attributi usano solo un namedtuple es.

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

Se si desidera che gli argomenti di default si può solo scrivere una classe wrapper attorno ad esso:

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

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

o forse sembra più bello come una funzione:

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

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

Se implemento solo __ getattr__, è ** ob.a ** readonly? – TieDad

+0

@EvanLi sì, se con questo intendi 'ob.data ['a']'. Puoi ancora impostare 'ob.a = 1', ma questo sarà impostato su 'ob .__ dict __ [' a ']' (il dizionario dell'istanza, non il tuo!). Quindi non chiamerà '__getattr__' quando si accede a' ob.a', poiché '__getattr__' viene ignorato quando l'attributo esiste già nell'istanza – jamylak

+0

C'è un modo per evitare di fare * ob.a = 1 *? Forse implementare \ __ setattr__ e generare un'eccezione? – TieDad

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

Potrebbe essere meglio sollevare un'eccezione in questo caso, invece di restituire "Nessuna". Ad ogni modo il tuo codice può essere abbreviato in 'return self.data.get (attr)' – jamylak

+1

E puoi specificare un valore predefinito da restituire se la chiave non è stata trovata. '.get (attr," not found ")' –

+0

@limelights "not found" +1 –

3

Mi piace prendere questo quindi.

L'ho preso da qualche parte, ma non ricordo dove.

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

Questo rende la __dict__ dell'oggetto stesso come se stesso, in modo che mappa degli attributi e la voce accesso alla stessa dict:

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

Ho pensato che fosse stato realizzato da Alex Martelli ma probabilmente ho torto, l'ho trovato qui http://stackoverflow.com/a/14620633 Credo che vada col nome "AttrDict' – jamylak

+0

@jamylak È molto più vecchio. Ho appena trovato https://github.com/brickZA/jsobject che è stato modificato l'ultima volta 3 anni fa e preso in prestito da http://www.jiaaro.com/making-python-objects-that-act-like-javascrip/. Usano il nome 'JsObject'. – glglgl

1

ho capito un'estensione di @ glglgl risposta che gestisce nidificato dizionari e dizionari elenchi interni che si trovano nel dizionario originale:

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

È possibile inizializzare il dizionario di classe tramite il costruttore:

def __init__(self,**data): 

e chiamarlo come segue:

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

Tutti dell'istanza attributi cui si accede (leggi) in __setattr__, hanno bisogno di essere dichiarato utilizzando il suo metodo parent (super), solo una volta:

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

O

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

Successivamente, si può accedere o assegnati nel solito modo:

self.data = data 

E istanza attributi non essere accessibili in __setattr__, può essere dichiarato nel modo usuale:

self.x = 1 

Il metodo __setattr__ sottoposto a override deve ora chiamare il metodo genitore al suo interno, affinché vengano dichiarate nuove variabili:

super().__setattr__(key,value) 

Una classe completo apparirebbe come segue:

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) 

prova:

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) 

uscita:

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

Penso che questo è più fresco implementare

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

In ritardo alla festa, ma ho trovato due ottime risorse che spiegano meglio (IMHO).

Come spiegato in http://western-skies.blogspot.com.br/2008/02/complete-example-of-getattr-in-python.html, è necessario utilizzare self.__dict__ per accedere ai campi da __getattr__, al fine di evitare la ricorsione infinita.L'esempio fornito è:

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] 

Nota: nella seconda riga (sopra), un modo più Pythonic sarebbe (has_key apparentemente è stato anche rimosso in Python 3):

if attrName not in self.__dict__: 

L'altra risorsa (http://farmdev.com/src/secrets/magicmethod/#introducing-getattr) spiega che lo __getattr__ viene richiamato solo quando l'attributo non viene trovato nell'oggetto e che restituisce sempre True se esiste un'implementazione per __getattr__. Esso fornisce il seguente esempio, per dimostrare:

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  
Problemi correlati