programacion poo otra orientada objetos metodo llamar lista instancia importar herencia ejemplos clases clase python properties runtime monkeypatching

python - poo - ¿Cómo agregar propiedades a una clase de forma dinámica?



poo python 3 (17)

El objetivo es crear una clase simulada que se comporte como un conjunto de resultados db.

Entonces, por ejemplo, si una consulta de base de datos retorna, usando una expresión dict, {''ab'':100, ''cd'':200} , entonces me gustaría ver:

>>> dummy.ab 100

Al principio pensé que tal vez podría hacerlo de esta manera:

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

pero c.ab devuelve un objeto de propiedad en su lugar.

Reemplazar la línea de setattr con k = property(lambda x: vs[i]) no sirve para nada.

Entonces, ¿cuál es la forma correcta de crear una propiedad de instancia en tiempo de ejecución?

PD. Estoy al tanto de una alternativa presentada en ¿Cómo se __getattribute__ método __getattribute__ ?


¿Cómo agregar propiedades a una clase python de forma dinámica?

Supongamos que tiene un objeto al que desea agregarle una propiedad. Normalmente, quiero usar propiedades cuando necesito comenzar a administrar el acceso a un atributo en el código que tiene un uso posterior, de modo que pueda mantener una API consistente. Ahora normalmente los agregaré al código fuente donde se define el objeto, pero supongamos que no tiene ese acceso, o necesita elegir dinámicamente sus funciones de forma programática.

Crear una clase

Usando un ejemplo basado en la documentación de la property , crearemos una clase de objeto con un atributo "oculto" y crearemos una instancia de este:

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

En Python, esperamos que haya una forma obvia de hacer las cosas. Sin embargo, en este caso, voy a mostrar dos formas: con notación de decorador y sin. Primero, sin notación de decorador. Esto puede ser más útil para la asignación dinámica de getters, setters o deleters.

Dinámico (también conocido como Monkey Patching)

Vamos a crear algunos para nuestra clase:

def getx(self): return self._x def setx(self, value): self._x = value def delx(self): del self._x

Y ahora asignamos estos a la propiedad. Tenga en cuenta que podríamos elegir nuestras funciones programáticamente aquí, respondiendo la pregunta dinámica:

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

Y el 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.

Decoradores

Podríamos hacer lo mismo que hicimos anteriormente con la notación del decorador, pero en este caso, debemos nombrar los métodos con el mismo nombre (y recomendaría mantenerlo igual que el atributo), por lo que la asignación programática no es tan trivial como está usando el método anterior:

@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

Y asigne el objeto de propiedad con sus setters y deleters aprovisionados a la clase:

C.x = x

Y el 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


El objetivo es crear una clase simulada que se comporte como un conjunto de resultados db.

Entonces, ¿qué quieres es un diccionario donde puedes deletrear un [''b''] como ab?

Eso es fácil:

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


Esto parece funcionar (pero ver a continuación):

class data(dict,object): def __init__(self,*args,**argd): dict.__init__(self,*args,**argd) self.__dict__.update(self) def __setattr__(self,name,value): raise AttributeError,"Attribute ''%s'' of ''%s'' object cannot be set"%(name,self.__class__.__name__) def __delattr__(self,name): raise AttributeError,"Attribute ''%s'' of ''%s'' object cannot be deleted"%(name,self.__class__.__name__)

Si necesita un comportamiento más complejo, no dude en editar su respuesta.

editar

Probablemente, lo siguiente sea más eficiente con la memoria para grandes conjuntos de datos:

class data(dict,object): def __init__(self,*args,**argd): dict.__init__(self,*args,**argd) def __getattr__(self,name): return self[name] def __setattr__(self,name,value): raise AttributeError,"Attribute ''%s'' of ''%s'' object cannot be set"%(name,self.__class__.__name__) def __delattr__(self,name): raise AttributeError,"Attribute ''%s'' of ''%s'' object cannot be deleted"%(name,self.__class__.__name__)


