Vraag Zijn statische klassenvariabelen mogelijk?


Is het mogelijk om statische klassenvariabelen of -methoden in python te gebruiken? Welke syntaxis is vereist om dit te doen?


1505
2017-09-16 01:46


oorsprong


antwoorden:


Variabelen die worden gedeclareerd in de klassedefinitie, maar niet binnen een methode, zijn klasse- of statische variabelen:

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

Als @millerdev wijst erop dat dit een klasseniveau creëert i variabele, maar dit is verschillend van elk instantieniveau i variabel, dus je zou kunnen hebben

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

Dit is anders dan C ++ en Java, maar niet zo verschillend van C #, waar een statisch lid niet kan worden benaderd met behulp van een verwijzing naar een exemplaar.

Zien wat de Python-tutorial te zeggen heeft over het onderwerp classes en class objects.

@Steve Johnson heeft al geantwoord met betrekking tot statische methoden, ook gedocumenteerd onder "Ingebouwde functies" in de Python Library Reference.

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

@beidy beveelt aan classmethods over static method, omdat de methode dan het klassetype als het eerste argument ontvangt, maar ik ben nog steeds een beetje wazig over de voordelen van deze benadering ten opzichte van statische methode. Als je dat ook bent, maakt het waarschijnlijk niet uit.


1479
2017-09-16 01:51



@Blair Conrad zei statische variabelen die zijn gedeclareerd in de klassendefinitie, maar niet in een methode zijn klasse- of "statische" variabelen:

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

Er zijn een paar gotcha's hier. Uitgaand van het bovenstaande voorbeeld:

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

Let op hoe de instantievariabele t.i is niet gesynchroniseerd met de "static" -klasvariabele wanneer het attribuut i was direct ingeschakeld t. Dit is zo omdat i werd opnieuw gebonden binnen de t naamruimte, die verschilt van de Test namespace. Als u de waarde van een "statische" variabele wilt wijzigen, moet u deze binnen het bereik (of object) wijzigen waar het oorspronkelijk was gedefinieerd. Ik zet "statisch" tussen aanhalingstekens omdat Python niet echt statische variabelen heeft in die zin dat C ++ en Java dat doen.

Hoewel het niets specifieks over statische variabelen of methoden zegt, is het Python-zelfstudie heeft wat relevante informatie over klassen en klasse-objecten.

@Steve Johnson antwoordde ook met betrekking tot statische methoden, ook gedocumenteerd onder "Ingebouwde functies" in de Python Library Reference.

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

@beid noemde ook class method, die vergelijkbaar is met statische methode. Het eerste argument van een class method is het klasseobject. Voorbeeld:

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

Zoals de andere antwoorden hebben opgemerkt, kunnen statische en klassemethoden gemakkelijk worden uitgevoerd met behulp van de ingebouwde decorateurs:

class Test(object):

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

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

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

Zoals gewoonlijk is het eerste argument om MyMethod() is gebonden aan het klasse-instantieobject. In tegenstelling, het eerste argument aan MyClassMethod() is gebonden aan het klasseobject zelf (bijvoorbeeld in dit geval Test). Voor MyStaticMethod(), geen van de argumenten is gebonden en argumenten hebben is optioneel.

"Statische variabelen"

Het implementeren van "statische variabelen" (wel, veranderlijk statische variabelen, hoe dan ook, als dat geen contradictio in terminis is ...) is niet zo eenvoudig. Als millerdev wees in zijn antwoord, het probleem is dat de klassenattributen van Python niet echt "statische variabelen" zijn. Overwegen:

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

Dit komt omdat de lijn x.i = 12 heeft een nieuw instantie-kenmerk toegevoegd i naar x in plaats van de waarde van de Test klasse i attribuut.

partieel verwachte gedrag van statische variabelen, d.w.z. synchronisatie van het attribuut tussen meerdere instances (maar niet met de klas zelf; zie "gotcha" hieronder), kan worden bereikt door het class-kenmerk in een eigenschap te veranderen:

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)

