2569 Stimmen

Statische Klassenvariablen und Methoden in Python

Wie erstelle ich statische Klassenvariablen oder Methoden in Python?

2353voto

Blair Conrad Punkte 217777

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.

771voto

millerdev Punkte 9423

@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

Pictorial Representation Of Above Example

289voto

Rick supports Monica Punkte 38114

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

47voto

Gregory Punkte 1420

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]

29voto

emb Punkte 619

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.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X