Sind statische Klassenvariablen in Python möglich?


Antworten:


1900

Variablen, die innerhalb der Klassendefinition, jedoch nicht innerhalb einer Methode deklariert sind, sind Klassen- oder statische Variablen:

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

Wie @ millerdev hervorhebt , wird dadurch eine iVariable auf Klassenebene erstellt , die sich jedoch von allen iVariablen auf Instanzebene unterscheidet

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

Dies unterscheidet sich von C ++ und Java, jedoch nicht von C #, bei dem auf ein statisches Mitglied nicht über einen Verweis auf eine Instanz zugegriffen werden kann.

Lesen Sie, was das Python-Tutorial zum Thema Klassen und Klassenobjekte zu sagen hat .

@Steve Johnson hat bereits zu statischen Methoden geantwortet , die auch unter "Integrierte Funktionen" in der Python-Bibliotheksreferenz dokumentiert sind .

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

@beidy empfiehlt Klassenmethoden gegenüber statischen Methoden, da die Methode dann den Klassentyp als erstes Argument empfängt, aber ich bin immer noch ein wenig verwirrt über die Vorteile dieses Ansatzes gegenüber statischen Methoden. Wenn Sie auch sind, dann ist es wahrscheinlich egal.


11
Ich lerne gerade Python, aber die Vorteile von @classmethodüber @staticmethodAFAIK ist , dass Sie immer den Namen der Klasse bekommen die Methode aufgerufen wurde auf, auch wenn es sich um eine Unterklasse ist. Einer statischen Methode fehlen diese Informationen, sodass sie beispielsweise keine überschriebene Methode aufrufen kann.
Seb

49
@theJollySin der pythonische Weg für Konstanten besteht darin, keine Klasse für Konstanten zu erstellen. Haben Sie nur einige const.pymit PI = 3.14und Sie können es überall importieren. from const import PI
Giszmo

30
Diese Antwort wird wahrscheinlich das Problem der statischen Variablen verwirren. Zunächst i = 3ist nicht eine statische Variable, sondern um ein Klassenattribut. Da es sich von einem Attribut auf Instanzebene iunterscheidet , verhält es sich in anderen Sprachen nicht wie eine statische Variable. Siehe millerdev Antwort , Yann Antwort , und meine Antwort unten.
Rick unterstützt Monica

2
Also wird nur eine Kopie von i(statische Variable) im Speicher sein, selbst wenn ich Hunderte von Instanzen dieser Klasse erstelle?
Traum

2
Für alle Interessierten, die Daniel in @ Dublow Kommentar erwähnt hat, ist es Millerdev ( Wayback-Maschine )
HeyJude

618

@Blair Conrad sagte, dass statische Variablen, die in der Klassendefinition, aber nicht in einer Methode deklariert sind, Klassen- oder "statische" Variablen sind:

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

Hier gibt es ein paar Fallstricke. Fortsetzung des obigen Beispiels:

>>> 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, dass die Instanzvariable t.inicht mehr mit der "statischen" Klassenvariablen synchronisiert wurde, als das Attribut idirekt aktiviert wurde t. Dies liegt daran i, dass innerhalb des tNamespace, der sich vom TestNamespace unterscheidet, neu gebunden wurde . Wenn Sie den Wert einer "statischen" Variablen ändern möchten, müssen Sie ihn innerhalb des Bereichs (oder Objekts) ändern, in dem er ursprünglich definiert wurde. Ich habe "statisch" in Anführungszeichen gesetzt, weil Python nicht wirklich statische Variablen in dem Sinne hat, wie es C ++ und Java tun.

Obwohl es nichts Spezifisches über statische Variablen oder Methoden aussagt, enthält das Python-Lernprogramm einige relevante Informationen zu Klassen und Klassenobjekten .

@Steve Johnson antwortete auch in Bezug auf statische Methoden, die ebenfalls unter "Integrierte Funktionen" in der Python-Bibliotheksreferenz dokumentiert sind.

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

@beid erwähnte auch die Klassenmethode, die der statischen Methode ähnelt. 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 be the same as Test.i = arg1

Bildliche Darstellung des obigen Beispiels