Nu kunt u doen:

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

De statische variabele blijft nu synchroon tussen alle klasseninstanties.

(OPMERKING: dat wil zeggen, tenzij een klasseninstantie besluit zijn eigen versie van te definiëren _i! Maar als iemand besluit dat te doen, verdienen ze wat ze krijgen, nietwaar ???)

Merk op dat technisch gezien i is nog steeds helemaal geen 'statische variabele'; het is een property, wat een speciaal type descriptor is. echter, de property gedrag is nu gelijk aan een (veranderlijke) statische variabele gesynchroniseerd over alle klasse-instanties.

Onveranderlijke "Statische Variabelen"

Voor onveranderbaar statisch variabel gedrag, laat eenvoudigweg de 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)

Nu probeert de instantie in te stellen i attribuut retourneert een AttributeError:

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

Eén Gotcha om op te letten

Merk op dat de bovenstaande methoden alleen werken met instanties van je klas - dat zullen ze niet werk bij het gebruik van de klasse zelf. Dus bijvoorbeeld:

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'

De lijn assert Test.i == x.i veroorzaakt een fout, omdat de i kenmerk van Test en x zijn twee verschillende objecten.

Veel mensen zullen dit verrassend vinden. Het zou echter niet moeten zijn. Als we teruggaan en onze inspecteren Test klassendefinitie (de tweede versie), nemen we kennis van deze regel:

    i = property(get_i) 

Het is duidelijk dat het lid i van Test moet een zijn property object, dat is het type object dat wordt geretourneerd door de property functie.

Als u het bovenstaande verwarrend vindt, denkt u er waarschijnlijk nog steeds aan vanuit het perspectief van andere talen (bijvoorbeeld Java of C ++). Je moet gaan studeren property object, over de volgorde waarin de Python-kenmerken worden geretourneerd, het descriptorprotocol en de methodeomrekeningsorder (MRO).

Ik presenteer een oplossing voor de bovenstaande 'gotcha' hieronder; nochtans zou ik - streng voorstellen - dat u om iets als het volgende niet probeert te doen tot - minstens - u grondig waarom begrijpt assert Test.i = x.i veroorzaakt een fout.

ECHT, DAADWERKELIJK Statische variabelen - Test.i == x.i

Ik presenteer de onderstaande (Python 3) -oplossing alleen voor informatieve doeleinden. Ik onderschrijf het niet als een "goede oplossing". Ik heb mijn twijfels of het ooit nodig is om het statische variabele gedrag van andere talen in Python te emuleren. Ongeacht of het echt nuttig is, moet hieronder echter worden uitgelegd hoe Python werkt.

UPDATE: deze poging is echt behoorlijk afschuwelijk; als je erop staat om zoiets te doen (hint: doe alsjeblieft niet, Python is een erg elegante taal en hoef je je niet te gedragen als een andere taal, het is gewoon niet nodig), gebruik de code in Het antwoord van Ethan Furman in plaats daarvan.

Het statisch variabele gedrag van andere talen emuleren met behulp van een metaclass

Een metaclass is de klasse van een klasse. De standaard metacursus voor alle klassen in Python (d.w.z. de klassen "nieuwe stijl" na Python 2.3 geloof ik) is type. Bijvoorbeeld:

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

U kunt echter uw eigen metaclass als volgt definiëren:

class MyMeta(type): pass

En pas het op deze manier toe op je eigen klasse (alleen Python 3):

class MyClass(metaclass = MyMeta):
    pass

type(MyClass)  # class MyMeta

Hieronder is een metaclass die ik heb gemaakt en die probeert om het "statische variabele" gedrag van andere talen te emuleren. Het werkt in principe door de standaard getter, setter en deleter te vervangen door versies die controleren of het aangevraagde kenmerk een "statische variabele" is.

