Frage Sind statische Klassenvariablen möglich?


Ist es möglich, statische Klassenvariablen oder Methoden in Python zu haben? Welche Syntax ist dafür erforderlich?


1505
2017-09-16 01:46


Ursprung


Antworten:


Innerhalb der Klassendefinition deklarierte Variablen, aber nicht innerhalb einer Methode, sind Klassen- oder statische Variablen:

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

Wie @Millerdev weist darauf hin, dass dies eine Klassenebene schafft i Variable, aber dies unterscheidet sich von jeder Instanzenebene i Variable, so könnte man haben

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

Dies unterscheidet sich von C ++ und Java, unterscheidet sich aber nicht so sehr von C #, wo ein statischer Member nicht mit einem Verweis auf eine Instanz aufgerufen werden kann.

Sehen was das Python-Tutorial zum Thema Klassen und Klassenobjekte zu sagen hat.

@Steve Johnson hat bereits bezüglich beantwortet statische Methoden, auch dokumentiert unter "Eingebaute Funktionen" in der Python-Bibliothek Referenz.

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

@beidy empfiehlt Klassenmethodes über staticmethod, da die Methode dann den Klassentyp als erstes Argument erhält, aber ich bin immer noch etwas unscharf über die Vorteile dieses Ansatzes gegenüber der statischen Methode. Wenn du es auch bist, dann ist es wahrscheinlich egal.


1479
2017-09-16 01:51



@Blair Conrad sagte statische Variablen deklariert innerhalb der Klassendefinition, aber nicht innerhalb einer Methode sind Klasse oder "statische" Variablen:

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

Es gibt ein paar Gotcha's hier. Weiter vom obigen Beispiel:

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

Beachten Sie, wie die Instanzvariable t.i mit dem Attribut "static" nicht mehr synchron ist i wurde direkt eingestellt t. Das ist weil i wurde innerhalb der gebunden t Namensraum, der sich von dem unterscheidet Test Namensraum. Wenn Sie den Wert einer "statischen" Variablen ändern möchten, müssen Sie sie innerhalb des Bereichs (oder Objekts) ändern, in dem sie ursprünglich definiert wurde. Ich setze "statisch" in Anführungszeichen, weil Python keine statischen Variablen in dem Sinne hat, wie C ++ und Java.

Obwohl es nichts Spezifisches über statische Variablen oder Methoden aussagt, kann das Python-Tutorial hat einige relevante Informationen über Klassen und Klassenobjekte.

@Steve Johnson antwortete auch auf statische Methoden, die auch unter "Built-in Functions" in der Python Library Reference dokumentiert sind.

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

@beid erwähnte auch die Klassenmethode, die der statischen Methode ähnlich ist. Das erste Argument einer Klassenmethode ist das Klassenobjekt. Beispiel:

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



Statische und Klassenmethoden

Wie die anderen Antworten angemerkt haben, können statische und Klassenmethoden leicht mit den eingebauten Dekoratoren erreicht werden:

class Test(object):

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

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

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

Wie üblich, das erste Argument zu MyMethod() ist an das Klasseninstanzobjekt gebunden. Im Gegensatz dazu, das erste Argument zu MyClassMethod() ist gebunden an das Klassenobjekt selbst (z.B. in diesem Fall Test). Zum MyStaticMethod(), keines der Argumente ist gebunden, und Argumente überhaupt zu haben, ist optional.

"Statische Variablen"

Implementieren von "statischen Variablen" (gut, veränderlich statische Variablen, wenn das kein Widerspruch in sich ist ...) ist nicht so geradlinig. Als Millerdev wies in seiner Antwort darauf hinDas Problem ist, dass Pythons Klassenattribute nicht wirklich "statische Variablen" sind. Erwägen:

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

Dies liegt an der Linie x.i = 12 hat ein neues Instanzattribut hinzugefügt i zu x anstatt den Wert des zu ändern Test Klasse i Attribut.