3
Ich schlage vor, Sie erweitern das Beispiel nur ein wenig: Wenn Sie nach dem Setzen von Test.i = 6 ein neues Objekt instanziieren (z. B. u = Test ()), "erbt" das neue Objekt den neuen Klassenwert (z. ui == 6)
Mark

2
Ein Weg , um die statischen Variablen synchron zu halten ist , sie Eigenschaften zu machen: class Test(object):, _i = 3, @property, def i(self), return type(self)._i, @i.setter, def i(self,val):, type(self)._i = val. Jetzt können Sie tun x = Test(), x.i = 12, assert x.i == Test.i.
Rick unterstützt Monica

1
Ich könnte also sagen, dass alle Variablen anfangs statisch sind und dann beim Zugriff auf Instanzen zur Laufzeit Instanzvariablen erstellt werden.
Ali

Vielleicht ist dies interessant: Wenn Sie in Test eine Methode definieren, die Test.i ändert, wirkt sich dies sowohl auf die Werte von Test.i als auch von ti aus.
Pablo

@millerdev, wie Sie bereits erwähnt haben Python hat keine statischen Variablen wie C ++ oder JAVA. Ist es also in Ordnung zu sagen, dass Test.i eher eine Klassenvariable als eine statische Variable ist?
Tyto

197

Statische und Klassenmethoden

Wie die anderen Antworten angemerkt haben, können statische und Klassenmethoden mit den eingebauten Dekoratoren leicht durchgeführt 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 ist das erste Argument MyMethod()an an das Klasseninstanzobjekt gebunden. Im Gegensatz zu dem ersten Argumente MyClassMethod()wird auf das Klassenobjekt gebunden selbst (zB in diesem Fall Test). ZumMyStaticMethod() keines der Argumente ist gebunden, und Argumente sind optional.

"Statische Variablen"

Die Implementierung von "statischen Variablen" (also veränderbare statische Variablen, wenn dies kein Widerspruch ist ...) ist jedoch nicht so einfach. Wie millerdev in seiner Antwort hervorhob , besteht das Problem darin, dass Pythons Klassenattribute keine wirklich "statischen 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 daran, dass die Zeile x.i = 12ein neues Instanzattribut hinzugefügt hat i, xanstatt den Wert des TestKlassenattributs zu ändern i.

Das teilweise erwartete Verhalten statischer Variablen, dh das Synchronisieren des Attributs zwischen mehreren Instanzen (jedoch nicht mit der Klasse selbst; siehe "gotcha" unten), 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 zwischen allen Klasseninstanzen synchron .

(HINWEIS: Das heißt, es sei denn, eine Klasseninstanz beschließt, ihre eigene Version von zu definieren _i! Aber wenn sich jemand dafür entscheidet, verdient er das, was er bekommt, nicht wahr ???)

Beachten Sie, dass es sich technisch gesehen iimmer noch überhaupt nicht um eine 'statische Variable' handelt. es ist ein property, was eine spezielle Art von Deskriptor ist. Das propertyVerhalten entspricht jetzt jedoch einer (veränderlichen) statischen Variablen, die über alle Klasseninstanzen hinweg synchronisiert wird.

Unveränderliche "statische Variablen"

Lassen Sie für ein unveränderliches Verhalten statischer Variablen einfach den propertySetter weg:

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)

Wenn Sie nun versuchen, das Instanzattribut ifestzulegen, wird Folgendes zurückgegeben AttributeError:

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

Ein Gotcha, dessen man sich bewusst sein sollte

Beachten Sie, dass die oben genannten Methoden nur mit Instanzen Ihrer Klasse funktionieren - sie funktionieren nicht , wenn Sie die Klasse selbst verwenden . 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 Zeile assert Test.i == x.ierzeugt einen Fehler, da das iAttribut von Testund xzwei verschiedene Objekte sind.

Viele Leute werden dies überraschend finden. Dies sollte jedoch nicht der Fall sein. Wenn wir zurückgehen und unsere TestKlassendefinition (die zweite Version) überprüfen , beachten wir diese Zeile:

    i = property(get_i) 

Das Mitglied ivon Testmuss eindeutig ein propertyObjekt sein. Dies ist der Objekttyp, der von der propertyFunktion zurückgegeben wird.

