Apa itu metaclasses dalam Python?

Jawaban:

2869

Metaclass adalah kelas suatu kelas. Kelas mendefinisikan bagaimana instance kelas (yaitu suatu objek) berperilaku sementara metaclass mendefinisikan bagaimana kelas berperilaku. Kelas adalah turunan dari metaclass.

Sementara di Python Anda bisa menggunakan callable acak untuk metaclasses (seperti yang ditunjukkan Jerub ), pendekatan yang lebih baik adalah menjadikannya kelas aktual itu sendiri. typeadalah metaclass biasa dalam Python. typeitu sendiri adalah kelas, dan itu adalah tipenya sendiri. Anda tidak akan dapat membuat ulang sesuatu seperti typemurni di Python, tetapi Python sedikit curang. Untuk membuat metaclass Anda sendiri di Python Anda benar-benar hanya ingin subclass type.

Metaclass paling umum digunakan sebagai pabrik kelas. Saat Anda membuat objek dengan memanggil kelas, Python membuat kelas baru (ketika mengeksekusi pernyataan 'kelas') dengan memanggil metaclass. Dikombinasikan dengan normal __init__dan __new__metode, metaclasses memungkinkan Anda untuk melakukan 'hal ekstra' saat membuat kelas, seperti mendaftarkan kelas baru dengan beberapa registri atau mengganti kelas dengan sesuatu yang lain sama sekali.

Ketika classpernyataan dieksekusi, Python pertama mengeksekusi tubuh classpernyataan sebagai blok kode normal. Namespace yang dihasilkan (dict) memiliki atribut dari calon kelas. Metaclass ditentukan dengan melihat baseclasses dari calon kelas (metaclasses diwariskan), pada __metaclass__atribut calon kelas (jika ada) atau __metaclass__variabel global. Metaclass kemudian dipanggil dengan nama, basis dan atribut dari kelas untuk instantiate.

Namun, metaclasses sebenarnya menentukan jenis kelas, bukan hanya pabrik untuk itu, sehingga Anda dapat melakukan lebih banyak dengan mereka. Anda dapat, misalnya, mendefinisikan metode normal pada metaclass. Metaclass-methods ini seperti metode-metode class dalam hal mereka dapat dipanggil di kelas tanpa instance, tetapi mereka juga tidak seperti metode-metode class dalam hal mereka tidak dapat dipanggil pada instance dari kelas. type.__subclasses__()adalah contoh metode pada typemetaclass. Anda juga dapat menentukan metode 'sihir' normal, seperti __add__, __iter__dan __getattr__, untuk mengimplementasikan atau mengubah cara kelas bertindak.

Berikut adalah contoh agregat dari potongan-potongan:

def make_hook(f):
    """Decorator to turn 'foo' method into '__foo__'"""
    f.is_hook = 1
    return f

class MyType(type):
    def __new__(mcls, name, bases, attrs):

        if name.startswith('None'):
            return None

        # Go over attributes and see if they should be renamed.
        newattrs = {}
        for attrname, attrvalue in attrs.iteritems():
            if getattr(attrvalue, 'is_hook', 0):
                newattrs['__%s__' % attrname] = attrvalue
            else:
                newattrs[attrname] = attrvalue

        return super(MyType, mcls).__new__(mcls, name, bases, newattrs)

    def __init__(self, name, bases, attrs):
        super(MyType, self).__init__(name, bases, attrs)

        # classregistry.register(self, self.interfaces)
        print "Would register class %s now." % self

    def __add__(self, other):
        class AutoClass(self, other):
            pass
        return AutoClass
        # Alternatively, to autogenerate the classname as well as the class:
        # return type(self.__name__ + other.__name__, (self, other), {})

    def unregister(self):
        # classregistry.unregister(self)
        print "Would unregister class %s now." % self

class MyObject:
    __metaclass__ = MyType


class NoneSample(MyObject):
    pass

# Will print "NoneType None"
print type(NoneSample), repr(NoneSample)

class Example(MyObject):
    def __init__(self, value):
        self.value = value
    @make_hook
    def add(self, other):
        return self.__class__(self.value + other.value)

# Will unregister the class
Example.unregister()

inst = Example(10)
# Will fail with an AttributeError
#inst.unregister()

print inst + inst
class Sibling(MyObject):
    pass

ExampleSibling = Example + Sibling
# ExampleSibling is now a subclass of both Example and Sibling (with no
# content of its own) although it will believe it's called 'AutoClass'
print ExampleSibling
print ExampleSibling.__mro__
Thomas Wouters
sumber
13
class A(type):pass<NEWLINE>class B(type,metaclass=A):pass<NEWLINE>b.__class__ = b
pppery
20
ppperry dia jelas berarti Anda tidak dapat membuat kembali tipe tanpa menggunakan tipe itu sendiri sebagai metaclass. Yang cukup adil untuk dikatakan.
Holle van
3
Bukankah seharusnya membatalkan registrasi () dipanggil dengan instance dari kelas Contoh?
Ciasto piekarz
5
Catatan yang __metaclass__tidak didukung dalam Python 3. Dalam penggunaan Python 3 class MyObject(metaclass=MyType), lihat python.org/dev/peps/pep-3115 dan jawabannya di bawah ini.
BlackShift
2
Dokumentasi menjelaskan bagaimana metaclass dipilih . Metaclass tidak diwarisi sebanyak itu diturunkan. Jika Anda menentukan metaclass, itu harus menjadi subtipe dari masing-masing metaclass kelas dasar; jika tidak, Anda akan menggunakan metaclass kelas dasar yang merupakan subtipe dari masing-masing metaclass kelas dasar lainnya. Perhatikan bahwa ada kemungkinan bahwa tidak ada metaclass yang valid dapat ditemukan, dan definisi akan gagal.
chepner
6820

Kelas sebagai objek

Sebelum memahami metaclasses, Anda harus menguasai kelas dengan Python. Dan Python memiliki ide yang sangat aneh tentang kelas apa, dipinjam dari bahasa Smalltalk.

Di sebagian besar bahasa, kelas hanyalah potongan kode yang menjelaskan cara membuat objek. Itu agak benar di Python juga:

>>> class ObjectCreator(object):
...       pass
...

>>> my_object = ObjectCreator()
>>> print(my_object)
<__main__.ObjectCreator object at 0x8974f2c>

Tetapi kelas lebih dari itu dalam Python. Kelas juga benda.

Ya, benda.

Segera setelah Anda menggunakan kata kunci class, Python menjalankannya dan membuat OBJECT. Intruksi

>>> class ObjectCreator(object):
...       pass
...

membuat dalam memori objek dengan nama "ObjectCreator".

Objek ini (kelas) itu sendiri mampu membuat objek (instance), dan inilah mengapa ia kelas .

Tapi tetap saja, itu sebuah objek, dan karena itu:

  • Anda dapat menetapkannya ke variabel
  • Anda bisa menyalinnya
  • Anda dapat menambahkan atribut padanya
  • Anda dapat meneruskannya sebagai parameter fungsi

misalnya:

>>> print(ObjectCreator) # you can print a class because it's an object
<class '__main__.ObjectCreator'>
>>> def echo(o):
...       print(o)
...
>>> echo(ObjectCreator) # you can pass a class as a parameter
<class '__main__.ObjectCreator'>
>>> print(hasattr(ObjectCreator, 'new_attribute'))
False
>>> ObjectCreator.new_attribute = 'foo' # you can add attributes to a class
>>> print(hasattr(ObjectCreator, 'new_attribute'))
True
>>> print(ObjectCreator.new_attribute)
foo
>>> ObjectCreatorMirror = ObjectCreator # you can assign a class to a variable
>>> print(ObjectCreatorMirror.new_attribute)
foo
>>> print(ObjectCreatorMirror())
<__main__.ObjectCreator object at 0x8997b4c>

Membuat kelas secara dinamis

Karena kelas adalah objek, Anda dapat membuatnya dengan cepat, seperti objek apa pun.

Pertama, Anda bisa membuat kelas dalam suatu fungsi menggunakan class:

>>> def choose_class(name):
...     if name == 'foo':
...         class Foo(object):
...             pass
...         return Foo # return the class, not an instance
...     else:
...         class Bar(object):
...             pass
...         return Bar
...
>>> MyClass = choose_class('foo')
>>> print(MyClass) # the function returns a class, not an instance
<class '__main__.Foo'>
>>> print(MyClass()) # you can create an object from this class
<__main__.Foo object at 0x89c6d4c>

Tapi itu tidak begitu dinamis, karena Anda masih harus menulis seluruh kelas sendiri.

Karena kelas adalah objek, mereka harus dihasilkan oleh sesuatu.

Saat Anda menggunakan classkata kunci, Python membuat objek ini secara otomatis. Tetapi seperti kebanyakan hal dalam Python, ini memberi Anda cara untuk melakukannya secara manual.

Ingat fungsinya type? Fungsi tua yang baik yang memungkinkan Anda mengetahui jenis objek adalah:

>>> print(type(1))
<type 'int'>
>>> print(type("1"))
<type 'str'>
>>> print(type(ObjectCreator))
<type 'type'>
>>> print(type(ObjectCreator()))
<class '__main__.ObjectCreator'>