Een catalogus van de "statische variabelen" wordt opgeslagen in de StaticVarMeta.statics attribuut. Alle attribuutverzoeken worden aanvankelijk geprobeerd te worden opgelost met behulp van een vervangende resolutievolgorde. Ik heb dit de "statische resolutie-volgorde" of "SRO" genoemd. Dit wordt gedaan door te zoeken naar het aangevraagde kenmerk in de reeks "statische variabelen" voor een bepaalde klasse (of de bovenliggende klassen). Als het kenmerk niet wordt weergegeven in de "SRO", zal de klasse terugvallen op het standaardkenmerk get / set / delete (d.w.z. "MRO").

from functools import wraps

class StaticVarsMeta(type):
    '''A metaclass for creating classes that emulate the "static variable" behavior
    of other languages. I do not advise actually using this for anything!!!

    Behavior is intended to be similar to classes that use __slots__. However, "normal"
    attributes and __statics___ can coexist (unlike with __slots__). 

    Example usage: 

        class MyBaseClass(metaclass = StaticVarsMeta):
            __statics__ = {'a','b','c'}
            i = 0  # regular attribute
            a = 1  # static var defined (optional)

        class MyParentClass(MyBaseClass):
            __statics__ = {'d','e','f'}
            j = 2              # regular attribute
            d, e, f = 3, 4, 5  # Static vars
            a, b, c = 6, 7, 8  # Static vars (inherited from MyBaseClass, defined/re-defined here)

        class MyChildClass(MyParentClass):
            __statics__ = {'a','b','c'}
            j = 2  # regular attribute (redefines j from MyParentClass)
            d, e, f = 9, 10, 11   # Static vars (inherited from MyParentClass, redefined here)
            a, b, c = 12, 13, 14  # Static vars (overriding previous definition in MyParentClass here)'''
    statics = {}
    def __new__(mcls, name, bases, namespace):
        # Get the class object
        cls = super().__new__(mcls, name, bases, namespace)
        # Establish the "statics resolution order"
        cls.__sro__ = tuple(c for c in cls.__mro__ if isinstance(c,mcls))

        # Replace class getter, setter, and deleter for instance attributes
        cls.__getattribute__ = StaticVarsMeta.__inst_getattribute__(cls, cls.__getattribute__)
        cls.__setattr__ = StaticVarsMeta.__inst_setattr__(cls, cls.__setattr__)
        cls.__delattr__ = StaticVarsMeta.__inst_delattr__(cls, cls.__delattr__)
        # Store the list of static variables for the class object
        # This list is permanent and cannot be changed, similar to __slots__
        try:
            mcls.statics[cls] = getattr(cls,'__statics__')
        except AttributeError:
            mcls.statics[cls] = namespace['__statics__'] = set() # No static vars provided
        # Check and make sure the statics var names are strings
        if any(not isinstance(static,str) for static in mcls.statics[cls]):
            typ = dict(zip((not isinstance(static,str) for static in mcls.statics[cls]), map(type,mcls.statics[cls])))[True].__name__
            raise TypeError('__statics__ items must be strings, not {0}'.format(typ))
        # Move any previously existing, not overridden statics to the static var parent class(es)
        if len(cls.__sro__) > 1:
            for attr,value in namespace.items():
                if attr not in StaticVarsMeta.statics[cls] and attr != ['__statics__']:
                    for c in cls.__sro__[1:]:
                        if attr in StaticVarsMeta.statics[c]:
                            setattr(c,attr,value)
                            delattr(cls,attr)
        return cls
    def __inst_getattribute__(self, orig_getattribute):
        '''Replaces the class __getattribute__'''
        @wraps(orig_getattribute)
        def wrapper(self, attr):
            if StaticVarsMeta.is_static(type(self),attr):
                return StaticVarsMeta.__getstatic__(type(self),attr)
            else:
                return orig_getattribute(self, attr)
        return wrapper
    def __inst_setattr__(self, orig_setattribute):
        '''Replaces the class __setattr__'''
        @wraps(orig_setattribute)
        def wrapper(self, attr, value):
            if StaticVarsMeta.is_static(type(self),attr):
                StaticVarsMeta.__setstatic__(type(self),attr, value)
            else:
                orig_setattribute(self, attr, value)
        return wrapper
    def __inst_delattr__(self, orig_delattribute):
        '''Replaces the class __delattr__'''
        @wraps(orig_delattribute)
        def wrapper(self, attr):
            if StaticVarsMeta.is_static(type(self),attr):
                StaticVarsMeta.__delstatic__(type(self),attr)
            else:
                orig_delattribute(self, attr)
        return wrapper
    def __getstatic__(cls,attr):
        '''Static variable getter'''
        for c in cls.__sro__:
            if attr in StaticVarsMeta.statics[c]:
                try:
                    return getattr(c,attr)
                except AttributeError:
                    pass
        raise AttributeError(cls.__name__ + " object has no attribute '{0}'".format(attr))
    def __setstatic__(cls,attr,value):
        '''Static variable setter'''
        for c in cls.__sro__:
            if attr in StaticVarsMeta.statics[c]:
                setattr(c,attr,value)
                break
    def __delstatic__(cls,attr):
        '''Static variable deleter'''
        for c in cls.__sro__:
            if attr in StaticVarsMeta.statics[c]:
                try:
                    delattr(c,attr)
                    break
                except AttributeError:
                    pass
        raise AttributeError(cls.__name__ + " object has no attribute '{0}'".format(attr))
    def __delattr__(cls,attr):
        '''Prevent __sro__ attribute from deletion'''
        if attr == '__sro__':
            raise AttributeError('readonly attribute')
        super().__delattr__(attr)
    def is_static(cls,attr):
        '''Returns True if an attribute is a static variable of any class in the __sro__'''
        if any(attr in StaticVarsMeta.statics[c] for c in cls.__sro__):
            return True
        return False

