[Python] Come aggiungere proprietà a una classe in modo dinamico?


Answers

L'obiettivo è creare una classe di simulazione che si comporta come un set di risultati db.

Quindi quello che vuoi è un dizionario in cui puoi scrivere una ['b'] come ab?

Questo è facile:

class atdict(dict):
    __getattr__= dict.__getitem__
    __setattr__= dict.__setitem__
    __delattr__= dict.__delitem__
Question

L'obiettivo è creare una classe di simulazione che si comporta come un set di risultati db.

Quindi, ad esempio, se una query di database restituisce, usando un'espressione dict, {'ab':100, 'cd':200} , allora mi piacerebbe vedere:

>>> dummy.ab
100

All'inizio ho pensato che forse potevo farlo in questo modo:

ks = ['ab', 'cd']
vs = [12, 34]
class C(dict):
    def __init__(self, ks, vs):
        for i, k in enumerate(ks):
            self[k] = vs[i]
            setattr(self, k, property(lambda x: vs[i], self.fn_readyonly))

    def fn_readonly(self, v)
        raise "It is ready only"

if __name__ == "__main__":
    c = C(ks, vs)
    print c.ab

ma c.ab restituisce invece un oggetto proprietà.

Sostituire la linea setattr con k = property(lambda x: vs[i]) è inutile.

Allora, qual è il modo giusto per creare una proprietà di istanza in fase di runtime?

PS Sono a conoscenza di un'alternativa presentata in Come viene utilizzato il metodo __getattribute__ ?




Il modo migliore per ottenere è definire __slots__ . In questo modo le tue istanze non possono avere nuovi attributi.

ks = ['ab', 'cd']
vs = [12, 34]

class C(dict):
    __slots__ = []
    def __init__(self, ks, vs): self.update(zip(ks, vs))
    def __getattr__(self, key): return self[key]

if __name__ == "__main__":
    c = C(ks, vs)
    print c.ab

Questo stampa 12

    c.ab = 33

Che dà: AttributeError: 'C' object has no attribute 'ab'




È possibile utilizzare il seguente codice per aggiornare gli attributi della classe utilizzando un oggetto dizionario:

class ExampleClass():
    def __init__(self, argv):
        for key, val in argv.items():
            self.__dict__[key] = val

if __name__ == '__main__':
    argv = {'intro': 'Hello World!'}
    instance = ExampleClass(argv)
    print instance.intro



Non è necessario utilizzare una proprietà per questo. Basta sovrascrivere __setattr__ per renderli di sola lettura.

class C(object):
    def __init__(self, keys, values):
        for (key, value) in zip(keys, values):
            self.__dict__[key] = value

    def __setattr__(self, name, value):
        raise Exception("It is read only!")

Tada.

>>> c = C('abc', [1,2,3])
>>> c.a
1
>>> c.b
2
>>> c.c
3
>>> c.d
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'C' object has no attribute 'd'
>>> c.d = 42
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 6, in __setattr__
Exception: It is read only!
>>> c.a = 'blah'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 6, in __setattr__
Exception: It is read only!



L'unico modo per collegare dinamicamente una proprietà è creare una nuova classe e la sua istanza con la tua nuova proprietà.

class Holder: p = property(lambda x: vs[i], self.fn_readonly)
setattr(self, k, Holder().p)



Come aggiungere proprietà a una classe Python in modo dinamico?

Supponi di avere un oggetto a cui desideri aggiungere una proprietà. In genere, desidero utilizzare le proprietà quando devo iniziare a gestire l'accesso a un attributo nel codice che utilizza l'utilizzo a valle, in modo da poter mantenere un'API coerente. Ora li aggiungerò tipicamente al codice sorgente in cui è definito l'oggetto, ma supponiamo che tu non abbia quell'accesso, o che devi scegliere dinamicamente le tue funzioni in modo programmatico.

Crea una classe

