¿Son posibles las variables de clase estáticas en Python?

2206

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

6
  • 7
    Si. La ausencia de la palabra clave "estática" puede ser engañosa, pero cualquier objeto inicializado dentro de la clase (solo una sangría dentro de la clase, y no en el constructor) es estático. No depende de la creación de instancias (porque no es parte del constructor). En cuanto a los métodos, puedes hacerlo con un decorador @staticmethod.
    user11991978
    30 oct.20 a las 9:16
  • 3
    usar el término estático para algo que existe durante todo el instante de una clase, siempre me pareció extraño 31 oct.20 a las 20:10
  • 2
    @ TonySuffolk66 Culpa (creo) a C ++, que simplemente se apropió de la palabra clave existente "estática" de C (donde indicaba que la vida útil de la variable persistió más allá del alcance en el que se declaró). C ++ extendió eso para significar una variable cuyo valor estaba fuera del "alcance" de una sola instancia de una clase. Python (más lógicamente) simplemente los llama atributos de clase, ya que son atributos asociados con la clase en sí, en lugar de una instancia de la clase.
    chepner
    23/12/20 a las 13:25
  • 1
    @chepner en staticrealidad significa varias cosas en C ++ (definiciones abreviadas debido a la longitud muy estricta de los comentarios). Hay un ámbito de archivo staticheredado de C, lo que significa que "esta variable / función se puede usar solo en este archivo", hay un ámbito de clase, lo staticque significa que "este método o campo está asociado con el tipo y no con ninguna instancia del tipo" (rara vez se usa en C ++ pero común en C # / Java / ObjC, por ejemplo, creo que esto es lo que el OP está preguntando), hay una variable local staticen las funciones que significa que "el valor de esta variable se retiene entre las llamadas a funciones".
    jrh
    18 abr a las 23:41
  • 2
    Cambiando el interruptor a "opinión", creo que muchas veces, los métodos estáticos en C # / Java se hicieron porque los lenguajes adoptaron una postura de línea dura "sin funciones", en C # / Java solo puedes tener métodos (es decir, un función que es parte de una clase), Python no tiene esta restricción (que es lo mejor, en mi opinión). Prefiero usar los espacios de nombres de C ++ o importar funciones desde un archivo (Python), personalmente, que crear una clase sin otra razón que la de mantener funciones. OOP tiene sus usos, pero a veces solo quieres una función.
    jrh
    18 abr a las 23:47
2125

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 señala @ millerdev , esto crea una ivariable de nivel de clase , pero esto es diferente de cualquier ivariable de nivel de instancia , 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 usando una referencia a una instancia.

Vea lo que el tutorial de Python tiene que decir sobre el tema de las clases y los objetos de clase .

@Steve Johnson ya ha respondido con respecto a los métodos estáticos , también documentados en "Funciones integradas " en la Referencia de la biblioteca de Python .

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

@beidy recomienda classmethod s sobre staticmethod, 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 staticmethod. Si usted también lo está, probablemente no importe.

13
  • 20
    Estoy aprendiendo de Python, pero las ventajas de @classmethodmás de @staticmethodyo sepa es que siempre se obtiene el nombre de la clase del método se invoca en, incluso si se trata de una subclase. Un método estático carece de esta información, por lo que no puede llamar a un método anulado, por ejemplo.
    Seb
    2/10/12 a las 15:58
  • 61
    @theJollySin la forma pitónica para las constantes es no hacer crecer una clase para las constantes. Sólo tienen algunos const.pycon PI = 3.14y se puede importar en todas partes.from const import PI
    Giszmo
    21/06/2013 a las 17:54
  • 43
    Es probable que esta respuesta confunda el problema de la variable estática. Para empezar, noi = 3 es una variable estática, es un atributo de clase y, dado que es distinto de un atributo a nivel de instancia , no se comporta como una variable estática en otros lenguajes. Ver la respuesta de millerdev , la respuesta de Yann , y mi respuesta a continuación.i 19/12/2014 a las 15:40
  • 6
    ¿Entonces solo una copia de i(variable estática) estará en la memoria incluso si creo cientos de instancias de esta clase? 21/07/2017 a las 12:53
  • 2
    Para cualquiera que esté interesado en quién es Daniel mencionado en el comentario de @Dubslow, es millerdev ( wayback machine )
    OfirD
    26/02/18 a las 21:22
689

@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 algunas trampas 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.ise desincronizó con la variable de clase "estática" cuando el atributo ise estableció directamente t. Esto se debe a que ise volvió a enlazar dentro del tespacio de nombres, que es distinto del Testespacio de nombres. Si desea cambiar el valor de una variable "estática", debe cambiarlo dentro del alcance (u objeto) donde se definió originalmente. Pongo "estático" entre comillas porque Python realmente no tiene variables estáticas en el sentido que tienen C ++ y Java.

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

@Steve Johnson también respondió con respecto a los métodos estáticos, también documentados en "Funciones integradas" 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 un método de clase 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 be the same as Test.i = arg1

Representación pictórica del ejemplo anterior

5
  • 6
    Le sugiero que amplíe el ejemplo un poco: si, después de configurar Test.i = 6, crea una instancia de un nuevo objeto (por ejemplo, u = Test ()), el nuevo objeto "heredará" el nuevo valor de clase (por ejemplo, ui == 6)
    Mark
    20/0814 a las 13:48
  • 6
    Una manera de mantener las variables estáticas en sincronía es que sean propiedades: class Test(object):, _i = 3, @property, def i(self), return type(self)._i, @i.setter, def i(self,val):, type(self)._i = val. Ahora usted puede hacer x = Test(), x.i = 12, assert x.i == Test.i. 19/12/14 a las 15:05
  • 1
    Entonces, ¿podría decir que todas las variables son estáticas inicialmente y luego acceder a las instancias crea variables de instancia en tiempo de ejecución?
    Ali
    4 nov a las 7:17
  • Quizás esto sea interesante: si define un método en Test que cambia Test.i, eso afectará AMBOS valores de Test.i y ti.
    Pablo
    7/03/18 a las 13:41
  • @millerdev, como mencionaste, Python no tiene variables estáticas como C ++ o JAVA. Entonces, ¿estará bien decir, Test.i es más una variable de clase que una variable estática? 21/09/18 a las 10:12
229

Métodos estáticos y de clases

Como se ha señalado en las otras respuestas, los métodos estáticos y de clase se logran fácilmente utilizando los decoradores integrados:

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 de MyMethod()está vinculado al objeto de instancia de clase. Por el contrario, el primer argumento de MyClassMethod()está vinculado al objeto de clase en sí mismo (por ejemplo, en este caso, Test). Porque MyStaticMethod(), ninguno de los argumentos está vinculado, y tener argumentos es opcional.

"Variables estáticas"

Sin embargo, implementar "variables estáticas" (bueno, variables estáticas mutables , de todos modos, si eso no es una contradicción en los términos ...) no es tan sencillo. Como señaló millerdev 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 se debe a que la línea x.i = 12ha agregado un nuevo atributo de instancia ien xlugar de cambiar el valor del atributo de Testclase i.

El comportamiento parcial esperado de la variable estática, es decir, la sincronización del atributo entre varias instancias (pero no con la clase en sí; consulte "gotcha" a continuación), se puede lograr convirtiendo 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, itodavía no es una 'variable estática' en absoluto; es a property, que es un tipo especial de descriptor. Sin embargo, el propertycomportamiento ahora es equivalente a una variable estática (mutable) sincronizada en todas las instancias de clase.

"Variables estáticas" inmutables

Para un comportamiento de variable estática inmutable, simplemente omita el establecedor property:

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, intentar establecer el iatributo de instancia devolverá un AttributeError:

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

Uno tengo que estar al tanto

Tenga en cuenta que los métodos anteriores solo funcionan con instancias de su clase; no funcionarán cuando use 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.iproduce un error, porque el iatributo de Testy xson dos objetos diferentes.

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

    i = property(get_i) 

Claramente, el miembro ide Testdebe ser un propertyobjeto, que es el tipo de objeto devuelto por la propertyfunción.

Si lo anterior le resulta confuso, lo más probable es que todavía lo esté pensando desde la perspectiva de otros lenguajes (por ejemplo, Java o c ++). Debería estudiar el propertyobjeto, el orden en el que se devuelven los atributos de Python, el protocolo del descriptor y el orden de resolución del método (MRO).

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

Variables estáticas REALES, ACTUALES -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 las variables estáticas de otros lenguajes en Python es realmente necesario. Sin embargo, independientemente de si es realmente útil, lo siguiente debería ayudar a comprender mejor cómo funciona Python.

ACTUALIZACIÓN: este intento es realmente terrible ; Si insiste en hacer algo como esto (pista: por favor, no lo haga; Python es un lenguaje muy elegante y no es necesario encajarlo para que se comporte como otro idioma), use el código en la respuesta de Ethan Furman .

Emulando el comportamiento de variables estáticas de otros lenguajes usando 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" posteriores a 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 así:

class MyMeta(type): pass

Y aplíquelo 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 "variable estática" de otros lenguajes. Básicamente, funciona reemplazando el getter, setter y deleter predeterminados con versiones que verifican si el atributo que se solicita es una "variable estática".

En el StaticVarMeta.staticsatributo se almacena un catálogo de las "variables estáticas" . Inicialmente, se intenta resolver todas las solicitudes de atributos mediante un orden de resolución sustituto. Lo he denominado "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 get / set / delete del atributo (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
15
  • Traté de usar su camino pero enfrenté un problema, por favor, eche un vistazo a mi pregunta aquí stackoverflow.com/questions/29329850/get-static-variable-value 29/03/15 a las 15:22
  • @RickTeachey: Supongo que, en general, debería ver cualquier cosa que haga en la instancia de la clase Test(antes de usarla para crear instancias) como si estuviera en el dominio de la metaprogramación. Por ejemplo, altera el comportamiento de la clase haciendo Test.i = 0(aquí simplemente destruye el objeto de propiedad por completo). Supongo que el "mecanismo de propiedad" se activa solo en el acceso a la propiedad en instancias de una clase (a menos que cambie el comportamiento subyacente utilizando una metaclase como intermedio, tal vez). Por cierto, termine esta respuesta :-) 11 de mayo de 2015 a las 15:20
  • 1
    @RickTeachey Gracias :-) Tu metaclase al final es interesante pero en realidad es un poco demasiado compleja para mi gusto. Podría ser útil en un marco / aplicación grande donde este mecanismo es absolutamente necesario. De todos modos, esto ejemplifica que si realmente se necesita un metacomportamiento nuevo (complejo) no predeterminado, Python lo hace posible :) 14 de mayo de 2015 a las 21:54
  • 1
    @OleThomsenBuus: Verifique mi respuesta para una metaclase más simple que haga el trabajo. 22/0217 a las 16:40
  • 1
    @taper Tienes razón; He editado la respuesta para solucionar el problema (¡no puedo creer que haya estado ahí mal durante tanto tiempo!). Perdón por la confusion. 8 dic 2017 a las 16:14
