Question Les variables de classe statique sont-elles possibles?


Est-il possible d'avoir des variables ou des méthodes de classe statiques en python? Quelle syntaxe est requise pour cela?


1505
2017-09-16 01:46


origine


Réponses:


Les variables déclarées dans la définition de classe, mais pas dans une méthode, sont des variables de classe ou des variables statiques:

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

Comme @millerdev souligne, cela crée un niveau de classe i variable, mais cela est distinct de tout niveau d'instance i variable, de sorte que vous pourriez avoir

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

Ceci est différent de C ++ et Java, mais pas si différent de C #, où un membre statique ne peut pas être consulté en utilisant une référence à une instance.

Voir ce que le tutoriel Python a à dire sur le sujet des classes et des objets de classe.

@Steve Johnson a déjà répondu à propos de méthodes statiques, également documenté sous "Fonctions intégrées" dans la référence de la bibliothèque Python.

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

@beidy recommande classmethods sur staticmethod, car la méthode reçoit alors le type de classe comme premier argument, mais je suis encore un peu flou sur les avantages de cette approche par rapport à staticmethod. Si vous l'êtes aussi, cela n'a probablement pas d'importance.


1479
2017-09-16 01:51



@Blair Conrad a déclaré que les variables statiques déclarées à l'intérieur de la définition de classe, mais pas à l'intérieur d'une méthode, sont des variables de classe ou des variables "statiques":

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

Il y a quelques trucs ici. Partant de l'exemple ci-dessus:

>>> 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__
{}

Remarquez comment la variable d'instance t.i est devenu désynchronisé avec la variable de classe "statique" lorsque l'attribut i a été réglé directement sur t. Ceci est dû au fait i a été re-lié dans le t namespace, qui est distinct de la Test espace de nommage. Si vous souhaitez modifier la valeur d'une variable "statique", vous devez la modifier dans la portée (ou l'objet) où elle a été définie à l'origine. J'ai mis "statique" entre guillemets parce que Python n'a pas vraiment de variables statiques dans le sens que C ++ et Java font.

Bien qu'il ne dise rien de spécifique sur les variables statiques ou les méthodes, le Didacticiel Python a des informations pertinentes sur classes et objets de classe.

@Steve Johnson a également répondu concernant les méthodes statiques, également documentées sous "Fonctions intégrées" dans la référence de la bibliothèque Python.

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

@beid a également mentionné classmethod, qui est similaire à staticmethod. Le premier argument d'un classmethod est l'objet de classe. Exemple:

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éthodes statiques et de classe

Comme les autres réponses l'ont noté, les méthodes statiques et de classe sont facilement accomplies en utilisant les décorateurs intégrés:

class Test(object):

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

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

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

Comme d'habitude, le premier argument MyMethod() est lié à l'objet d'instance de classe. En revanche, le premier argument à MyClassMethod() est lié à l'objet de classe lui-même (par exemple, dans ce cas, Test). Pour MyStaticMethod(), aucun des arguments n'est lié, et avoir des arguments est optionnel.

"Variables statiques"

Cependant, mettre en œuvre des "variables statiques" (bien, mutable variables statiques, de toute façon, si ce n'est pas une contradiction dans les termes ...) n'est pas aussi simple. Comme millerdev souligné dans sa réponse, le problème est que les attributs de classe de Python ne sont pas vraiment des "variables statiques". Considérer:

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

C'est parce que la ligne x.i = 12 a ajouté un nouvel attribut d'instance i à x au lieu de changer la valeur de la Test classe i attribut.

Partiel comportement de variable statique attendue, c'est-à-dire, synchronisation de l'attribut entre plusieurs instances (mais ne pas avec la classe elle-même; voir "gotcha" ci-dessous), peut être atteint en transformant l'attribut class en une propriété:

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)

Maintenant vous pouvez faire:

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

La variable statique va maintenant rester en synchronisation entre toutes les instances de classe.

(NOTE: À moins qu'une instance de classe décide de définir sa propre version de _i! Mais si quelqu'un décide de faire ça, ils méritent ce qu'ils obtiennent, n'est-ce pas ???)

Notez que techniquement, i n'est toujours pas une «variable statique»; c'est un property, qui est un type spécial de descripteur. Cependant, le property Le comportement est maintenant équivalent à une variable statique (mutable) synchronisée dans toutes les instances de classe.

"Variables statiques" immuables

Pour un comportement de variable statique immuable, il suffit d'omettre 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)

Essayez maintenant de définir l'instance i attribut retournera un AttributeError:

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

Un Gotcha d'être conscient de

Notez que les méthodes ci-dessus ne fonctionnent qu'avec instances de votre classe - ils seront ne pas travail lors de l'utilisation de la classe elle-même. Donc par exemple:

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 ligne assert Test.i == x.i produit une erreur, car le i attribut de Test et x sont deux objets différents.

Beaucoup de gens trouveront cela surprenant. Cependant, cela ne devrait pas l'être. Si nous revenons et inspectons notre Test définition de la classe (la deuxième version), nous prenons note de cette ligne:

    i = property(get_i) 

Clairement, le membre i de Test Doit être un property objet, qui est le type d'objet renvoyé par le property fonction.

Si vous trouvez la confusion ci-dessus, vous y pensez probablement encore du point de vue d'autres langages (par exemple Java ou c ++). Vous devriez aller étudier le property objet, sur l'ordre dans lequel les attributs Python sont renvoyés, le protocole de descripteur et l'ordre de résolution de méthode (MRO).