Nah, typememiliki kemampuan yang sama sekali berbeda, ia juga dapat membuat kelas dengan cepat. typedapat mengambil deskripsi kelas sebagai parameter, dan mengembalikan kelas.

(Saya tahu, konyol bahwa fungsi yang sama dapat memiliki dua kegunaan yang sama sekali berbeda sesuai dengan parameter yang Anda berikan. Ini masalah karena kompatibilitas ke belakang di Python)

type bekerja dengan cara ini:

type(name, bases, attrs)

Dimana:

  • name: nama kelas
  • bases: tuple dari kelas induk (untuk warisan, bisa kosong)
  • attrs: kamus yang berisi nama atribut dan nilai

misalnya:

>>> class MyShinyClass(object):
...       pass

dapat dibuat secara manual dengan cara ini:

>>> MyShinyClass = type('MyShinyClass', (), {}) # returns a class object
>>> print(MyShinyClass)
<class '__main__.MyShinyClass'>
>>> print(MyShinyClass()) # create an instance with the class
<__main__.MyShinyClass object at 0x8997cec>

Anda akan melihat bahwa kami menggunakan "MyShinyClass" sebagai nama kelas dan sebagai variabel untuk menyimpan referensi kelas. Mereka bisa berbeda, tetapi tidak ada alasan untuk mempersulit.

typemenerima kamus untuk mendefinisikan atribut kelas. Begitu:

>>> class Foo(object):
...       bar = True

Dapat diterjemahkan ke:

>>> Foo = type('Foo', (), {'bar':True})

Dan digunakan sebagai kelas normal:

>>> print(Foo)
<class '__main__.Foo'>
>>> print(Foo.bar)
True
>>> f = Foo()
>>> print(f)
<__main__.Foo object at 0x8a9b84c>
>>> print(f.bar)
True

Dan tentu saja, Anda dapat mewarisi darinya, jadi:

>>>   class FooChild(Foo):
...         pass

akan menjadi:

>>> FooChild = type('FooChild', (Foo,), {})
>>> print(FooChild)
<class '__main__.FooChild'>
>>> print(FooChild.bar) # bar is inherited from Foo
True

Akhirnya, Anda ingin menambahkan metode ke kelas Anda. Cukup tentukan fungsi dengan tanda tangan yang tepat dan tetapkan sebagai atribut.

>>> def echo_bar(self):
...       print(self.bar)
...
>>> FooChild = type('FooChild', (Foo,), {'echo_bar': echo_bar})
>>> hasattr(Foo, 'echo_bar')
False
>>> hasattr(FooChild, 'echo_bar')
True
>>> my_foo = FooChild()
>>> my_foo.echo_bar()
True

Dan Anda dapat menambahkan lebih banyak metode setelah Anda secara dinamis membuat kelas, seperti menambahkan metode ke objek kelas yang dibuat secara normal.

>>> def echo_bar_more(self):
...       print('yet another method')
...
>>> FooChild.echo_bar_more = echo_bar_more
>>> hasattr(FooChild, 'echo_bar_more')
True

Anda melihat tujuan kita: dengan Python, kelas adalah objek, dan Anda dapat membuat kelas dengan cepat, secara dinamis.

Inilah yang Python lakukan ketika Anda menggunakan kata kunci class, dan melakukannya dengan menggunakan metaclass.

Apa itu metaclasses (akhirnya)

Metaclasses adalah 'barang' yang menciptakan kelas.

Anda mendefinisikan kelas untuk membuat objek, bukan?

Tapi kami belajar bahwa kelas Python adalah objek.

Nah, metaclasses adalah yang menciptakan objek-objek ini. Mereka adalah kelas kelas, Anda bisa membayangkannya dengan cara ini:

MyClass = MetaClass()
my_object = MyClass()

Anda telah melihat yang typememungkinkan Anda melakukan sesuatu seperti ini:

MyClass = type('MyClass', (), {})

Itu karena fungsinya typesebenarnya metaclass. typeadalah metaclass yang digunakan Python untuk membuat semua kelas di belakang layar.

Sekarang Anda bertanya-tanya mengapa sih itu ditulis dalam huruf kecil, dan bukan Type?

Yah, saya kira itu masalah konsistensi dengan str, kelas yang membuat objek string, dan intkelas yang membuat objek integer. typehanya kelas yang menciptakan objek kelas.

Anda melihatnya dengan memeriksa __class__atribut.

Semuanya, dan maksud saya segalanya, adalah objek dengan Python. Itu termasuk int, string, fungsi dan kelas. Semuanya adalah benda. Dan semuanya telah dibuat dari sebuah kelas:

>>> age = 35
>>> age.__class__
<type 'int'>
>>> name = 'bob'
>>> name.__class__
<type 'str'>
>>> def foo(): pass
>>> foo.__class__
<type 'function'>
>>> class Bar(object): pass
>>> b = Bar()
>>> b.__class__
<class '__main__.Bar'>

Sekarang, apa __class__salahnya __class__?

>>> age.__class__.__class__
<type 'type'>
>>> name.__class__.__class__
<type 'type'>
>>> foo.__class__.__class__
<type 'type'>
>>> b.__class__.__class__
<type 'type'>

Jadi, metaclass hanyalah hal-hal yang menciptakan objek kelas.

Anda dapat menyebutnya 'pabrik kelas' jika diinginkan.

type adalah metaclass built-in yang digunakan Python, tetapi tentu saja, Anda dapat membuat metaclass Anda sendiri.

The __metaclass__atribut

Di Python 2, Anda bisa menambahkan __metaclass__atribut saat menulis kelas (lihat bagian selanjutnya untuk sintaks Python 3):

class Foo(object):
    __metaclass__ = something...
    [...]

Jika Anda melakukannya, Python akan menggunakan metaclass untuk membuat kelas Foo.

Hati-hati, ini rumit.

Anda menulis class Foo(object)terlebih dahulu, tetapi objek kelas Foobelum dibuat dalam memori.

Python akan mencari __metaclass__dalam definisi kelas. Jika menemukannya, ia akan menggunakannya untuk membuat kelas objek Foo. Jika tidak, itu akan digunakan typeuntuk membuat kelas.

Baca itu beberapa kali.

Saat kamu melakukan:

class Foo(Bar):
    pass

Python melakukan hal berikut:

Apakah ada __metaclass__atribut di dalamnya Foo?

Jika ya, buat dalam objek memori kelas (saya katakan objek kelas, tetap dengan saya di sini), dengan nama Foodengan menggunakan apa yang ada di __metaclass__.

Jika Python tidak dapat menemukan __metaclass__, ia akan mencari __metaclass__pada tingkat MODUL, dan mencoba melakukan hal yang sama (tetapi hanya untuk kelas yang tidak mewarisi apa pun, pada dasarnya kelas gaya lama).

Kemudian jika tidak dapat menemukan __metaclass__sama sekali, itu akan menggunakan Barmetaclass sendiri (orang tua pertama) (yang mungkin merupakan default type) untuk membuat objek kelas.

Hati-hati di sini bahwa __metaclass__atribut tidak akan diwarisi, metaclass dari parent ( Bar.__class__) akan menjadi. Jika Barmenggunakan __metaclass__atribut yang dibuat Bardengan type()(dan tidak type.__new__()), subclass tidak akan mewarisi perilaku itu.

Sekarang pertanyaan besarnya adalah, apa yang bisa Anda masukkan __metaclass__?

Jawabannya adalah: sesuatu yang bisa membuat kelas.

Dan apa yang bisa membuat kelas? type, atau apa pun yang membuat subclass atau menggunakannya.

Metaclasses dalam Python 3

Sintaks untuk mengatur metaclass telah diubah dengan Python 3:

class Foo(object, metaclass=something):
    ...

yaitu __metaclass__atribut tidak lagi digunakan, mendukung argumen kata kunci dalam daftar kelas dasar.

Namun perilaku metaclasses sebagian besar tetap sama .

Satu hal yang ditambahkan ke metaclasses dalam python 3 adalah Anda juga dapat meneruskan atribut sebagai argumen-kata kunci ke dalam metaclass, seperti:

class Foo(object, metaclass=something, kwarg1=value1, kwarg2=value2):
    ...

Baca bagian di bawah ini untuk cara python menangani ini.

Metaclasses khusus

Tujuan utama dari sebuah metaclass adalah untuk mengubah kelas secara otomatis, ketika itu dibuat.

Anda biasanya melakukan ini untuk API, di mana Anda ingin membuat kelas yang cocok dengan konteks saat ini.

Bayangkan sebuah contoh bodoh, di mana Anda memutuskan bahwa semua kelas dalam modul Anda harus memiliki atribut mereka ditulis dalam huruf besar. Ada beberapa cara untuk melakukan ini, tetapi satu cara adalah dengan mengatur __metaclass__di tingkat modul.

Dengan cara ini, semua kelas modul ini akan dibuat menggunakan metaclass ini, dan kita hanya perlu memberi tahu metaclass untuk mengubah semua atribut menjadi huruf besar.

