class
, definisi dinamis menggunakan kelas sebaris type
.
Ketik metaclass
Kelas tipe sering digunakan untuk mendapatkan tipe suatu objek. Contohnya seperti ini:
h = "hello"
type(h)
<class 'str'>
Tetapi memiliki kegunaan lain. Itu dapat menginisialisasi tipe baru.Seperti yang Anda ketahui, semua yang ada di Python adalah objek. Oleh karena itu, semua definisi memiliki tipe, termasuk kelas dan objek. Misalnya:
class A:
pass
type(A)
<class 'type'>
Mungkin tidak sepenuhnya jelas mengapa kelas diberi jenis kelas
type
, sebagai lawan dari contohnya:
a = A()
type(a)
<class '__main__.A'>
Objek
a
diberi kelas sebagai tipe. Beginilah cara interpreter memperlakukan objek sebagai instance kelas. Kelas itu sendiri memiliki tipe kelas type
karena mewarisi dari kelas dasar object
:
A.__bases__
(<class 'object'>,)
Jenis kelas
object
:
type(object)
<class 'type'>
Kelas
object
diwarisi oleh semua kelas secara default, yaitu:
class A(object):
pass
Sama dengan:
class A:
pass
Kelas yang didefinisikan mewarisi kelas dasar sebagai tipe. Namun, ini tidak menjelaskan mengapa kelas dasar
object
adalah tipe kelas type
. Intinya adalah, ini type
adalah metaclass. Seperti yang telah Anda ketahui, semua kelas mewarisi dari kelas dasar object
, yang merupakan tipe metaclass type
. Oleh karena itu, semua kelas juga memiliki tipe ini, termasuk metaclass itu sendiri type
:
type(type)
<class 'type'>
Ini adalah "titik akhir pengetikan" dengan Python. Rantai pewarisan tipe ditutup di kelas
type
. Metaclass type
berfungsi sebagai basis untuk semua kelas dengan Python. Mudah untuk memverifikasi ini:
builtins = [list, dict, tuple]
for obj in builtins:
type(obj)
<class 'type'>
<class 'type'>
<class 'type'>
Kelas adalah tipe data abstrak, dan instance-nya memiliki referensi kelas sebagai tipenya.
Menginisialisasi tipe baru dengan kelas tipe
Saat memeriksa tipe, kelas
type
diinisialisasi dengan satu argumen:
type(object) -> type
Dengan melakukan itu, ia mengembalikan tipe objek. Namun, kelas mengimplementasikan metode inisialisasi yang berbeda dengan tiga argumen, yang mengembalikan tipe baru:
type(name, bases, dict) -> new type
Ketik parameter inisialisasi
name
Sebuah string yang mendefinisikan nama kelas baru (tipe).bases
Tupel kelas dasar (kelas yang akan diwariskan oleh kelas baru).dict
Kamus dengan atribut kelas masa depan. Biasanya dengan string dalam kunci dan tipe nilai yang dapat dipanggil.
Definisi kelas dinamis
Kami menginisialisasi kelas tipe baru, menyediakan semua argumen yang diperlukan dan menyebutnya:
MyClass = type("MyClass", (object, ), dict())
MyClass
<class '__main__.MyClass'>
Anda dapat bekerja dengan kelas baru seperti biasa:
m = MyClass()
m
<__main__.MyClass object at 0x7f8b1d69acc0>
Selain itu, metode ini setara dengan definisi kelas biasa:
class MyClass:
pass
Mendefinisikan atribut kelas secara dinamis
Ada gunanya dalam kelas kosong, jadi muncul pertanyaan: bagaimana menambahkan atribut dan metode?
Untuk menjawab pertanyaan ini, pertimbangkan kode inisialisasi awal:
MyClass = type(“MyClass”, (object, ), dict())
Biasanya, atribut ditambahkan ke kelas pada tahap inisialisasi sebagai argumen ketiga - kamus. Anda dapat menentukan nama dan nilai atribut dalam kamus. Misalnya, ini bisa berupa variabel:
MyClass = type(“MyClass”, (object, ), dict(foo=“bar”)
m = MyClass()
m.foo
'bar'
Definisi metode dinamis
Objek yang dapat dipanggil juga bisa diteruskan ke kamus, misalnya, metode:
def foo(self):
return “bar”
MyClass = type(“MyClass”, (object, ), dict(foo=foo))
m = MyClass()
m.foo
'bar'
Metode ini memiliki satu kelemahan yang signifikan - kebutuhan untuk mendefinisikan metode secara statis (menurut saya dalam konteks tugas metaprogramming, ini dapat dianggap sebagai kelemahan). Selain itu, definisi metode dengan parameter di
self
luar badan kelas terlihat aneh. Oleh karena itu, mari kembali ke inisialisasi dinamis dari kelas tanpa atribut:
MyClass = type(“MyClass”, (object, ), dict())
Setelah menginisialisasi kelas kosong, Anda dapat menambahkan metode ke dalamnya secara dinamis, yaitu tanpa definisi statis eksplisit:
code = compile('def foo(self): print(“bar”)', "<string>", "exec")
compile
Adalah fungsi built-in yang mengkompilasi kode sumber menjadi suatu objek. Kode dapat dijalankan dengan fungsi exec()
atau eval()
.
Kompilasi parameter fungsi
- source
Source code, dapat berupa link ke modul. - filename
Nama file tempat objek akan dikompilasi. - mode
Jika ditentukan"exec"
, fungsi akan mengkompilasi kode sumber menjadi modul.
Hasil dari pekerjaan
compile
ini adalah objek kelas code
:
type(code)
<class 'code'>
Objek
code
perlu diubah menjadi metode. Karena metode adalah fungsi, kita mulai dengan mengubah code
objek kelas menjadi objek kelas function
. Untuk melakukan ini, impor modul types
:
from types import FunctionType, MethodType
Saya akan mengimpornya
MethodType
karena saya akan membutuhkannya nanti untuk mengonversi fungsi ke metode kelas.
function = FunctionType(code.co_consts[0], globals(), “foo”)
Parameter metode inisialisasi FunctionType
code
Objek kelascode
.code.co_consts[0]
Adalah panggilan ke deskriptorco_consts
kelascode
, yang merupakan tupel dengan konstanta dalam kode objek. Bayangkan sebuah objekcode
sebagai modul dengan satu fungsi yang kita coba tambahkan sebagai metode kelas.0
Apakah indeksnya, karena itu adalah satu-satunya konstanta dalam modul.globals()
Kamus variabel global.name
Parameter opsional yang menentukan nama fungsi.
Hasilnya adalah sebuah fungsi:
function
<function foo at 0x7fc79cb5ed90>
type(function)
<class 'function'>
Selanjutnya, Anda perlu menambahkan fungsi ini sebagai metode kelas
MyClass
:
MyClass.foo = MethodType(function, MyClass)
Ekspresi yang cukup sederhana yang menetapkan fungsi kita ke metode kelas
MyClass
.
m = MyClass()
m.foo()
bar
Peringatan
Dalam 99% kasus, Anda bisa bertahan dengan definisi kelas statis. Namun, konsep metaprogramming bagus dalam mengungkap internal Python. Kemungkinan besar, akan sulit bagi Anda untuk menemukan penerapan metode yang dijelaskan di sini, meskipun dalam praktik saya ada kasus seperti itu.
Sudahkah Anda bekerja dengan objek dinamis? Mungkin dalam bahasa lain?