Apa tujuan dari metode kelas?


250

Saya mengajar diri saya sendiri Python dan pelajaran saya yang terbaru adalah bahwa Python bukan Java , jadi saya baru saja menghabiskan waktu mengubah semua metode Kelas saya menjadi fungsi.

Saya sekarang menyadari bahwa saya tidak perlu menggunakan metode Kelas untuk apa yang akan saya lakukan dengan staticmetode di Jawa, tapi sekarang saya tidak yakin kapan saya akan menggunakannya. Semua saran yang dapat saya temukan tentang metode Kelas Python adalah sepanjang jalur pemula seperti saya harus menghindari mereka, dan dokumentasi standar paling buram ketika membahasnya.

Adakah yang punya contoh yang baik menggunakan metode Kelas dalam Python atau setidaknya seseorang dapat memberi tahu saya ketika metode Kelas dapat digunakan secara masuk akal?

Jawaban:


178

Metode kelas adalah ketika Anda perlu memiliki metode yang tidak spesifik untuk contoh tertentu, tetapi masih melibatkan kelas dalam beberapa cara. Hal yang paling menarik tentang mereka adalah bahwa mereka dapat ditimpa oleh subclass, sesuatu yang sama sekali tidak mungkin dalam metode statis Java atau fungsi tingkat modul Python.

Jika Anda memiliki kelas MyClass, dan fungsi tingkat modul yang beroperasi pada MyClass (pabrik, rintisan injeksi ketergantungan, dll), buatlah a classmethod. Maka itu akan tersedia untuk subclass.


Saya melihat. Tetapi, bagaimana jika saya hanya ingin mengkategorikan suatu metode, yang tidak memerlukan MyClass, tetapi masih dalam beberapa hal masuk akal jika saya mengelompokkan dalam MyClass lainnya? Saya bisa memikirkan untuk memindahkannya ke modul pembantu ..
swdev

Saya punya pertanyaan terkait dengan yang asli, mungkin Anda bisa menjawabnya dalam waktu bersamaan? Cara memanggil metode kelas suatu objek adalah "lebih baik", atau "lebih idiomatis": obj.cls_mthd(...)atau type(obj).cls_mthd(...)?
Alexey

63

Metode pabrik (konstruktor alternatif) memang merupakan contoh klasik dari metode kelas.

Pada dasarnya, metode kelas cocok kapan saja Anda ingin memiliki metode yang secara alami cocok dengan namespace kelas, tetapi tidak terkait dengan instance kelas tertentu.

Sebagai contoh, dalam modul unipath yang sangat baik :

Direktori saat ini

  • Path.cwd()
    • Kembalikan direktori aktual saat ini; misalnya Path("/tmp/my_temp_dir"),. Ini adalah metode kelas.
  • .chdir()
    • Jadikan diri direktori saat ini.

Karena direktori saat ini adalah proses yang luas, cwdmetode ini tidak memiliki instance khusus yang harus dikaitkan. Namun, mengubah cwdke direktori Pathinstance yang diberikan memang harus menjadi metode instance.

Hmmm ... karena Path.cwd()memang mengembalikan Pathcontoh, saya kira itu bisa dianggap sebagai metode pabrik ...


1
Ya ... metode pabrik adalah hal pertama yang terlintas dalam pikiran. Selain itu, ada hal-hal yang akan menerapkan pola Singleton, seperti: getAndOptionallyCreateSomeSingleInstanceOfSomeResource ()
Jemenake

3
Dalam python, itu adalah metode statis, bukan metode kelas.
Jaykul

46

Pikirkan seperti ini: metode normal berguna untuk menyembunyikan detail pengiriman: Anda bisa mengetik myobj.foo()tanpa khawatir tentang apakah foo()metode ini diterapkan oleh kelas myobjobjek atau salah satu kelas induknya. Metode kelas persis analog dengan ini, tetapi dengan objek kelas sebagai gantinya: mereka membiarkan Anda menelepon MyClass.foo()tanpa harus khawatir apakah foo()diimplementasikan secara khusus oleh MyClasskarena diperlukan versi khusus sendiri, atau apakah itu membiarkan kelas induknya menangani panggilan.

