Metode perbedaan kelas dalam Python: terikat, tidak terikat dan statis

242

Apa perbedaan antara metode kelas berikut?

Apakah yang satu itu statis dan yang lain tidak?

class Test(object):
  def method_one(self):
    print "Called method_one"

  def method_two():
    print "Called method_two"

a_test = Test()
a_test.method_one()
a_test.method_two()
Franck Mesirard
sumber
18
Tidak ada perbedaan selain dari method_two () definisi tidak valid dan panggilannya gagal.
anatoly techtonik
14
@techtonik: Tidak ada yang salah dengan definisi dari method_two! Itu disebut dalam spec yang salah / tidak valid, yaitu dengan argumen tambahan.
0xc0de
1
Keduanya adalah metode instan , bukan metode kelas. Anda membuat metode kelas dengan menerapkan @classmethoddefinisi. Parameter pertama harus dipanggil clsalih-alih selfdan akan menerima objek kelas daripada instance kelas Anda: Test.method_three()dan a_test.method_three()setara.
Lutz Prechelt
Mengapa Anda ingin membuat definisi fungsi tanpa selfargumen? Apakah ada kasus penggunaan yang kuat untuk ini?
alpha_989

Jawaban:

412

Dalam Python, ada perbedaan antara terikat dan terikat metode.

Pada dasarnya, panggilan ke fungsi anggota (seperti method_one), fungsi terikat

a_test.method_one()

diterjemahkan ke

Test.method_one(a_test)

yaitu panggilan ke metode tidak terikat. Karena itu, panggilan ke versi Anda method_twoakan gagal dengan aTypeError

>>> a_test = Test() 
>>> a_test.method_two()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: method_two() takes no arguments (1 given) 

Anda dapat mengubah perilaku metode menggunakan dekorator

class Test(object):
    def method_one(self):
        print "Called method_one"

    @staticmethod
    def method_two():
        print "Called method two"

Dekorator memberi tahu metaclass bawaan bawaan type(kelas kelas, lih. Pertanyaan ini ) untuk tidak membuat metode terikat method_two.

Sekarang, Anda dapat memanggil metode statis baik secara instan atau di kelas secara langsung:

>>> a_test = Test()
>>> a_test.method_one()
Called method_one
>>> a_test.method_two()
Called method_two
>>> Test.method_two()
Called method_two
Torsten Marek
sumber
17
Saya mendukung jawaban ini, lebih unggul dari saya. Torsten yang bagus :)
freespace
24
di python 3 metode tidak terikat sudah usang. alih-alih hanya ada fungsi.
boldnik
@ Boldnik, mengapa Anda mengatakan metode tidak terikat sudah usang? metode statis masih ada dalam dokumentasi: docs.python.org/3/library/functions.html#staticmethod
alpha_989
195

Metode dengan Python adalah hal yang sangat, sangat sederhana setelah Anda memahami dasar-dasar sistem deskriptor. Bayangkan kelas berikut:

class C(object):
    def foo(self):
        pass

Sekarang mari kita lihat kelas itu di shell:

>>> C.foo
<unbound method C.foo>
>>> C.__dict__['foo']
<function foo at 0x17d05b0>

Seperti yang Anda lihat jika Anda mengakses fooatribut di kelas Anda mendapatkan kembali metode yang tidak terikat, namun di dalam penyimpanan kelas (dict) ada fungsi. Kenapa begitu? Alasan untuk ini adalah bahwa kelas kelas Anda mengimplementasikan a __getattribute__yang menyelesaikan deskriptor. Kedengarannya rumit, tetapi tidak. C.fookira-kira setara dengan kode ini dalam kasus khusus:

>>> C.__dict__['foo'].__get__(None, C)
<unbound method C.foo>

Itu karena fungsi memiliki __get__metode yang membuat mereka deskriptor. Jika Anda memiliki instance kelas hampir sama, hanya itu Noneadalah instance kelas:

>>> c = C()
>>> C.__dict__['foo'].__get__(c, C)
<bound method C.foo of <__main__.C object at 0x17bd4d0>>

Sekarang mengapa Python melakukan itu? Karena objek metode mengikat parameter pertama dari suatu fungsi ke turunan kelas. Dari situlah datangnya diri. Sekarang kadang-kadang Anda tidak ingin kelas Anda membuat fungsi sebagai metode, di situlah staticmethodikut bermain:

 class C(object):
  @staticmethod
  def foo():
   pass

The staticmethoddekorator membungkus kelas Anda dan alat dummy __get__yang mengembalikan fungsi dibungkus sebagai fungsi dan bukan sebagai metode:

>>> C.__dict__['foo'].__get__(None, C)
<function foo at 0x17d0c30>

Harapan itu menjelaskannya.

Armin Ronacher
sumber
12
The staticmethoddekorator membungkus kelas Anda (...) Frasa ini adalah sedikit menyesatkan sebagai kelas yang sedang dibungkus adalah kelas dari metode foodan bukan kelas di mana foodidefinisikan.
Piotr Dobrogost
12

Saat Anda memanggil anggota kelas, Python secara otomatis menggunakan referensi ke objek sebagai parameter pertama. Variabel selfsebenarnya tidak berarti apa-apa, itu hanya konvensi pengkodean. Anda bisa menyebutnya gargaloojika Anda mau. Yang mengatakan, panggilan untuk method_twoakan menaikkan TypeError, karena Python secara otomatis mencoba untuk melewatkan parameter (referensi ke objek induknya) ke metode yang didefinisikan sebagai tidak memiliki parameter.

Untuk benar-benar membuatnya berfungsi, Anda bisa menambahkan ini ke definisi kelas Anda:

method_two = staticmethod(method_two)

atau Anda bisa menggunakan @staticmethod fungsi dekorator .

Justin Poliey
sumber
4
Maksud Anda "sintaks penghias fungsi fungsi @staticmethod".
tzot
11
>>> class Class(object):
...     def __init__(self):
...         self.i = 0
...     def instance_method(self):
...         self.i += 1
...         print self.i
...     c = 0
...     @classmethod
...     def class_method(cls):
...         cls.c += 1
...         print cls.c
...     @staticmethod
...     def static_method(s):
...         s += 1
...         print s
... 
>>> a = Class()
>>> a.class_method()
1
>>> Class.class_method()    # The class shares this value across instances
2
>>> a.instance_method()
1
>>> Class.instance_method() # The class cannot use an instance method
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unbound method instance_method() must be called with Class instance as first argument (got nothing instead)
>>> Class.instance_method(a)
2
>>> b = 0
>>> a.static_method(b)
1
>>> a.static_method(a.c) # Static method does not have direct access to 
>>>                      # class or instance properties.
3
>>> Class.c        # a.c above was passed by value and not by reference.
2
>>> a.c
2
>>> a.c = 5        # The connection between the instance
>>> Class.c        # and its class is weak as seen here.
2
>>> Class.class_method()
3
>>> a.c
5
kzh
sumber
2
Class.instance_method() # The class cannot use an instance methoditu bisa digunakan. Cukup berikan contoh secara manual:Class.instance_method(a)
warvariuc
@warwaruk Ada di sana, lihat garis di bawah TyeErrorgaris.
kzh
ya saya melihatnya nanti. masih, imo, itu tidak benar untuk mengatakan 'Kelas tidak dapat menggunakan metode contoh', karena Anda baru saja melakukannya satu baris di bawah ini.
warvariuc
@ kzh, Terima kasih atas penjelasan Anda. Ketika Anda menelepon a.class_method(), tampaknya a.csudah diperbarui 1, jadi panggilan Class.class_method(), memperbarui Class.cvariabel ke 2. Namun, ketika Anda ditugaskan a.c=5, mengapa Class.ctidak diperbarui 5?
alpha_989
@ alpha_989 python terlebih dahulu mencari atribut langsung pada ovjects instance sendiri terlebih dahulu dan jika tidak ada di sana, ia mencarinya di kelasnya secara default. Jika Anda memiliki pertanyaan lain tentang ini, silakan membuka pertanyaan dan menautkannya di sini dan saya akan dengan senang hati membantu lebih lanjut.
kzh
4