Untungnya, __metaclass__sebenarnya bisa berupa panggilan apa pun, tidak perlu menjadi kelas formal (saya tahu, sesuatu dengan 'kelas' dalam namanya tidak perlu menjadi kelas, pikir angka ... tapi ini membantu).

Jadi kita akan mulai dengan contoh sederhana, dengan menggunakan fungsi.

# the metaclass will automatically get passed the same argument
# that you usually pass to `type`
def upper_attr(future_class_name, future_class_parents, future_class_attrs):
    """
      Return a class object, with the list of its attribute turned
      into uppercase.
    """
    # pick up any attribute that doesn't start with '__' and uppercase it
    uppercase_attrs = {
        attr if attr.startswith("__") else attr.upper(): v
        for attr, v in future_class_attrs.items()
    }

    # let `type` do the class creation
    return type(future_class_name, future_class_parents, uppercase_attrs)

__metaclass__ = upper_attr # this will affect all classes in the module

class Foo(): # global __metaclass__ won't work with "object" though
    # but we can define __metaclass__ here instead to affect only this class
    # and this will work with "object" children
    bar = 'bip'

Mari kita periksa:

>>> hasattr(Foo, 'bar')
False
>>> hasattr(Foo, 'BAR')
True
>>> Foo.BAR
'bip'

Sekarang, mari kita lakukan hal yang persis sama, tetapi menggunakan kelas nyata untuk metaclass:

# remember that `type` is actually a class like `str` and `int`
# so you can inherit from it
class UpperAttrMetaclass(type):
    # __new__ is the method called before __init__
    # it's the method that creates the object and returns it
    # while __init__ just initializes the object passed as parameter
    # you rarely use __new__, except when you want to control how the object
    # is created.
    # here the created object is the class, and we want to customize it
    # so we override __new__
    # you can do some stuff in __init__ too if you wish
    # some advanced use involves overriding __call__ as well, but we won't
    # see this
    def __new__(upperattr_metaclass, future_class_name,
                future_class_parents, future_class_attrs):
        uppercase_attrs = {
            attr if attr.startswith("__") else attr.upper(): v
            for attr, v in future_class_attrs.items()
        }
        return type(future_class_name, future_class_parents, uppercase_attrs)

Mari kita menulis ulang di atas, tetapi dengan nama variabel yang lebih pendek dan lebih realistis sekarang kita tahu apa artinya:

class UpperAttrMetaclass(type):
    def __new__(cls, clsname, bases, attrs):
        uppercase_attrs = {
            attr if attr.startswith("__") else attr.upper(): v
            for attr, v in attrs.items()
        }
        return type(clsname, bases, uppercase_attrs)

Anda mungkin telah memperhatikan argumen tambahan cls. Tidak ada yang istimewa tentang itu: __new__selalu menerima kelas yang didefinisikannya, sebagai parameter pertama. Sama seperti yang Anda miliki selfuntuk metode biasa yang menerima instance sebagai parameter pertama, atau kelas yang menentukan untuk metode kelas.

Tapi ini bukan OOP yang tepat. Kami menelepon typelangsung dan kami tidak mengabaikan atau memanggil orang tua __new__. Mari kita lakukan itu sebagai gantinya:

class UpperAttrMetaclass(type):
    def __new__(cls, clsname, bases, attrs):
        uppercase_attrs = {
            attr if attr.startswith("__") else attr.upper(): v
            for attr, v in attrs.items()
        }
        return type.__new__(cls, clsname, bases, uppercase_attrs)

Kita dapat membuatnya lebih bersih dengan menggunakan super, yang akan memudahkan pewarisan (karena ya, Anda dapat memiliki metaclasses, mewarisi dari metaclasses, mewarisi dari jenis):

class UpperAttrMetaclass(type):
    def __new__(cls, clsname, bases, attrs):
        uppercase_attrs = {
            attr if attr.startswith("__") else attr.upper(): v
            for attr, v in attrs.items()
        }
        return super(UpperAttrMetaclass, cls).__new__(
            cls, clsname, bases, uppercase_attrs)

Oh, dan dengan python 3 jika Anda melakukan panggilan ini dengan argumen kata kunci, seperti ini:

class Foo(object, metaclass=MyMetaclass, kwarg1=value1):
    ...

Ini diterjemahkan ke dalam metaclass untuk menggunakannya:

class MyMetaclass(type):
    def __new__(cls, clsname, bases, dct, kwargs1=default):
        ...

Itu dia. Benar-benar tidak ada lagi tentang metaclasses.

Alasan di balik kerumitan kode menggunakan metaclasses bukan karena metaclasses, itu karena Anda biasanya menggunakan metaclasses untuk melakukan hal-hal memutar yang mengandalkan introspeksi, memanipulasi warisan, vars seperti __dict__, dll.

Memang, metaclasses sangat berguna untuk melakukan ilmu hitam, dan karena itu hal-hal rumit. Tetapi dengan sendirinya, mereka sederhana:

  • mencegat ciptaan kelas
  • memodifikasi kelas
  • kembalikan kelas yang dimodifikasi

Mengapa Anda menggunakan kelas metaclasses alih-alih fungsi?

Karena __metaclass__dapat menerima callable, mengapa Anda menggunakan kelas karena itu jelas lebih rumit?

Ada beberapa alasan untuk melakukannya:

  • Niatnya jelas. Ketika Anda membaca UpperAttrMetaclass(type), Anda tahu apa yang akan diikuti
  • Anda dapat menggunakan OOP. Metaclass dapat mewarisi dari metaclass, menimpa metode induk. Metaclasses bahkan dapat menggunakan metaclasses.
  • Subclass dari suatu kelas akan menjadi instance dari metaclassnya jika Anda menentukan kelas metaclass, tetapi tidak dengan fungsi metaclass.
  • Anda dapat menyusun kode dengan lebih baik. Anda tidak pernah menggunakan metaclasses untuk sesuatu yang sepele seperti contoh di atas. Biasanya untuk sesuatu yang rumit. Memiliki kemampuan untuk membuat beberapa metode dan mengelompokkannya dalam satu kelas sangat berguna untuk membuat kode lebih mudah dibaca.
  • Anda dapat terhubung __new__, __init__dan __call__. Yang akan memungkinkan Anda melakukan hal-hal yang berbeda. Bahkan jika biasanya Anda bisa melakukan semuanya __new__, beberapa orang hanya lebih nyaman menggunakannya __init__.
  • Ini disebut metaclasses, sial! Pasti ada artinya!

Mengapa Anda menggunakan metaclasses?

Sekarang pertanyaan besar. Mengapa Anda menggunakan beberapa fitur rawan kesalahan yang tidak jelas?

Ya, biasanya Anda tidak:

Metaclasses adalah keajaiban yang lebih dalam yang tidak perlu dikhawatirkan oleh 99% pengguna. Jika Anda bertanya-tanya apakah Anda membutuhkannya, Anda tidak (orang-orang yang benar-benar membutuhkannya tahu dengan pasti bahwa mereka membutuhkannya, dan tidak memerlukan penjelasan tentang alasannya).

Python Guru Tim Peters

Kasing penggunaan utama untuk metaclass adalah membuat API. Contoh khas dari ini adalah Django ORM. Ini memungkinkan Anda untuk mendefinisikan sesuatu seperti ini:

class Person(models.Model):
    name = models.CharField(max_length=30)
    age = models.IntegerField()

Tetapi jika Anda melakukan ini:

person = Person(name='bob', age='35')
print(person.age)

Itu tidak akan mengembalikan IntegerFieldobjek. Ini akan mengembalikan int, dan bahkan dapat mengambilnya langsung dari database.

Ini dimungkinkan karena models.Modelmendefinisikan __metaclass__dan menggunakan beberapa sihir yang akan mengubah PersonAnda yang baru saja didefinisikan dengan pernyataan sederhana menjadi kait kompleks ke bidang database.

Django membuat sesuatu yang kompleks terlihat sederhana dengan mengekspos API sederhana dan menggunakan metaclasses, membuat ulang kode dari API ini untuk melakukan pekerjaan nyata di belakang layar.

Kata terakhir

Pertama, Anda tahu bahwa kelas adalah objek yang dapat membuat instance.

Sebenarnya, kelas itu sendiri adalah contoh. Dari metaclasses.

>>> class Foo(object): pass
>>> id(Foo)
142630324

Semuanya adalah objek dengan Python, dan semuanya adalah instance dari kelas atau instance dari metaclasses.

Kecuali untuk type.

typesebenarnya metaclass sendiri. Ini bukan sesuatu yang Anda dapat mereproduksi dalam Python murni, dan dilakukan dengan menipu sedikit di tingkat implementasi.

Kedua, metaclasses rumit. Anda mungkin tidak ingin menggunakannya untuk perubahan kelas yang sangat sederhana. Anda dapat mengubah kelas dengan menggunakan dua teknik berbeda:

99% dari waktu Anda membutuhkan perubahan kelas, Anda lebih baik menggunakan ini.

Tapi 98% dari waktu, Anda tidak perlu perubahan kelas sama sekali.

