Pertanyaan Apakah variabel kelas statis mungkin?


Apakah mungkin untuk memiliki variabel atau metode kelas statis dalam python? Sintaks apa yang diperlukan untuk melakukan ini?


1505
2017-09-16 01:46


asal


Jawaban:


Variabel yang dideklarasikan di dalam definisi kelas, tetapi tidak di dalam suatu metode adalah variabel kelas atau statis:

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

Sebagai @millerdev menunjukkan, ini menciptakan tingkat kelas i variabel, tetapi ini berbeda dari tingkat instance i variabel, jadi Anda bisa

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

Ini berbeda dari C ++ dan Java, tetapi tidak begitu berbeda dari C #, di mana anggota statis tidak dapat diakses menggunakan referensi ke instance.

Lihat apa yang tutorial Python katakan tentang subjek kelas dan objek kelas.

@Steve Johnson telah menjawab tentang metode statis, juga didokumentasikan di bawah "Built-in Functions" di Referensi Perpustakaan Python.

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

@beidy merekomendasikan classmethods atas staticmethod, sebagai metode kemudian menerima jenis kelas sebagai argumen pertama, tapi aku masih agak kabur pada keuntungan dari pendekatan ini atas metode statis. Jika Anda juga, maka itu mungkin tidak masalah.


1479
2017-09-16 01:51



@Blair Conrad mengatakan variabel statis dideklarasikan di dalam definisi kelas, tetapi tidak di dalam suatu metode adalah variabel kelas atau "statis":

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

Ada beberapa gotcha di sini. Teruskan dari contoh di atas:

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

Perhatikan bagaimana variabel instance t.i tidak sinkron dengan variabel kelas "statis" ketika atribut i disetel langsung t. Hal ini karena i kembali terikat dalam t namespace, yang berbeda dari Test ruang nama. Jika Anda ingin mengubah nilai variabel "statis", Anda harus mengubahnya dalam lingkup (atau objek) di mana ia awalnya didefinisikan. Saya menaruh "statis" dalam tanda kutip karena Python tidak benar-benar memiliki variabel statis dalam arti bahwa C ++ dan Java melakukannya.

Meskipun tidak mengatakan sesuatu yang spesifik tentang variabel atau metode statis, Tutorial Python memiliki beberapa informasi yang relevan kelas dan objek kelas.

@ Steve Johnson juga menjawab tentang metode statis, juga didokumentasikan di bawah "Fungsi Built-in" di Referensi Perpustakaan Python.

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

@beid juga menyebutkan classmethod, yang mirip dengan metode statis. Argumen pertama classmethod adalah objek kelas. Contoh:

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



Metode Statis dan Kelas

Seperti yang telah dijawab oleh jawaban lain, metode statis dan kelas mudah dicapai menggunakan dekorator bawaan:

class Test(object):

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

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

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

Seperti biasa, argumen pertama MyMethod() terikat ke objek instance kelas. Sebaliknya, argumen pertama MyClassMethod() aku s terikat ke objek kelas itu sendiri (mis., dalam hal ini, Test). Untuk MyStaticMethod(), tidak ada satupun argumen yang terikat, dan memiliki argumen sama sekali adalah opsional.

"Variabel Statis"

Namun, menerapkan "variabel statis" (baik, yg mungkin berubah variabel statis, bagaimanapun, jika itu bukan kontradiksi dalam hal ...) tidak lurus ke depan. Sebagai millerdev ditunjukkan dalam jawabannya, masalahnya adalah bahwa atribut kelas Python tidak benar-benar "variabel statis". Mempertimbangkan:

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

Ini karena garis x.i = 12 telah menambahkan atribut instan baru i untuk x alih-alih mengubah nilai dari Test kelas i atribut.

Sebagian perilaku variabel statis yang diharapkan, yaitu, menyinkronkan atribut antara beberapa instance (namun tidak dengan kelas itu sendiri; lihat "gotcha" di bawah), dapat dicapai dengan mengubah atribut class menjadi properti:

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)

Sekarang Anda bisa melakukannya:

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

Variabel statis sekarang akan tetap sinkron di antara semua instance kelas.

(CATATAN: Yaitu, kecuali instance kelas memutuskan untuk menentukan versinya sendiri _i! Tetapi jika seseorang memutuskan untuk melakukan itu, mereka berhak mendapatkan apa yang mereka dapatkan, bukankah demikian ???)

Perhatikan bahwa secara teknis, i masih bukan 'variabel statis' sama sekali; ini adalah sebuah property, yang merupakan jenis deskriptor khusus. Namun, itu property perilaku sekarang setara dengan variabel statis (dapat diubah) yang disinkronkan di semua instance kelas.

"Variabel Statis" Tidak Bergerak

Untuk perilaku variabel statis yang tidak berubah, abaikan saja 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)

Sekarang mencoba mengatur instance i atribut akan mengembalikan AttributeError:

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

Satu Gotcha menjadi Sadar

Perhatikan bahwa metode di atas hanya berfungsi contoh kelas Anda - mereka akan melakukannya tidak kerja saat menggunakan kelas itu sendiri. Jadi misalnya:

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'

Garis assert Test.i == x.i menghasilkan kesalahan, karena i atribut dari Test dan x dua benda berbeda.

Banyak orang akan menemukan ini mengejutkan. Namun, seharusnya tidak demikian. Jika kita kembali dan memeriksa kami Test definisi kelas (versi kedua), kami mencatat baris ini:

    i = property(get_i) 

Jelas, anggota i dari Test harus menjadi a property objek, yang merupakan jenis objek yang dikembalikan dari property fungsi.