Teilweise erwartetes statisches Variablenverhalten, d.h. Synchronisierung des Attributs zwischen mehreren Instanzen (aber nicht mit der Klasse selbst; siehe unten "Gotcha", kann erreicht werden, indem das Klassenattribut in eine Eigenschaft umgewandelt wird:

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)

Jetzt können Sie tun:

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

Die statische Variable bleibt nun synchron zwischen allen Klasseninstanzen.

(HINWEIS: Das heißt, es sei denn, eine Klasseninstanz entscheidet, ihre eigene Version von _i! Aber wenn sich jemand dafür entscheidet, verdienen sie, was sie bekommen, oder?

Beachten Sie, dass technisch gesprochen, i ist überhaupt noch keine "statische Variable"; es ist ein property, die eine spezielle Art von Deskriptor ist. Aber die property Das Verhalten entspricht nun einer (veränderbaren) statischen Variable, die über alle Klasseninstanzen hinweg synchronisiert wird.

Unveränderbare "statische Variablen"

Für unveränderliches statisches variables Verhalten lassen Sie einfach die 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)

Versuchen Sie nun, die Instanz festzulegen i Attribut gibt ein zurück AttributeError:

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

Ein Gotcha zu beachten

Beachten Sie, dass die obigen Methoden nur mit funktionieren Instanzen von deiner Klasse - werden sie nicht Arbeit wenn die Klasse selbst verwendet wird. Also zum Beispiel:

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'

Die Linie assert Test.i == x.i erzeugt einen Fehler, weil der i Attribut von Test und x sind zwei verschiedene Objekte.

Viele Menschen werden das überraschend finden. Es sollte jedoch nicht sein. Wenn wir zurückgehen und unsere Test Klassendefinition (die zweite Version), nehmen wir diese Zeile zur Kenntnis:

    i = property(get_i) 

Klar, das Mitglied i von Test muss ein sein property Objekt, das ist der Objekttyp, der von der zurückgegeben wird property Funktion.

Wenn Sie das Obige als verwirrend empfinden, denken Sie wahrscheinlich immer noch daran aus der Perspektive anderer Sprachen (z. B. Java oder C ++). Du solltest das lernen property Objekt, über die Reihenfolge, in der Python-Attribute zurückgegeben werden, das Deskriptorprotokoll und die Methodenauflösungsreihenfolge (MRO).

Ich präsentiere eine Lösung für die obige "Gotcha" unten; Aber ich würde - energisch - vorschlagen, dass Sie nicht versuchen, etwas wie das Folgende zu tun, bis - zumindest - Sie verstehen, warum assert Test.i = x.i verursacht einen Fehler.

ECHT, AKTUELL Statische Variablen - Test.i == x.i

Ich präsentiere die (Python 3) Lösung unten nur zu Informationszwecken. Ich befürworte es nicht als "gute Lösung". Ich habe Zweifel, ob das statische Variablenverhalten anderer Sprachen in Python jemals wirklich emuliert werden muss. Unabhängig davon, ob es tatsächlich nützlich ist, sollten die folgenden Informationen helfen, Python besser zu verstehen.

UPDATE: Dieser Versuch ist wirklich ziemlich schrecklich; Wenn Sie darauf bestehen, so etwas zu tun (Hinweis: Bitte nicht; Python ist eine sehr elegante Sprache, und es ist nicht nötig, sich so zu benehmen, als wäre eine andere Sprache erforderlich), verwenden Sie den Code in Ethan Furmans Antwort stattdessen.

Emulation des statischen Variablenverhaltens anderer Sprachen mit Hilfe einer Metaklasse

Eine Metaklasse ist die Klasse einer Klasse. Die Standard-Metaklasse für alle Klassen in Python (d. H. Die "neuen Stil" -Klassen nach Python 2.3 glaube ich) ist type. Beispielsweise:

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

Sie können jedoch Ihre eigene Metaklasse wie folgt definieren:

class MyMeta(type): pass

Und wenden Sie es auf Ihre eigene Klasse an (nur Python 3):

class MyClass(metaclass = MyMeta):
    pass