e-satis
sumber
30
Tampaknya di Django models.Modeltidak digunakan __metaclass__tetapi class Model(metaclass=ModelBase):untuk referensi ModelBasekelas yang kemudian melakukan sihir metaclass tersebut. Pos yang bagus! Inilah sumber Django: github.com/django/django/blob/master/django/db/models/…
Max Goodridge
15
<< Hati-hati di sini bahwa __metaclass__atribut tidak akan diwarisi, metaclass dari parent ( Bar.__class__) akan menjadi. Jika Barmenggunakan __metaclass__atribut yang dibuat Bardengan type()(dan tidak type.__new__()), subclass tidak akan mewarisi perilaku itu. >> - Bisakah Anda / seseorang tolong jelaskan sedikit lebih dalam bagian ini?
petrux
15
@ Maxgoodridge Itulah sintaks Python 3 untuk metaclasses. Lihat Python 3.6 Model data VS Python 2.7 Model data
TBBle
2
Now you wonder why the heck is it written in lowercase, and not Type?- baik karena diimplementasikan dalam C - itu adalah alasan yang sama defaultdict adalah huruf kecil sedangkan OrderedDict (dalam python 2) adalah CamelCase normal
Mr_and_Mrs_D
15
Ini adalah jawaban wiki komunitas (jadi, mereka yang berkomentar dengan koreksi / peningkatan mungkin mempertimbangkan untuk mengedit komentar mereka menjadi jawaban, jika mereka yakin itu benar).
Brōtsyorfuzthrāx
403

Catatan, jawaban ini untuk Python 2.x seperti yang ditulis pada 2008, metaclasses sedikit berbeda dalam 3.x.

Metaclasses adalah saus rahasia yang membuat 'kelas' bekerja. Metaclass default untuk objek gaya baru disebut 'type'.

class type(object)
  |  type(object) -> the object's type
  |  type(name, bases, dict) -> a new type

Metaclasses mengambil 3 args. ' nama ', ' pangkalan ' dan ' dict '

Di sinilah rahasianya dimulai. Cari dari mana nama, pangkalan dan dict berasal dari contoh definisi kelas ini.

class ThisIsTheName(Bases, Are, Here):
    All_the_code_here
    def doesIs(create, a):
        dict

Mari kita mendefinisikan metaclass yang akan menunjukkan bagaimana ' class: ' menyebutnya.

def test_metaclass(name, bases, dict):
    print 'The Class Name is', name
    print 'The Class Bases are', bases
    print 'The dict has', len(dict), 'elems, the keys are', dict.keys()

    return "yellow"

class TestName(object, None, int, 1):
    __metaclass__ = test_metaclass
    foo = 1
    def baz(self, arr):
        pass

print 'TestName = ', repr(TestName)

# output => 
The Class Name is TestName
The Class Bases are (<type 'object'>, None, <type 'int'>, 1)
The dict has 4 elems, the keys are ['baz', '__module__', 'foo', '__metaclass__']
TestName =  'yellow'

Dan sekarang, contoh yang benar-benar berarti sesuatu, ini akan secara otomatis membuat variabel dalam daftar "atribut" yang ditetapkan pada kelas, dan diatur ke Tidak ada.

def init_attributes(name, bases, dict):
    if 'attributes' in dict:
        for attr in dict['attributes']:
            dict[attr] = None

    return type(name, bases, dict)

class Initialised(object):
    __metaclass__ = init_attributes
    attributes = ['foo', 'bar', 'baz']

print 'foo =>', Initialised.foo
# output=>
foo => None

Perhatikan bahwa perilaku ajaib yang Initialisedmendapatkan dengan memiliki metaclass init_attributestidak diteruskan ke subkelas dari Initialised.

Berikut ini adalah contoh yang lebih konkret, yang menunjukkan bagaimana Anda dapat mensubklasifikasikan 'tipe' untuk membuat metaclass yang melakukan tindakan saat kelas dibuat. Ini cukup rumit:

class MetaSingleton(type):
    instance = None
    def __call__(cls, *args, **kw):
        if cls.instance is None:
            cls.instance = super(MetaSingleton, cls).__call__(*args, **kw)
        return cls.instance

class Foo(object):
    __metaclass__ = MetaSingleton

a = Foo()
b = Foo()
assert a is b
Jerub
sumber
169

Yang lain telah menjelaskan bagaimana metaclasses bekerja dan bagaimana mereka masuk ke dalam sistem tipe Python. Berikut ini contoh untuk apa mereka dapat digunakan. Dalam kerangka pengujian yang saya tulis, saya ingin melacak urutan di mana kelas didefinisikan, sehingga saya nanti bisa instantiate mereka dalam urutan ini. Saya merasa paling mudah untuk melakukan ini menggunakan metaclass.

class MyMeta(type):

    counter = 0

    def __init__(cls, name, bases, dic):
        type.__init__(cls, name, bases, dic)
        cls._order = MyMeta.counter
        MyMeta.counter += 1

class MyType(object):              # Python 2
    __metaclass__ = MyMeta

class MyType(metaclass=MyMeta):    # Python 3
    pass

Apa pun yang merupakan subkelas MyTypelalu mendapat atribut kelas _orderyang mencatat urutan di mana kelas didefinisikan.

baik hati
sumber
Terima kasih untuk contohnya. Mengapa Anda menemukan ini lebih mudah daripada mewarisi dari MyBase, __init__(self)kata siapa type(self)._order = MyBase.counter; MyBase.counter += 1?
Michael Gundlach
1
Saya ingin kelas-kelas itu sendiri, bukan instance mereka, diberi nomor.
Kindall
Benar ya Terima kasih. Kode saya akan mengatur ulang atribut MyType pada setiap instantiation, dan tidak akan pernah mengatur atribut jika instance MyType tidak pernah dibuat. Ups. (Dan properti kelas juga bisa berfungsi, tetapi tidak seperti metaclass, ia tidak menawarkan tempat yang jelas untuk menyimpan konter.)
Michael Gundlach
1
Ini adalah contoh yang sangat menarik, paling tidak karena orang dapat benar-benar melihat mengapa sebuah metaclass bisa diperlukan dengan ini, untuk menyediakan solusi untuk kesulitan tertentu. OTOH Saya berjuang untuk diyakinkan bahwa ada orang yang benar-benar perlu membuat instance objek dalam urutan di mana kelas mereka didefinisikan: Saya kira kita hanya perlu mengambil kata-kata Anda untuk itu :).
mike rodent
159

Salah satu penggunaan metaclasses adalah menambahkan properti dan metode baru ke instance secara otomatis.

Misalnya, jika Anda melihat model Django , definisi mereka terlihat sedikit membingungkan. Tampaknya Anda hanya mendefinisikan properti kelas:

class Person(models.Model):
    first_name = models.CharField(max_length=30)
    last_name = models.CharField(max_length=30)

Namun, pada saat runtime objek Person diisi dengan segala macam metode yang berguna. Lihat sumber untuk beberapa metaclassery yang menakjubkan.

Antti Rasinen
sumber
6
Bukankah penggunaan kelas meta menambahkan properti dan metode baru ke kelas dan bukan sebuah instance? Sejauh yang saya mengerti, kelas meta mengubah kelas itu sendiri dan sebagai hasilnya instance dapat dibangun secara berbeda oleh kelas yang diubah. Bisa jadi agak menyesatkan orang yang mencoba mendapatkan sifat meta class. Memiliki metode yang berguna pada instance dapat dicapai dengan bawaan normal. Referensi ke kode Django sebagai contoh bagus.
trixn
119

Saya pikir pengantar ONLamp untuk pemrograman metaclass ditulis dengan baik dan memberikan pengantar yang sangat baik untuk topik meskipun sudah beberapa tahun.

http://www.onlamp.com/pub/a/python/2003/04/17/metaclasses.html (diarsipkan di https://web.archive.org/web/20080206005253/http://www.onlamp. com / pub / a / python / 2003/04/17 / metaclasses.html )

Singkatnya: Kelas adalah cetak biru untuk pembuatan instance, metaclass adalah cetak biru untuk pembuatan kelas. Dapat dengan mudah dilihat bahwa dalam kelas Python perlu objek kelas satu juga untuk mengaktifkan perilaku ini.

Saya tidak pernah menulis sendiri, tapi saya pikir salah satu penggunaan terbaik dari metaclasses dapat dilihat dalam kerangka Django . Kelas model menggunakan pendekatan metaclass untuk memungkinkan gaya deklaratif menulis model baru atau kelas bentuk. Sementara metaclass menciptakan kelas, semua anggota mendapatkan kemungkinan untuk menyesuaikan kelas itu sendiri.

Hal yang tersisa untuk dikatakan adalah: Jika Anda tidak tahu apa itu metaclasses, probabilitas bahwa Anda tidak akan membutuhkannya adalah 99%.

Matthias Kestenholz
sumber
109

Apa itu metaclasses? Untuk apa Anda menggunakannya?

TLDR: Sebuah metaclass instantiate dan mendefinisikan perilaku untuk kelas seperti halnya kelas instantiate dan mendefinisikan perilaku untuk sebuah instance.

Kodesemu:

>>> Class(...)
instance

Di atas seharusnya terlihat akrab. Nah, dari mana datangnya Class? Ini adalah instance dari metaclass (juga pseudocode):