Metode kelas sangat penting ketika Anda melakukan pengaturan atau perhitungan yang mendahului pembuatan instance aktual, karena sampai instance ada Anda jelas tidak dapat menggunakan instance sebagai titik pengiriman untuk panggilan metode Anda. Contoh yang baik dapat dilihat dalam kode sumber SQLAlchemy; lihat dbapi()metode kelas di tautan berikut:

https://github.com/zzzeek/sqlalchemy/blob/ab6946769742602e40fb9ed9dde5f642885d1906/lib/sqlalchemy/dialects/mssql/pymssql.py#L47

Anda dapat melihat bahwa dbapi() metode, yang digunakan backend basis data untuk mengimpor pustaka database khusus vendor yang dibutuhkan sesuai permintaan, adalah metode kelas karena itu perlu dijalankan sebelum instance koneksi database tertentu mulai dibuat - tetapi tidak bisa menjadi fungsi sederhana atau fungsi statis, karena mereka ingin dapat memanggil metode lain yang mendukung yang mungkin juga perlu ditulis lebih khusus dalam subkelas daripada di kelas induknya. Dan jika Anda mengirim ke suatu fungsi atau kelas statis, maka Anda "lupa" dan kehilangan pengetahuan tentang kelas mana yang melakukan inisialisasi.


Tautan rusak, harap sesuaikan
piertoni

28

Baru-baru ini saya menginginkan kelas logging yang sangat ringan yang akan menghasilkan jumlah output yang bervariasi tergantung pada level logging yang dapat diatur secara programatis. Tapi saya tidak ingin membuat instance kelas setiap kali saya ingin menampilkan pesan debugging atau kesalahan atau peringatan. Tetapi saya juga ingin merangkum fungsi dari fasilitas logging ini dan membuatnya dapat digunakan kembali tanpa pernyataan global.

Jadi saya menggunakan variabel kelas dan @classmethoddekorator untuk mencapai ini.

Dengan kelas Logging sederhana saya, saya bisa melakukan hal berikut:

Logger._level = Logger.DEBUG

Kemudian, dalam kode saya, jika saya ingin mengeluarkan banyak informasi debug, saya hanya perlu kode

Logger.debug( "this is some annoying message I only want to see while debugging" )

Kesalahan bisa diatasi

Logger.error( "Wow, something really awful happened." )

Dalam lingkungan "produksi", saya dapat menentukan

Logger._level = Logger.ERROR

dan sekarang, hanya pesan kesalahan yang akan ditampilkan. Pesan debug tidak akan dicetak.

Inilah kelas saya:

class Logger :
    ''' Handles logging of debugging and error messages. '''

    DEBUG = 5
    INFO  = 4
    WARN  = 3
    ERROR = 2
    FATAL = 1
    _level = DEBUG

    def __init__( self ) :
        Logger._level = Logger.DEBUG

    @classmethod
    def isLevel( cls, level ) :
        return cls._level >= level

    @classmethod
    def debug( cls, message ) :
        if cls.isLevel( Logger.DEBUG ) :
            print "DEBUG:  " + message

    @classmethod
    def info( cls, message ) :
        if cls.isLevel( Logger.INFO ) :
            print "INFO :  " + message

    @classmethod
    def warn( cls, message ) :
        if cls.isLevel( Logger.WARN ) :
            print "WARN :  " + message

    @classmethod
    def error( cls, message ) :
        if cls.isLevel( Logger.ERROR ) :
            print "ERROR:  " + message

    @classmethod
    def fatal( cls, message ) :
        if cls.isLevel( Logger.FATAL ) :
            print "FATAL:  " + message

Dan beberapa kode yang mengujinya sedikit saja:

def logAll() :
    Logger.debug( "This is a Debug message." )
    Logger.info ( "This is a Info  message." )
    Logger.warn ( "This is a Warn  message." )
    Logger.error( "This is a Error message." )
    Logger.fatal( "This is a Fatal message." )

if __name__ == '__main__' :

    print "Should see all DEBUG and higher"
    Logger._level = Logger.DEBUG
    logAll()

    print "Should see all ERROR and higher"
    Logger._level = Logger.ERROR
    logAll()