141
2017-12-19 15:16



U kunt ook klassenvariabelen toevoegen aan klassen tijdens de vlucht

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

En klasseninstanties kunnen klassenvariabelen wijzigen

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



Persoonlijk zou ik een klassemethode gebruiken wanneer ik een statische methode nodig had. Vooral omdat ik de klas krijg als argument.

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

of gebruik een decorateur

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

Voor statische eigenschappen .. Het is tijd dat je een python-definitie opzoekt. Variabele kan altijd veranderen. Er zijn twee soorten mutable en onveranderlijke. Ook zijn er klassekenmerken en instantiekarakteristieken. Niets houdt echt van statische attributen in de zin van java & c ++

Waarom zou je de statische methode in pythonische zin gebruiken, als het geen enkele relatie heeft met de klas! Als ik jou was, zou ik classmethod gebruiken of de methode onafhankelijk van de klas definiëren.


12
2017-09-16 02:02



Statische methoden in python worden genoemd classmethods. Bekijk de volgende code

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

Merk op dat wanneer we de methode noemen myInstanceMethod, we krijgen een foutmelding. Dit komt omdat het vereist dat die methode wordt aangeroepen op een instantie van deze klasse. De methode myStaticMethod is ingesteld als een klassenmethode met behulp van de decorateur  @classmethod.

Alleen voor schoppen en gegiechel, konden we bellen myInstanceMethod op de klas door in een instantie van de klas door te geven, zoals zo:

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

11
2017-09-16 02:05



Een bijzonder ding om op te merken over statische eigenschappen & instantie-eigenschappen, getoond in het onderstaande voorbeeld:

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

Dit betekent voordat u de waarde toewijst aan de instantie-eigenschap, als we proberen de eigenschap te openen via 'instantie', wordt de statische waarde gebruikt. Elke eigenschap gedeclareerd in de python-klasse heeft altijd een statische sleuf in het geheugen.


10
2018-03-08 06:06