Jawaban:
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. type
adalah metaclass biasa dalam Python. type
itu sendiri adalah kelas, dan itu adalah tipenya sendiri. Anda tidak akan dapat membuat ulang sesuatu seperti type
murni 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 class
pernyataan dieksekusi, Python pertama mengeksekusi tubuh class
pernyataan 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 type
metaclass. 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__
__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.
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:
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>
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 class
kata 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, type
memiliki kemampuan yang sama sekali berbeda, ia juga dapat membuat kelas dengan cepat. type
dapat 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 kelasbases
: tuple dari kelas induk (untuk warisan, bisa kosong)attrs
: kamus yang berisi nama atribut dan nilaimisalnya:
>>> 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.
type
menerima 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.
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 type
memungkinkan Anda melakukan sesuatu seperti ini:
MyClass = type('MyClass', (), {})
Itu karena fungsinya type
sebenarnya metaclass. type
adalah 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 int
kelas yang membuat objek integer. type
hanya 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.
__metaclass__
atributDi 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 Foo
belum 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
type
untuk 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 Foo
dengan 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 Bar
metaclass 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 Bar
menggunakan __metaclass__
atribut yang dibuat Bar
dengan 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.
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.
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 self
untuk metode biasa yang menerima instance sebagai parameter pertama, atau kelas yang menentukan untuk metode kelas.
Tapi ini bukan OOP yang tepat. Kami menelepon type
langsung 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:
Karena __metaclass__
dapat menerima callable, mengapa Anda menggunakan kelas karena itu jelas lebih rumit?
Ada beberapa alasan untuk melakukannya:
UpperAttrMetaclass(type)
, Anda tahu apa yang akan diikuti__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__
.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 IntegerField
objek. Ini akan mengembalikan int
, dan bahkan dapat mengambilnya langsung dari database.
Ini dimungkinkan karena models.Model
mendefinisikan __metaclass__
dan menggunakan beberapa sihir yang akan mengubah Person
Anda 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.
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
.
type
sebenarnya 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.
models.Model
tidak digunakan __metaclass__
tetapi class Model(metaclass=ModelBase):
untuk referensi ModelBase
kelas yang kemudian melakukan sihir metaclass tersebut. Pos yang bagus! Inilah sumber Django: github.com/django/django/blob/master/django/db/models/…
__metaclass__
atribut tidak akan diwarisi, metaclass dari parent ( Bar.__class__
) akan menjadi. Jika Bar
menggunakan __metaclass__
atribut yang dibuat Bar
dengan type()
(dan tidak type.__new__()
), subclass tidak akan mewarisi perilaku itu. >> - Bisakah Anda / seseorang tolong jelaskan sedikit lebih dalam bagian ini?
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
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 Initialised
mendapatkan dengan memiliki metaclass init_attributes
tidak 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
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 MyType
lalu mendapat atribut kelas _order
yang mencatat urutan di mana kelas didefinisikan.
__init__(self)
kata siapa type(self)._order = MyBase.counter; MyBase.counter += 1
?
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.
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%.
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'>
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.
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 type
dengan 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.)
type
seperti 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__
).
__prepare__
namespaceJika, 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>})
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 dict
digunakan.
__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 int
atau 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
__set_name__(cls, name)
di deskriptor ( ValidateType
) untuk mengatur nama di deskriptor ( self.name
dan dalam kasus ini juga self.attr
). Ini ditambahkan ke tidak harus menyelam ke dalam metaclasses untuk kasus penggunaan umum khusus ini (lihat PEP 487).
__call__()
metode metaclass ' saat membuat instance kelasJika 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 type
adalah Meta_1
's kelas induk ( type
menjadi 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
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()
The type(obj)
Fungsi membuat Anda jenis objek.
The type()
kelas adalah yang metaclass .
Untuk menggunakan metaclass:
class Foo(object):
__metaclass__ = MyMetaClass
type
adalah 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.
type
sebenarnya metaclass
- kelas yang menciptakan kelas lain. Sebagian besar metaclass
adalah subclass dari type
. The metaclass
menerima new
kelas 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
.
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.
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.
Selain jawaban yang dipublikasikan saya dapat mengatakan bahwa a metaclass
mendefinisikan perilaku untuk kelas. Jadi, Anda dapat secara eksplisit mengatur metaclass Anda. Setiap kali Python mendapatkan kata kunci class
maka itu mulai mencari metaclass
. Jika tidak ditemukan - tipe metaclass default digunakan untuk membuat objek kelas. Menggunakan __metaclass__
atribut, Anda dapat mengatur metaclass
kelas 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 metaclass
untuk mendefinisikan perilaku kelas apa pun yang dibuat menggunakan kelas Anda.
Untuk melakukan itu, metaclass
kelas 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'
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.
Berikut ini contoh lain dari apa yang dapat digunakan untuk:
metaclass
untuk 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 metaclass
kuat, ada banyak hal (seperti sihir monyet) yang bisa Anda lakukan dengan itu, tapi hati-hati ini mungkin hanya diketahui oleh Anda.
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: -
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: -
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 .
Metaclass adalah jenis kelas yang mendefinisikan bagaimana kelas akan berperilaku seperti atau kita dapat mengatakan bahwa kelas itu sendiri adalah turunan dari sebuah metaclass.
class A(type):pass<NEWLINE>class B(type,metaclass=A):pass<NEWLINE>b.__class__ = b