36

También puede agregar variables de clase a las 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]
3
  • 3
    ¿Se mantendrán las nuevas variables de clase incluso si la clase se importa a otro módulo? 1 de marzo de 2013 a las 12:20
  • Si. Las clases son efectivamente singleton, independientemente del espacio de nombres desde el que las llame.
    Pedro
    25/07/18 a las 14:11
  • @ Gregory dijiste "Y las instancias de clase pueden cambiar las variables de clase" En realidad, este ejemplo se llama acceso, no modificación. La modificación fue realizada por el propio objeto a través de su propia función append (). 26/11/19 a las 12:18
21

Personalmente, usaría un método de clase siempre que necesite 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 que busque 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 .. Nada como los atributos estáticos en el sentido de java & c ++

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

2
  • 2
    Las variables no son mutables ni inmutables; los objetos son. (Sin embargo, un objeto puede, con diversos grados de éxito, intentar evitar la asignación a algunos de sus atributos). 21 de septiembre de 2017 a las 3:55
  • Java y C ++ usan estático (mal uso de la palabra, en mi humilde opinión) exactamente como usa instancia versus atributo de clase. Un atributo / método de clase es estático en Java y C ++, no hay diferencia, excepto que en Python el primer parámetro para una llamada de método de clase es la clase. 1 de noviembre de 2019 a las 8:35