10
Hal ini tidak tampak bagi saya seperti contoh metode kelas yang baik adalah baik untuk karena bisa memiliki semua dilakukan dengan cara yang kurang kompleks dengan hanya membuat semua metode kelas fungsi modul-tingkat dan melakukan jauh dengan kelas sama sekali.
martineau

10
Oh, yang lebih penting lagi, Python menyediakan kelas logging yang sangat mudah digunakan. Jadi lebih buruk daripada menciptakan solusi yang kurang optimal, saya menemukan kembali rodanya. Tamparan tangan ganda. Tapi itu memberikan contoh kerja, setidaknya. Tidak yakin saya setuju untuk menyingkirkan kelas, pada tingkat konseptual. Terkadang Anda ingin mengenkapsulasi kode agar mudah digunakan kembali, dan metode pencatatan merupakan target yang bagus untuk digunakan kembali.
Marvo

3
Mengapa Anda tidak menggunakan metode statis?
bdforbes


11

Ketika pengguna masuk di situs web saya, objek Pengguna () dipakai dari nama pengguna dan kata sandi.

Jika saya memerlukan objek pengguna tanpa pengguna berada di sana untuk login (mis. Pengguna admin mungkin ingin menghapus akun pengguna lain, jadi saya perlu instantiate pengguna itu dan memanggil metode hapusnya):

Saya memiliki metode kelas untuk mengambil objek pengguna.

class User():
    #lots of code
    #...
    # more code

    @classmethod
    def get_by_username(cls, username):
        return cls.query(cls.username == username).get()

    @classmethod
    def get_by_auth_id(cls, auth_id):
        return cls.query(cls.auth_id == auth_id).get()

9

Saya pikir jawaban yang paling jelas adalah jawaban AmanKow . Itu bermuara pada bagaimana Anda ingin mengatur kode Anda. Anda dapat menulis semuanya sebagai fungsi level modul yang dibungkus dalam namespace dari modul yaitu

module.py (file 1)
---------
def f1() : pass
def f2() : pass
def f3() : pass


usage.py (file 2)
--------
from module import *
f1()
f2()
f3()
def f4():pass 
def f5():pass

usage1.py (file 3)
-------------------
from usage import f4,f5
f4()
f5()

Kode prosedural di atas tidak terorganisir dengan baik, seperti yang Anda lihat setelah hanya 3 modul yang membingungkan, apa yang dilakukan masing-masing metode? Anda dapat menggunakan nama deskriptif panjang untuk fungsi (seperti di java) tetapi kode Anda tetap tidak dapat dikelola dengan sangat cepat.

Cara berorientasi objek adalah memecah kode Anda menjadi blok yang dapat dikelola yaitu Kelas & objek dan fungsi dapat dikaitkan dengan instance objek atau dengan kelas.

Dengan fungsi-fungsi kelas, Anda memperoleh level pembagian lain dalam kode Anda dibandingkan dengan fungsi-fungsi level modul. Jadi, Anda bisa mengelompokkan fungsi terkait dalam kelas untuk membuatnya lebih spesifik untuk tugas yang Anda tetapkan untuk kelas itu. Misalnya Anda dapat membuat kelas utilitas file:

class FileUtil ():
  def copy(source,dest):pass
  def move(source,dest):pass
  def copyDir(source,dest):pass
  def moveDir(source,dest):pass

//usage
FileUtil.copy("1.txt","2.txt")
FileUtil.moveDir("dir1","dir2")

Dengan cara ini lebih fleksibel dan lebih mudah dikelola, Anda mengelompokkan fungsi bersama dan lebih jelas dengan apa yang dilakukan masing-masing fungsi. Anda juga mencegah konflik nama, misalnya salinan fungsi mungkin ada di modul impor lainnya (misalnya salinan jaringan) yang Anda gunakan dalam kode Anda, jadi ketika Anda menggunakan nama lengkap FileUtil.copy () Anda menghapus masalah dan kedua fungsi salin dapat digunakan berdampingan.


1
Untuk menggunakan f1 (), f2 () dan seterusnya, seperti yang Anda lakukan, bukankah seharusnya Anda menggunakan dari impor modul * dan dari impor penggunaan *?
Jblasco

@ Jblasco Saya mengoreksi pernyataan impor untuk menghapus kebingungan, ya jika Anda mengimpor modul Anda harus awalan fungsi dengan nama modul. yaitu modul impor -> module.f1 () dll
firephil

