2014-12-24 17 views
8

Vorrei definire il metaclass che mi consentirà di creare proprietà (cioè setter, getter) in una nuova classe sulla base degli attributi della classe.Python - metaclasse - aggiunta di proprietà

Per esempio, vorrei definire la classe:

class Person(metaclass=MetaReadOnly): 
    name = "Ketty" 
    age = 22 

    def __str__(self): 
     return ("Name: " + str(self.name) + "; age: " + str(self.age)) 

Ma mi piacerebbe avere qualcosa di simile:

class Person(): 
    __name = "Ketty" 
    __age = 22 

    @property 
    def name(self): 
     return self.__name; 
    @name.setter 
    def name(self, value): 
     raise RuntimeError("Read only") 

    @property 
    def age(self): 
     return self.__age 
    @age.setter 
    def age(self, value): 
     raise RuntimeError("Read only") 

    def __str__(self): 
     return ("Name: " + str(self.name) + "; age: " + str(self.age)) 

Ecco la metaclasse ho scritto:

class MetaReadOnly(type): 
    def __new__(cls, clsname, bases, dct): 

     result_dct = {} 

     for key, value in dct.items(): 
      if not key.startswith("__"): 
       result_dct["__" + key] = value 

       fget = lambda self: getattr(self, "__%s" % key) 
       fset = lambda self, value: setattr(self, "__" + key, value) 

       result_dct[key] = property(fget, fset) 

      else: 
       result_dct[key] = value  

     inst = super(MetaReadOnly, cls).__new__(cls, clsname, bases, result_dct) 

     return inst 

    def raiseerror(self, attribute): 
     raise RuntimeError("%s is read only." % attribute) 

Tuttavia, non funziona correttamente.

client = Person() 
print(client) 

A volte ottengo:

Name: Ketty; age: Ketty 
volte

:

Name: 22; age: 22 

o addirittura di errore:

Traceback (most recent call last): 
    File "F:\Projects\TestP\src\main.py", line 38, in <module> 
    print(client) 
    File "F:\Projects\TestP\src\main.py", line 34, in __str__ 
    return ("Name: " + str(self.name) + "; age: " + str(self.age)) 
    File "F:\Projects\TestP\src\main.py", line 13, in <lambda> 
    fget = lambda self: getattr(self, "__%s" % key) 
AttributeError: 'Person' object has no attribute '____qualname__' 

ho trovato esempio di come si può fare altro modo (Python classes: Dynamic properties), ma lo farei piace farlo con metaclass. Hai idea di come si possa fare o è possibile?

risposta

11

Bind l'attuale valore didel key al parametro nei tuoi fget e fset definizioni:

fget = lambda self, k=key: getattr(self, "__%s" % k) 
fset = lambda self, value, k=key: setattr(self, "__" + k, value) 

Questa è una trappola Python classico. Quando si definisce

fget = lambda self: getattr(self, "__%s" % key) 

il valore di key è determinato quando fget viene chiamato, non quando fget è definito. Poiché è una variabile non locale, il suo valore si trova nell'ambito della funzione __new__. Quando viene chiamato il numero fget, lo for-loop è terminato, quindi il valore ultimo corrisponde a key. Il metodo dict.item di Python3 restituisce gli articoli in un ordine imprevedibile, quindi a volte l'ultima chiave è, ad esempio, __qualname__, motivo per cui talvolta viene sollevato un errore sorprendente ea volte viene restituito lo stesso valore errato per tutti gli attributi senza errori.


Quando si definisce una funzione con un parametro con un valore di default, il valore predefinito è legato al parametro al momento viene definita la funzione. Quindi, i valori correnti di key vengono corretti vincolati a fget e fset quando si associano i valori predefiniti a k.

Diversamente da prima, k è ora una variabile locale. Il valore predefinito è memorizzato in fget.__defaults__ e fset.__defaults__.


Un'altra opzione è utilizzare un . È possibile definire questo al di fuori della meta-classe:

def make_fget(key): 
    def fget(self): 
     return getattr(self, "__%s" % key) 
    return fget 
def make_fset(key): 
    def fset(self, value): 
     setattr(self, "__" + key, value) 
    return fset 

e utilizzarlo all'interno della metaclasse in questo modo:

result_dct[key] = property(make_fget(key), make_fset(key)) 

Ora, quando fget o fset si chiama, il valore corretto di key si trova nel racchiude portata di make_fget o make_fset.

+0

Grazie mille :) – hakubaa

Problemi correlati