Wenn Sie das oben Genannte verwirrend finden, denken Sie höchstwahrscheinlich immer noch aus der Perspektive anderer Sprachen (z. B. Java oder C ++) darüber nach. Du solltest das studieren gehenproperty Objekt untersuchen, ü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 das obige 'Gotcha' unten; Ich würde jedoch mit Nachdruck vorschlagen, dass Sie nicht versuchen, Folgendes zu tun, bis Sie zumindest gründlich verstanden haben, warum assert Test.i = x.iein Fehler verursacht wird.

ECHTE, TATSÄCHLICHE statische Variablen -Test.i == x.i

Ich präsentiere die (Python 3) -Lösung unten nur zu Informationszwecken. Ich unterstütze es nicht als "gute Lösung". Ich habe meine Zweifel, ob die Emulation des statischen Variablenverhaltens anderer Sprachen in Python jemals wirklich notwendig ist. Unabhängig davon, ob es tatsächlich nützlich ist, sollte das Folgende zum besseren Verständnis der Funktionsweise von Python beitragen.

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 einfach nicht notwendig, sich wie eine andere Sprache zu verhalten), verwenden Sie stattdessen den Code in Ethan Furmans Antwort .

Emulieren des Verhaltens statischer Variablen anderer Sprachen mithilfe einer Metaklasse

Eine Metaklasse ist die Klasse einer Klasse. Die Standard-Metaklasse für alle Klassen in Python (dh die "New Style" -Klassen nach Python 2.3, glaube ich) ist type. Zum Beispiel:

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

Sie können Ihre eigene Metaklasse jedoch folgendermaßen definieren:

class MyMeta(type): pass

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

class MyClass(metaclass = MyMeta):
    pass

type(MyClass)  # class MyMeta

Unten ist eine von mir erstellte Metaklasse, die versucht, das Verhalten "statischer Variablen" anderer Sprachen zu emulieren. Grundsätzlich wird der Standard-Getter, -Setter und -Löscher durch Versionen ersetzt, die prüfen, ob das angeforderte Attribut eine "statische Variable" ist.

Ein Katalog der "statischen Variablen" wird im StaticVarMeta.staticsAttribut gespeichert . Es wird zunächst versucht, alle Attributanforderungen mithilfe einer Ersatzauflösungsreihenfolge aufzulösen. Ich habe dies die "statische Auflösungsreihenfolge" oder "SRO" genannt. Dies erfolgt durch Suchen nach dem angeforderten Attribut in der Menge der "statischen Variablen" für eine bestimmte Klasse (oder ihre übergeordneten Klassen). Wenn das Attribut nicht in der "SRO" angezeigt wird, greift die Klasse auf das Standardattribut get / set / delete zurück (dh "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

Ich habe versucht, Ihren Weg zu benutzen, aber ich hatte ein Problem. Schauen Sie sich bitte
Muhammed Refaat

@ RickTeachey: Ich denke, Sie sollten generell alles, was Sie in der Klasseninstanz tun Test(bevor Sie es zum Instanziieren von Instanzen verwenden), als im Bereich der Metaprogrammierung befindlich ansehen ? Zum Beispiel ändern Sie das Klassenverhalten, indem Sie dies tun Test.i = 0(hier zerstören Sie einfach das Eigenschaftsobjekt vollständig). Ich denke, der "Eigenschaftsmechanismus" wird nur beim Eigenschaftszugriff auf Instanzen einer Klasse aktiviert (es sei denn, Sie ändern das zugrunde liegende Verhalten möglicherweise mithilfe einer Metaklasse als Zwischenstufe). Übrigens, bitte beenden Sie diese Antwort :-)
Ole Thomsen Buus

1
@ RickTeachey Danke :-) Deine Metaklasse ist am Ende interessant, aber eigentlich etwas zu komplex für meinen Geschmack. Dies kann in einem großen Framework / einer großen Anwendung nützlich sein, in dem dieser Mechanismus unbedingt erforderlich ist. Wie auch immer, dies zeigt beispielhaft, dass Python es möglich macht, wenn neues (komplexes) Nicht-Standard-Meta-Verhalten wirklich benötigt wird :)
Ole Thomsen Buus

