Apa perbedaan antara metode `classmethod` dan metode metaclass?

12

Dengan Python, saya bisa membuat metode kelas menggunakan @classmethoddekorator:

>>> class C:
...     @classmethod
...     def f(cls):
...             print(f'f called with cls={cls}')
...
>>> C.f()
f called with cls=<class '__main__.C'>

Atau, saya bisa menggunakan metode normal (instance) pada metaclass:

>>> class M(type):
...     def f(cls):
...             print(f'f called with cls={cls}')
...
>>> class C(metaclass=M):
...     pass
...
>>> C.f()
f called with cls=<class '__main__.C'>

Seperti yang ditunjukkan oleh output dari C.f(), kedua pendekatan ini menyediakan fungsionalitas yang serupa.

Apa perbedaan antara menggunakan @classmethoddan menggunakan metode normal pada metaclass?

pengguna200783
sumber

Jawaban:

5

Karena kelas adalah instance dari metaclass, maka tidak terduga bahwa "metode instance" pada metaclass akan berperilaku seperti metode class.

Namun, ya, ada perbedaan - dan beberapa di antaranya lebih dari sekadar semantik:

  1. Perbedaan yang paling penting adalah bahwa metode dalam metaclass tidak "terlihat" dari suatu kelas instance . Itu terjadi karena pencarian atribut dalam Python (dengan cara yang disederhanakan - deskriptor dapat diutamakan) mencari atribut dalam instance - jika tidak ada dalam instance, Python kemudian mencari di kelas instance itu, dan kemudian pencarian melanjutkan kacamata super kelas, tetapi tidak pada kelas kelas. Stdlib Python memanfaatkan fitur ini dalam abc.ABCMeta.registermetode ini. Fitur itu dapat digunakan untuk selamanya, karena metode yang berhubungan dengan kelas itu sendiri bebas untuk digunakan kembali sebagai atribut instance tanpa konflik apa pun (tetapi metode masih akan konflik).
  2. Perbedaan lain, meskipun jelas, adalah bahwa metode yang dideklarasikan dalam metaclass dapat tersedia di beberapa kelas, jika tidak terkait - jika Anda memiliki hierarki kelas yang berbeda, tidak terkait sama sekali dalam apa yang mereka hadapi, tetapi ingin beberapa fungsi umum untuk semua kelas , Anda harus membuat kelas mixin, yang harus dimasukkan sebagai basis di kedua hierarki (katakanlah untuk menyertakan semua kelas dalam registri aplikasi). (NB. Mixin terkadang menjadi panggilan yang lebih baik daripada metaclass)
  3. Classmethod adalah objek "classmethod" khusus, sedangkan metode dalam metaclass adalah fungsi biasa.

Jadi, kebetulan bahwa mekanisme yang digunakan oleh metode metode adalah " protokol deskriptor ". Sementara fungsi normal menampilkan __get__metode yang akan menyisipkan selfargumen ketika mereka diambil dari sebuah instance, dan membiarkan argumen itu kosong ketika diambil dari suatu kelas, sebuah classmethod objek memiliki yang berbeda __get__, yang akan menyisipkan kelas itu sendiri ("pemilik") sebagai parameter pertama dalam kedua situasi.

Ini tidak membuat perbedaan praktis sebagian besar waktu, tetapi jika Anda ingin akses ke metode sebagai fungsi, untuk tujuan menambahkan secara dinamis menambahkan dekorator ke dalamnya, atau yang lain, untuk metode dalam metaclass meta.methodmengambil fungsi, siap digunakan , sementara Anda harus menggunakan cls.my_classmethod.__func__ untuk mengambilnya dari suatu metoda (dan kemudian Anda harus membuat yang lainclassmethod objek dan menetapkan kembali, jika Anda melakukan pembungkus).

Pada dasarnya, ini adalah 2 contoh:


class M1(type):
    def clsmethod1(cls):
        pass

class CLS1(metaclass=M1):
    pass

def runtime_wrap(cls, method_name, wrapper):
    mcls = type(cls)
    setattr(mcls, method_name,  wrapper(getatttr(mcls, method_name)))

def wrapper(classmethod):
    def new_method(cls):
        print("wrapper called")
        return classmethod(cls)
    return new_method