method_two tidak akan berfungsi karena Anda mendefinisikan fungsi anggota tetapi tidak memberi tahu apa fungsi anggota tersebut. Jika Anda mengeksekusi baris terakhir Anda akan mendapatkan:

>>> a_test.method_two()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: method_two() takes no arguments (1 given)

Jika Anda mendefinisikan fungsi anggota untuk suatu kelas, argumen pertama harus selalu 'mandiri'.

Jon Cage
sumber
3

Penjelasan yang akurat dari Armin Ronacher di atas, memperluas jawabannya sehingga pemula seperti saya memahaminya dengan baik:

Perbedaan dalam metode yang didefinisikan dalam suatu kelas, apakah metode statis atau contoh (masih ada jenis lain - metode kelas - tidak dibahas di sini sehingga melewatkannya), terletak pada fakta apakah mereka entah bagaimana terikat pada instance kelas atau tidak. Sebagai contoh, katakan apakah metode menerima referensi ke instance kelas selama runtime

class C:
    a = [] 
    def foo(self):
        pass

C # this is the class object
C.a # is a list object (class property object)
C.foo # is a function object (class property object)
c = C() 
c # this is the class instance

The __dict__properti kamus dari objek kelas memegang referensi untuk semua properti dan metode dari objek kelas dan dengan demikian

>>> C.__dict__['foo']
<function foo at 0x17d05b0>

metode foo dapat diakses seperti di atas. Poin penting yang perlu diperhatikan di sini adalah bahwa segala sesuatu di python adalah objek dan referensi dalam kamus di atas sendiri menunjuk ke objek lain. Izinkan saya menyebutnya Objek Properti Kelas - atau sebagai CPO dalam lingkup jawaban saya untuk singkatnya.

Jika CPO adalah descriptor, maka python interpretor memanggil __get__()metode CPO untuk mengakses nilai yang dikandungnya.

Untuk menentukan apakah CPO adalah deskriptor, penafsir python memeriksa jika mengimplementasikan protokol deskriptor. Untuk mengimplementasikan protokol deskriptor adalah dengan mengimplementasikan 3 metode

def __get__(self, instance, owner)
def __set__(self, instance, value)
def __delete__(self, instance)

untuk mis

>>> C.__dict__['foo'].__get__(c, C)

dimana

  • self adalah CPO (bisa berupa instance dari daftar, str, fungsi dll) dan disediakan oleh runtime
  • instance adalah turunan dari kelas di mana CPO ini didefinisikan (objek 'c' di atas) dan perlu kejelasan yang disediakan oleh kami
  • owneradalah kelas di mana CPO ini didefinisikan (objek kelas 'C' di atas) dan perlu disediakan oleh kami. Namun ini karena kami menyebutnya di CPO. ketika kita menyebutnya sebagai instance, kita tidak perlu menyediakan ini karena runtime dapat menyediakan instance atau kelasnya (polimorfisme)
  • value adalah nilai yang dimaksudkan untuk CPO dan perlu dipasok oleh kami

Tidak semua CPO adalah deskriptor. Sebagai contoh

>>> C.__dict__['foo'].__get__(None, C)
<function C.foo at 0x10a72f510> 
>>> C.__dict__['a'].__get__(None, C)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'list' object has no attribute '__get__'

Ini karena kelas daftar tidak mengimplementasikan protokol deskriptor.

