Wie erstelle ich statische Klassenvariablen oder Methoden in Python?
Antworten
Zu viele Anzeigen?Variablen, die innerhalb der Klassendefinition, aber nicht innerhalb einer Methode deklariert werden, sind Klassen- oder statische Variablen:
>>> class MyClass:
... i = 3
...
>>> MyClass.i
3
Als @ millerdev weist darauf hin, dass dadurch eine Klassenstufe entsteht i
Variable, aber diese unterscheidet sich von jeder Instanz-Ebene i
Variable, so dass Sie Folgendes haben könnten
>>> m = MyClass()
>>> m.i = 4
>>> MyClass.i, m.i
>>> (3, 4)
Dies unterscheidet sich von C++ und Java, aber nicht so sehr von C#, wo auf ein statisches Mitglied nicht über einen Verweis auf eine Instanz zugegriffen werden kann.
参照 was das Python-Tutorial zum Thema Klassen und Klassenobjekte zu sagen hat .
@Steve Johnson hat bereits geantwortet bezüglich statische Methoden auch dokumentiert unter "Eingebaute Funktionen" in der Python-Bibliothek-Referenz .
class C:
@staticmethod
def f(arg1, arg2, ...): ...
@beidy empfiehlt Klassenmethode s über staticmethod, da die Methode dann den Klassentyp als erstes Argument erhält.
@Blair Conrad sagte, dass statische Variablen, die innerhalb der Klassendefinition, aber nicht innerhalb einer Methode deklariert werden, Klassen- oder "statische" Variablen sind:
>>> class Test(object):
... i = 3
...
>>> Test.i
3
Hier gibt es ein paar Stolpersteine. Um bei dem obigen Beispiel zu bleiben:
>>> 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.i
nicht mehr mit der "statischen" Klassenvariablen übereinstimmt, wenn das Attribut i
wurde direkt auf t
. Der Grund dafür ist i
wurde innerhalb der t
Namespace, der sich von dem Test
Namensraum. Wenn Sie den Wert einer "statischen" Variablen ändern wollen, müssen Sie ihn in dem Bereich (oder Objekt) ändern, in dem sie ursprünglich definiert wurde. Ich habe "statisch" in Anführungszeichen gesetzt, weil es in Python nicht wirklich statische Variablen im Sinne von C++ und Java gibt.
Obwohl es nichts Spezielles über statische Variablen oder Methoden aussagt, ist die Python-Tutorial hat einige relevante Informationen über Klassen und Klassenobjekte .
@Steve Johnson antwortete auch in Bezug 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 classmethod, die ähnlich wie staticmethod ist. Das erste Argument einer classmethod 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
Statische und Klassenmethoden
Wie bereits in den anderen Antworten erwähnt, lassen sich statische und Klassenmethoden leicht mit den eingebauten Dekoratoren realisieren:
class Test(object):
# regular instance method:
def my_method(self):
pass
# class method:
@classmethod
def my_class_method(cls):
pass
# static method:
@staticmethod
def my_static_method():
pass
Wie üblich ist das erste Argument von my_method()
ist an das Klasseninstanzobjekt gebunden. Im Gegensatz dazu wird das erste Argument von my_class_method()
es an das Klassenobjekt selbst gebunden (z.B. in diesem Fall, Test
). Para my_static_method()
ist keines der Argumente gebunden, und die Angabe von Argumenten ist optional.
"Statische Variablen"
Allerdings ist die Implementierung von "statischen Variablen" (naja, änderbar statische Variablen, jedenfalls, wenn das kein Widerspruch in sich ist...) ist nicht so einfach. Wie millerdev in seiner Antwort hervorgehoben Das Problem ist, dass die Klassenattribute von Python keine echten "statischen Variablen" sind. Bedenken Sie:
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 Linie x.i = 12
hat ein neues Instanzattribut hinzugefügt i
a x
anstatt den Wert des Parameters Test
Klasse i
Attribut.
Teilweise das erwartete Verhalten statischer Variablen, d. h. die Synchronisierung des Attributs zwischen mehreren Instanzen (aber no mit der Klasse selbst; siehe 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)
Das können Sie jetzt 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 synchronisiert zwischen allen Klasseninstanzen .
(HINWEIS: Das heißt, sofern eine Klasseninstanz nicht beschließt, ihre eigene Version von _i
! Aber wenn sich jemand dazu entschließt, DAS zu tun, dann verdient er, was er bekommt, nicht wahr???)
Beachten Sie, dass technisch gesehen, i
ist immer noch keine "statische Variable", sondern eine property
der eine besondere Art von Deskriptor ist. Allerdings ist die property
Verhalten entspricht nun einer (veränderbaren) statischen Variablen, die über alle Klasseninstanzen hinweg synchronisiert wird.
Unveränderliche "statische Variablen"
Für unveränderliches statisches Variablenverhalten lassen Sie einfach die property
Setzer:
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)
Jetzt wird versucht, die Instanz zu setzen i
Attribut gibt ein AttributeError
:
x = Test()
assert x.i == 3 # success
x.i = 12 # ERROR
Ein Fehler, der beachtet werden muss
Beachten Sie, dass die oben genannten Methoden nur mit Instanzen Ihrer Klasse - sie werden no Arbeit wenn Sie die Klasse selbst verwenden . So 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
führt zu einem Fehler, da die i
Attribut von Test
y x
sind zwei verschiedene Objekte.
Das wird viele Menschen überraschen. Das sollte es aber nicht sein. Wenn wir zurückgehen und unsere Test
Klassendefinition (der zweiten Version), nehmen wir diese Zeile zur Kenntnis:
i = property(get_i)
Es ist klar, dass das Mitglied i
von Test
muss ein property
Objekt, das der Typ des Objekts ist, das von der Methode property
Funktion.
Wenn Sie die obigen Ausführungen verwirrend finden, denken Sie höchstwahrscheinlich noch aus der Perspektive anderer Sprachen (z. B. Java oder C++) darüber nach. Sie sollten sich mit der property
Objekt, über die Reihenfolge, in der Python-Attribute zurückgegeben werden, das Deskriptorprotokoll und die Reihenfolge der Methodenauflösung (MRO).
Ich präsentiere im Folgenden eine Lösung für das obige "Problem"; ich würde jedoch dringend empfehlen, dass Sie nicht versuchen, etwas wie das Folgende zu tun, bis Sie - zumindest - gründlich verstehen, warum assert Test.i = x.i
verursacht einen Fehler.
REAL, TATSÄCHLICH Statische Variablen - Test.i == x.i
Ich stelle die (Python 3) Lösung unten nur zu Informationszwecken vor. Ich empfehle sie nicht als "gute Lösung". Ich habe meine Zweifel, ob die Nachahmung des statischen Variablenverhaltens anderer Sprachen in Python jemals wirklich notwendig ist. Unabhängig davon, ob sie tatsächlich nützlich ist, sollte die folgende Lösung zum besseren Verständnis der Funktionsweise von Python beitragen.
UPDATE: dieser Versuch ist wirklich ziemlich furchtbar Wenn Sie darauf bestehen, so etwas zu tun (Hinweis: Bitte tun Sie es nicht; Python ist eine sehr elegante Sprache und es ist einfach nicht notwendig, sie so zu gestalten, dass sie sich wie eine andere Sprache verhält), verwenden Sie den Code in Antwort von Ethan Furman stattdessen.
Emulation des Verhaltens statischer Variablen in anderen Sprachen mit Hilfe einer Metaklasse
Eine Metaklasse ist die Klasse einer Klasse. Die Standard-Metaklasse für alle Klassen in Python (d.h. die Klassen im "neuen Stil" 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 jedoch Ihre eigene Metaklasse wie folgt definieren:
class MyMeta(type): pass
Und wenden Sie es auf Ihre eigene Klasse wie folgt an (nur Python 3):
class MyClass(metaclass = MyMeta):
pass
type(MyClass) # class MyMeta
Im Folgenden finden Sie eine Metaklasse, die ich erstellt habe und die versucht, das Verhalten von "statischen Variablen" in anderen Sprachen zu emulieren. Sie funktioniert im Wesentlichen, indem sie die Standard-Getter, -Setter und -Deletters durch Versionen ersetzt, die prüfen, ob das angeforderte Attribut eine "statische Variable" ist.
Ein Katalog der "statischen Variablen" ist in der Datei StaticVarMeta.statics
Attribut. Bei allen Attributanträgen wird zunächst versucht, sie mit Hilfe einer Ersatzauflösungsreihenfolge zu lösen. Ich habe dies als "statische Auflösungsreihenfolge" oder "SRO" bezeichnet. Dazu wird das angeforderte Attribut in der Menge der "statischen Variablen" für eine bestimmte Klasse (oder ihre Elternklassen) gesucht. Wenn das Attribut nicht in der "SRO" auftaucht, greift die Klasse auf das Standard-Attribut-Get/Set/Löschen-Verhalten zurück (d.h. "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
Sie können Klassenvariablen auch spontan zu Klassen 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]
Ich persönlich würde eine Klassenmethode verwenden, wenn ich eine statische Methode benötige. Vor allem, weil ich die Klasse als Argument bekomme.
class myObj(object):
def myMethod(cls)
...
myMethod = classmethod(myMethod)
oder einen Dekorateur verwenden
class myObj(object):
@classmethod
def myMethod(cls)
Für statische Eigenschaften Seine Zeit, die 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++
Wozu eine statische Methode im pythonischen Sinne verwenden, wenn sie keinerlei Bezug zur Klasse hat! Wenn ich Sie wäre, würde ich entweder classmethod verwenden oder die Methode unabhängig von der Klasse definieren.
- See previous answers
- Weitere Antworten anzeigen