18

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 la clase Python siempre tiene una ranura estática en la memoria .

18

Los métodos estáticos en Python se denominan classmethod s. 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

Observe 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 diversión y risas, podríamos llamar a myInstanceMethod en la clase pasando una instancia de la clase, así:

>>> MyClass.myInstanceMethod(MyClass())
output from an instance method
1
  • 2
    Umm ... los métodos estáticos se hacen con @staticmethod; @classmethodes (obviamente) para métodos de clase (que están destinados principalmente para su uso como constructores alternativos, pero pueden servir en un apuro como métodos estáticos que reciben una referencia a la clase a través de la cual fueron llamados). 14 de junio de 2019 a las 3:20
13

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á definida.

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
1
  • La muesca está rota. Esto no se ejecutará 16/07/19 a las 8:56
11

Es posible tener staticvariables de clase, pero probablemente no valga la pena el esfuerzo.

Aquí hay una prueba de concepto escrita en Python 3: si alguno de los detalles exactos es incorrecto, el código puede modificarse para que coincida con casi lo que quiera decir con static variable:


class Static:
    def __init__(self, value, doc=None):
        self.deleted = False
        self.value = value
        self.__doc__ = doc
    def __get__(self, inst, cls=None):
        if self.deleted:
            raise AttributeError('Attribute not set')
        return self.value
    def __set__(self, inst, value):
        self.deleted = False
        self.value = value
    def __delete__(self, inst):
        self.deleted = True