Jadi argumen sendiri c.foo(self)diperlukan karena tanda tangan metodenya sebenarnya ini C.__dict__['foo'].__get__(c, C)(seperti yang dijelaskan di atas, C tidak diperlukan karena dapat ditemukan atau polymorphed) Dan ini juga mengapa Anda mendapatkan TypeError jika Anda tidak memberikan argumen contoh yang diperlukan.

Jika Anda perhatikan metode ini masih direferensikan melalui kelas Object C dan pengikatan dengan instance kelas dicapai melalui melewati konteks dalam bentuk objek instance ke dalam fungsi ini.

Ini cukup luar biasa karena jika Anda memilih untuk tidak menyimpan konteks atau tidak mengikat pada instance, yang diperlukan hanyalah menulis kelas untuk membungkus deskriptor CPO dan mengganti __get__()metodenya sehingga tidak memerlukan konteks. Kelas baru ini adalah apa yang kita sebut dekorator dan diterapkan melalui kata kunci@staticmethod

class C(object):
  @staticmethod
  def foo():
   pass

Tidak adanya konteks dalam CPO yang dibungkus baru footidak menimbulkan kesalahan dan dapat diverifikasi sebagai berikut:

>>> C.__dict__['foo'].__get__(None, C)
<function foo at 0x17d0c30>

Use case dari metode statis lebih merupakan penamaan namespace dan kode rawat (mengeluarkannya dari kelas dan membuatnya tersedia di seluruh modul dll).

Mungkin lebih baik untuk menulis metode statis daripada metode contoh bila memungkinkan, kecuali tentu saja Anda perlu membuat kontekstual metode (seperti variabel akses contoh, variabel kelas dll). Salah satu alasannya adalah untuk memudahkan pengumpulan sampah dengan tidak menyimpan referensi yang tidak diinginkan ke objek.

supi
sumber
1

itu adalah kesalahan.

pertama-tama, baris pertama harus seperti ini (hati-hati dengan modal)

class Test(object):

Setiap kali Anda memanggil metode kelas, itu mendapatkan dirinya sebagai argumen pertama (maka nama diri) dan method_two memberikan kesalahan ini

>>> a.method_two()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: method_two() takes no arguments (1 given)
hayalci
sumber
1

Yang kedua tidak akan berfungsi karena ketika Anda memanggilnya seperti itu python secara internal mencoba menyebutnya dengan contoh a_test sebagai argumen pertama, tetapi method_two Anda tidak menerima argumen apa pun, jadi itu tidak akan berfungsi, Anda akan mendapatkan runtime kesalahan. Jika Anda ingin yang setara dengan metode statis, Anda dapat menggunakan metode kelas. Ada jauh lebih sedikit kebutuhan untuk metode kelas dalam Python daripada metode statis dalam bahasa seperti Java atau C #. Paling sering solusi terbaik adalah dengan menggunakan metode dalam modul, di luar definisi kelas, mereka bekerja lebih efisien daripada metode kelas.

Vasil
sumber
Jika saya mendefinisikan fungsi Class Test(object): @staticmethod def method_two(): print(“called method_two”) Satu kasus penggunaan, yang saya pikirkan adalah ketika Anda ingin fungsi menjadi bagian dari kelas, tetapi tidak ingin pengguna mengakses fungsi secara langsung. Jadi method_twobisa dipanggil oleh fungsi lain di dalam Testinstance, tetapi tidak bisa disebut menggunakan a_test.method_two(). Jika saya menggunakan def method_two(), apakah ini akan berfungsi untuk kasus penggunaan ini? Atau adakah cara yang lebih baik untuk memodifikasi definisi fungsi, sehingga berfungsi seperti yang dimaksudkan untuk use case di atas?
alpha_989
1

Panggilan ke method_two akan mengeluarkan pengecualian untuk tidak menerima parameter sendiri runtime Python akan secara otomatis melewatinya.

Jika Anda ingin membuat metode statis di kelas Python, hiasi dengan staticmethod decorator.

Class Test(Object):
  @staticmethod
  def method_two():
    print "Called method_two"