Usando un esempio basato sulla documentazione per la property , creiamo una classe di oggetti con un attributo "nascosto" e ne creiamo un'istanza:

class C(object):
    '''basic class'''
    _x = None

o = C()

In Python, ci aspettiamo che ci sia un modo ovvio di fare le cose. Tuttavia, in questo caso, ho intenzione di mostrare due modi: con la notazione decoratore, e senza. Innanzitutto, senza notazione decoratore. Questo può essere più utile per l'assegnazione dinamica di getter, setter o deleter.

Dinamica (alias Monkey Patching)

Creiamo alcuni per la nostra classe:

def getx(self):
    return self._x

def setx(self, value):
    self._x = value

def delx(self):
    del self._x

E ora li assegniamo alla proprietà. Nota che potremmo scegliere le nostre funzioni in modo programmatico qui, rispondendo alla domanda dinamica:

C.x = property(getx, setx, delx, "I'm the 'x' property.")

E l'uso:

>>> o.x = 'foo'
>>> o.x
'foo'
>>> del o.x
>>> print(o.x)
None
>>> help(C.x)
Help on property:

    I'm the 'x' property.

decoratori

Potremmo fare lo stesso come abbiamo fatto sopra con la notazione decorator, ma in questo caso, dobbiamo nominare tutti i metodi con lo stesso nome (e ti consiglio di mantenerlo uguale all'attributo), quindi l'assegnazione programmatica non è così banale come sta usando il metodo sopra:

@property
def x(self):
    '''I'm the 'x' property.'''
    return self._x

@x.setter
def x(self, value):
    self._x = value

@x.deleter
def x(self):
    del self._x

E assegna alla classe l'oggetto proprietà con i setter e i delet settati in dotazione:

C.x = x

E l'uso:

>>> help(C.x)
Help on property:

    I'm the 'x' property.

>>> o.x
>>> o.x = 'foo'
>>> o.x
'foo'
>>> del o.x
>>> print(o.x)
None



Per rispondere alla spinta principale della tua domanda, vuoi un attributo di sola lettura da un dict come origine dati immutabile:

L'obiettivo è creare una classe di simulazione che si comporta come un set di risultati db.

Ad esempio, se una query di database restituisce, usando un'espressione dict, {'ab':100, 'cd':200} , quindi vorrei vedere

>>> dummy.ab
100

namedtuple come usare un namedtuple dal modulo collections per realizzare proprio questo:

import collections

data = {'ab':100, 'cd':200}

def maketuple(d):
    '''given a dict, return a namedtuple'''
    Tup = collections.namedtuple('TupName', d.keys()) # iterkeys in Python2
    return Tup(**d)

dummy = maketuple(data)
dummy.ab

restituisce 100




Ho fatto una domanda simile su questo post di Overflow dello stack per creare una factory class che ha creato tipi semplici. Il risultato fu questa risposta che aveva una versione funzionante della fabbrica di classe. Ecco un frammento della risposta:

def Struct(*args, **kwargs):
    def init(self, *iargs, **ikwargs):
        for k,v in kwargs.items():
            setattr(self, k, v)
        for i in range(len(iargs)):
            setattr(self, args[i], iargs[i])
        for k,v in ikwargs.items():
            setattr(self, k, v)

    name = kwargs.pop("name", "MyStruct")
    kwargs.update(dict((k, None) for k in args))
    return type(name, (object,), {'__init__': init, '__slots__': kwargs.keys()})

>>> Person = Struct('fname', 'age')
>>> person1 = Person('Kevin', 25)
>>> person2 = Person(age=42, fname='Terry')
>>> person1.age += 10
>>> person2.age -= 10
>>> person1.fname, person1.age, person2.fname, person2.age
('Kevin', 35, 'Terry', 32)
>>>

È possibile utilizzare alcune varianti di questo per creare valori predefiniti che è il tuo obiettivo (c'è anche una risposta in quella domanda che si occupa di questo).