>>> Metaclass(...)
Class

Dalam kode nyata, kita dapat melewatkan metaclass default type,, semua yang kita butuhkan untuk membuat instance sebuah kelas dan kita mendapatkan sebuah kelas:

>>> type('Foo', (object,), {}) # requires a name, bases, and a namespace
<class '__main__.Foo'>

Secara berbeda

  • Kelas adalah instance sebagai metaclass adalah kelas.

    Ketika kita instantiate objek, kita mendapatkan instance:

    >>> object()                          # instantiation of class
    <object object at 0x7f9069b4e0b0>     # instance

    Demikian juga, ketika kita mendefinisikan kelas secara eksplisit dengan metaclass default type,, kita instantiate:

    >>> type('Object', (object,), {})     # instantiation of metaclass
    <class '__main__.Object'>             # instance
  • Dengan kata lain, kelas adalah turunan dari metaclass:

    >>> isinstance(object, type)
    True
  • Dengan kata lain, metaclass adalah kelas kelas.

    >>> type(object) == type
    True
    >>> object.__class__
    <class 'type'>

Ketika Anda menulis definisi kelas dan Python mengeksekusi itu, ia menggunakan metaclass untuk instantiate objek kelas (yang, pada gilirannya, akan digunakan untuk instantiate instance dari kelas itu).

Seperti halnya kita dapat menggunakan definisi kelas untuk mengubah bagaimana instance objek kustom berperilaku, kita dapat menggunakan definisi kelas metaclass untuk mengubah cara objek kelas berperilaku.

Untuk apa mereka digunakan? Dari dokumen :

Penggunaan potensial untuk metaclasses tidak terbatas. Beberapa ide yang telah dieksplorasi termasuk penebangan, pemeriksaan antarmuka, pendelegasian otomatis, pembuatan properti otomatis, proksi, kerangka kerja, dan penguncian / sinkronisasi sumber daya otomatis.

Namun demikian, biasanya disarankan bagi pengguna untuk menghindari penggunaan metaclasses kecuali benar-benar diperlukan.

Anda menggunakan metaclass setiap kali Anda membuat kelas:

Ketika Anda menulis definisi kelas, misalnya, seperti ini,

class Foo(object): 
    'demo'

Anda instantiate objek kelas.

>>> Foo
<class '__main__.Foo'>
>>> isinstance(Foo, type), isinstance(Foo, object)
(True, True)

Ini sama dengan pemanggilan fungsional typedengan argumen yang sesuai dan menugaskan hasilnya ke variabel nama itu:

name = 'Foo'
bases = (object,)
namespace = {'__doc__': 'demo'}
Foo = type(name, bases, namespace)

Catatan, beberapa hal secara otomatis ditambahkan ke __dict__, yaitu, namespace:

>>> Foo.__dict__
dict_proxy({'__dict__': <attribute '__dict__' of 'Foo' objects>, 
'__module__': '__main__', '__weakref__': <attribute '__weakref__' 
of 'Foo' objects>, '__doc__': 'demo'})

The metaclass dari objek yang kita buat, dalam kedua kasus, adalah type.

(Catatan samping pada isi kelas __dict__: __module__ada karena kelas harus tahu di mana mereka didefinisikan, dan __dict__dan __weakref__ada di sana karena kita tidak mendefinisikan __slots__- jika kita mendefinisikan__slots__ kita akan menghemat sedikit ruang dalam instance, seperti kita dapat melarang __dict__dan __weakref__dengan mengecualikannya. Misalnya:

>>> Baz = type('Bar', (object,), {'__doc__': 'demo', '__slots__': ()})
>>> Baz.__dict__
mappingproxy({'__doc__': 'demo', '__slots__': (), '__module__': '__main__'})

... tapi saya ngelantur.)

Kita dapat memperluas typeseperti definisi kelas lainnya:

Inilah standar __repr__kelas:

>>> Foo
<class '__main__.Foo'>

Salah satu hal paling berharga yang dapat kita lakukan secara default dalam menulis objek Python adalah menyediakannya dengan yang baik __repr__. Ketika kami menelepon, help(repr)kami belajar bahwa ada tes yang bagus untuk __repr__itu yang juga membutuhkan tes untuk kesetaraan - obj == eval(repr(obj)). Implementasi sederhana berikut __repr__dan __eq__untuk instance kelas dari tipe kelas kami memberi kami demonstrasi yang dapat meningkatkan pada default __repr__kelas:

class Type(type):
    def __repr__(cls):
        """
        >>> Baz
        Type('Baz', (Foo, Bar,), {'__module__': '__main__', '__doc__': None})
        >>> eval(repr(Baz))
        Type('Baz', (Foo, Bar,), {'__module__': '__main__', '__doc__': None})
        """
        metaname = type(cls).__name__
        name = cls.__name__
        parents = ', '.join(b.__name__ for b in cls.__bases__)
        if parents:
            parents += ','
        namespace = ', '.join(': '.join(
          (repr(k), repr(v) if not isinstance(v, type) else v.__name__))
               for k, v in cls.__dict__.items())
        return '{0}(\'{1}\', ({2}), {{{3}}})'.format(metaname, name, parents, namespace)
    def __eq__(cls, other):
        """
        >>> Baz == eval(repr(Baz))
        True            
        """
        return (cls.__name__, cls.__bases__, cls.__dict__) == (
                other.__name__, other.__bases__, other.__dict__)

Jadi sekarang ketika kita membuat objek dengan metaclass ini, __repr__gema pada baris perintah memberikan tampilan yang jauh lebih jelek daripada default:

>>> class Bar(object): pass
>>> Baz = Type('Baz', (Foo, Bar,), {'__module__': '__main__', '__doc__': None})
>>> Baz
Type('Baz', (Foo, Bar,), {'__module__': '__main__', '__doc__': None})

Dengan __repr__definisi yang bagus untuk instance kelas, kami memiliki kemampuan yang lebih kuat untuk men-debug kode kami. Namun, pemeriksaan lebih lanjut dengan eval(repr(Class))tidak mungkin (karena fungsi agak tidak mungkin untuk eval dari standarnya __repr__).

Penggunaan yang diharapkan: __prepare__namespace

Jika, misalnya, kita ingin tahu bagaimana urutan metode kelas dibuat, kita bisa memberikan perintah dict sebagai namespace kelas. Kami akan melakukan ini dengan __prepare__yang mengembalikan dict namespace untuk kelas jika diimplementasikan dalam Python 3 :

from collections import OrderedDict

class OrderedType(Type):
    @classmethod
    def __prepare__(metacls, name, bases, **kwargs):
        return OrderedDict()
    def __new__(cls, name, bases, namespace, **kwargs):
        result = Type.__new__(cls, name, bases, dict(namespace))
        result.members = tuple(namespace)
        return result

Dan penggunaan:

class OrderedMethodsObject(object, metaclass=OrderedType):
    def method1(self): pass
    def method2(self): pass
    def method3(self): pass
    def method4(self): pass

Dan sekarang kita memiliki catatan urutan metode ini (dan atribut kelas lainnya) dibuat:

>>> OrderedMethodsObject.members
('__module__', '__qualname__', 'method1', 'method2', 'method3', 'method4')

Catatan, contoh ini diadaptasi dari dokumentasi - enum baru di perpustakaan standar melakukan ini.

Jadi yang kami lakukan adalah membuat sebuah metaclass dengan membuat kelas. Kita juga bisa memperlakukan metaclass seperti halnya kelas lainnya. Ini memiliki urutan resolusi metode:

>>> inspect.getmro(OrderedType)
(<class '__main__.OrderedType'>, <class '__main__.Type'>, <class 'type'>, <class 'object'>)

Dan memiliki kira-kira yang benar repr(yang kita tidak bisa lagi eval kecuali kita dapat menemukan cara untuk mewakili fungsi kita.):

