Kami terus berbicara tentang metaprogramming dengan Python. Ketika digunakan dengan benar, ini memungkinkan Anda untuk dengan cepat dan elegan menerapkan pola desain yang rumit. Di bagian terakhir artikel ini, kami menunjukkan bagaimana metaclasses dapat digunakan untuk mengubah atribut instance dan kelas.

Sekarang mari kita lihat bagaimana Anda dapat mengubah panggilan metode. Anda dapat mempelajari lebih lanjut tentang opsi metaprogramming di kursus Advanced Python .
Men-debug dan melacak panggilan
Seperti yang sudah Anda pahami, menggunakan metaclass, kelas apa pun dapat ditransformasikan melampaui pengakuan. Misalnya, ganti semua metode kelas dengan yang lain atau terapkan dekorator yang sewenang-wenang untuk setiap metode. Anda dapat menggunakan ide ini untuk men-debug kinerja aplikasi.
Metaclass berikut ini mengukur waktu eksekusi setiap metode di kelas dan instansnya, serta waktu pembuatan instance itu sendiri:
from contextlib import contextmanager import logging import time import wrapt @contextmanager def timing_context(operation_name): """ """ start_time = time.time() try: yield finally: logging.info('Operation "%s" completed in %0.2f seconds', operation_name, time.time() - start_time) @wrapt.decorator def timing(func, instance, args, kwargs): """ . https://wrapt.readthedocs.io/en/latest/ """ with timing_context(func.__name__): return func(*args, **kwargs) class DebugMeta(type): def __new__(mcs, name, bases, attrs): for attr, method in attrs.items(): if not attr.startswith('_'):
Mari kita lihat tindakan debugging:
class User(metaclass=DebugMeta): def __init__(self, name): self.name = name time.sleep(.7) def login(self): time.sleep(1) def logout(self): time.sleep(2) @classmethod def create(cls): time.sleep(.5) user = User('Michael') user.login() user.logout() user.create()
Coba DebugMeta
dan logging tanda tangan dari metode dan stack-trace mereka.
Pola penyendiri dan larangan pewarisan
Dan sekarang mari kita beralih ke kasus eksotis menggunakan metaclasses dalam proyek Python.
Tentunya banyak dari Anda menggunakan modul Python biasa untuk menerapkan pola desain sendiri (alias Singleton), karena jauh lebih nyaman dan lebih cepat daripada menulis metaclass yang sesuai. Namun, mari kita tulis salah satu implementasinya demi kepentingan akademis:
class Singleton(type): instance = None def __call__(cls, *args, **kwargs): if cls.instance is None: cls.instance = super().__call__(*args, **kwargs) return cls.instance class User(metaclass=Singleton): def __init__(self, name): self.name = name def __repr__(self): return f'<User: {self.name}>' u1 = User('Pavel')
Implementasi ini memiliki nuansa yang menarik - karena konstruktor kelas tidak dipanggil untuk yang kedua kalinya, Anda dapat membuat kesalahan dan tidak melewatkan parameter yang diperlukan di sana, dan tidak ada yang akan terjadi saat runtime jika instance sudah dibuat. Sebagai contoh:
>>> User('Roman') <User: Roman> >>> User('Alexey', 'Petrovich', 66)
Sekarang mari kita lihat opsi yang lebih eksotis: larangan warisan dari kelas tertentu.
class FinalMeta(type): def __new__(mcs, name, bases, attrs): for cls in bases: if isinstance(cls, FinalMeta): raise TypeError(f"Can't inherit {name} class from final {cls.__name__}") return super().__new__(mcs, name, bases, attrs) class A(metaclass=FinalMeta): """ !""" pass class B(A): pass
Dalam contoh sebelumnya, kami menggunakan metaclasses untuk menyesuaikan pembuatan kelas, tetapi Anda dapat melangkah lebih jauh dan mulai mengukur parameter perilaku metaclasses.
Misalnya, Anda bisa meneruskan fungsi ke parameter metaclass saat mendeklarasikan kelas dan mengembalikan instance berbeda dari metaclasses tergantung pada beberapa kondisi, misalnya:
def get_meta(name, bases, attrs): if SOME_SETTING: return MetaClass1(name, bases, attrs) else: return MetaClass2(name, bases, attrs) class A(metaclass=get_meta): pass
Tetapi contoh yang lebih menarik adalah penggunaan parameter extra_kwargs
ketika mendeklarasikan kelas. Misalkan Anda ingin menggunakan metaclass untuk mengubah perilaku metode tertentu dalam suatu kelas, dan setiap kelas mungkin memiliki nama yang berbeda untuk metode ini. Apa yang harus dilakukan Dan inilah yang terjadi
Menurut saya, ternyata sangat elegan! Anda dapat menemukan banyak pola untuk menggunakan parameterisasi ini, tetapi ingat aturan utamanya - semuanya baik-baik saja.
__prepare__
Metode
Akhirnya, saya akan berbicara tentang kemungkinan penggunaan metode __prepare__
. Seperti disebutkan di atas, metode ini harus mengembalikan objek kamus, yang __prepare__
oleh penerjemah pada saat menguraikan badan kelas, misalnya, jika __prepare__
mengembalikan objek d = dict()
, maka ketika membaca kelas berikut:
class A: x = 12 y = 'abc' z = {1: 2}
Penerjemah akan melakukan operasi berikut:
d['x'] = 12 d['y'] = 'abc' d['z'] = {1: 2}
Ada beberapa kemungkinan penggunaan untuk fitur ini. Mereka semua memiliki berbagai tingkat utilitas, jadi:
- Dalam versi Python = <3.5, jika kita perlu menjaga urutan metode mendeklarasikan di dalam kelas, kita bisa mengembalikan
collections.OrderedDict
. __prepare__
metode __prepare__
, dalam versi yang lebih lama, kamus built-in sudah mempertahankan urutan penambahan kunci, jadi OrderedDict
tidak lagi diperlukan. - Modul perpustakaan standar
enum
menggunakan objek seperti dict khusus untuk menentukan kapan atribut kelas diduplikasi pada deklarasi. Kode dapat ditemukan di sini . - Sama sekali bukan kode siap produksi, tetapi contoh yang sangat bagus adalah dukungan untuk polimorfisme parametrik .
Sebagai contoh, pertimbangkan kelas berikut dengan tiga implementasi metode polimorfik tunggal:
class Terminator: def terminate(self, x: int): print(f'Terminating INTEGER {x}') def terminate(self, x: str): print(f'Terminating STRING {x}') def terminate(self, x: dict): print(f'Terminating DICTIONARY {x}') t1000 = Terminator() t1000.terminate(10) t1000.terminate('Hello, world!') t1000.terminate({'hello': 'world'})
Jelas, metode terminate
terakhir dinyatakan menimpa implementasi dari dua yang pertama, dan kita perlu metode yang akan dipilih tergantung pada jenis argumen yang diteruskan. Untuk mencapai ini, kami memprogram beberapa objek pembungkus tambahan:
class PolyDict(dict): """ , PolyMethod. """ def __setitem__(self, key: str, func): if not key.startswith('_'): if key not in self: super().__setitem__(key, PolyMethod()) self[key].add_implementation(func) return None return super().__setitem__(key, func) class PolyMethod: """ , . , : instance method, staticmethod, classmethod. """ def __init__(self): self.implementations = {} self.instance = None self.cls = None def __get__(self, instance, cls): self.instance = instance self.cls = cls return self def _get_callable_func(self, impl):
Hal yang paling menarik dalam kode di atas adalah objek PolyMethod
, yang menyimpan registri dengan implementasi metode yang sama, tergantung pada jenis argumen yang diteruskan ke metode ini. Kami akan mengembalikan objek PolyDict
dari metode __prepare__
dan dengan demikian menyimpan implementasi metode yang berbeda dengan nama yang sama terminate
. Poin penting - ketika membaca tubuh kelas dan ketika membuat objek attrs
, penerjemah menempatkan apa yang disebut fungsi unbound
sana, fungsi-fungsi ini belum tahu kelas atau contoh mana mereka akan dipanggil. Kami harus mengimplementasikan protokol deskriptor untuk mendefinisikan konteks selama pemanggilan fungsi dan meneruskan self
atau cls
sebagai parameter pertama, atau tidak memberikan apa-apa jika staticmethod
dipanggil.
Sebagai hasilnya, kita akan melihat keajaiban berikut:
class PolyMeta(type): @classmethod def __prepare__(mcs, name, bases): return PolyDict() class Terminator(metaclass=PolyMeta): ... t1000 = Terminator() t1000.terminate(10) t1000.terminate('Hello, world!') t1000.terminate({'hello': 'world'})
Jika Anda tahu ada kegunaan lain dari metode __prepare__
, silakan tulis di komentar.
Kesimpulan
Metaprogramming adalah salah satu dari banyak topik yang saya bicarakan dalam Advanced Python . Sebagai bagian dari kursus, saya juga akan memberi tahu Anda cara menggunakan prinsip-prinsip SOLID dan GRASP secara efektif dalam pengembangan proyek-proyek Python besar, merancang arsitektur aplikasi dan menulis kode kinerja tinggi dan berkualitas tinggi. Saya akan senang melihat Anda di dinding Distrik Biner!