Hice una pregunta similar en esta publicación de para crear una fábrica de clases que creara tipos simples. El resultado fue esta respuesta que tenía una versión funcional de la fábrica de la clase. Aquí hay un fragmento de la respuesta:

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) >>>

Puede usar alguna variación de esto para crear valores predeterminados, que es su objetivo (también hay una respuesta en esa pregunta que trata sobre esto).


La única forma de adjuntar dinámicamente una propiedad es crear una nueva clase y su instancia con su nueva propiedad.

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


La mejor forma de lograrlo es definiendo __slots__ . De esta manera, sus instancias no pueden tener nuevos atributos.

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

Eso imprime 12

c.ab = 33

Eso da: AttributeError: ''C'' object has no attribute ''ab''


No estoy seguro si entiendo completamente la pregunta, pero puede modificar las propiedades de instancia en tiempo de ejecución con el __dict__ de su clase:

class C(object): def __init__(self, ks, vs): self.__dict__ = dict(zip(ks, vs)) if __name__ == "__main__": ks = [''ab'', ''cd''] vs = [12, 34] c = C(ks, vs) print(c.ab) # 12


No necesita usar una propiedad para eso. Simplemente anule __setattr__ para que solo sean de lectura.

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!


No puede agregar una nueva property() a una instancia en tiempo de ejecución, porque las propiedades son descriptores de datos. En su lugar, debe crear dinámicamente una nueva clase o sobrecargar __getattribute__ para procesar descriptores de datos en instancias.


Para aquellos que vienen de los motores de búsqueda, aquí están las dos cosas que estaba buscando cuando hablo de las propiedades dinámicas :

class Foo: def __init__(self): # we can dynamically have access to the properties dict using __dict__ self.__dict__[''foo''] = ''bar'' assert Foo().foo == ''bar'' # or we can use __getattr__ and __setattr__ to execute code on set/get class Bar: def __init__(self): self._data = {} def __getattr__(self, key): return self._data[key] def __setattr__(self, key, value): self._data[key] = value bar = Bar() bar.foo = ''bar'' assert bar.foo == ''bar''

__dict__ es bueno si quieres poner propiedades creadas dinámicamente. __getattr__ es bueno solo hacer algo cuando el valor es necesario, como consultar una base de datos. El conjunto combinado set / get es bueno para simplificar el acceso a los datos almacenados en la clase (como en el ejemplo anterior).

Si solo desea una propiedad dinámica, eche un vistazo a la función incorporada de property() .


Para responder al objetivo principal de su pregunta, desea un atributo de solo lectura de un dict como fuente de datos inmutable:

El objetivo es crear una clase simulada que se comporte como un conjunto de resultados db.

Entonces, por ejemplo, si una consulta de base de datos retorna, usando una expresión dict, {''ab'':100, ''cd'':200} , entonces yo vería

>>> dummy.ab 100

Demostraré cómo usar una namedtuple del módulo de collections para lograr solo esto:

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

devuelve 100


Parece que puedes resolver este problema mucho más simplemente con una namedtuple , ya que conoces toda la lista de campos con anticipación.

from collections import namedtuple Foo = namedtuple(''Foo'', [''bar'', ''quux'']) foo = Foo(bar=13, quux=74) print foo.bar, foo.quux foo2 = Foo() # error

Si necesitas escribir tu propio setter, tendrás que hacer la metaprogramación en el nivel de clase; property() no funciona en instancias.


Puede usar el siguiente código para actualizar los atributos de clase utilizando un objeto de diccionario:

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


Recientemente me encontré con un problema similar, la solución que se me ocurrió utiliza __getattr__ y __setattr__ para las propiedades que quiero que maneje, todo lo demás pasa a los originales.

