¿Cómo agregar propiedad a una clase de forma dinámica?
El objetivo es crear una clase simulada que se comporte como un conjunto de resultados de base de datos.
Entonces, por ejemplo, si una consulta de base de datos devuelve, 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
en su lugar devuelve un objeto de propiedad.
Reemplazar la setattr
línea con k = property(lambda x: vs[i])
no sirve de nada.
Entonces, ¿cuál es la forma correcta de crear una propiedad de instancia en tiempo de ejecución?
PD: Conozco una alternativa presentada en ¿Cómo se utiliza el __getattribute__
método?
Supongo que debería ampliar esta respuesta ahora que soy mayor, 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: tienes que agregarlo a la clase .
>>> class Foo(object):
... pass
...
>>> foo = Foo()
>>> foo.a = 3
>>> Foo.b = property(lambda self: self.a + 1)
>>> foo.b
4
A property
es en realidad una implementación simple de algo llamado descriptor . Es un objeto que proporciona un manejo personalizado para un atributo determinado, en una clase determinada . Algo así como una forma de factorizar un if
árbol enorme __getattribute__
.
Cuando pregunto foo.b
en el ejemplo anterior, Python ve que lo b
definido en la clase implementa el protocolo descriptor , lo que simplemente significa que es un objeto con un método __get__
, __set__
o __delete__
. El descriptor asume la responsabilidad de manejar ese atributo, por lo que Python llama Foo.b.__get__(foo, Foo)
y el valor de retorno se le devuelve como el valor del atributo. En el caso de property
, cada uno de estos métodos simplemente llama a fget
, fset
o fdel
usted pasó al property
constructor.
Los descriptores son en realidad la forma que tiene Python de exponer la plomería de toda su implementación OO. De hecho, existe otro tipo de descriptor incluso más común que 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 humilde método es sólo otro tipo de descripción. Se __get__
centra en la instancia que llama como primer argumento; en efecto, hace esto:
def __get__(self, instance, owner):
return functools.partial(self.function, instance)
De todos modos, sospecho que esta es la razón por la que los descriptores solo funcionan en clases: son una formalización de las cosas que impulsan las clases en primer lugar. Incluso son la excepción a la regla: obviamente puedes asignar descriptores a una clase, ¡y las clases son en sí mismas instancias de type
! De hecho, intentar leer Foo.bar
llamadas fijas property.__get__
; es simplemente idiomático que los descriptores regresen ellos mismos cuando se accede a ellos como atributos de clase.
Creo que es genial que prácticamente todo el sistema OO de Python se pueda expresar en Python. :)
Ah, y hace un tiempo escribí una publicación de blog extensa sobre descriptores, si estás interesado.
El objetivo es crear una clase simulada que se comporte como un conjunto de resultados de base de datos.
Entonces, ¿lo que quieres es un diccionario donde puedas deletrear a['b'] como ab?
Eso es fácil:
class atdict(dict):
__getattr__= dict.__getitem__
__setattr__= dict.__setitem__
__delattr__= dict.__delitem__
No es necesario utilizar una propiedad para eso. Simplemente anule __setattr__
para que sean de solo 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!