Je présente une solution à la 'gotcha' ci-dessus ci-dessous; Cependant, je suggérerais - énergiquement - que vous n'essayez pas de faire quelque chose comme ce qui suit - au moins - vous comprenez parfaitement pourquoi assert Test.i = x.i provoque une erreur.

RÉEL, RÉEL Variables statiques - Test.i == x.i

Je présente la solution (Python 3) ci-dessous à titre informatif seulement. Je ne l'entérine pas comme une "bonne solution". J'ai des doutes quant à savoir si l'émulation du comportement de la variable statique d'autres langages en Python est réellement nécessaire. Cependant, que ce soit utile ou non, ce qui suit devrait aider à mieux comprendre le fonctionnement de Python.

UPDATE: cette tentative est vraiment horrible; si vous insistez pour faire quelque chose comme ça (indice: s'il vous plaît ne pas, Python est un langage très élégant et chaussure-l'éterner en se comportant comme une autre langue n'est tout simplement pas nécessaire), utilisez le code dans La réponse d'Ethan Furman au lieu.

Emulation du comportement de variable statique d'autres langages à l'aide d'une métaclasse

Une métaclasse est la classe d'une classe. La métaclasse par défaut pour toutes les classes en Python (c'est-à-dire, les classes "new style" après Python 2.3 je crois) est type. Par exemple:

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

Cependant, vous pouvez définir votre propre métaclasse comme ceci:

class MyMeta(type): pass

Et appliquez-le à votre propre classe comme celle-ci (Python 3 seulement):

class MyClass(metaclass = MyMeta):
    pass

type(MyClass)  # class MyMeta

Ci-dessous une métaclasse que j'ai créée et qui tente d'émuler le comportement de "variable statique" d'autres langages. Il fonctionne essentiellement en remplaçant le getter, le setter et le deleter par défaut avec des versions qui vérifient si l'attribut demandé est une "variable statique".

Un catalogue des "variables statiques" est stocké dans le StaticVarMeta.statics attribut. Toutes les demandes d'attribut sont initialement tentées d'être résolues en utilisant un ordre de résolution de remplacement. J'ai surnommé ceci "l'ordre de résolution statique", ou "SRO". Cela est fait en recherchant l'attribut demandé dans l'ensemble des "variables statiques" pour une classe donnée (ou ses classes parentes). Si l'attribut n'apparaît pas dans le "SRO", la classe retombera sur le comportement get / set / delete de l'attribut par défaut (c'est-à-dire "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



Vous pouvez également ajouter des variables de classe à des classes à la volée

>>> 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

Et les instances de classe peuvent modifier les variables de classe

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



Personnellement j'utiliserais un classmethod chaque fois que j'aurais besoin d'une méthode statique. Principalement parce que je reçois la classe comme argument.

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

ou utilisez un décorateur

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

Pour les propriétés statiques .. Il est temps de chercher une définition de python ... variable peut toujours changer. Il y en a deux types mutables et immuables. De plus, il y a des attributs de classe et des attributs d'instance. Rien ne ressemble vraiment aux attributs statiques au sens de java et c ++.

Pourquoi utiliser la méthode statique au sens pythonique, si elle n'a aucune relation avec la classe! Si j'étais vous, j'utiliserais classmethod ou définirais la méthode indépendamment de la classe.


12
2017-09-16 02:02



Les méthodes statiques en python sont appelées classmethods. Jetez un oeil sur le code suivant

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

Notez que lorsque nous appelons la méthode myInstanceMethod, nous avons une erreur C'est parce qu'il nécessite que cette méthode soit appelée sur une instance de cette classe. La méthode myStaticMethod est défini comme une méthode de classe en utilisant le décorateur  @classmethod.

Juste pour les coups de pied et les fous rires, nous pourrions appeler myInstanceMethod sur la classe en passant dans une instance de la classe, comme ceci:

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

11
2017-09-16 02:05



Une chose spéciale à noter à propos des propriétés statiques et des propriétés d'instance, montrée dans l'exemple ci-dessous:

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

Cela signifie avant d'affecter la valeur à la propriété d'instance, si nous essayons d'accéder à la propriété à travers l'instance, la valeur statique est utilisée. Chaque propriété déclarée dans la classe python a toujours un emplacement statique dans la mémoire.


10
2018-03-08 06:06



Lorsque vous définissez une variable membre en dehors de toute méthode membre, la variable peut être statique ou non statique selon la manière dont la variable est exprimée.

  • CLASSNAME.var est une variable statique
  • INSTANCENAME.var n'est pas une variable statique.
  • self.var à l'intérieur de la classe n'est pas une variable statique.
  • var à l'intérieur de la fonction membre de la classe n'est pas définie.

Par exemple:

#!/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()

Les résultats sont

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

7
2018-03-26 17:56



Vous pouvez également forcer une classe à être statique en utilisant la métaclasse.

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

Puis, par accident, vous essayez d'initialiser Ma classe vous obtiendrez une StaticClassError.


6
2017-11-20 12:06



Il est possible d'avoir static variables de classe, mais ne valent probablement pas l'effort.

Voici une preuve de concept écrite en Python 3 - si l'un des détails exacts est faux, le code peut être modifié pour correspondre à peu près à ce que vous voulez dire par un 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)

et en cours d'utilisation:

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')

et quelques tests:

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

4
2018-03-25 09:02