Jika Anda menemukan kebingungan di atas, kemungkinan besar Anda masih memikirkannya dari perspektif bahasa lain (misalnya Java atau c ++). Anda harus pergi belajar property objek, tentang urutan di mana atribut Python dikembalikan, protokol deskriptor, dan urutan resolusi metode (MRO).

Saya menyajikan solusi untuk 'gotcha' di bawah; namun saya akan menyarankan - dengan keras - bahwa Anda tidak mencoba melakukan sesuatu seperti yang berikut sampai - minimal - Anda benar-benar mengerti mengapa assert Test.i = x.i menyebabkan kesalahan.

NYATA, AKTUAL Variabel Statis - Test.i == x.i

Saya menyajikan solusi (Python 3) di bawah ini untuk tujuan informasi saja. Saya tidak mendukungnya sebagai "solusi yang baik". Saya memiliki keraguan apakah meniru perilaku variabel statis dari bahasa lain dengan Python benar-benar diperlukan. Namun, terlepas dari apakah itu benar-benar berguna, di bawah ini akan membantu pemahaman lebih lanjut tentang cara kerja Python.

UPDATE: upaya ini sungguh sangat mengerikan; jika Anda bersikeras untuk melakukan sesuatu seperti ini (petunjuk: jangan, Python adalah bahasa yang sangat elegan dan sepatu-horning ke berperilaku seperti bahasa lain tidak diperlukan), gunakan kode di Jawaban Ethan Furman sebagai gantinya.

Meniru perilaku variabel statis dari bahasa lain menggunakan metaclass

Metaclass adalah kelas dari sebuah kelas. Metaclass default untuk semua kelas dengan Python (yaitu kelas "gaya baru" posting Python 2.3 Saya percaya) adalah type. Sebagai contoh:

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

Namun, Anda dapat menentukan metaclass Anda sendiri seperti ini:

class MyMeta(type): pass

Dan menerapkannya ke kelas Anda sendiri seperti ini (Python 3 saja):

class MyClass(metaclass = MyMeta):
    pass

type(MyClass)  # class MyMeta

Di bawah ini adalah metaclass yang saya buat yang mencoba untuk meniru perilaku "variabel statis" dari bahasa lain. Ini pada dasarnya bekerja dengan mengganti pengambil default, setter, dan deleter dengan versi yang memeriksa untuk melihat apakah atribut yang diminta adalah "variabel statis".

Katalog "variabel statis" disimpan dalam StaticVarMeta.statics atribut. Semua permintaan atribut pada awalnya dicoba untuk diselesaikan menggunakan perintah resolusi pengganti. Saya telah menjuluki ini "perintah resolusi statis", atau "SRO". Ini dilakukan dengan mencari atribut yang diminta dalam himpunan "variabel statis" untuk kelas tertentu (atau kelas induknya). Jika atribut tidak muncul di "SRO", kelas akan jatuh kembali pada perilaku default atribut get / set / delete (mis., "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



Anda juga dapat menambahkan variabel kelas ke kelas dengan cepat

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

Dan instance kelas dapat mengubah variabel kelas

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



Secara pribadi saya akan menggunakan metode kelas setiap kali saya membutuhkan metode statis. Terutama karena saya mendapatkan kelas sebagai argumen.

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

atau menggunakan dekorator

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

Untuk properti statis .. Saatnya Anda mencari beberapa definisi python .. variabel dapat selalu berubah. Ada dua jenis dari mereka bisa berubah dan berubah .. Juga, ada atribut kelas dan atribut contoh .. Tidak ada yang benar-benar seperti atribut statis dalam arti java & c ++

Mengapa menggunakan metode statis dalam arti pythonic, jika tidak ada hubungan apa pun dengan kelas! Jika saya adalah Anda, saya akan menggunakan classmethod atau mendefinisikan metode independen dari kelas.


12
2017-09-16 02:02



Metode statis dalam python disebut classmethods. Lihatlah kode berikut

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

Perhatikan bahwa ketika kita memanggil metode myInstanceMethod, kami mendapat kesalahan. Ini karena membutuhkan metode yang dipanggil pada sebuah instance dari kelas ini. Metode myStaticMethod disetel sebagai classmethod menggunakan penghias  @classmethod.

Hanya untuk iseng dan cekikikan, kita bisa menelepon myInstanceMethod di kelas dengan meneruskan instance kelas, seperti:

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

11
2017-09-16 02:05



Satu hal khusus untuk diperhatikan tentang properti statis & properti instan, ditunjukkan pada contoh di bawah ini:

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

Ini berarti sebelum menetapkan nilai ke properti instance, jika kita mencoba mengakses properti melalui 'instance, nilai statis digunakan. Setiap properti yang dideklarasikan dalam kelas python selalu memiliki slot statis dalam memori.


10
2018-03-08 06:06



Ketika mendefinisikan beberapa variabel anggota di luar metode anggota, variabel dapat berupa statis atau non-statis tergantung pada bagaimana variabel tersebut diekspresikan.

  • CLASSNAME.var adalah variabel statis
  • INSTANCENAME.var bukan variabel statis.
  • kelas dalam self.var bukanlah variabel statis.
  • var di dalam fungsi anggota kelas tidak didefinisikan.

Sebagai contoh:

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

Hasilnya adalah

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

7
2018-03-26 17:56



Anda juga bisa menerapkan kelas menjadi statis menggunakan metaclass.

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

Lalu kapan pun secara tidak sengaja Anda mencoba menginisialisasi Kelasku Anda akan mendapatkan StaticClassError.


6
2017-11-20 12:06