Saya tidak setuju dengan jawaban ini; Python bukan Java. Masalah kode yang tidak dikelola hanya muncul karena pilihan yang disengaja dari nama-nama miskin dalam contoh. Jika alih-alih Anda mereplikasi FileUtilmetode kelas sebagai fungsi dari file_utilmodul, itu akan sebanding dan tidak menyalahgunakan OOP ketika tidak ada objek yang sebenarnya (sebenarnya Anda bisa berpendapat itu lebih disukai, karena Anda tidak berakhir dengan from file_util import FileUtilkata kerja lain). Konflik nama juga dapat dihindari dalam kode prosedural dengan melakukan import file_utilalih - alih from file_util import ....
ForgottenUmbrella

7

Secara jujur? Saya tidak pernah menemukan penggunaan untuk staticmethod atau classmethod. Saya belum melihat operasi yang tidak dapat dilakukan menggunakan fungsi global atau metode instance.

Akan berbeda jika python menggunakan anggota pribadi dan dilindungi lebih seperti Java. Di Jawa, saya memerlukan metode statis untuk dapat mengakses anggota pribadi instance untuk melakukan hal-hal. Dalam Python, itu jarang diperlukan.

Biasanya, saya melihat orang menggunakan staticmethods dan classmethods ketika semua yang benar-benar perlu mereka lakukan adalah menggunakan ruang nama modul-tingkat python lebih baik.


1
Pribadi: _variable_name dan Dilindungi: __variable_name
Bradley Kreider

1
Salah satu penggunaan yang sangat diperlukan adalah setUpClass dan tearDownClass unittest . Anda menggunakan unittests, kan? :)
dbn

7

Ini memungkinkan Anda untuk menulis metode kelas generik yang dapat Anda gunakan dengan kelas yang kompatibel.

Sebagai contoh:

@classmethod
def get_name(cls):
    print cls.name

class C:
    name = "tester"

C.get_name = get_name

#call it:
C.get_name()

Jika Anda tidak menggunakan, @classmethodAnda dapat melakukannya dengan kata kunci sendiri tetapi membutuhkan turunan Kelas:

def get_name(self):
    print self.name

class C:
    name = "tester"

C.get_name = get_name

#call it:
C().get_name() #<-note the its an instance of class C

5

Saya dulu bekerja dengan PHP dan baru-baru ini saya bertanya pada diri sendiri, apa yang terjadi dengan metode kelas ini? Manual Python sangat teknis dan sangat pendek dalam kata-kata sehingga tidak akan membantu dengan memahami fitur itu. Saya telah googling dan googling dan saya menemukan jawaban -> http://code.anjanesh.net/2007/12/python-classmethods.html .

Jika Anda malas mengkliknya. Penjelasan saya lebih pendek dan di bawah. :)

dalam PHP (mungkin tidak semua dari Anda tahu PHP, tetapi bahasa ini sangat jelas sehingga semua orang harus mengerti apa yang saya bicarakan) kami memiliki variabel statis seperti ini:


class A
{

    static protected $inner_var = null;

    static public function echoInnerVar()
    {
        echo self::$inner_var."\n";
    }

    static public function setInnerVar($v)
    {
        self::$inner_var = $v;
    }

}

class B extends A
{
}

A::setInnerVar(10);
B::setInnerVar(20);

A::echoInnerVar();
B::echoInnerVar();

Outputnya akan dalam kedua kasus 20.

Namun dalam python kita dapat menambahkan dekorator @classmethod dan dengan demikian dimungkinkan untuk memiliki output 10 dan 20 masing-masing. Contoh:


class A(object):
    inner_var = 0

    @classmethod
    def setInnerVar(cls, value):
        cls.inner_var = value

    @classmethod
    def echoInnerVar(cls):
        print cls.inner_var


class B(A):
    pass


A.setInnerVar(10)
B.setInnerVar(20)

A.echoInnerVar()
B.echoInnerVar()

Pintar, bukan?


Salah satu potensi masalah dengan contoh Python Anda adalah jika B.setInnerVar(20)dibiarkan, itu akan mencetak 10dua kali (daripada memberikan kesalahan pada panggilan echoInnerBar kedua) yang tidak inner_vardidefinisikan.
martineau