>>> OrderedMethodsObject
OrderedType('OrderedMethodsObject', (object,), {'method1': <function OrderedMethodsObject.method1 at 0x0000000002DB01E0>, 'members': ('__module__', '__qualname__', 'method1', 'method2', 'method3', 'method4'), 'method3': <function OrderedMet
hodsObject.method3 at 0x0000000002DB02F0>, 'method2': <function OrderedMethodsObject.method2 at 0x0000000002DB0268>, '__module__': '__main__', '__weakref__': <attribute '__weakref__' of 'OrderedMethodsObject' objects>, '__doc__': None, '__d
ict__': <attribute '__dict__' of 'OrderedMethodsObject' objects>, 'method4': <function OrderedMethodsObject.method4 at 0x0000000002DB0378>})
Aaron Hall
sumber
78

Pembaruan Python 3

Ada (pada titik ini) dua metode utama dalam sebuah metaclass:

  • __prepare__, dan
  • __new__

__prepare__memungkinkan Anda menyediakan pemetaan khusus (seperti a OrderedDict) untuk digunakan sebagai namespace saat kelas sedang dibuat. Anda harus mengembalikan instance namespace apa pun yang Anda pilih. Jika Anda tidak menerapkan __prepare__normal dictdigunakan.

__new__ bertanggung jawab atas pembuatan / modifikasi aktual dari kelas akhir.

Metaclass tanpa tulang, tidak melakukan apa-apa ekstra akan suka:

class Meta(type):

    def __prepare__(metaclass, cls, bases):
        return dict()

    def __new__(metacls, cls, bases, clsdict):
        return super().__new__(metacls, cls, bases, clsdict)

Contoh sederhana:

Katakanlah Anda ingin kode validasi sederhana dijalankan pada atribut Anda - seperti itu harus selalu berupa a intatau a str. Tanpa metaclass, kelas Anda akan terlihat seperti:

class Person:
    weight = ValidateType('weight', int)
    age = ValidateType('age', int)
    name = ValidateType('name', str)

Seperti yang Anda lihat, Anda harus mengulangi nama atribut dua kali. Hal ini memungkinkan kesalahan ketik bersama dengan bug menjengkelkan.

Metaclass sederhana dapat mengatasi masalah itu:

class Person(metaclass=Validator):
    weight = ValidateType(int)
    age = ValidateType(int)
    name = ValidateType(str)

Inilah yang akan terlihat seperti metaclass (tidak digunakan __prepare__karena tidak diperlukan):

class Validator(type):
    def __new__(metacls, cls, bases, clsdict):
        # search clsdict looking for ValidateType descriptors
        for name, attr in clsdict.items():
            if isinstance(attr, ValidateType):
                attr.name = name
                attr.attr = '_' + name
        # create final class and return it
        return super().__new__(metacls, cls, bases, clsdict)

Contoh menjalankan:

p = Person()
p.weight = 9
print(p.weight)
p.weight = '9'

menghasilkan:

9
Traceback (most recent call last):
  File "simple_meta.py", line 36, in <module>
    p.weight = '9'
  File "simple_meta.py", line 24, in __set__
    (self.name, self.type, value))
TypeError: weight must be of type(s) <class 'int'> (got '9')

Catatan : Contoh ini cukup sederhana, bisa juga dilakukan dengan dekorator kelas, tetapi mungkin metaclass yang sebenarnya akan melakukan lebih banyak.

Kelas 'ValidateType' untuk referensi:

class ValidateType:
    def __init__(self, type):
        self.name = None  # will be set by metaclass
        self.attr = None  # will be set by metaclass
        self.type = type
    def __get__(self, inst, cls):
        if inst is None:
            return self
        else:
            return inst.__dict__[self.attr]
    def __set__(self, inst, value):
        if not isinstance(value, self.type):
            raise TypeError('%s must be of type(s) %s (got %r)' %
                    (self.name, self.type, value))
        else:
            inst.__dict__[self.attr] = value
Ethan Furman
sumber
Wow, ini adalah fitur baru yang luar biasa yang saya tidak tahu ada di Python 3. Terima kasih atas contohnya !!
Rich Lysakowski PhD
Perhatikan bahwa sejak python 3.6, Anda bisa menggunakan __set_name__(cls, name)di deskriptor ( ValidateType) untuk mengatur nama di deskriptor ( self.namedan dalam kasus ini juga self.attr). Ini ditambahkan ke tidak harus menyelam ke dalam metaclasses untuk kasus penggunaan umum khusus ini (lihat PEP 487).
Lars
68

Peran __call__()metode metaclass ' saat membuat instance kelas

Jika Anda telah melakukan pemrograman Python selama lebih dari beberapa bulan, Anda akhirnya akan menemukan kode yang terlihat seperti ini:

# define a class
class SomeClass(object):
    # ...
    # some definition here ...
    # ...

# create an instance of it
instance = SomeClass()

# then call the object as if it's a function
result = instance('foo', 'bar')

Yang terakhir dimungkinkan ketika Anda menerapkan __call__()metode ajaib di kelas.

class SomeClass(object):
    # ...
    # some definition here ...
    # ...

    def __call__(self, foo, bar):
        return bar + foo

The __call__()Metode dipanggil saat sebuah instance dari kelas yang digunakan sebagai callable. Tetapi seperti yang telah kita lihat dari jawaban sebelumnya, kelas itu sendiri adalah turunan dari metaclass, jadi ketika kita menggunakan kelas sebagai callable (yaitu ketika kita membuat instance dari itu) kita sebenarnya memanggil __call__()metode metaclassnya . Pada titik ini sebagian besar programmer Python agak bingung karena mereka telah diberitahu bahwa ketika membuat instance seperti ini, instance = SomeClass()Anda memanggil __init__()metodenya. Beberapa yang sudah digali sedikit lebih tahu bahwa sebelum __init__()ada __new__(). Nah, hari ini lapisan kebenaran lain sedang diungkapkan, sebelum __new__()ada metaclass ' __call__().

Mari kita pelajari metode panggilan rantai dari perspektif khusus membuat instance kelas.

Ini adalah metaclass yang mencatat tepat saat sebelum instance dibuat dan saat itu akan mengembalikannya.

class Meta_1(type):
    def __call__(cls):
        print "Meta_1.__call__() before creating an instance of ", cls
        instance = super(Meta_1, cls).__call__()
        print "Meta_1.__call__() about to return instance."
        return instance

Ini adalah kelas yang menggunakan metaclass itu

class Class_1(object):

    __metaclass__ = Meta_1

    def __new__(cls):
        print "Class_1.__new__() before creating an instance."
        instance = super(Class_1, cls).__new__(cls)
        print "Class_1.__new__() about to return instance."
        return instance

    def __init__(self):
        print "entering Class_1.__init__() for instance initialization."
        super(Class_1,self).__init__()
        print "exiting Class_1.__init__()."

Dan sekarang mari kita buat instance dari Class_1

instance = Class_1()
# Meta_1.__call__() before creating an instance of <class '__main__.Class_1'>.
# Class_1.__new__() before creating an instance.
# Class_1.__new__() about to return instance.
# entering Class_1.__init__() for instance initialization.
# exiting Class_1.__init__().
# Meta_1.__call__() about to return instance.

Perhatikan bahwa kode di atas tidak benar-benar melakukan apa pun selain mencatat tugas. Setiap metode mendelegasikan pekerjaan aktual untuk implementasi orang tuanya, sehingga menjaga perilaku default. Sejak typeadalah Meta_1's kelas induk ( typemenjadi default orangtua metaclass) dan mempertimbangkan urutan urutan output di atas, kita sekarang memiliki petunjuk mengenai apa yang akan menjadi pelaksanaan semu dari type.__call__():

class type:
    def __call__(cls, *args, **kwarg):

        # ... maybe a few things done to cls here

        # then we call __new__() on the class to create an instance
        instance = cls.__new__(cls, *args, **kwargs)

        # ... maybe a few things done to the instance here

        # then we initialize the instance with its __init__() method
        instance.__init__(*args, **kwargs)

        # ... maybe a few more things done to instance here

        # then we return it
        return instance

Kita dapat melihat bahwa metode metaclass ' __call__()adalah yang disebut pertama. Ini kemudian mendelegasikan pembuatan instance ke metode kelas __new__()dan inisialisasi ke instance __init__(). Itu juga yang akhirnya mengembalikan instance.

Dari __call__()penjelasan di atas, tampaknya metaclass ' juga diberikan kesempatan untuk memutuskan apakah panggilan ke Class_1.__new__()atau Class_1.__init__()pada akhirnya akan dilakukan. Selama pelaksanaannya itu benar-benar bisa mengembalikan objek yang belum tersentuh oleh salah satu dari metode ini. Ambil contoh pendekatan ini pada pola tunggal:

class Meta_2(type):
    singletons = {}

    def __call__(cls, *args, **kwargs):
        if cls in Meta_2.singletons:
            # we return the only instance and skip a call to __new__()
            # and __init__()
            print ("{} singleton returning from Meta_2.__call__(), "
                   "skipping creation of new instance.".format(cls))
            return Meta_2.singletons[cls]

        # else if the singleton isn't present we proceed as usual
        print "Meta_2.__call__() before creating an instance."
        instance = super(Meta_2, cls).__call__(*args, **kwargs)
        Meta_2.singletons[cls] = instance
        print "Meta_2.__call__() returning new instance."
        return instance

class Class_2(object):

    __metaclass__ = Meta_2

    def __new__(cls, *args, **kwargs):
        print "Class_2.__new__() before creating instance."
        instance = super(Class_2, cls).__new__(cls)
        print "Class_2.__new__() returning instance."
        return instance

    def __init__(self, *args, **kwargs):
        print "entering Class_2.__init__() for initialization."
        super(Class_2, self).__init__()
        print "exiting Class_2.__init__()."

Mari kita amati apa yang terjadi ketika berulang kali mencoba membuat objek tipe Class_2

a = Class_2()
# Meta_2.__call__() before creating an instance.
# Class_2.__new__() before creating instance.
# Class_2.__new__() returning instance.
# entering Class_2.__init__() for initialization.
# exiting Class_2.__init__().
# Meta_2.__call__() returning new instance.

b = Class_2()
# <class '__main__.Class_2'> singleton returning from Meta_2.__call__(), skipping creation of new instance.

c = Class_2()
# <class '__main__.Class_2'> singleton returning from Meta_2.__call__(), skipping creation of new instance.

a is b is c # True
Michael Ekoka
sumber
Ini adalah tambahan yang bagus untuk "jawaban yang diterima" yang sebelumnya telah diperbarui. Ini memberikan contoh untuk coders menengah untuk dikunyah.
Rich Lysakowski PhD
56

Metaclass adalah kelas yang memberi tahu bagaimana (beberapa) kelas lain harus dibuat.

Ini adalah kasus di mana saya melihat metaclass sebagai solusi untuk masalah saya: Saya punya masalah yang sangat rumit, yang mungkin bisa diselesaikan secara berbeda, tetapi saya memilih untuk menyelesaikannya menggunakan metaclass. Karena kerumitannya, ini adalah salah satu dari beberapa modul yang telah saya tulis di mana komentar dalam modul melampaui jumlah kode yang telah ditulis. Ini dia...

#!/usr/bin/env python

# Copyright (C) 2013-2014 Craig Phillips.  All rights reserved.

# This requires some explaining.  The point of this metaclass excercise is to
# create a static abstract class that is in one way or another, dormant until
# queried.  I experimented with creating a singlton on import, but that did
# not quite behave how I wanted it to.  See now here, we are creating a class
# called GsyncOptions, that on import, will do nothing except state that its
# class creator is GsyncOptionsType.  This means, docopt doesn't parse any
# of the help document, nor does it start processing command line options.
# So importing this module becomes really efficient.  The complicated bit
# comes from requiring the GsyncOptions class to be static.  By that, I mean
# any property on it, may or may not exist, since they are not statically
# defined; so I can't simply just define the class with a whole bunch of
# properties that are @property @staticmethods.
#
# So here's how it works:
#
# Executing 'from libgsync.options import GsyncOptions' does nothing more
# than load up this module, define the Type and the Class and import them
# into the callers namespace.  Simple.
#
# Invoking 'GsyncOptions.debug' for the first time, or any other property
# causes the __metaclass__ __getattr__ method to be called, since the class
# is not instantiated as a class instance yet.  The __getattr__ method on
# the type then initialises the class (GsyncOptions) via the __initialiseClass
# method.  This is the first and only time the class will actually have its
# dictionary statically populated.  The docopt module is invoked to parse the
# usage document and generate command line options from it.  These are then
# paired with their defaults and what's in sys.argv.  After all that, we
# setup some dynamic properties that could not be defined by their name in
# the usage, before everything is then transplanted onto the actual class
# object (or static class GsyncOptions).
#
# Another piece of magic, is to allow command line options to be set in
# in their native form and be translated into argparse style properties.
#
# Finally, the GsyncListOptions class is actually where the options are
# stored.  This only acts as a mechanism for storing options as lists, to
# allow aggregation of duplicate options or options that can be specified
# multiple times.  The __getattr__ call hides this by default, returning the
# last item in a property's list.  However, if the entire list is required,
# calling the 'list()' method on the GsyncOptions class, returns a reference
# to the GsyncListOptions class, which contains all of the same properties
# but as lists and without the duplication of having them as both lists and
# static singlton values.
#
# So this actually means that GsyncOptions is actually a static proxy class...
#
# ...And all this is neatly hidden within a closure for safe keeping.
def GetGsyncOptionsType():
    class GsyncListOptions(object):
        __initialised = False

    class GsyncOptionsType(type):
        def __initialiseClass(cls):
            if GsyncListOptions._GsyncListOptions__initialised: return

            from docopt import docopt
            from libgsync.options import doc
            from libgsync import __version__

            options = docopt(
                doc.__doc__ % __version__,
                version = __version__,
                options_first = True
            )

            paths = options.pop('<path>', None)
            setattr(cls, "destination_path", paths.pop() if paths else None)
            setattr(cls, "source_paths", paths)
            setattr(cls, "options", options)

            for k, v in options.iteritems():
                setattr(cls, k, v)

            GsyncListOptions._GsyncListOptions__initialised = True

        def list(cls):
            return GsyncListOptions

        def __getattr__(cls, name):
            cls.__initialiseClass()
            return getattr(GsyncListOptions, name)[-1]

        def __setattr__(cls, name, value):
            # Substitut option names: --an-option-name for an_option_name
            import re
            name = re.sub(r'^__', "", re.sub(r'-', "_", name))
            listvalue = []

            # Ensure value is converted to a list type for GsyncListOptions
            if isinstance(value, list):
                if value:
                    listvalue = [] + value
                else:
                    listvalue = [ None ]
            else:
                listvalue = [ value ]

            type.__setattr__(GsyncListOptions, name, listvalue)

    # Cleanup this module to prevent tinkering.
    import sys
    module = sys.modules[__name__]
    del module.__dict__['GetGsyncOptionsType']

    return GsyncOptionsType

# Our singlton abstract proxy class.
class GsyncOptions(object):
    __metaclass__ = GetGsyncOptionsType()
Craig
sumber
43

Versi tl; dr

The type(obj)Fungsi membuat Anda jenis objek.

The type()kelas adalah yang metaclass .

Untuk menggunakan metaclass:

class Foo(object):
    __metaclass__ = MyMetaClass

typeadalah metaclass sendiri. Kelas kelas adalah metaclass - tubuh kelas adalah argumen yang diteruskan ke metaclass yang digunakan untuk membangun kelas.

Di sini Anda dapat membaca tentang cara menggunakan metaclasses untuk menyesuaikan konstruksi kelas.

noɥʇʎԀʎzɐɹƆ
sumber
42

typesebenarnya metaclass- kelas yang menciptakan kelas lain. Sebagian besar metaclassadalah subclass dari type. The metaclassmenerima newkelas sebagai argumen pertama dan menyediakan akses ke objek kelas dengan perincian seperti yang disebutkan di bawah ini:

>>> class MetaClass(type):
...     def __init__(cls, name, bases, attrs):
...         print ('class name: %s' %name )
...         print ('Defining class %s' %cls)
...         print('Bases %s: ' %bases)
...         print('Attributes')
...         for (name, value) in attrs.items():
...             print ('%s :%r' %(name, value))
... 

>>> class NewClass(object, metaclass=MetaClass):
...    get_choch='dairy'
... 
class name: NewClass
Bases <class 'object'>: 
Defining class <class 'NewClass'>
get_choch :'dairy'
__module__ :'builtins'
__qualname__ :'NewClass'

Note:

Perhatikan bahwa kelas tidak dipakai setiap saat; tindakan sederhana menciptakan kelas memicu eksekusi metaclass.

Mushahid Khan
sumber
27

Kelas python adalah objek itu sendiri - sebagai contoh - dari meta-class mereka.

Metaclass default, yang diterapkan ketika ketika Anda menentukan kelas sebagai:

class foo:
    ...

kelas meta digunakan untuk menerapkan beberapa aturan ke seluruh rangkaian kelas. Misalnya, anggap Anda sedang membangun ORM untuk mengakses database, dan Anda ingin catatan dari setiap tabel dari kelas yang dipetakan ke tabel itu (berdasarkan bidang, aturan bisnis, dll.,), Kemungkinan penggunaan metaclass adalah misalnya, koneksi pool logic, yang dibagikan oleh semua kelas catatan dari semua tabel. Penggunaan lain adalah logika untuk mendukung kunci asing, yang melibatkan beberapa kelas catatan.

ketika Anda mendefinisikan metaclass, Anda mengetikkan subclass, dan dapat mengganti metode ajaib berikut untuk memasukkan logika Anda.

class somemeta(type):
    __new__(mcs, name, bases, clsdict):
      """
  mcs: is the base metaclass, in this case type.
  name: name of the new class, as provided by the user.
  bases: tuple of base classes 
  clsdict: a dictionary containing all methods and attributes defined on class

  you must return a class object by invoking the __new__ constructor on the base metaclass. 
 ie: 
    return type.__call__(mcs, name, bases, clsdict).

  in the following case:

  class foo(baseclass):
        __metaclass__ = somemeta

  an_attr = 12

  def bar(self):
      ...

  @classmethod
  def foo(cls):
      ...

      arguments would be : ( somemeta, "foo", (baseclass, baseofbase,..., object), {"an_attr":12, "bar": <function>, "foo": <bound class method>}

      you can modify any of these values before passing on to type
      """
      return type.__call__(mcs, name, bases, clsdict)


    def __init__(self, name, bases, clsdict):
      """ 
      called after type has been created. unlike in standard classes, __init__ method cannot modify the instance (cls) - and should be used for class validaton.
      """
      pass


    def __prepare__():
        """
        returns a dict or something that can be used as a namespace.
        the type will then attach methods and attributes from class definition to it.

        call order :

        somemeta.__new__ ->  type.__new__ -> type.__init__ -> somemeta.__init__ 
        """
        return dict()

    def mymethod(cls):
        """ works like a classmethod, but for class objects. Also, my method will not be visible to instances of cls.
        """
        pass

Bagaimanapun, keduanya adalah kait yang paling umum digunakan. metaclassing sangat kuat, dan di atas tidak ada daftar dekat dan lengkap kegunaan untuk metaclassing.

Xingzhou Liu
sumber
21

Fungsi type () dapat mengembalikan tipe objek atau membuat tipe baru,

sebagai contoh, kita dapat membuat kelas Hi dengan fungsi type () dan tidak perlu menggunakan cara ini dengan kelas Hi (objek):

def func(self, name='mike'):
    print('Hi, %s.' % name)

Hi = type('Hi', (object,), dict(hi=func))
h = Hi()
h.hi()
Hi, mike.

type(Hi)
type

type(h)
__main__.Hi

Selain menggunakan tipe () untuk membuat kelas secara dinamis, Anda bisa mengontrol perilaku pembuatan kelas dan menggunakan metaclass.

Menurut model objek Python, kelas adalah objek, sehingga kelas harus merupakan turunan dari kelas tertentu lainnya. Secara default, kelas Python adalah turunan dari kelas tipe. Yaitu, tipe adalah metaclass dari sebagian besar kelas bawaan dan metaclass dari kelas yang ditentukan pengguna.

class ListMetaclass(type):
    def __new__(cls, name, bases, attrs):
        attrs['add'] = lambda self, value: self.append(value)
        return type.__new__(cls, name, bases, attrs)

class CustomList(list, metaclass=ListMetaclass):
    pass

lst = CustomList()
lst.add('custom_list_1')
lst.add('custom_list_2')

lst
['custom_list_1', 'custom_list_2']

Magic akan berlaku ketika kita memberikan argumen kata kunci dalam metaclass, ini menunjukkan interpreter Python untuk membuat CustomList melalui ListMetaclass. new (), pada titik ini, kita dapat memodifikasi definisi kelas, misalnya, dan menambahkan metode baru dan kemudian mengembalikan definisi yang direvisi.

binbjz
sumber
11

Selain jawaban yang dipublikasikan saya dapat mengatakan bahwa a metaclassmendefinisikan perilaku untuk kelas. Jadi, Anda dapat secara eksplisit mengatur metaclass Anda. Setiap kali Python mendapatkan kata kunci classmaka itu mulai mencari metaclass. Jika tidak ditemukan - tipe metaclass default digunakan untuk membuat objek kelas. Menggunakan __metaclass__atribut, Anda dapat mengatur metaclasskelas Anda:

class MyClass:
   __metaclass__ = type
   # write here other method
   # write here one more method

print(MyClass.__metaclass__)

Ini akan menghasilkan output seperti ini:

class 'type'

Dan, tentu saja, Anda dapat membuat Anda sendiri metaclassuntuk mendefinisikan perilaku kelas apa pun yang dibuat menggunakan kelas Anda.

Untuk melakukan itu, metaclasskelas tipe default Anda harus diwarisi karena ini adalah yang utama metaclass:

class MyMetaClass(type):
   __metaclass__ = type
   # you can write here any behaviour you want

class MyTestClass:
   __metaclass__ = MyMetaClass

Obj = MyTestClass()
print(Obj.__metaclass__)
print(MyMetaClass.__metaclass__)

Outputnya adalah:

class '__main__.MyMetaClass'
class 'type'
Andy
sumber
4

Dalam pemrograman berorientasi objek, metaclass adalah kelas yang instansnya adalah kelas. Sama seperti kelas biasa mendefinisikan perilaku objek tertentu, metaclass mendefinisikan perilaku kelas tertentu dan contohnya. Istilah metaclass berarti sesuatu yang digunakan untuk membuat kelas. Dengan kata lain, itu adalah kelas suatu kelas. Metaclass digunakan untuk membuat kelas jadi seperti objek menjadi turunan dari kelas, kelas adalah turunan dari metaclass. Dalam kelas python juga dianggap objek.

Venu Gopal Tewari
sumber
Daripada memberikan definisi kutu buku, akan lebih baik jika Anda menambahkan beberapa contoh. Baris pertama jawaban Anda tampaknya telah disalin dari entri Metaclasses Wikipedia.
verisimilitude
@verisimilitude Saya juga belajar, bisakah Anda membantu saya meningkatkan jawaban ini dengan memberikan beberapa contoh praktis dari pengalaman Anda ??
Venu Gopal Tewari
2

Berikut ini contoh lain dari apa yang dapat digunakan untuk:

  • Anda dapat menggunakan metaclassuntuk mengubah fungsi instance-nya (kelas).
class MetaMemberControl(type):
    __slots__ = ()

    @classmethod
    def __prepare__(mcs, f_cls_name, f_cls_parents,  # f_cls means: future class
                    meta_args=None, meta_options=None):  # meta_args and meta_options is not necessarily needed, just so you know.
        f_cls_attr = dict()
        if not "do something or if you want to define your cool stuff of dict...":
            return dict(make_your_special_dict=None)
        else:
            return f_cls_attr

    def __new__(mcs, f_cls_name, f_cls_parents, f_cls_attr,
                meta_args=None, meta_options=None):

        original_getattr = f_cls_attr.get('__getattribute__')
        original_setattr = f_cls_attr.get('__setattr__')

        def init_getattr(self, item):
            if not item.startswith('_'):  # you can set break points at here
                alias_name = '_' + item
                if alias_name in f_cls_attr['__slots__']:
                    item = alias_name
            if original_getattr is not None:
                return original_getattr(self, item)
            else:
                return super(eval(f_cls_name), self).__getattribute__(item)

        def init_setattr(self, key, value):
            if not key.startswith('_') and ('_' + key) in f_cls_attr['__slots__']:
                raise AttributeError(f"you can't modify private members:_{key}")
            if original_setattr is not None:
                original_setattr(self, key, value)
            else:
                super(eval(f_cls_name), self).__setattr__(key, value)

        f_cls_attr['__getattribute__'] = init_getattr
        f_cls_attr['__setattr__'] = init_setattr

        cls = super().__new__(mcs, f_cls_name, f_cls_parents, f_cls_attr)
        return cls


class Human(metaclass=MetaMemberControl):
    __slots__ = ('_age', '_name')

    def __init__(self, name, age):
        self._name = name
        self._age = age

    def __getattribute__(self, item):
        """
        is just for IDE recognize.
        """
        return super().__getattribute__(item)

    """ with MetaMemberControl then you don't have to write as following
    @property
    def name(self):
        return self._name

    @property
    def age(self):
        return self._age
    """


def test_demo():
    human = Human('Carson', 27)
    # human.age = 18  # you can't modify private members:_age  <-- this is defined by yourself.
    # human.k = 18  # 'Human' object has no attribute 'k'  <-- system error.
    age1 = human._age  # It's OK, although the IDE will show some warnings. (Access to a protected member _age of a class)

    age2 = human.age  # It's OK! see below:
    """
    if you do not define `__getattribute__` at the class of Human,
    the IDE will show you: Unresolved attribute reference 'age' for class 'Human'
    but it's ok on running since the MetaMemberControl will help you.
    """


if __name__ == '__main__':
    test_demo()

Yang metaclasskuat, ada banyak hal (seperti sihir monyet) yang bisa Anda lakukan dengan itu, tapi hati-hati ini mungkin hanya diketahui oleh Anda.

Carson
sumber
2

Kelas, dengan Python, adalah objek, dan seperti objek lainnya, itu adalah turunan dari "sesuatu". "Sesuatu" ini adalah apa yang disebut sebagai Metaclass. Metaclass ini adalah jenis kelas khusus yang menciptakan objek kelas lain. Karenanya, metaclass bertanggung jawab untuk membuat kelas baru. Ini memungkinkan programmer untuk menyesuaikan cara kelas dihasilkan.

Untuk membuat metaclass, biasanya dilakukan penimpa metode baru () dan init (). new () dapat diganti untuk mengubah cara objek dibuat, sedangkan init () dapat diganti untuk mengubah cara menginisialisasi objek. Metaclass dapat dibuat dengan beberapa cara. Salah satu caranya adalah menggunakan fungsi type (). fungsi type (), ketika dipanggil dengan 3 parameter, membuat metaclass. Parameternya adalah: -

  1. Nama kelas
  2. Tuple memiliki kelas dasar yang diwarisi oleh kelas
  3. Kamus yang memiliki semua metode kelas dan variabel kelas

Cara lain untuk membuat metaclass terdiri dari kata kunci 'metaclass'. Tentukan metaclass sebagai kelas sederhana. Dalam parameter kelas bawaan, lulus metaclass = metaclass_name

Metaclass dapat secara khusus digunakan dalam situasi berikut: -

  1. ketika efek tertentu harus diterapkan ke semua subclass
  2. Diperlukan perubahan kelas secara otomatis (saat pembuatan)
  3. Oleh pengembang API
Swati Srivastava
sumber
2

Perhatikan bahwa pada python 3.6, metode dunder baru __init_subclass__(cls, **kwargs)diperkenalkan untuk menggantikan banyak kasus penggunaan umum untuk metaclasses. Apakah dipanggil ketika subclass dari kelas mendefinisikan dibuat. Lihat dokumen python .

Lars
sumber
-3

Metaclass adalah jenis kelas yang mendefinisikan bagaimana kelas akan berperilaku seperti atau kita dapat mengatakan bahwa kelas itu sendiri adalah turunan dari sebuah metaclass.

IKLAN Teknis
sumber