1
@OleThomsenBuus: Überprüfen Sie meine Antwort auf eine einfachere Metaklasse, die den Job erledigt.
Ethan Furman

1
@taper Du bist richtig; Ich habe die Antwort bearbeitet, um das Problem zu beheben (ich kann nicht glauben, dass sie so lange falsch sitzt!). Entschuldigung für die Verwirrung.
Rick unterstützt Monica

33

Sie können Klassen auch im laufenden Betrieb 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]

3
Bleiben die neuen Klassenvariablen auch dann erhalten, wenn die Klasse in ein anderes Modul importiert wird?
Zakdances

Ja. Klassen sind effektiv Singletons, unabhängig davon, aus welchem ​​Namespace Sie sie aufrufen.
Pedro

@Gregory Sie sagten "Und Klasseninstanzen können Klassenvariablen ändern" Dieses Beispiel heißt eigentlich "Zugriff statt Änderung". Die Änderung wurde vom Objekt selbst über seine eigene Funktion append () vorgenommen.
Amr ALHOSSARY

19

Persönlich würde ich eine Klassenmethode verwenden, wenn ich eine statische Methode benötigte. Hauptsächlich, weil ich die Klasse als Argument bekomme.

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

oder verwenden Sie einen Dekorateur

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

Für statische Eigenschaften. Es ist an der Zeit, eine Python-Definition nachzuschlagen. Die Variable kann sich jederzeit ändern. Es gibt zwei Arten von ihnen, die veränderlich und unveränderlich sind. Außerdem gibt es Klassenattribute und Instanzattribute. Nichts wie statische Attribute im Sinne von Java & C ++

Warum statische Methode im pythonischen Sinne verwenden, wenn sie keinerlei 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.


1
Variablen sind nicht veränderlich oder unveränderlich; Objekte sind. (Ein Objekt kann jedoch mit unterschiedlichem Erfolg versuchen, die Zuordnung zu bestimmten Attributen zu verhindern.)
Davis Herring

Java und C ++ verwenden statisch (falsche Verwendung des Wortes, imho) genau so, wie Sie das Attribut Instanz versus Klasse verwenden. Ein Klassenattribut / eine Klassenmethode ist in Java und C ++ statisch, kein Unterschied, außer dass in Python der erste Parameter für einen Klassenmethodenaufruf die Klasse ist.
Angel O'Sphere

16

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

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, über die Instanz auf die Eigenschaft zuzugreifen. Jede in der Python-Klasse deklarierte Eigenschaft verfügt immer über einen statischen Speicherplatz .


16

Statische Methoden in Python werden aufgerufen Klassenmethoden bezeichnet . Schauen 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 beim Aufrufen der Methode myInstanceMethod eine Fehlermeldung angezeigt wird. Dies liegt daran, dass die Methode für eine Instanz dieser Klasse aufgerufen werden muss. Die Methode myStaticMethod wird mit der Methode als Klassenmethode festgelegt Decorator @classmethod festgelegt .

Nur für Tritte und Kichern könnten wir myInstanceMethod für die Klasse aufrufen, indem wir eine Instanz der Klasse übergeben, wie folgt :

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

2
Umm ... statische Methoden werden mit gemacht @staticmethod; @classmethodist (offensichtlich) für Klassenmethoden (die hauptsächlich zur Verwendung als alternative Konstruktoren vorgesehen sind, aber zur Not als statische Methoden dienen können, die zufällig einen Verweis auf die Klasse erhalten, durch die sie aufgerufen wurden).
ShadowRanger

11

Wenn Sie eine Elementvariable außerhalb einer Elementmethode definieren, kann die Variable je nach Ausdruck der Variablen entweder statisch oder nicht statisch sein.

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

Zum Beispiel:

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

Die Einrückung ist gebrochen. Dies wird nicht ausgeführt
Thomas Weller

9

Es ist möglich zu haben static Klassenvariablen , aber die Mühe lohnt sich wahrscheinlich nicht.

Hier ist ein in Python 3 geschriebener Proof-of-Concept. Wenn eines der genauen Details falsch ist, kann der Code so angepasst werden, dass er genau dem entspricht, was Sie mit a meinen 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)

und in Gebrauch:

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

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