runtime_wrap(cls1, "clsmethod1", wrapper)

class CLS2:
    @classmethod
    def classmethod2(cls):
        pass

 def runtime_wrap2(cls, method_name, wrapper):
    setattr(cls, method_name,  classmethod(
                wrapper(getatttr(cls, method_name).__func__)
        )
    )

runtime_wrap2(cls1, "clsmethod1", wrapper)

Dengan kata lain: terlepas dari perbedaan penting bahwa metode yang didefinisikan dalam metaclass terlihat dari instance dan aclassmethod objek tidak, perbedaan lainnya, pada saat runtime akan tampak tidak jelas dan tidak berarti - tetapi itu terjadi karena bahasa tidak perlu pergi keluar dari jalannya dengan aturan khusus untuk metode kelas: Kedua cara menyatakan metode kelas adalah mungkin, sebagai konsekuensi dari desain bahasa - satu, karena fakta bahwa kelas itu sendiri merupakan objek, dan yang lain, sebagai kemungkinan di antara banyak, dari penggunaan protokol deskriptor yang memungkinkan seseorang untuk mengkhususkan akses atribut dalam sebuah instance dan dalam sebuah kelas:

The classmethodbuiltin didefinisikan dalam kode asli, tapi bisa saja akan dikodekan dalam python murni dan akan bekerja dengan cara yang sama persis. Kelas 5 baris di bawah ini dapat digunakan sebagai classmethoddekorator tanpa perbedaan runtime dengan repr` @classmethod" at all (though distinguishable through introspection such as calls toisinstance built-in , and evententunya):


class myclassmethod:
    def __init__(self, func):
        self.__func__ = func
    def __get__(self, instance, owner):
        return lambda *args, **kw: self.__func__(owner, *args, **kw)

Dan, di luar metode, menarik untuk diingat bahwa atribut khusus seperti @propertypada metaclass akan berfungsi sebagai atribut kelas khusus, sama saja, tanpa perilaku yang mengejutkan sama sekali.

jsbueno
sumber
2

Saat Anda mengucapkannya seperti yang Anda lakukan pada pertanyaan, @classmethodmetaclasses dan mungkin terlihat serupa tetapi mereka memiliki tujuan yang agak berbeda. Kelas yang disuntikkan dalam @classmethodargumen biasanya digunakan untuk membangun sebuah instance (yaitu konstruktor alternatif). Di sisi lain, metaclasses biasanya digunakan untuk memodifikasi kelas itu sendiri (misalnya seperti apa yang dilakukan Django dengan model-modelnya DSL).

Itu bukan untuk mengatakan bahwa Anda tidak dapat memodifikasi kelas di dalam suatu metode kelas. Tetapi kemudian pertanyaannya menjadi mengapa Anda tidak mendefinisikan kelas dengan cara Anda ingin memodifikasinya? Jika tidak, mungkin disarankan refactor untuk menggunakan beberapa kelas.

Mari sedikit memperluas contoh pertama.

class C:
    @classmethod
    def f(cls):
        print(f'f called with cls={cls}')

Meminjam dari Python docs , hal di atas akan berkembang menjadi sesuatu seperti berikut:

class ClassMethod(object):
    "Emulate PyClassMethod_Type() in Objects/funcobject.c"

    def __init__(self, f):
        self.f = f

    def __get__(self, obj, klass=None):
        if klass is None:
            klass = type(obj)
        def newfunc(*args):
            return self.f(klass, *args)
        return newfunc

class C:
    def f(cls):
        print(f'f called with cls={cls}')
    f = ClassMethod(f)

Perhatikan bagaimana __get__bisa mengambil instance atau kelas (atau keduanya), dan dengan demikian Anda bisa melakukan keduanya C.fdan C().f. Ini tidak seperti contoh metaclass yang Anda berikan yang akan membuang AttributeErrorfor C().f.

Selain itu, dalam contoh metaclass, ftidak ada di C.__dict__. Ketika mencari atribut fdengan C.f, penerjemah melihat C.__dict__dan kemudian setelah gagal menemukan, melihat type(C).__dict__(yang M.__dict__). Ini mungkin masalah jika Anda ingin fleksibilitas untuk menimpa fdi C, meskipun saya ragu ini akan pernah menjadi penggunaan praktis.

Kent Shikama
sumber
0

Dalam contoh Anda, perbedaannya akan di beberapa kelas lain yang akan memiliki M ditetapkan sebagai metaclass mereka.

class M(type):
    def f(cls):
        pass

class C(metaclass=M):
    pass

class C2(metaclass=M):
    pass

C.f()
C2.f()
class M(type):
     pass

class C(metaclass=M):
     @classmethod
     def f(cls):
        pass

class C2(metaclass=M):
    pass

C.f()
# C2 does not have 'f'

Berikut ini lebih lanjut tentang metaclasses. Apa beberapa kasus penggunaan (konkret) untuk metaclasses?

andole
sumber
0

Baik @classmethod dan Metaclass berbeda.

Segala sesuatu di python adalah objek. Setiap hal berarti segalanya.

Apa itu Metaclass?

Seperti yang dikatakan setiap hal adalah objek. Kelas juga objek dalam fakta kelas adalah contoh dari objek misterius lainnya yang secara formal disebut sebagai meta-class. Metaclass default dalam python adalah "type" jika tidak ditentukan

Secara default semua kelas yang didefinisikan adalah turunan tipe.

Kelas adalah contoh dari Meta-Classes

Beberapa poin penting adalah untuk memahami perilaku metion

  • Kelas adalah contoh dari kelas meta.
  • Seperti setiap objek instantiated, seperti objek (instance) mendapatkan atributnya dari kelas. Kelas akan mendapatkan atributnya dari Meta-Class

Pertimbangkan Kode Berikut

class Meta(type):
    def foo(self):
        print(f'foo is called self={self}')
        print('{} is instance of {}: {}'.format(self, Meta, isinstance(self, Meta)))

class C(metaclass=Meta):
    pass

C.foo()

Dimana,

  • kelas C adalah turunan dari kelas Meta
  • "class C" adalah objek kelas yang merupakan instance dari "class Meta"
  • Seperti objek lain (contoh) "kelas C" memiliki akses atribut / metode yang didefinisikan dalam kelas "kelas Meta"
  • Jadi, decoding "C.foo ()". "C" adalah turunan dari "Meta" dan "foo" adalah metode yang memanggil instance dari "Meta" yang merupakan "C".
  • Argumen pertama dari metode "foo" adalah referensi ke instance bukan kelas tidak seperti "classmethod"

Kami dapat memverifikasi seolah-olah "kelas C" adalah turunan dari "Kelas Meta

  isinstance(C, Meta)

Apa itu metode kelas?

Metode python dikatakan terikat. Sebagai python memaksakan pembatasan bahwa metode harus dipanggil dengan instance saja. Terkadang kita mungkin ingin memanggil metode secara langsung melalui kelas tanpa instance (seperti anggota statis di java) tanpa harus membuat instance. Dengan instance default diperlukan untuk memanggil metode. Sebagai solusi, python menyediakan fungsi metode class built-in untuk mengikat metode yang diberikan ke kelas, bukan sebagai contoh.

Sebagai metode kelas terikat ke kelas. Dibutuhkan setidaknya satu argumen yang merujuk ke kelas itu sendiri, bukan contoh (diri)

jika metode fungsi / dekorator built-in digunakan. Argumen pertama akan merujuk ke kelas, bukan contoh

class ClassMethodDemo:
    @classmethod
    def foo(cls):
        print(f'cls is ClassMethodDemo: {cls is ClassMethodDemo}')

Seperti yang telah kita gunakan "classmethod" kita sebut metode "foo" tanpa membuat instance sebagai berikut

ClassMethodDemo.foo()

Panggilan metode di atas akan mengembalikan True. Sejak argumen pertama, cls memang merujuk ke "ClassMethodDemo"

Ringkasan:

  • Classmethod menerima argumen pertama yang merupakan "referensi ke kelas (secara tradisional disebut cls) itu sendiri"
  • Metode meta-class bukanlah metode kelas. Metode Meta-kelas menerima argumen pertama yang merupakan "referensi ke instance (secara tradisional disebut diri) bukan kelas"
Neotam
sumber