class StaticType(type):
    def __delattr__(cls, name):
        obj = cls.__dict__.get(name)
        if isinstance(obj, Static):
            obj.__delete__(name)
        else:
            super(StaticType, cls).__delattr__(name)
    def __getattribute__(cls, *args):
        obj = super(StaticType, cls).__getattribute__(*args)
        if isinstance(obj, Static):
            obj = obj.__get__(cls, cls.__class__)
        return obj
    def __setattr__(cls, name, val):
        # check if object already exists
        obj = cls.__dict__.get(name)
        if isinstance(obj, Static):
            obj.__set__(name, val)
        else:
            super(StaticType, cls).__setattr__(name, val)

y en uso:

class MyStatic(metaclass=StaticType):
    """
    Testing static vars
    """
    a = Static(9)
    b = Static(12)
    c = 3

class YourStatic(MyStatic):
    d = Static('woo hoo')
    e = Static('doo wop')

y algunas pruebas:

ms1 = MyStatic()
ms2 = MyStatic()
ms3 = MyStatic()
assert ms1.a == ms2.a == ms3.a == MyStatic.a
assert ms1.b == ms2.b == ms3.b == MyStatic.b
assert ms1.c == ms2.c == ms3.c == MyStatic.c
ms1.a = 77
assert ms1.a == ms2.a == ms3.a == MyStatic.a
ms2.b = 99
assert ms1.b == ms2.b == ms3.b == MyStatic.b
MyStatic.a = 101
assert ms1.a == ms2.a == ms3.a == MyStatic.a
MyStatic.b = 139
assert ms1.b == ms2.b == ms3.b == MyStatic.b
del MyStatic.b
for inst in (ms1, ms2, ms3):
    try:
        getattr(inst, 'b')
    except AttributeError:
        pass
    else:
        print('AttributeError not raised on %r' % attr)
ms1.c = 13
ms2.c = 17
ms3.c = 19
assert ms1.c == 13
assert ms2.c == 17
assert ms3.c == 19
MyStatic.c = 43
assert ms1.c == 13
assert ms2.c == 17
assert ms3.c == 19