5

Metode kelas menyediakan "gula semantik" (tidak tahu apakah istilah ini banyak digunakan) - atau "kenyamanan semantik".

Contoh: Anda mendapat satu set kelas yang mewakili objek. Anda mungkin ingin memiliki metode kelas all()atau find()menulis User.all()atau User.find(firstname='Guido'). Itu bisa dilakukan menggunakan fungsi level modul tentu saja ...


Contoh Anda mengasumsikan, tentu saja, bahwa kelas melacak semua instansnya dan dapat mengaksesnya setelah dibuat - sesuatu yang tidak dilakukan secara otomatis.
martineau

1
"gula semantik" sepertinya cocok untuk "gula sintaksis".
XTL

3

Apa yang baru saja mengenai saya, yang berasal dari Ruby, adalah bahwa apa yang disebut metode kelas dan apa yang disebut metode instan hanyalah sebuah fungsi dengan makna semantik yang diterapkan pada parameter pertamanya, yang diam-diam diteruskan ketika fungsi tersebut disebut sebagai metode sebuah objek (misobj.meth() ).

Biasanya objek itu harus berupa instance tetapi @classmethod dekorator metode mengubah aturan untuk lulus kelas. Anda dapat memanggil metode kelas dengan instance (hanya fungsi) - argumen pertama adalah kelasnya.

Karena itu hanya fungsi , itu hanya dapat dideklarasikan sekali dalam ruang lingkup yang diberikan (yaitu classdefinisi). Karena itu, jika mengikuti, sebagai kejutan bagi seorang Rubyist, bahwa Anda tidak dapat memiliki metode kelas dan metode contoh dengan nama yang sama .

Pertimbangkan ini:

class Foo():
  def foo(x):
    print(x)

Anda dapat memanggil foosebuah instance

Foo().foo()
<__main__.Foo instance at 0x7f4dd3e3bc20>

Tapi tidak di kelas:

Foo.foo()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unbound method foo() must be called with Foo instance as first argument (got nothing instead)

Sekarang tambahkan @classmethod:

class Foo():
  @classmethod
  def foo(x):
    print(x)

Memanggil instance sekarang melewati kelasnya:

Foo().foo()
__main__.Foo

seperti halnya memanggil kelas:

Foo.foo()
__main__.Foo

Hanya konvensi yang menentukan yang kami gunakan selfuntuk argumen pertama tentang metode instance dan clsmetode kelas. Saya tidak menggunakan di sini untuk menggambarkan bahwa itu hanya argumen. Di Ruby, selfadalah kata kunci.

Kontras dengan Ruby:

class Foo
  def foo()
    puts "instance method #{self}"
  end
  def self.foo()
    puts "class method #{self}"
  end
end

Foo.foo()
class method Foo

Foo.new.foo()
instance method #<Foo:0x000000020fe018>

Metode kelas Python hanyalah fungsi yang didekorasi dan Anda dapat menggunakan teknik yang sama untuk membuat dekorator Anda sendiri . Metode yang dihiasi membungkus metode nyata (dalam kasus @classmethoditu melewati argumen kelas tambahan). Metode yang mendasarinya masih ada, tersembunyi tetapi masih dapat diakses .


catatan kaki: Saya menulis ini setelah bentrokan nama antara kelas dan metode contoh membuat saya penasaran. Saya jauh dari ahli Python dan ingin komentar jika semua ini salah.


"yang diam-diam disahkan ketika fungsi ini disebut sebagai metode objek" - saya percaya ini tidak tepat. AFICT, tidak ada yang "diam-diam berlalu" dengan Python. IMO, Python jauh lebih masuk akal daripada Ruby dalam pengertian ini (kecuali untuk super()). AFICT, "keajaiban" terjadi ketika atribut diatur atau dibaca. Panggilan obj.meth(arg)dengan Python (tidak seperti Ruby) berarti sederhana (obj.meth)(arg). Tidak ada yang diam-diam berlalu di mana saja, obj.methhanya objek yang bisa dipanggil yang menerima satu argumen kurang dari fungsi yang dibuatnya.
Alexey

