Pregunta ¿Son posibles las variables de clase estáticas?


¿Es posible tener variables de clase estáticas o métodos en python? ¿Qué sintaxis se requiere para hacer esto?


1505
2017-09-16 01:46


origen


Respuestas:


Las variables declaradas dentro de la definición de clase, pero no dentro de un método son variables de clase o estáticas:

>>> class MyClass:
...     i = 3
...
>>> MyClass.i
3 

Como @millerdev señala, esto crea un nivel de clase i variable, pero esto es distinto de cualquier nivel de instancia i variable, por lo que podría tener

>>> m = MyClass()
>>> m.i = 4
>>> MyClass.i, m.i
>>> (3, 4)

Esto es diferente de C ++ y Java, pero no tan diferente de C #, donde no se puede acceder a un miembro estático utilizando una referencia a una instancia.

Ver lo que el tutorial de Python tiene que decir sobre el tema de clases y objetos de clase.

@Steve Johnson ya ha respondido sobre métodos estáticos, también documentado bajo "Funciones incorporadas" en la referencia de la biblioteca de Python.

class C:
    @staticmethod
    def f(arg1, arg2, ...): ...

@beidy recomienda classmethods sobre el método estático, ya que el método recibe el tipo de clase como primer argumento, pero todavía estoy un poco confuso sobre las ventajas de este enfoque sobre el método estático. Si es así, entonces probablemente no importe.


1479
2017-09-16 01:51



@Blair Conrad dijo que las variables estáticas declaradas dentro de la definición de clase, pero no dentro de un método son variables de clase o "estáticas":

>>> class Test(object):
...     i = 3
...
>>> Test.i
3

Hay algunos gotcha aquí. Continuando con el ejemplo anterior:

>>> t = Test()
>>> t.i     # static variable accessed via instance
3
>>> t.i = 5 # but if we assign to the instance ...
>>> Test.i  # we have not changed the static variable
3
>>> t.i     # we have overwritten Test.i on t by creating a new attribute t.i
5
>>> Test.i = 6 # to change the static variable we do it by assigning to the class
>>> t.i
5
>>> Test.i
6
>>> u = Test()
>>> u.i
6           # changes to t do not affect new instances of Test

# Namespaces are one honking great idea -- let's do more of those!
>>> Test.__dict__
{'i': 6, ...}
>>> t.__dict__
{'i': 5}
>>> u.__dict__
{}

Observe cómo la variable de instancia t.i se desincronizó con la variable de clase "estática" cuando el atributo i se estableció directamente en t. Esto es porque i fue re-bound dentro de la t espacio de nombres, que es distinto del Test espacio de nombres Si desea cambiar el valor de una variable "estática", debe cambiarla dentro del alcance (u objeto) donde se definió originalmente. Puse "estático" entre comillas porque Python no tiene realmente variables estáticas en el sentido en que lo hacen C ++ y Java.

Aunque no dice nada específico sobre variables estáticas o métodos, el Tutorial de Python tiene alguna información relevante sobre clases y objetos de clase.

@Steve Johnson también respondió sobre los métodos estáticos, también documentados en "Funciones incorporadas" en la Referencia de la Biblioteca de Python.

class Test(object):
    @staticmethod
    def f(arg1, arg2, ...):
        ...

@beid también mencionó classmethod, que es similar a staticmethod. El primer argumento de classmethod es el objeto de clase. Ejemplo:

class Test(object):
    i = 3 # class (or static) variable
    @classmethod
    def g(cls, arg):
        # here we can use 'cls' instead of the class name (Test)
        if arg > cls.i:
            cls.i = arg # would the the same as  Test.i = arg1

Pictorial Representation Of Above Example


524
2017-09-16 03:04



Métodos estáticos y de clase

Como las otras respuestas han notado, los métodos estáticos y de clase se logran fácilmente usando los decoradores incorporados:

class Test(object):

    # regular instance method:
    def MyMethod(self):
        pass

    # class method:
    @classmethod
    def MyClassMethod(klass):
        pass

    # static method:
    @staticmethod
    def MyStaticMethod():
        pass

Como de costumbre, el primer argumento para MyMethod() está vinculado al objeto de instancia de clase. Por el contrario, el primer argumento para MyClassMethod() es unido al objeto de clase en sí (por ejemplo, en este caso, Test) por MyStaticMethod(), ninguno de los argumentos está vinculado, y tener argumentos es opcional.

"Variables estáticas"

Sin embargo, implementando "variables estáticas" (bueno, mudable variables estáticas, de todos modos, si eso no es una contradicción en términos ...) no es tan sencillo. Como millerdev señaló en su respuesta, el problema es que los atributos de clase de Python no son verdaderamente "variables estáticas". Considerar:

class Test(object):
    i = 3  # This is a class attribute

x = Test()
x.i = 12   # Attempt to change the value of the class attribute using x instance
assert x.i == Test.i  # ERROR
assert Test.i == 3    # Test.i was not affected
assert x.i == 12      # x.i is a different object than Test.i

Esto es porque la línea x.i = 12 ha agregado un nuevo atributo de instancia i a x en lugar de cambiar el valor de la Test clase i atributo.

Parcial comportamiento esperado de la variable estática, es decir, sincronización del atributo entre instancias múltiples (pero no con la clase en sí misma; ver "gotcha" a continuación), puede lograrse al convertir el atributo de clase en una propiedad:

class Test(object):

    _i = 3

    @property
    def i(self):
        return type(self)._i

    @i.setter
    def i(self,val):
        type(self)._i = val

## ALTERNATIVE IMPLEMENTATION - FUNCTIONALLY EQUIVALENT TO ABOVE ##
## (except with separate methods for getting and setting i) ##

class Test(object):

    _i = 3

    def get_i(self):
        return type(self)._i

    def set_i(self,val):
        type(self)._i = val

    i = property(get_i, set_i)

Ahora puedes hacer:

x1 = Test()
x2 = Test()
x1.i = 50
assert x2.i == x1.i  # no error
assert x2.i == 50    # the property is synced

La variable estática ahora permanecerá sincronizada entre todas las instancias de clase.