8

Sie können eine Klasse auch mithilfe der Metaklasse als statisch erzwingen.

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

Wenn Sie dann versehentlich versuchen, MyClass zu initialisieren, erhalten Sie einen StaticClassError.


4
Warum ist es überhaupt eine Klasse, wenn Sie sie nicht instanziieren wollen? Das fühlt sich an, als würde man Python drehen, um es in Java umzuwandeln ...
Ned Batchelder

1
Die Borg-Sprache ist ein besserer Weg, um damit umzugehen.
Rick unterstützt Monica

@NedBatchelder Es ist eine abstrakte Klasse, die nur zum Unterklassen (und Instanziieren der Unterklassen)
gedacht ist

1
Ich hoffe, die Unterklassen verwenden nicht super (), um die __new__ihrer Eltern aufzurufen ...
Ned Batchelder

7

Ein sehr interessanter Punkt bei Pythons Attributsuche ist, dass damit " virtuelle Variablen" erstellt werden können:

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!

Normalerweise gibt es keine Zuordnungen zu diesen, nachdem sie erstellt wurden. Beachten Sie, dass die Suche verwendet wird, selfda der Wert zwar labelstatisch in dem Sinne ist, dass er keiner bestimmten Instanz zugeordnet ist, der Wert jedoch immer noch von der (Klasse der) Instanz abhängt.


6

In Bezug auf diese Antwort können Sie für eine konstante statische Variable einen Deskriptor verwenden. Hier ist ein Beispiel:

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

ergebend ...

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

Sie können jederzeit eine Ausnahme auslösen, wenn passes nicht Ihre Sache ist , den Einstellwert ( oben) stillschweigend zu ignorieren . Wenn Sie nach einer statischen Klassenvariablen im C ++ - Java-Stil suchen:

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

Weitere Informationen zu Deskriptoren finden Sie in dieser Antwort und in den offiziellen HOWTO- Dokumenten .


2
Sie können auch einfach verwenden @property, was mit einem Deskriptor identisch ist, aber es ist viel weniger Code.
Rick unterstützt Monica

6

Absolut Ja, Python selbst hat kein explizites statisches Datenelement, aber wir können dies tun

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

Ausgabe

0
0
1
1

Erläuterung

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

6

Ja, es ist definitiv möglich, statische Variablen und Methoden in Python zu schreiben.

Statische Variablen: Auf Klassenebene deklarierte Variablen werden als statische Variablen bezeichnet, auf die direkt über den Klassennamen zugegriffen werden kann.

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

    >>> print(A.my_var)
        shagun

Instanzvariablen: Variablen, die von einer Instanz einer Klasse verknüpft sind und auf die zugegriffen wird, sind Instanzvariablen.

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

Statische Methoden: Ähnlich wie bei Variablen kann auf statische Methoden direkt über den Klassennamen zugegriffen werden. Sie müssen keine Instanz erstellen.

Beachten Sie jedoch, dass eine statische Methode in Python keine nicht statische Methode aufrufen kann.

    >>> class A:
   ...     @staticmethod
   ...     def my_static_method():
   ...             print("Yippey!!")
   ... 
   >>> A.my_static_method()
   Yippey!!

4

Um mögliche Verwirrung zu vermeiden, möchte ich statische Variablen und unveränderliche Objekte gegenüberstellen.

Einige primitive Objekttypen wie Ganzzahlen, Gleitkommazahlen, Zeichenfolgen und Touple sind in Python unveränderlich. Dies bedeutet, dass sich das Objekt, auf das mit einem bestimmten Namen verwiesen wird, nicht ändern kann, wenn es von einem der oben genannten Objekttypen ist. Der Name kann einem anderen Objekt zugewiesen werden, das Objekt selbst darf jedoch nicht geändert werden.

Wenn Sie eine Variable statisch machen, gehen Sie noch einen Schritt weiter, indem Sie nicht zulassen, dass der Variablenname auf ein anderes Objekt als das zeigt, auf das er derzeit verweist. (Hinweis: Dies ist ein allgemeines Softwarekonzept und nicht spezifisch für Python. Informationen zum Implementieren von Statik in Python finden Sie in den Beiträgen anderer.)


4

