python - oriented - Usando property() en classmethods
python object oriented programming pdf (14)
¿Es posible usar la función property () con classmethod decorated functions?
No.
Sin embargo, un método de clase es simplemente un método vinculado (una función parcial) en una clase accesible desde instancias de esa clase.
Como la instancia es una función de la clase y puede derivar la clase de la instancia, puede obtener el comportamiento deseado que desee de una propiedad de clase con property
:
class Example(object):
_class_property = None
@property
def class_property(self):
return self._class_property
@class_property.setter
def class_property(self, value):
type(self)._class_property = value
@class_property.deleter
def class_property(self):
del type(self)._class_property
Este código se puede usar para probar: debe pasar sin generar ningún error:
ex1 = Example()
ex2 = Example()
ex1.class_property = None
ex2.class_property = ''Example''
assert ex1.class_property is ex2.class_property
del ex2.class_property
assert not hasattr(ex1, ''class_property'')
Y tenga en cuenta que no necesitamos metaclases en absoluto, y no accede directamente a una metaclase a través de las instancias de sus clases de todos modos.
escribiendo un decorador @classproperty
En realidad, puede crear un decorador de classproperty
en unas pocas líneas de código classproperty
property
(está implementado en C, pero aquí puede ver el Python equivalente):
class classproperty(property):
def __get__(self, obj, objtype=None):
return super(classproperty, self).__get__(objtype)
def __set__(self, obj, value):
super(classproperty, self).__set__(type(obj), value)
def __delete__(self, obj):
super(classproperty, self).__delete__(type(obj))
Luego trate al decorador como si fuera un método de clase combinado con propiedad:
class Foo(object):
_bar = 5
@classproperty
def bar(cls):
"""this is the bar attribute - each subclass of Foo gets its own.
Lookups should follow the method resolution order.
"""
return cls._bar
@bar.setter
def bar(cls, value):
cls._bar = value
@bar.deleter
def bar(cls):
del cls._bar
Y este código debería funcionar sin errores:
def main():
f = Foo()
print(f.bar)
f.bar = 4
print(f.bar)
del f.bar
try:
f.bar
except AttributeError:
pass
else:
raise RuntimeError(''f.bar must have worked - inconceivable!'')
help(f) # includes the Foo.bar help.
f.bar = 5
class Bar(Foo):
"a subclass of Foo, nothing more"
help(Bar) # includes the Foo.bar help!
b = Bar()
b.bar = ''baz''
print(b.bar) # prints baz
del b.bar
print(b.bar) # prints 5 - looked up from Foo!
if __name__ == ''__main__'':
main()
Pero no estoy seguro de lo bien que esto sería. Un viejo article lista de correo sugiere que no debería funcionar.
Obtener la propiedad para trabajar en la clase:
La desventaja de lo anterior es que la "propiedad de la clase" no es accesible desde la clase, porque simplemente sobrescribiría el descriptor de datos de la clase __dict__
.
Sin embargo, podemos anular esto con una propiedad definida en la metaclase __dict__
. Por ejemplo:
class MetaWithFooClassProperty(type):
@property
def foo(cls):
"""The foo property is a function of the class -
in this case, the trivial case of the identity function.
"""
return cls
Y luego, una instancia de clase de la metaclase podría tener una propiedad que acceda a la propiedad de la clase utilizando el principio ya demostrado en las secciones anteriores:
class FooClassProperty(metaclass=MetaWithFooClassProperty):
@property
def foo(self):
"""access the class''s property"""
return type(self).foo
Y ahora vemos tanto la instancia
>>> FooClassProperty().foo
<class ''__main__.FooClassProperty''>
y la clase
>>> FooClassProperty.foo
<class ''__main__.FooClassProperty''>
tener acceso a la propiedad de la clase.
Tengo una clase con dos métodos de clase (usando la función classmethod ()) para obtener y establecer lo que es esencialmente una variable estática. Traté de usar la función property () con estos, pero resulta en un error. Pude reproducir el error con lo siguiente en el intérprete:
class Foo(object):
_var = 5
@classmethod
def getvar(cls):
return cls._var
@classmethod
def setvar(cls, value):
cls._var = value
var = property(getvar, setvar)
Puedo demostrar los métodos de clase, pero no funcionan como propiedades:
>>> f = Foo()
>>> f.getvar()
5
>>> f.setvar(4)
>>> f.getvar()
4
>>> f.var
Traceback (most recent call last):
File "<stdin>", line 1, in ?
TypeError: ''classmethod'' object is not callable
>>> f.var=5
Traceback (most recent call last):
File "<stdin>", line 1, in ?
TypeError: ''classmethod'' object is not callable
¿Es posible usar la función property () con classmethod decorated functions?
Debido a que necesito modificar un atributo que de tal manera sea visto por todas las instancias de una clase, y en el alcance desde el que se invocan estos métodos de clase, no tiene referencias a todas las instancias de la clase.
¿Tiene acceso a al menos una instancia de la clase? Puedo pensar en una forma de hacerlo entonces:
class MyClass (object):
__var = None
def _set_var (self, value):
type (self).__var = value
def _get_var (self):
return self.__var
var = property (_get_var, _set_var)
a = MyClass ()
b = MyClass ()
a.var = "foo"
print b.var
Python 3!
Pregunta anterior, muchas vistas, muy necesitadas de una verdadera forma de Python 3.
Afortunadamente, es fácil con la metaclass
kwarg:
class FooProperties(type):
@property
def var(cls):
return cls._var
class Foo(object, metaclass=FooProperties):
_var = ''FOO!''
Entonces, >>> Foo.var
''¡FOO!''
Al leer las notas de la versión de Python 2.2 , encuentro lo siguiente.
El método get [de una propiedad] no se invocará cuando se acceda a la propiedad como un atributo de clase (Cx) en lugar de como un atributo de instancia (C (). X). Si desea anular la operación __get__ para las propiedades cuando se usa como un atributo de clase, puede subclase la propiedad, es un tipo de estilo nuevo en sí mismo, para extender su método __get__, o puede definir un tipo de descriptor desde cero creando un nuevo clase de estilo que define los métodos __get__, __set__ y __delete__.
NOTA: El método a continuación no funciona para setters, solo getters.
Por lo tanto, creo que la solución prescrita es crear una propiedad de clase como una subclase de propiedad.
class ClassProperty(property):
def __get__(self, cls, owner):
return self.fget.__get__(None, owner)()
class foo(object):
_var=5
def getvar(cls):
return cls._var
getvar=classmethod(getvar)
def setvar(cls,value):
cls._var=value
setvar=classmethod(setvar)
var=ClassProperty(getvar,setvar)
assert foo.getvar() == 5
foo.setvar(4)
assert foo.getvar() == 4
assert foo.var == 4
foo.var = 3
assert foo.var == 3
Sin embargo, los incubadores en realidad no funcionan:
foo.var = 4
assert foo.var == foo._var # raises AssertionError
foo._var
se modifica, simplemente sobrescribió la propiedad con un nuevo valor.
También puede usar ClassProperty
como decorador:
class foo(object):
_var = 5
@ClassProperty
@classmethod
def var(cls):
return cls._var
@var.setter
@classmethod
def var(cls, value):
cls._var = value
assert foo.var == 5
Aquí está mi sugerencia. No use métodos de clase.
Seriamente.
¿Cuál es la razón para usar métodos de clase en este caso? ¿Por qué no tener un objeto ordinario de una clase ordinaria?
Si simplemente desea cambiar el valor, una propiedad no es realmente muy útil, ¿verdad? Simplemente configure el valor del atributo y termine con él.
Una propiedad solo debe usarse si hay algo que ocultar, algo que podría cambiar en una implementación futura.
Tal vez tu ejemplo sea muy simplificado, y hay un cálculo infernal que has dejado. Pero no parece que la propiedad agregue un valor significativo.
Las técnicas de "privacidad" influenciadas por Java (en Python, nombres de atributos que comienzan con _) no son realmente muy útiles. ¿Privado de quién? El punto de privado es un poco nebuloso cuando tienes la fuente (como lo haces en Python).
Los getters y setters de estilo EJB influenciados por Java (a menudo hechos como propiedades en Python) están ahí para facilitar la introspección primitiva de Java así como para aprobar el compilador de lenguaje estático. Todos esos getters y setters no son tan útiles en Python.
Aquí hay una solución que debería funcionar tanto para el acceso a través de la clase como para el acceso a través de una instancia que usa una metaclase.
In [1]: class ClassPropertyMeta(type):
...: @property
...: def prop(cls):
...: return cls._prop
...: def __new__(cls, name, parents, dct):
...: # This makes overriding __getattr__ and __setattr__ in the class impossible, but should be fixable
...: dct[''__getattr__''] = classmethod(lambda cls, attr: getattr(cls, attr))
...: dct[''__setattr__''] = classmethod(lambda cls, attr, val: setattr(cls, attr, val))
...: return super(ClassPropertyMeta, cls).__new__(cls, name, parents, dct)
...:
In [2]: class ClassProperty(object):
...: __metaclass__ = ClassPropertyMeta
...: _prop = 42
...: def __getattr__(self, attr):
...: raise Exception(''Never gets called'')
...:
In [3]: ClassProperty.prop
Out[3]: 42
In [4]: ClassProperty.prop = 1
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-4-e2e8b423818a> in <module>()
----> 1 ClassProperty.prop = 1
AttributeError: can''t set attribute
In [5]: cp = ClassProperty()
In [6]: cp.prop
Out[6]: 42
In [7]: cp.prop = 1
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-7-e8284a3ee950> in <module>()
----> 1 cp.prop = 1
<ipython-input-1-16b7c320d521> in <lambda>(cls, attr, val)
6 # This makes overriding __getattr__ and __setattr__ in the class impossible, but should be fixable
7 dct[''__getattr__''] = classmethod(lambda cls, attr: getattr(cls, attr))
----> 8 dct[''__setattr__''] = classmethod(lambda cls, attr, val: setattr(cls, attr, val))
9 return super(ClassPropertyMeta, cls).__new__(cls, name, parents, dct)
AttributeError: can''t set attribute
Esto también funciona con un setter definido en la metaclase.
Después de buscar en diferentes lugares, encontré un método para definir una propiedad de clase válida con Python 2 y 3.
from future.utils import with_metaclass
class BuilderMetaClass(type):
@property
def load_namespaces(self):
return (self.__sourcepath__)
class BuilderMixin(with_metaclass(BuilderMetaClass, object)):
__sourcepath__ = ''sp''
print(BuilderMixin.load_namespaces)
Espero que esto pueda ayudar a alguien :)
Espero que este decorador de @classproperty
solo lectura y de solo @classproperty
ayude a alguien a buscar propiedades de clase.
class classproperty(object):
def __init__(self, fget):
self.fget = fget
def __get__(self, owner_self, owner_cls):
return self.fget(owner_cls)
class C(object):
@classproperty
def x(cls):
return 1
assert C.x == 1
assert C().x == 1
Esta no es una respuesta adicional, es solo una extensión de algunos de los ejemplos anteriores para demostrar que el comportamiento ''setter'' no funciona para las propiedades de la clase. Lo escribí para ayudarme a entender lo que estaba pasando y pensé que podría ayudar a alguien más a entender el problema. (Gracias a los autores de muchas de estas respuestas, especialmente @Jason R. Coombs, @Denis Ryzhkov y @Aaron Hall).
# @Aaron Hall''s example
class classproperty(property):
def __get__(self, obj, objtype=None):
return super(classproperty, self).__get__(objtype)
def __set__(self, obj, value):
super(classproperty, self).__set__(type(obj), value)
def __delete__(self, obj):
super(classproperty, self).__delete__(type(obj))
class Demo:
base_value = 100
via_property_value = None
@classmethod
def calc_val(cls):
return cls.base_value + 1
@classproperty
def calc_via_property(cls):
if cls.via_property_value:
return cls.base_value + cls.via_property_value
return cls.base_value
@calc_via_property.setter
def set_via_property_value(cls, value):
# This NEVER gets called
cls.via_property_value = value
# test it works via a classmethod
assert Demo.calc_val() == 101
assert Demo().calc_val() == 101
# test it works via a ''classproperty''
assert Demo.calc_via_property == 100
assert Demo().calc_via_property == 100
# test is works via a ''classproperty'' if the internal value is changed
Demo.via_property_value = 100
assert Demo.calc_via_property == 200
assert Demo().calc_via_property == 200
# test it sets the internal value correctly
# this actually overrides the property definition instead of the internal value
Demo.calc_via_property = 50
assert Demo.via_property_value == 50 # remains unchanged at 100
assert Demo.calc_via_property == 150 # is now the value 50 rather than a function
assert Demo().calc_via_property == 150 # is now the value 50 rather than a function
Establecerlo solo en la metaclase no ayuda si desea acceder a la propiedad de la clase a través de un objeto instanciado, en este caso también necesita instalar una propiedad normal en el objeto (que se distribuye a la propiedad de la clase). Creo que lo siguiente es un poco más claro:
#!/usr/bin/python
class classproperty(property):
def __get__(self, obj, type_):
return self.fget.__get__(None, type_)()
def __set__(self, obj, value):
cls = type(obj)
return self.fset.__get__(None, cls)(value)
class A (object):
_foo = 1
@classproperty
@classmethod
def foo(cls):
return cls._foo
@foo.setter
@classmethod
def foo(cls, value):
cls.foo = value
a = A()
print a.foo
b = A()
print b.foo
b.foo = 5
print a.foo
A.foo = 10
print b.foo
print A.foo
La mitad de una solución, __set__ en la clase no funciona, aún. La solución es una clase de propiedad personalizada que implementa una propiedad y un método estático
class ClassProperty(object):
def __init__(self, fget, fset):
self.fget = fget
self.fset = fset
def __get__(self, instance, owner):
return self.fget()
def __set__(self, instance, value):
self.fset(value)
class Foo(object):
_bar = 1
def get_bar():
print ''getting''
return Foo._bar
def set_bar(value):
print ''setting''
Foo._bar = value
bar = ClassProperty(get_bar, set_bar)
f = Foo()
#__get__ works
f.bar
Foo.bar
f.bar = 2
Foo.bar = 3 #__set__ does not
No hay una forma razonable de hacer que este sistema de "propiedad de clase" funcione en Python.
Aquí hay una forma irrazonable de hacerlo funcionar. Sin dudas, puedes hacerlo más fluido con cantidades cada vez mayores de magia de metaclases.
class ClassProperty(object):
def __init__(self, getter, setter):
self.getter = getter
self.setter = setter
def __get__(self, cls, owner):
return getattr(cls, self.getter)()
def __set__(self, cls, value):
getattr(cls, self.setter)(value)
class MetaFoo(type):
var = ClassProperty(''getvar'', ''setvar'')
class Foo(object):
__metaclass__ = MetaFoo
_var = 5
@classmethod
def getvar(cls):
print "Getting var =", cls._var
return cls._var
@classmethod
def setvar(cls, value):
print "Setting var =", value
cls._var = value
x = Foo.var
print "Foo.var = ", x
Foo.var = 42
x = Foo.var
print "Foo.var = ", x
El problema es que las propiedades son lo que Python llama "descriptores". No hay una manera breve y fácil de explicar cómo funciona este tipo de metaprogramación, así que debo indicarle el descriptor cómo .
Solo necesita comprender este tipo de cosas si está implementando un marco bastante avanzado. Como una persistencia de objeto transparente o un sistema RPC, o un tipo de lenguaje específico de dominio.
Sin embargo, en un comentario a una respuesta anterior, dices que
necesidad de modificar un atributo que de tal manera que sea visto por todas las instancias de una clase, y en el alcance desde el cual se llaman estos métodos de clase no tiene referencias a todas las instancias de la clase.
Me parece que lo que realmente quieres es un patrón de diseño de Observer .
Pruébalo, hace el trabajo sin tener que cambiar / agregar una gran cantidad de código existente.
>>> class foo(object):
... _var = 5
... def getvar(cls):
... return cls._var
... getvar = classmethod(getvar)
... def setvar(cls, value):
... cls._var = value
... setvar = classmethod(setvar)
... var = property(lambda self: self.getvar(), lambda self, val: self.setvar(val))
...
>>> f = foo()
>>> f.var
5
>>> f.var = 3
>>> f.var
3
La función de property
necesita dos argumentos callable
. dales envolturas lambda (que pasa la instancia como primer argumento) y todo está bien.
Una propiedad se crea en una clase pero afecta una instancia. Entonces, si quiere una propiedad classmethod, cree la propiedad en la metaclase.
>>> class foo(object):
_var = 5
class __metaclass__(type):
pass
@classmethod
def getvar(cls):
return cls._var
@classmethod
def setvar(cls, value):
cls._var = value
>>> foo.__metaclass__.var = property(foo.getvar.im_func, foo.setvar.im_func)
>>> foo.var
5
>>> foo.var = 3
>>> foo.var
3
Pero ya que estás usando una metaclase de todos modos, se leerá mejor si mueves los métodos de clase allí.
>>> class foo(object):
_var = 5
class __metaclass__(type):
@property
def var(cls):
return cls._var
@var.setter
def var(cls, value):
cls._var = value
>>> foo.var
5
>>> foo.var = 3
>>> foo.var
3