obj.meth, pada gilirannya, berarti sederhana getattr(obj, "meth").
Alexey

Masuk akal untuk memiliki jawaban yang dirancang untuk orang-orang yang datang dari bahasa umum lainnya. Namun, satu hal yang hilang dari percakapan ini, yang mengikatnya bersama, adalah gagasan tentang deskriptor Python . Fungsinya adalah deskriptor , dan begitulah argumen pertama "otomatis" dilewatkan sebagai instance ketika suatu fungsi adalah atribut ke kelas. Ini juga bagaimana classmethods diimplementasikan, dan implementasi python murni disediakan di HOWTO I tertaut. Fakta bahwa itu juga dekorator adalah semacam tambahan
juanpa.arrivillaga

2

Ini adalah topik yang menarik. Menurut saya adalah bahwa metode python beroperasi seperti singleton daripada pabrik (yang menghasilkan instance kelas). Alasannya adalah singleton adalah bahwa ada objek umum yang diproduksi (kamus) tetapi hanya sekali untuk kelas tetapi dibagikan oleh semua contoh.

Untuk menggambarkan ini di sini adalah contohnya. Perhatikan bahwa semua instance memiliki referensi ke kamus tunggal. Ini bukan pola pabrik seperti yang saya mengerti. Ini mungkin sangat unik untuk python.

class M():
 @classmethod
 def m(cls, arg):
     print "arg was",  getattr(cls, "arg" , None),
     cls.arg = arg
     print "arg is" , cls.arg

 M.m(1)   # prints arg was None arg is 1
 M.m(2)   # prints arg was 1 arg is 2
 m1 = M()
 m2 = M() 
 m1.m(3)  # prints arg was 2 arg is 3  
 m2.m(4)  # prints arg was 3 arg is 4 << this breaks the factory pattern theory.
 M.m(5)   # prints arg was 4 arg is 5

2

Saya bertanya pada diri sendiri pertanyaan yang sama beberapa kali. Dan meskipun orang-orang di sini berusaha keras untuk menjelaskannya, IMHO jawaban terbaik (dan paling sederhana) jawaban yang saya temukan adalah deskripsi metode Kelas dalam Dokumentasi Python.

Ada juga referensi ke metode Statis. Dan jika seseorang sudah tahu metode contoh (yang saya asumsikan), jawaban ini mungkin merupakan bagian terakhir untuk menyatukan semuanya ...

Elaborasi lebih lanjut dan lebih dalam tentang topik ini dapat ditemukan juga di dokumentasi: Hirarki jenis standar (gulir ke bawah ke bagian Metode instan)


1

@classmethoddapat berguna untuk dengan mudah membuat instance objek kelas itu dari sumber daya luar. Pertimbangkan yang berikut ini:

import settings

class SomeClass:
    @classmethod
    def from_settings(cls):
        return cls(settings=settings)

    def __init__(self, settings=None):
        if settings is not None:
            self.x = settings['x']
            self.y = settings['y']

Kemudian di file lain:

from some_package import SomeClass

inst = SomeClass.from_settings()

Mengakses inst.x akan memberikan nilai yang sama dengan pengaturan ['x'].


0

Kelas mendefinisikan satu set instance, tentu saja. Dan metode kelas bekerja pada instance individual. Metode kelas (dan variabel) tempat untuk menggantung informasi lain yang terkait dengan set instance atas semua.

Sebagai contoh jika kelas Anda mendefinisikan satu set siswa, Anda mungkin ingin variabel kelas atau metode yang mendefinisikan hal-hal seperti set kelas yang siswa dapat menjadi anggota.

Anda juga dapat menggunakan metode kelas untuk mendefinisikan alat untuk bekerja di seluruh set. Misalnya Student.all_of_em () mungkin mengembalikan semua siswa yang dikenal. Jelas jika set instance Anda memiliki lebih banyak struktur daripada hanya set, Anda dapat memberikan metode kelas untuk mengetahui tentang struktur itu. Students.all_of_em (grade = 'junior')

Teknik seperti ini cenderung mengarah pada menyimpan anggota dari set instance ke dalam struktur data yang berakar pada variabel kelas. Anda harus berhati-hati untuk menghindari frustrasi pengumpulan sampah itu.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.