Usando la propiedad () en classmethods

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. Intenté usar la función property () con estos, pero da como resultado 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 "", line 1, in ? TypeError: 'classmethod' object is not callable >>> f.var=5 Traceback (most recent call last): File "", line 1, in ? TypeError: 'classmethod' object is not callable 

¿Es posible utilizar la función de propiedad () con funciones decoradas de método de clase?

Se crea una propiedad en una clase pero afecta a una instancia. Entonces, si desea una propiedad classmethod, cree la propiedad en la metaclase.

 >>> class foo(object): ... _var = 5 ... class __metaclass__(type): # Python 2 syntax for metaclasses ... 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 como de todos modos está utilizando una metaclase, se leerá mejor si simplemente mueve los métodos de clase allí.

 >>> class foo(object): ... _var = 5 ... class __metaclass__(type): # Python 2 syntax for metaclasses ... @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 

o, utilizando la syntax metaclass=... Python 3, y la metaclass definida fuera del cuerpo de la clase foo , y la metaclass responsable de establecer el valor inicial de _var :

 >>> class foo_meta(type): ... def __init__(cls, *args, **kwargs): ... cls._var = 5 ... @property ... def var(cls): ... return cls._var ... @var.setter ... def var(cls, value): ... cls._var = value ... >>> class foo(metaclass=foo_meta): ... pass ... >>> foo.var 5 >>> foo.var = 3 >>> foo.var 3 

Leyendo las notas de lanzamiento de Python 2.2 , encuentro lo siguiente.

El método de obtención [de una propiedad] no se llamará 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 utiliza como un atributo de clase, puede subclase de propiedad (es un tipo de estilo nuevo en sí mismo) para ampliar 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 los establecedores, solo los que obtienen.

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 setters realmente no funcionan:

 foo.var = 4 assert foo.var == foo._var # raises AssertionError 

foo._var ha cambiado, simplemente ha sobrescrito la propiedad con un nuevo valor.

También puedes 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 

Espero que este decorador @classproperty simple de solo @classproperty pueda ayudar a alguien que busque 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 Cx == 1 assert C().x == 1 

¿Es posible utilizar la función de propiedad () con funciones decoradas de método de clase?

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.

Dado que la instancia es una función de la clase y puede derivar la clase de la instancia, puede obtener el comportamiento 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 realizar pruebas; debe aprobarse 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 de todos modos no puede acceder directamente a una metaclase a través de las instancias de sus clases.

escribiendo un decorador @classproperty

En realidad, puede crear un decorador de classproperty en solo unas pocas líneas de código por subclasificación de la property (se implementa en C, pero puede ver Python equivalente aquí ):

 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 aconsejado que sería esto. Un viejo artículo de la lista de correo sugiere que no debería funcionar.

Conseguir que la propiedad funcione en la clase:

La desventaja de lo anterior es que la “propiedad de 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  

y la clase

 >>> FooClassProperty.foo  

Tener acceso a la propiedad de clase.

Python 3!

Pregunta antigua, muchas vistas, muy necesitadas de una forma única 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!’

No hay una forma razonable de hacer que este sistema de “propiedad de clase” funcione en Python.

Aquí hay una manera irrazonable de hacerlo funcionar. Ciertamente, puedes hacerlo más fácil con cantidades crecientes de magia de metaclase.

 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 nudo del problema es que las propiedades son lo que Python llama “descriptores”. No hay una manera breve y sencilla de explicar cómo funciona este tipo de metaprogtwigción, por lo que debo indicarle cómo usar el descriptor .

Solo necesita entender este tipo de cosas si está implementando un marco bastante avanzado. Como una persistencia de objetos transparente o un sistema RPC, o un tipo de lenguaje específico del dominio.

Sin embargo, en un comentario a una respuesta anterior, usted dice que

es necesario modificar un atributo que, de tal manera, sea visto por todas las instancias de una clase, y en el ámbito 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 Observer .

Establecerlo solo en la meta clase no ayuda si desea acceder a la propiedad de clase a través de un objeto instanciado, en este caso también necesita instalar una propiedad normal en el objeto (que se envía a la propiedad de 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 

Porque necesito modificar un atributo que, de tal manera, sea visto por todas las instancias de una clase, y en el ámbito desde el cual se llaman estos métodos de clase no tiene referencias a todas las instancias de la clase.

¿Tienes acceso a al menos una instancia de la clase? Puedo pensar en una manera 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 

La mitad de una solución, __set__ en la clase no funciona, todavía. 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 

Pruebe esto, hace el trabajo sin tener que cambiar / agregar muchos códigos existentes.

 >>> 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 . Denles envoltorios lambda (que pasa la instancia como su primer argumento) y todo está bien.

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 utiliza 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)  in () ----> 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)  in () ----> 1 cp.prop = 1  in (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 🙂

Aquí está mi sugerencia. No utilice 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? Solo establece el valor del atributo y termina con él.

Una propiedad solo debe usarse si hay algo que ocultar, algo que podría cambiar en una implementación futura.

Tal vez su ejemplo sea muy sencillo, y hay algunos cálculos infernales que ha dejado. Pero no parece que la propiedad agregue un valor significativo.

Las técnicas de “privacidad” influenciadas por Java (en Python, los nombres de atributos que comienzan con _) no son muy útiles. Privado de quien? El punto de privacidad es un poco nebuloso cuando tiene la fuente (como lo hace en Python).

Los captadores y definidores de estilo EJB influenciados en Java (a menudo hechos como propiedades en Python) están allí para facilitar la introspección primitiva de Java, así como para pasar la prueba con el comstackdor de lenguaje estático. Todos esos captadores y definidores no son tan útiles en Python.