(NOTA: Es decir, a menos que una instancia de clase decida definir su propia versión de _i! Pero si alguien decide hacer eso, se merecen lo que obtienen, ¿no?

Tenga en cuenta que, técnicamente hablando, i todavía no es una 'variable estática' en absoluto; es un property, que es un tipo especial de descriptor. sin embargo, el property el comportamiento ahora es equivalente a una variable estática (mutable) sincronizada en todas las instancias de clase.

"Variables estáticas" inmutables

Para el comportamiento de la variable estática inmutable, simplemente omita el property setter:

class Test(object):

    _i = 3

    @property
    def i(self):
        return type(self)._i

## ALTERNATIVE IMPLEMENTATION - FUNCTIONALLY EQUIVALENT TO ABOVE ##
## (except with separate methods for getting i) ##

class Test(object):

    _i = 3

    def get_i(self):
        return type(self)._i

    i = property(get_i)

Ahora intentando establecer la instancia i atributo devolverá un AttributeError:

x = Test()
assert x.i == 3  # success
x.i = 12         # ERROR

Uno tiene que ser consciente de

Tenga en cuenta que los métodos anteriores solo funcionan con instancias de su clase - lo harán no trabajo cuando usas la clase en sí. Así por ejemplo:

x = Test()
assert x.i == Test.i  # ERROR

# x.i and Test.i are two different objects:
type(Test.i)  # class 'property'
type(x.i)     # class 'int'

La línea assert Test.i == x.i produce un error, porque el i atributo de Test y x son dos objetos diferentes

Mucha gente encontrará esto sorprendente. Sin embargo, no debería ser. Si volvemos e inspeccionamos nuestra Test definición de clase (la segunda versión), tomamos nota de esta línea:

    i = property(get_i) 

Claramente, el miembro i de Test debe ser un property objeto, que es el tipo de objeto devuelto por property función.

Si considera que lo anterior es confuso, lo más probable es que siga pensando en ello desde la perspectiva de otros idiomas (por ejemplo, Java o c ++). Deberías ir a estudiar el property objeto, sobre el orden en que se devuelven los atributos de Python, el protocolo descriptor y el orden de resolución de método (MRO).

Presento una solución al anterior 'gotcha' a continuación; Sin embargo, sugeriría, enérgicamente, que no intente hacer algo como lo siguiente hasta que, como mínimo, comprenda por qué assert Test.i = x.i causa un error

REAL, REAL Variables estáticas Test.i == x.i

Presento la solución (Python 3) a continuación solo con fines informativos. No lo estoy respaldando como una "buena solución". Tengo mis dudas sobre si emular el comportamiento de la variable estática de otros idiomas en Python es realmente necesario. Sin embargo, independientemente de si es realmente útil, el siguiente debería ayudar a comprender mejor cómo funciona Python.

ACTUALIZACIÓN: este intento es realmente bastante horrible; si insistes en hacer algo como esto (sugerencia: no lo hagas, Python es un lenguaje muy elegante, y compórtate en que se comporta como si no fuera necesario otro idioma), usa el código de La respuesta de Ethan Furman en lugar.

Emular comportamiento de variables estáticas de otros lenguajes utilizando una metaclase

Una metaclase es la clase de una clase. La metaclase predeterminada para todas las clases en Python (es decir, las clases de "nuevo estilo" publican Python 2.3, creo) es type. Por ejemplo:

type(int)  # class 'type'
type(str)  # class 'type'
class Test(): pass
type(Test) # class 'type'

Sin embargo, puede definir su propia metaclase de esta manera:

class MyMeta(type): pass

Y aplicarlo a su propia clase de esta manera (solo Python 3):

class MyClass(metaclass = MyMeta):
    pass

type(MyClass)  # class MyMeta

A continuación se muestra una metaclase que he creado que intenta emular el comportamiento de "variables estáticas" de otros idiomas. Básicamente funciona reemplazando el getter, el setter y el eliminador predeterminados por versiones que comprueban si el atributo que se solicita es una "variable estática".

Un catálogo de las "variables estáticas" se almacena en StaticVarMeta.statics atributo. Inicialmente, todas las solicitudes de atributos se intentan resolver mediante una orden de resolución sustituta. Lo denominé "orden de resolución estática" o "SRO". Esto se hace buscando el atributo solicitado en el conjunto de "variables estáticas" para una clase determinada (o sus clases principales). Si el atributo no aparece en el "SRO", la clase recurrirá al comportamiento predeterminado de obtener / establecer / eliminar (es decir, "MRO").

from functools import wraps

class StaticVarsMeta(type):
    '''A metaclass for creating classes that emulate the "static variable" behavior
    of other languages. I do not advise actually using this for anything!!!

    Behavior is intended to be similar to classes that use __slots__. However, "normal"
    attributes and __statics___ can coexist (unlike with __slots__). 

    Example usage: 

        class MyBaseClass(metaclass = StaticVarsMeta):
            __statics__ = {'a','b','c'}
            i = 0  # regular attribute
            a = 1  # static var defined (optional)

        class MyParentClass(MyBaseClass):
            __statics__ = {'d','e','f'}
            j = 2              # regular attribute
            d, e, f = 3, 4, 5  # Static vars
            a, b, c = 6, 7, 8  # Static vars (inherited from MyBaseClass, defined/re-defined here)

        class MyChildClass(MyParentClass):
            __statics__ = {'a','b','c'}
            j = 2  # regular attribute (redefines j from MyParentClass)
            d, e, f = 9, 10, 11   # Static vars (inherited from MyParentClass, redefined here)
            a, b, c = 12, 13, 14  # Static vars (overriding previous definition in MyParentClass here)'''
    statics = {}
    def __new__(mcls, name, bases, namespace):
        # Get the class object
        cls = super().__new__(mcls, name, bases, namespace)
        # Establish the "statics resolution order"
        cls.__sro__ = tuple(c for c in cls.__mro__ if isinstance(c,mcls))

        # Replace class getter, setter, and deleter for instance attributes
        cls.__getattribute__ = StaticVarsMeta.__inst_getattribute__(cls, cls.__getattribute__)
        cls.__setattr__ = StaticVarsMeta.__inst_setattr__(cls, cls.__setattr__)
        cls.__delattr__ = StaticVarsMeta.__inst_delattr__(cls, cls.__delattr__)
        # Store the list of static variables for the class object
        # This list is permanent and cannot be changed, similar to __slots__
        try:
            mcls.statics[cls] = getattr(cls,'__statics__')
        except AttributeError:
            mcls.statics[cls] = namespace['__statics__'] = set() # No static vars provided
        # Check and make sure the statics var names are strings
        if any(not isinstance(static,str) for static in mcls.statics[cls]):
            typ = dict(zip((not isinstance(static,str) for static in mcls.statics[cls]), map(type,mcls.statics[cls])))[True].__name__
            raise TypeError('__statics__ items must be strings, not {0}'.format(typ))
        # Move any previously existing, not overridden statics to the static var parent class(es)
        if len(cls.__sro__) > 1:
            for attr,value in namespace.items():
                if attr not in StaticVarsMeta.statics[cls] and attr != ['__statics__']:
                    for c in cls.__sro__[1:]:
                        if attr in StaticVarsMeta.statics[c]:
                            setattr(c,attr,value)
                            delattr(cls,attr)
        return cls
    def __inst_getattribute__(self, orig_getattribute):
        '''Replaces the class __getattribute__'''
        @wraps(orig_getattribute)
        def wrapper(self, attr):
            if StaticVarsMeta.is_static(type(self),attr):
                return StaticVarsMeta.__getstatic__(type(self),attr)
            else:
                return orig_getattribute(self, attr)
        return wrapper
    def __inst_setattr__(self, orig_setattribute):
        '''Replaces the class __setattr__'''
        @wraps(orig_setattribute)
        def wrapper(self, attr, value):
            if StaticVarsMeta.is_static(type(self),attr):
                StaticVarsMeta.__setstatic__(type(self),attr, value)
            else:
                orig_setattribute(self, attr, value)
        return wrapper
    def __inst_delattr__(self, orig_delattribute):
        '''Replaces the class __delattr__'''
        @wraps(orig_delattribute)
        def wrapper(self, attr):
            if StaticVarsMeta.is_static(type(self),attr):
                StaticVarsMeta.__delstatic__(type(self),attr)
            else:
                orig_delattribute(self, attr)
        return wrapper
    def __getstatic__(cls,attr):
        '''Static variable getter'''
        for c in cls.__sro__:
            if attr in StaticVarsMeta.statics[c]:
                try:
                    return getattr(c,attr)
                except AttributeError:
                    pass
        raise AttributeError(cls.__name__ + " object has no attribute '{0}'".format(attr))
    def __setstatic__(cls,attr,value):
        '''Static variable setter'''
        for c in cls.__sro__:
            if attr in StaticVarsMeta.statics[c]:
                setattr(c,attr,value)
                break
    def __delstatic__(cls,attr):
        '''Static variable deleter'''
        for c in cls.__sro__:
            if attr in StaticVarsMeta.statics[c]:
                try:
                    delattr(c,attr)
                    break
                except AttributeError:
                    pass
        raise AttributeError(cls.__name__ + " object has no attribute '{0}'".format(attr))
    def __delattr__(cls,attr):
        '''Prevent __sro__ attribute from deletion'''
        if attr == '__sro__':
            raise AttributeError('readonly attribute')
        super().__delattr__(attr)
    def is_static(cls,attr):
        '''Returns True if an attribute is a static variable of any class in the __sro__'''
        if any(attr in StaticVarsMeta.statics[c] for c in cls.__sro__):
            return True
        return False

141
2017-12-19 15:16



También puede agregar variables de clase a clases sobre la marcha

>>> class X:
...     pass
... 
>>> X.bar = 0
>>> x = X()
>>> x.bar
0
>>> x.foo
Traceback (most recent call last):
  File "<interactive input>", line 1, in <module>
AttributeError: X instance has no attribute 'foo'
>>> X.foo = 1
>>> x.foo
1

Y las instancias de clase pueden cambiar las variables de clase

class X:
  l = []
  def __init__(self):
    self.l.append(1)

print X().l
print X().l

>python test.py
[1]
[1, 1]

23
2017-09-17 08:06



Personalmente, usaría un método de clase cada vez que necesitara un método estático. Principalmente porque obtengo la clase como argumento.

class myObj(object):
   def myMethod(cls)
     ...
   myMethod = classmethod(myMethod) 

o usa un decorador

class myObj(object):
   @classmethod
   def myMethod(cls)

Para propiedades estáticas ... Es hora de buscar alguna definición de python ... la variable siempre puede cambiar. Hay dos tipos de ellos mutables e inmutables. Además, hay atributos de clase y atributos de instancia. A nada realmente le gustan los atributos estáticos en el sentido de Java y C ++.

¿Por qué utilizar el método estático en sentido pitónico, si no tiene ninguna relación con la clase? Si yo fuera usted, usaría classmethod o definiría el método independientemente de la clase.


12
2017-09-16 02:02



Los métodos estáticos en Python se llaman classmethods. Eche un vistazo al siguiente código

class MyClass:

    def myInstanceMethod(self):
        print 'output from an instance method'

    @classmethod
    def myStaticMethod(cls):
        print 'output from a static method'

>>> MyClass.myInstanceMethod()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unbound method myInstanceMethod() must be called [...]

>>> MyClass.myStaticMethod()
output from a static method

Tenga en cuenta que cuando llamamos al método myInstanceMethod, obtenemos un error. Esto se debe a que requiere que se llame al método en una instancia de esta clase. El método myStaticMethod se establece como un método de clase utilizando el decorador  @classmethod.

Solo por patadas y risas, podríamos llamar myInstanceMethod en la clase al pasar en una instancia de la clase, así:

>>> MyClass.myInstanceMethod(MyClass())
output from an instance method

11
2017-09-16 02:05



Una cosa especial a tener en cuenta sobre las propiedades estáticas y las propiedades de instancia, que se muestra en el siguiente ejemplo:

class my_cls:
  my_prop = 0

#static property
print my_cls.my_prop  #--> 0

#assign value to static property
my_cls.my_prop = 1 
print my_cls.my_prop  #--> 1

#access static property thru' instance
my_inst = my_cls()
print my_inst.my_prop #--> 1

#instance property is different from static property 
#after being assigned a value
my_inst.my_prop = 2
print my_cls.my_prop  #--> 1
print my_inst.my_prop #--> 2

Esto significa que antes de asignar el valor a la propiedad de la instancia, si intentamos acceder a la propiedad a través de la instancia, se usa el valor estático. Cada propiedad declarada en Python Class siempre tiene un slot estático en la memoria.


10
2018-03-08 06:06



Cuando se define alguna variable miembro fuera de cualquier método miembro, la variable puede ser estática o no estática dependiendo de cómo se exprese la variable.

  • CLASSNAME.var es una variable estática
  • INSTANCENAME.var no es una variable estática.
  • self.var dentro de la clase no es una variable estática.
  • var dentro de la función miembro de clase no está definido.

Por ejemplo:

#!/usr/bin/python

class A:
    var=1

    def printvar(self):
        print "self.var is %d" % self.var
        print "A.var is %d" % A.var


    a = A()
    a.var = 2
    a.printvar()

    A.var = 3
    a.printvar()

Los resultados son

self.var is 2
A.var is 1
self.var is 2
A.var is 3

7
2018-03-26 17:56