ys1 = YourStatic()
ys2 = YourStatic()
ys3 = YourStatic()
MyStatic.b = 'burgler'
assert ys1.a == ys2.a == ys3.a == YourStatic.a == MyStatic.a
assert ys1.b == ys2.b == ys3.b == YourStatic.b == MyStatic.b
assert ys1.d == ys2.d == ys3.d == YourStatic.d
assert ys1.e == ys2.e == ys3.e == YourStatic.e
ys1.a = 'blah'
assert ys1.a == ys2.a == ys3.a == YourStatic.a == MyStatic.a
ys2.b = 'kelp'
assert ys1.b == ys2.b == ys3.b == YourStatic.b == MyStatic.b
ys1.d = 'fee'
assert ys1.d == ys2.d == ys3.d == YourStatic.d
ys2.e = 'fie'
assert ys1.e == ys2.e == ys3.e == YourStatic.e
MyStatic.a = 'aargh'
assert ys1.a == ys2.a == ys3.a == YourStatic.a == MyStatic.a
0
10

También puede hacer que una clase sea estática usando metaclase.

class StaticClassError(Exception):
    pass


class StaticClass:
    __metaclass__ = abc.ABCMeta

    def __new__(cls, *args, **kw):
        raise StaticClassError("%s is a static class and cannot be initiated."
                                % cls)

class MyClass(StaticClass):
    a = 1
    b = 3

    @staticmethod
    def add(x, y):
        return x+y

Entonces, cuando por accidente intente inicializar MyClass , obtendrá un StaticClassError.

4
  • 4
    ¿Por qué es incluso una clase si no va a crear una instancia? Esto se siente como torcer Python para convertirlo en Java ... 24 de agosto de 2014 a las 17:44
  • 1
    El idioma Borg es una mejor manera de manejar esto. 26/01/15 a las 15:03
  • @NedBatchelder Es una clase abstracta, pensada solo para subclases (y instanciar las subclases) 24 sep 2018 a las 16:53
  • 1
    Espero que las subclases no usen super () para invocar el __new__de sus padres ... 24 sep 2018 a las 20:29
9

Un punto muy interesante sobre la búsqueda de atributos de Python es que se puede usar para crear " variables virtuales ":

class A(object):

  label="Amazing"

  def __init__(self,d): 
      self.data=d

  def say(self): 
      print("%s %s!"%(self.label,self.data))

class B(A):
  label="Bold"  # overrides A.label

A(5).say()      # Amazing 5!
B(3).say()      # Bold 3!

Normalmente no hay asignaciones a estos después de que se crean. Tenga en cuenta que la búsqueda se usa selfporque, aunque labeles estática en el sentido de no estar asociada con una instancia en particular , el valor aún depende de la (clase de) instancia.

8

Sí, definitivamente es posible escribir variables y métodos estáticos en Python.

Variables estáticas: las variables declaradas a nivel de clase se denominan variable estática a la que se puede acceder directamente utilizando el nombre de la clase.

    >>> class A:
        ...my_var = "shagun"

    >>> print(A.my_var)
        shagun

Variables de instancia: las variables relacionadas y a las que se accede por instancia de una clase son variables de instancia.

   >>> a = A()
   >>> a.my_var = "pruthi"
   >>> print(A.my_var,a.my_var)
       shagun pruthi

Métodos estáticos: de forma similar a las variables, se puede acceder a los métodos estáticos directamente mediante el nombre de la clase. No es necesario crear una instancia.

Pero tenga en cuenta que un método estático no puede llamar a un método no estático en Python.

    >>> class A:
   ...     @staticmethod
   ...     def my_static_method():
   ...             print("Yippey!!")
   ... 
   >>> A.my_static_method()
   Yippey!!
1
  • Lo que usted llama variables 'estáticas' son, creo, variables de clase. Viz: class A (): inner_var = 0 class B (A): pass A.inner_var = 15 B.inner_var = 30 print ("A: static =" + str (A.inner_var)) print ("B: static = "+ str (B.inner_var)) # Salida: # A: estática = 15 # B: estática = 30
    Andrew
    16 de enero a las 13:13