Test.method_two()
MvdD
sumber
1

Silakan baca dokumen ini dari Guido First Class, semuanya dengan jelas menjelaskan bagaimana metode Unbound, Bound dilahirkan.

James Sapam
sumber
0

Definisi method_twotidak valid. Saat Anda menelepon method_two, Anda akan dapatkan TypeError: method_two() takes 0 positional arguments but 1 was givendari penerjemah.

Metode instance adalah fungsi yang dibatasi ketika Anda menyebutnya suka a_test.method_two(). Secara otomatis menerima self, yang menunjuk ke instance Test, sebagai parameter pertama. Melalui selfparameter, metode instance dapat dengan bebas mengakses atribut dan memodifikasinya pada objek yang sama.

Yossarian42
sumber
0

Metode Tidak Terbatas

Metode tidak terikat adalah metode yang belum terikat ke instance kelas tertentu.

Metode Terikat

Metode Bound adalah metode yang terikat pada instance kelas tertentu.

Seperti yang didokumentasikan di sini , diri dapat merujuk pada hal-hal yang berbeda tergantung pada fungsi yang terikat, tidak terikat atau statis.

Lihatlah contoh berikut:

class MyClass:    
    def some_method(self):
        return self  # For the sake of the example

>>> MyClass().some_method()
<__main__.MyClass object at 0x10e8e43a0># This can also be written as:>>> obj = MyClass()

>>> obj.some_method()
<__main__.MyClass object at 0x10ea12bb0>

# Bound method call:
>>> obj.some_method(10)
TypeError: some_method() takes 1 positional argument but 2 were given

# WHY IT DIDN'T WORK?
# obj.some_method(10) bound call translated as
# MyClass.some_method(obj, 10) unbound method and it takes 2 
# arguments now instead of 1 

# ----- USING THE UNBOUND METHOD ------
>>> MyClass.some_method(10)
10

Karena kami tidak menggunakan instance kelas - obj - pada panggilan terakhir, kita dapat mengatakan itu terlihat seperti metode statis.

Jika demikian, apa perbedaan antara MyClass.some_method(10)panggilan dan panggilan ke fungsi statis yang dihiasi dengan a@staticmethod dekorator?

Dengan menggunakan dekorator, kami secara eksplisit memperjelas bahwa metode tersebut akan digunakan tanpa membuat instance terlebih dahulu. Biasanya seseorang tidak akan mengharapkan metode anggota kelas untuk digunakan tanpa instance dan mengaksesnya dapat menyebabkan kesalahan yang mungkin tergantung pada struktur metode.

Juga, dengan menambahkan @staticmethoddekorator, kami memungkinkan untuk dijangkau melalui objek juga.

class MyClass:    
    def some_method(self):
        return self    

    @staticmethod
    def some_static_method(number):
        return number

>>> MyClass.some_static_method(10)   # without an instance
10
>>> MyClass().some_static_method(10)   # Calling through an instance
10

Anda tidak dapat melakukan contoh di atas dengan metode instance. Anda mungkin selamat dari yang pertama (seperti yang kami lakukan sebelumnya) tetapi yang kedua akan diterjemahkan ke dalam panggilan tidak terikat MyClass.some_method(obj, 10)yang akan memunculkan TypeErrorkarena metode instance mengambil satu argumen dan Anda tidak sengaja mencoba untuk melewati dua.

Kemudian, Anda mungkin berkata, "jika saya bisa memanggil metode statis baik melalui instance dan kelas, MyClass.some_static_methoddan MyClass().some_static_methodharus metode yang sama." Iya!

alexa
sumber
0

Metode terikat = metode instance

Metode tidak terikat = metode statis.

Pemula Python
sumber
Silakan tambahkan beberapa penjelasan lebih lanjut untuk jawaban Anda sehingga orang lain dapat belajar darinya. Sebagai contoh, lihat jawaban lain untuk pertanyaan ini
Nico Haase