Der beste Weg, den ich gefunden habe, ist, eine andere Klasse zu verwenden. Sie können ein Objekt erstellen und es dann für andere Objekte verwenden.

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

Mit dem obigen Beispiel habe ich eine Klasse mit dem Namen erstellt staticFlag .

Diese Klasse sollte die statische Variable __success(Private Static Var) darstellen.

tryIt Klasse repräsentiert die reguläre Klasse, die wir verwenden müssen.

Jetzt habe ich ein Objekt für eine Flagge gemacht (staticFlag ). Dieses Flag wird als Referenz auf alle regulären Objekte gesendet.

Alle diese Objekte werden der Liste hinzugefügt tryArr.


Dieses Skript Ergebnisse:

False
False
False
False
False
True
True
True
True
True

2

Statische Variablen in Class Factory Python3.6

Für alle, die eine Klassenfactory mit Python3.6 oder höher verwenden , verwenden Sie das nonlocalSchlüsselwort, um sie dem Bereich / Kontext der Klasse hinzuzufügen, die wie folgt erstellt wird:

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

ja, aber in diesem Fall hasattr(SomeClass, 'x')ist False. Ich bezweifle, dass dies überhaupt jemand mit einer statischen Variablen meint.
Rick unterstützt Monica

@ RickTeachey lol, haben Sie Ihren statischen Variablencode gesehen, stackoverflow.com/a/27568860/2026508 +1 Internet Sir, und ich dachte, hasattr hat so nicht funktioniert? ist also some_varunveränderlich und statisch definiert, oder nicht? Was hat der externe Getter-Zugriff damit zu tun, dass eine Variable statisch ist oder nicht? Ich habe jetzt so viele Fragen. würde gerne einige Antworten hören, wenn Sie die Zeit bekommen.
jmunsch

Ja, diese Metaklasse ist ziemlich lächerlich. Ich bin mir nicht sicher, ob ich die Fragen verstehe, aber meiner Meinung nach ist some_varoben überhaupt kein Klassenmitglied. In Python kann von außerhalb der Klasse auf alle Klassenmitglieder zugegriffen werden.
Rick unterstützt Monica

Der nonlocalKeywoard "stößt" den Bereich der Variablen an. Der Bereich einer Klassenkörperdefinition ist unabhängig von dem Bereich, in dem sie sich befindet, wenn Sie sagen nonlocal some_var, dass lediglich ein nicht lokaler ( sprich : NICHT im Bereich der Klassendefinition) Namensreferenz auf ein anderes benanntes Objekt erstellt wird. Daher wird es nicht an die Klassendefinition angehängt, da es nicht im Bereich des Klassenkörpers enthalten ist.
Rick unterstützt Monica

1

Das ist also wahrscheinlich ein Hack, aber ich habe eval(str)in Python 3 ein statisches Objekt erhalten, eine Art Widerspruch.

Es gibt eine Records.py-Datei, in der nur classObjekte mit statischen Methoden und Konstruktoren definiert sind, die einige Argumente speichern. Dann muss ich aus einer anderen .py-Datei import Recordsjedes Objekt dynamisch auswählen und es dann bei Bedarf entsprechend der Art der eingelesenen Daten instanziieren.

Also, wo object_name = 'RecordOne'oder den Klassennamen rufe ich auf cur_type = eval(object_name)und dann, um ihn zu instanziieren, tun cur_inst = cur_type(args) Sie dies. Bevor Sie jedoch instanziieren, können Sie statische Methoden aufrufen, cur_type.getName()beispielsweise wie eine abstrakte Basisklassenimplementierung oder was auch immer das Ziel ist. Im Backend ist es jedoch wahrscheinlich in Python instanziiert und nicht wirklich statisch, da eval ein Objekt zurückgibt, das instanziiert worden sein muss und ein statisches Verhalten ergibt.


0

Sie können eine Liste oder ein Wörterbuch verwenden, um "statisches Verhalten" zwischen Instanzen zu erhalten.

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

0

Wenn Sie versuchen, eine statische Variable freizugeben, um sie beispielsweise für andere Instanzen zu erhöhen, funktioniert so etwas wie dieses Skript einwandfrei:

# -*- 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)
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.