7

Con respecto a esta respuesta , para una variable estática constante , puede usar un descriptor. He aquí un ejemplo:

class ConstantAttribute(object):
    '''You can initialize my value but not change it.'''
    def __init__(self, value):
        self.value = value

    def __get__(self, obj, type=None):
        return self.value

    def __set__(self, obj, val):
        pass


class Demo(object):
    x = ConstantAttribute(10)


class SubDemo(Demo):
    x = 10


demo = Demo()
subdemo = SubDemo()
# should not change
demo.x = 100
# should change
subdemo.x = 100
print "small demo", demo.x
print "small subdemo", subdemo.x
print "big demo", Demo.x
print "big subdemo", SubDemo.x

Resultando en ...

small demo 10
small subdemo 100
big demo 10
big subdemo 10

Siempre puede generar una excepción si ignorar silenciosamente el valor de configuración ( passarriba) no es lo suyo. Si está buscando una variable de clase estática de estilo Java en C ++:

class StaticAttribute(object):
    def __init__(self, value):
        self.value = value

    def __get__(self, obj, type=None):
        return self.value

    def __set__(self, obj, val):
        self.value = val

Eche un vistazo a esta respuesta y los documentos oficiales COMO para obtener más información sobre los descriptores.

1
  • 2
    También puede usar @property, que es lo mismo que usar un descriptor, pero es mucho menos código. 19/12/2014 a las 15:34
7

Absolutamente sí, Python por sí solo no tiene ningún miembro de datos estáticos explícitamente, pero podemos tenerlo al hacerlo

class A:
    counter =0
    def callme (self):
        A.counter +=1
    def getcount (self):
        return self.counter  
>>> x=A()
>>> y=A()
>>> print(x.getcount())
>>> print(y.getcount())
>>> x.callme() 
>>> print(x.getcount())
>>> print(y.getcount())

producción

0
0
1
1

explicación

here object (x) alone increment the counter variable
from 0 to 1 by not object y. But result it as "static counter"
5

Para evitar cualquier confusión potencial, me gustaría contrastar variables estáticas y objetos inmutables.

Algunos tipos de objetos primitivos como enteros, flotantes, cadenas y parejas son inmutables en Python. Esto significa que el objeto al que se hace referencia con un nombre dado no puede cambiar si pertenece a uno de los tipos de objeto mencionados anteriormente. El nombre se puede reasignar a un objeto diferente, pero el objeto en sí no se puede cambiar.

Hacer una variable estática lleva esto un paso más allá al no permitir que el nombre de la variable apunte a cualquier objeto que no sea al que apunta actualmente. (Nota: este es un concepto de software general y no específico de Python; consulte las publicaciones de otros para obtener información sobre la implementación de estática en Python).

5

La mejor manera que encontré es usar otra clase. Puede crear un objeto y luego usarlo en otros objetos.

class staticFlag:
    def __init__(self):
        self.__success = False
    def isSuccess(self):
        return self.__success
    def succeed(self):
        self.__success = True

class tryIt:
    def __init__(self, staticFlag):
        self.isSuccess = staticFlag.isSuccess
        self.succeed = staticFlag.succeed

tryArr = []
flag = staticFlag()
for i in range(10):
    tryArr.append(tryIt(flag))
    if i == 5:
        tryArr[i].succeed()
    print tryArr[i].isSuccess()

Con el ejemplo anterior, hice una clase llamada staticFlag.

Esta clase debe presentar la var estática __success(Private Static Var).

tryIt class representó la clase regular que necesitamos usar.

Ahora hice un objeto para una bandera ( staticFlag). Esta bandera se enviará como referencia a todos los objetos regulares.

Todos estos objetos se están agregando a la lista tryArr.


Resultados de este guión:

False
False
False
False
False
True
True
True
True
True
2

Variables estáticas en la fábrica de clases Python3.6

Para cualquiera que use una fábrica de clases con python3.6 en adelante, use la nonlocalpalabra clave para agregarla al alcance / contexto de la clase que se está creando así:

>>> def SomeFactory(some_var=None):
...     class SomeClass(object):
...         nonlocal some_var
...         def print():
...             print(some_var)
...     return SomeClass
... 
>>> SomeFactory(some_var="hello world").print()
hello world
4
  • sí, pero en este caso lo hasattr(SomeClass, 'x')es False. Dudo que esto sea lo que alguien quiere decir con una variable estática. 25 de julio de 2017 a las 2:55
  • @RickTeachey lol, vi su código de variable estática, stackoverflow.com/a/27568860/2026508 +1 internet señor, ¿y pensé que hasattr no funcionaba así? por tanto, es some_varinmutable y está definido de forma estática, ¿o no? ¿Qué tiene que ver el acceso del getter externo con que una variable sea estática o no? tengo tantas preguntas ahora. Me encantaría escuchar algunas respuestas cuando tenga tiempo.
    jmunsch
    25 de julio de 2017 a las 19:09
  • Sí, esa metaclase es bastante ridícula. No estoy seguro de entender las preguntas, pero en mi opinión, lo some_varanterior no es un miembro de la clase en absoluto. En Python, se puede acceder a todos los miembros de la clase desde fuera de la clase. 25/07/2017 a las 19:55
  • El nonlocalteclado "amplía" el alcance de la variable. El alcance de una definición de cuerpo de clase es independiente del alcance en el que se encuentra cuando usted dice nonlocal some_var, eso es simplemente crear una referencia de nombre no local (léase: NO en el alcance de definición de clase) a otro objeto nombrado. Por lo tanto, no se adjunta a la definición de clase porque no está en el ámbito del cuerpo de la clase. 25/07/2017 a las 19:59
2

Entonces, esto probablemente sea un truco, pero lo he estado usando eval(str)para obtener un objeto estático, una especie de contradicción, en Python 3.

Hay un archivo Records.py que no tiene más que classobjetos definidos con métodos estáticos y constructores que guardan algunos argumentos. Luego, desde otro archivo .py, import Recordspero necesito seleccionar dinámicamente cada objeto y luego instanciarlo a pedido de acuerdo con el tipo de datos que se están leyendo.

Entonces, dónde object_name = 'RecordOne'o el nombre de la clase, llamo cur_type = eval(object_name)y luego para instanciarlo, lo hace. cur_inst = cur_type(args) Sin embargo, antes de instanciar, puede llamar a métodos estáticos desde, cur_type.getName()por ejemplo, una especie de implementación de clase base abstracta o cualquiera que sea el objetivo. Sin embargo, en el backend, probablemente se instancia en Python y no es verdaderamente estático, porque eval está devolviendo un objeto ... que debe haber sido instanciado ... que da un comportamiento similar a estático.

2

@dataclass definiciones proporcionan nombres de nivel de clase que se utilizan para definir las variables de instancia y el método de inicialización, __init__(). Si desea una variable de nivel de clase, @dataclassdebe usar la typing.ClassVarsugerencia de tipo. Los ClassVarparámetros del tipo definen el tipo de variable de nivel de clase.

from typing import ClassVar
from dataclasses import dataclass

@dataclass
class Test:
    i: ClassVar[int] = 10
    x: int
    y: int
    
    def __repr__(self):
        return f"Test({self.x=}, {self.y=}, {Test.i=})"

Ejemplos de uso:

> test1 = Test(5, 6)
> test2 = Test(10, 11)

> test1
Test(self.x=5, self.y=6, Test.i=10)
> test2
Test(self.x=10, self.y=11, Test.i=10)
1

Puede utilizar una lista o un diccionario para obtener un "comportamiento estático" entre instancias.

class Fud:

     class_vars = {'origin_open':False}

     def __init__(self, origin = True):
         self.origin = origin
         self.opened = True
         if origin:
             self.class_vars['origin_open'] = True


     def make_another_fud(self):
         ''' Generating another Fud() from the origin instance '''

         return Fud(False)


     def close(self):
         self.opened = False
         if self.origin:
             self.class_vars['origin_open'] = False