type(MyClass)  # class MyMeta

Unten ist eine Metaklasse, die ich erstellt habe, die versucht, "statische Variable" Verhalten anderer Sprachen zu emulieren. Es funktioniert im Grunde genommen, indem der Standard-Getter, Setter und Deleter durch Versionen ersetzt wird, die prüfen, ob das angeforderte Attribut eine "statische Variable" ist.

Ein Katalog der "statischen Variablen" ist in der StaticVarMeta.statics Attribut. Alle Attributanforderungen werden anfänglich versucht, unter Verwendung einer Ersatzauflösungsreihenfolge aufgelöst zu werden. Ich habe dies als "statische Auflösungsreihenfolge" oder "SRO" bezeichnet. Dies wird durchgeführt, indem nach dem angeforderten Attribut in der Menge von "statischen Variablen" für eine gegebene Klasse (oder ihre Elternklassen) gesucht wird. Wenn das Attribut nicht in der "SRO" erscheint, greift die Klasse auf das Standardattribut get / set / delete (d. H. "MRO") zurück.

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



Sie können den Klassen auch im Handumdrehen Klassenvariablen hinzufügen

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

Und Klasseninstanzen können Klassenvariablen ändern

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



Persönlich würde ich eine Klassenmethode verwenden, wann immer ich eine statische Methode brauchte. Vor allem, weil ich die Klasse als Argument verstehe.

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

oder benutze einen Dekorateur

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

Für statische Eigenschaften .. Es ist Zeit, Sie einige Python-Definition nachschlagen .. Variable kann immer ändern. Es gibt zwei Arten von ihnen veränderlich und unveränderlich. Außerdem gibt es Klassenattribute und Instanzattribute. Nichts wirklich wie statische Attribute im Sinne von Java & C ++

Warum statische Methode im pythonischen Sinn verwenden, wenn sie überhaupt keine Beziehung zur Klasse hat! Wenn ich Sie wäre, würde ich entweder die Klassenmethode verwenden oder die Methode unabhängig von der Klasse definieren.


12
2017-09-16 02:02



Statische Methoden in Python werden aufgerufen Klassenmethodes. Sehen Sie sich den folgenden Code an

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

Beachten Sie, dass wenn wir die Methode aufrufen myInstanceMethodWir erhalten einen Fehler. Dies liegt daran, dass diese Methode für eine Instanz dieser Klasse aufgerufen werden muss. Die Methode myStaticMethode wird als eine Klassenmethode mit der Dekorateur  @ Klassenmethode.

Nur für Tritte und Kichern konnten wir anrufen myInstanceMethod auf die Klasse, indem man eine Instanz der Klasse wie folgt eingibt:

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

11
2017-09-16 02:05



Eine besondere Anmerkung zu statischen Eigenschaften und Instanzeigenschaften, die im folgenden Beispiel gezeigt werden:

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

Dies bedeutet, dass vor dem Zuweisen des Werts zur Instanzeigenschaft der statische Wert verwendet wird, wenn wir versuchen, auf die Eigenschaft über die Instanz zuzugreifen. Jede in der Python-Klasse deklarierte Eigenschaft hat immer einen statischen Slot im Speicher.


10
2018-03-08 06:06



Wenn Sie eine Membervariable außerhalb einer Membermethode definieren, kann die Variable abhängig davon, wie die Variable ausgedrückt wird, entweder statisch oder nicht statisch sein.

  • CLASSNAME.var ist eine statische Variable
  • INSTANCENAME.var ist keine statische Variable.
  • self.var in der Klasse ist keine statische Variable.
  • var innerhalb der Klassenmemberfunktion ist nicht definiert.

Beispielsweise:

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

Die Ergebnisse sind

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

7
2018-03-26 17:56



Sie könnten auch eine Klasse erzwingen, die mithilfe der Metaklasse statisch ist.

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

Dann, wann immer du zufällig versuchst zu initialisieren Meine Klasse Sie erhalten einen StaticClassError.


6
2017-11-20 12:06