class C(object): def __init__(self, properties): self.existing = "Still Here" self.properties = properties def __getattr__(self, name): if "properties" in self.__dict__ and name in self.properties: return self.properties[name] # Or call a function, etc return self.__dict__[name] def __setattr__(self, name, value): if "properties" in self.__dict__ and name in self.properties: self.properties[name] = value else: self.__dict__[name] = value if __name__ == "__main__": my_properties = {''a'':1, ''b'':2, ''c'':3} c = C(my_properties) assert c.a == 1 assert c.existing == "Still Here" c.b = 10 assert c.properties[''b''] == 10


Solo otro ejemplo de cómo lograr el efecto deseado

class Foo(object): _bar = None @property def bar(self): return self._bar @bar.setter def bar(self, value): self._bar = value def __init__(self, dyn_property_name): setattr(Foo, dyn_property_name, Foo.bar)

Entonces ahora podemos hacer cosas como estas:

>>> foo = Foo(''baz'') >>> foo.baz = 5 >>> foo.bar 5 >>> foo.baz 5


Supongo que debería ampliar esta respuesta, ahora que soy más viejo y más sabio y sé lo que está pasando. Mejor tarde que nunca.

Puede agregar una propiedad a una clase de forma dinámica. Pero ese es el problema: debes agregarlo a la clase .

>>> class Foo(object): ... pass ... >>> foo = Foo() >>> foo.a = 3 >>> Foo.b = property(lambda self: self.a + 1) >>> foo.b 4

Una property es en realidad una implementación simple de una cosa llamada descriptor . Es un objeto que proporciona un manejo personalizado para un atributo dado, en una clase dada . Algo así como una forma de factorizar un gran árbol if __getattribute__ .

Cuando solicito foo.b en el ejemplo anterior, Python ve que b definido en la clase implementa el protocolo de descriptor, lo que significa que es un objeto con un __get__ , __set__ o __delete__ . El descriptor se responsabiliza por el manejo de ese atributo, por lo que Python llama a Foo.b.__get__(foo, Foo) y el valor devuelto se le transfiere a usted como el valor del atributo. En el caso de la property , cada uno de estos métodos simplemente llama a fget , fset o fdel que pasó al constructor de la property .

Los descriptores son en realidad la forma en que Python expone las tuberías de toda su implementación de OO. De hecho, hay otro tipo de descriptor aún más común que la property .

>>> class Foo(object): ... def bar(self): ... pass ... >>> Foo().bar <bound method Foo.bar of <__main__.Foo object at 0x7f2a439d5dd0>> >>> Foo().bar.__get__ <method-wrapper ''__get__'' of instancemethod object at 0x7f2a43a8a5a0>

El método humilde es solo otro tipo de descriptor. Su __get__ tachuelas en la instancia de llamada como primer argumento; en efecto, hace esto:

def __get__(self, instance, owner): return functools.partial(self.function, instance)

De todos modos, sospecho que es por eso que los descriptores solo funcionan en las clases: son una formalización de las cosas que dan poder a las clases en primer lugar. Incluso son la excepción a la regla: obviamente puede asignar descriptores a una clase, ¡y las clases son en sí mismas instancias de type ! De hecho, al tratar de leer, Foo.b aún llama property.__get__ ; es simplemente idiomático que los descriptores se devuelvan a sí mismos cuando se accede como atributos de clase.

Creo que es genial que prácticamente todo el sistema OO de Python se pueda expresar en Python. :)

Oh, y hace un tiempo escribí una publicación en un blog verbo sobre descripciones, si está interesado.


class atdict(dict): def __init__(self, value, **kwargs): super().__init__(**kwargs) self.__dict = value def __getattr__(self, name): for key in self.__dict: if type(self.__dict[key]) is list: for idx, item in enumerate(self.__dict[key]): if type(item) is dict: self.__dict[key][idx] = atdict(item) if type(self.__dict[key]) is dict: self.__dict[key] = atdict(self.__dict[key]) return self.__dict[name] d1 = atdict({''a'' : {''b'': [{''c'': 1}, 2]}}) print(d1.a.b[0].c)

Y la salida es:

>> 1