fud1 = Fud()
fud2 = fud1.make_another_fud()

print (f"is this the original fud: {fud2.origin}")
print (f"is the original fud open: {fud2.class_vars['origin_open']}")
# is this the original fud: False
# is the original fud open: True

fud1.close()

print (f"is the original fud open: {fud2.class_vars['origin_open']}")
# is the original fud open: False
1

Si está intentando compartir una variable estática para, por ejemplo, aumentarla en otras instancias, algo como este script funciona bien:

# -*- coding: utf-8 -*-
class Worker:
    id = 1

    def __init__(self):
        self.name = ''
        self.document = ''
        self.id = Worker.id
        Worker.id += 1

    def __str__(self):
        return u"{}.- {} {}".format(self.id, self.name, self.document).encode('utf8')


class Workers:
    def __init__(self):
        self.list = []

    def add(self, name, doc):
        worker = Worker()
        worker.name = name
        worker.document = doc
        self.list.append(worker)


if __name__ == "__main__":
    workers = Workers()
    for item in (('Fiona', '0009898'), ('Maria', '66328191'), ("Sandra", '2342184'), ('Elvira', '425872')):
        workers.add(item[0], item[1])
    for worker in workers.list:
        print(worker)
    print("next id: %i" % Worker.id)
1

Con los tipos de datos de Objeto es posible. Pero con tipos primitivos como bool, int, floato strbahaviour es diferente de otros lenguajes de POO. Porque en la clase heredada el atributo estático no existe. Si el atributo no existe en la clase heredada, Python comienza a buscarlo en la clase principal. Si se encuentra en la clase principal, se devolverá su valor. Cuando decida cambiar el valor en la clase heredada, se creará un atributo estático en tiempo de ejecución. La próxima vez que lea el atributo estático heredado, se devolverá su valor, porque ya está definido. Los objetos (listas, dictados) funcionan como referencias, por lo que es seguro usarlos como atributos estáticos y heredarlos. La dirección del objeto no cambia cuando cambia sus valores de atributo.

Ejemplo con tipo de datos entero:

class A:
    static = 1


class B(A):
    pass


print(f"int {A.static}")  # get 1 correctly
print(f"int {B.static}")  # get 1 correctly

A.static = 5
print(f"int {A.static}")  # get 5 correctly
print(f"int {B.static}")  # get 5 correctly

B.static = 6
print(f"int {A.static}")  # expected 6, but get 5 incorrectly
print(f"int {B.static}")  # get 6 correctly

A.static = 7
print(f"int {A.static}")  # get 7 correctly
print(f"int {B.static}")  # get unchanged 6

Solución basada en la biblioteca refdatatypes :

from refdatatypes.refint import RefInt


class AAA:
    static = RefInt(1)


class BBB(AAA):
    pass


print(f"refint {AAA.static.value}")  # get 1 correctly
print(f"refint {BBB.static.value}")  # get 1 correctly

AAA.static.value = 5
print(f"refint {AAA.static.value}")  # get 5 correctly
print(f"refint {BBB.static.value}")  # get 5 correctly

BBB.static.value = 6
print(f"refint {AAA.static.value}")  # get 6 correctly
print(f"refint {BBB.static.value}")  # get 6 correctly

AAA.static.value = 7
print(f"refint {AAA.static.value}")  # get 7 correctly
print(f"refint {BBB.static.value}")  # get 7 correctly
0

Póngalo de esta manera, la variable estática se crea cuando una clase definida por el usuario entra en existencia y la definición de una variable estática debe seguir la palabra clave self,

class Student:

    the correct way of static declaration
    i = 10

    incorrect
    self.i = 10
0

No como las @staticmethodvariables de clase, pero son métodos estáticos de clase y se comparten con todas las instancias.

Ahora puedes acceder a él como

instance = MyClass()
print(instance.i)

o

print(MyClass.i)

tienes que asignar el valor a estas variables

estaba intentando

class MyClass:
  i: str

y asignando el valor en una llamada al método, en ese caso no funcionará y arrojará un error

i is not attribute of MyClass