Saya memiliki teman yang suka menggunakan metaclasses, dan secara teratur menawarkannya sebagai solusi.
Saya berpikir bahwa Anda hampir tidak perlu menggunakan metaclasses. Mengapa? karena saya pikir jika Anda melakukan sesuatu seperti itu di kelas, Anda mungkin harus melakukannya pada suatu objek. Dan desain ulang / refactor kecil sudah beres.
Mampu menggunakan metaclasses telah menyebabkan banyak orang di banyak tempat menggunakan kelas sebagai semacam objek kelas dua, yang tampaknya berbahaya bagi saya. Apakah pemrograman akan digantikan oleh meta-pemrograman? Sayangnya, penambahan dekorator kelas membuatnya semakin dapat diterima.
Jadi tolong, saya sangat ingin mengetahui kasus penggunaan Anda yang valid (konkret) untuk metaclasses dengan Python. Atau untuk mendapatkan pencerahan tentang mengapa kelas bermutasi lebih baik daripada bermutasi objek, kadang-kadang.
Aku akan mulai:
Terkadang saat menggunakan pustaka pihak ketiga, akan berguna untuk dapat memutasi kelas dengan cara tertentu.
(Ini adalah satu-satunya kasus yang dapat saya pikirkan, dan ini tidak konkret)
Jawaban:
Saya memiliki kelas yang menangani plotting non-interaktif, sebagai antarmuka untuk Matplotlib. Namun, terkadang seseorang ingin melakukan plotting interaktif. Dengan hanya beberapa fungsi saya menemukan bahwa saya dapat menambah jumlah angka, memanggil menggambar secara manual, dll, tetapi saya perlu melakukan ini sebelum dan sesudah setiap panggilan plotting. Jadi untuk membuat pembungkus plotting interaktif dan pembungkus plotting offscreen, saya merasa lebih efisien untuk melakukan ini melalui metaclasses, membungkus metode yang sesuai, daripada melakukan sesuatu seperti:
Metode ini tidak mengikuti perubahan API dan seterusnya, tetapi metode yang mengulangi atribut kelas
__init__
sebelum menyetel ulang atribut kelas lebih efisien dan menjaga semuanya tetap mutakhir:Tentu saja, mungkin ada cara yang lebih baik untuk melakukan ini, tetapi menurut saya ini efektif. Tentu saja, ini juga dapat dilakukan di
__new__
atau__init__
, tetapi ini adalah solusi yang menurut saya paling mudah.sumber
Saya ditanyai pertanyaan yang sama baru-baru ini, dan mendapatkan beberapa jawaban. Saya harap tidak apa-apa untuk menghidupkan kembali utas ini, karena saya ingin menguraikan beberapa kasus penggunaan yang disebutkan, dan menambahkan beberapa yang baru.
Kebanyakan metaclasses yang pernah saya lihat melakukan salah satu dari dua hal berikut:
Pendaftaran (menambahkan kelas ke struktur data):
Setiap kali Anda membuat subkelas
Model
, kelas Anda terdaftar dimodels
kamus:Ini juga dapat dilakukan dengan dekorator kelas:
Atau dengan fungsi registrasi eksplisit:
Sebenarnya, ini kurang lebih sama: Anda menyebut dekorator kelas dengan tidak baik, tapi sebenarnya itu tidak lebih dari gula sintaksis untuk pemanggilan fungsi di kelas, jadi tidak ada keajaiban tentang itu.
Bagaimanapun, keuntungan dari metaclass dalam hal ini adalah inheritance, karena metaclass bekerja untuk setiap subclass, sedangkan solusi lain hanya bekerja untuk subclass yang didekorasi atau didaftarkan secara eksplisit.
Refactoring (memodifikasi atribut kelas atau menambahkan yang baru):
Setiap kali Anda membuat subkelas
Model
dan menentukan beberapaField
atribut, atribut tersebut akan dimasukkan dengan namanya (untuk pesan kesalahan yang lebih informatif, misalnya), dan dikelompokkan ke dalam_fields
kamus (untuk iterasi yang mudah, tanpa harus melihat semua atribut kelas dan semua kelas dasarnya. atribut setiap waktu):Sekali lagi, ini dapat dilakukan (tanpa warisan) dengan dekorator kelas:
Atau secara eksplisit:
Meskipun, bertentangan dengan anjuran Anda untuk pemrograman non-meta yang dapat dibaca dan dipelihara, ini jauh lebih rumit, berlebihan, dan rawan kesalahan:
Setelah mempertimbangkan kasus penggunaan yang paling umum dan konkret, satu-satunya kasus di mana Anda benar-benar HARUS menggunakan metaclass adalah saat Anda ingin mengubah nama kelas atau daftar kelas dasar, karena setelah ditentukan, parameter ini dimasukkan ke dalam kelas, dan tidak ada dekorator atau fungsi dapat membatalkannya.
Ini mungkin berguna dalam kerangka kerja untuk mengeluarkan peringatan setiap kali kelas dengan nama yang mirip atau pohon warisan yang tidak lengkap didefinisikan, tapi saya tidak bisa memikirkan alasan selain trolling untuk benar-benar mengubah nilai-nilai ini. Mungkin David Beazley bisa.
Bagaimanapun, di Python 3, metaclasses juga memiliki
__prepare__
metode, yang memungkinkan Anda mengevaluasi badan kelas ke dalam pemetaan selain adict
, sehingga mendukung atribut yang dipesan, atribut yang kelebihan beban, dan hal-hal keren lainnya yang jahat:Anda mungkin berpendapat bahwa atribut yang dipesan dapat dicapai dengan penghitung pembuatan, dan kelebihan beban dapat disimulasikan dengan argumen default:
Selain jauh lebih jelek, ini juga kurang fleksibel: bagaimana jika Anda ingin atribut literal yang diurutkan, seperti integer dan string? Bagaimana jika
None
nilai yang valid untukx
?Inilah cara kreatif untuk menyelesaikan masalah pertama:
Dan inilah cara kreatif untuk menyelesaikan yang kedua:
Tapi ini jauh, JAUH voodoo-er dari metaclass sederhana (terutama yang pertama, yang benar-benar melelehkan otak Anda). Maksud saya adalah, Anda melihat metaclass sebagai sesuatu yang asing dan kontra-intuitif, tetapi Anda juga dapat melihatnya sebagai langkah evolusi berikutnya dalam bahasa pemrograman: Anda hanya perlu menyesuaikan pola pikir Anda. Lagi pula, Anda mungkin bisa melakukan semuanya di C, termasuk mendefinisikan struct dengan pointer fungsi dan meneruskannya sebagai argumen pertama ke fungsinya. Seseorang yang melihat C ++ untuk pertama kalinya mungkin berkata, "sihir apa ini? Mengapa compiler secara implisit meneruskan
this
metode, tetapi tidak untuk fungsi biasa dan statis? Lebih baik bersikap eksplisit dan bertele-tele tentang argumen Anda ". Tapi kemudian, pemrograman berorientasi objek jauh lebih kuat setelah Anda mendapatkannya; dan begitu juga ini, uh ... pemrograman berorientasi aspek kuasi, saya rasa. Dan begitu Anda memahami metaclasses, sebenarnya sangat sederhana, jadi mengapa tidak menggunakannya bila nyaman?Dan terakhir, metaclass adalah rad, dan pemrograman harus menyenangkan. Menggunakan konstruksi pemrograman standar dan pola desain sepanjang waktu itu membosankan dan tidak menginspirasi, serta menghalangi imajinasi Anda. Hidup sedikit! Ini metametaclass, khusus untuk Anda.
Sunting
Ini adalah pertanyaan yang cukup lama, tetapi masih mendapatkan suara positif, jadi saya pikir saya akan menambahkan tautan ke jawaban yang lebih komprehensif. Jika Anda ingin membaca lebih lanjut tentang metaclasses dan penggunaannya, saya baru saja menerbitkan artikel tentangnya di sini .
sumber
__metaclass__
atribut, tetapi atribut ini tidak lagi khusus di Python 3. Apakah ada cara untuk membuat "kelas anak-anak juga dibangun oleh metaclass orang tua" bekerja dengan Python 3 ?init_subclass
, jadi sekarang Anda dapat memanipulasi subclass dalam baseclass, dan tidak lagi membutuhkan metaclass untuk tujuan itu.Tujuan dari metaclass bukanlah untuk mengganti perbedaan kelas / objek dengan metaclass / class - itu untuk mengubah perilaku definisi kelas (dan dengan demikian contohnya) dalam beberapa cara. Secara efektif itu untuk mengubah perilaku pernyataan kelas dengan cara yang mungkin lebih berguna untuk domain tertentu Anda daripada default. Hal-hal yang telah saya gunakan untuk mereka adalah:
Melacak subclass, biasanya untuk mendaftarkan penangan. Ini berguna saat menggunakan pengaturan gaya plugin, di mana Anda ingin mendaftarkan penangan untuk hal tertentu hanya dengan membuat subkelas dan menyiapkan beberapa atribut kelas. misalnya. misalkan Anda menulis penangan untuk berbagai format musik, di mana setiap kelas mengimplementasikan metode yang sesuai (tag play / get, dll) untuk tipenya. Menambahkan penangan untuk tipe baru menjadi:
Metaclass kemudian memelihara kamus
{'.mp3' : MP3File, ... }
dll, dan membangun objek dengan tipe yang sesuai saat Anda meminta penangan melalui fungsi pabrik.Mengubah perilaku. Anda mungkin ingin menambahkan arti khusus pada atribut tertentu, yang mengakibatkan perubahan perilaku saat atribut tersebut ada. Misalnya, Anda mungkin ingin mencari metode dengan nama
_get_foo
dan_set_foo
dan secara transparan mengonversinya menjadi properti. Sebagai contoh dunia nyata, berikut adalah resep yang saya tulis untuk memberikan lebih banyak definisi struct mirip C. Metaclass digunakan untuk mengubah item yang dideklarasikan menjadi string format struct, menangani pewarisan dll, dan menghasilkan kelas yang mampu menghadapinya.Untuk contoh-contoh nyata lainnya, lihatlah berbagai ORMs, seperti SQLAlchemy ini ORM atau SQLObject . Sekali lagi, tujuannya adalah untuk menafsirkan definisi (di sini definisi kolom SQL) dengan arti tertentu.
sumber
Mari kita mulai dengan kutipan klasik Tim Peter:
Karena itu, saya telah (secara berkala) menemukan penggunaan metaclass yang sebenarnya. Yang terlintas dalam pikiran adalah di Django dimana semua model anda mewarisi dari model.Model. models.Model, pada gilirannya, melakukan beberapa keajaiban serius untuk membungkus model DB Anda dengan kebaikan ORM Django. Keajaiban itu terjadi melalui metaclass. Ini menciptakan segala macam kelas pengecualian, kelas manajer, dll.
Lihat django / db / models / base.py, kelas ModelBase () untuk permulaan cerita.
sumber
Metaclasses dapat berguna untuk konstruksi Bahasa Khusus Domain dengan Python. Contoh konkretnya adalah Django, sintaks deklaratif SQLObject dari skema basis data.
Contoh dasar dari A Conservative Metaclass oleh Ian Bicking:
Beberapa teknik lain: Bahan untuk Membangun DSL dengan Python (pdf).
Edit (oleh Ali): Contoh melakukan ini menggunakan koleksi dan contoh adalah apa yang saya lebih suka. Fakta pentingnya adalah contoh, yang memberi Anda lebih banyak kekuatan, dan menghilangkan alasan untuk menggunakan metaclass. Perlu diperhatikan lebih lanjut bahwa contoh Anda menggunakan campuran class dan instance, yang tentunya merupakan indikasi bahwa Anda tidak dapat melakukan semuanya dengan metaclass. Dan menciptakan cara yang benar-benar tidak seragam untuk melakukannya.
Itu tidak sempurna, tetapi sudah hampir tidak ada sihir, tidak perlu metaclass, dan keseragaman yang ditingkatkan.
sumber
Pola penggunaan metaclass yang wajar adalah melakukan sesuatu satu kali saat kelas didefinisikan, bukan berulang kali setiap kali kelas yang sama dibuat.
Ketika beberapa kelas berbagi perilaku khusus yang sama, pengulangan
__metaclass__=X
jelas lebih baik daripada mengulang kode tujuan khusus dan / atau memperkenalkan kelas super ad-hoc bersama.Tetapi bahkan dengan hanya satu kelas khusus dan tidak ada ekstensi yang dapat diperkirakan,
__new__
dan__init__
metaclass adalah cara yang lebih bersih untuk menginisialisasi variabel kelas atau data global lainnya daripada mencampurkan kode tujuan khusus dan normaldef
danclass
pernyataan dalam badan definisi kelas.sumber
Satu-satunya saat saya menggunakan metaclasses dengan Python adalah saat menulis pembungkus untuk Flickr API.
Tujuan saya adalah mengikis situs api flickr dan secara dinamis menghasilkan hierarki kelas lengkap untuk memungkinkan akses API menggunakan objek Python:
Jadi dalam contoh itu, karena saya membuat seluruh Python Flickr API dari situs web, saya benar-benar tidak tahu definisi kelas saat runtime. Mampu menghasilkan tipe secara dinamis sangat berguna.
sumber
Saya memikirkan hal yang sama kemarin dan sepenuhnya setuju. Komplikasi dalam kode yang disebabkan oleh upaya untuk membuatnya lebih deklaratif umumnya membuat basis kode lebih sulit untuk dipelihara, lebih sulit dibaca dan kurang pythonic menurut saya. Ini juga biasanya membutuhkan banyak copy.copy () ing (untuk mempertahankan warisan dan menyalin dari kelas ke instance) dan berarti Anda harus mencari di banyak tempat untuk melihat apa yang terjadi (selalu mencari dari metaclass ke atas) yang bertentangan dengan biji-bijian python juga. Saya telah memilih melalui kode formencode dan kode sqlalchemy untuk melihat apakah gaya deklaratif seperti itu layak dilakukan dan jelas tidak. Gaya seperti itu harus diserahkan kepada deskriptor (seperti properti dan metode) dan data yang tidak dapat diubah. Ruby memiliki dukungan yang lebih baik untuk gaya deklaratif seperti itu dan saya senang bahasa inti python tidak mengikuti jalur itu.
Saya dapat melihat penggunaannya untuk debugging, menambahkan metaclass ke semua kelas dasar Anda untuk mendapatkan info yang lebih kaya. Saya juga melihat penggunaannya hanya dalam proyek (sangat) besar untuk menghilangkan beberapa kode boilerplate (tetapi kehilangan kejelasan). sqlalchemy misalnya menggunakannya di tempat lain, untuk menambahkan metode kustom tertentu ke semua subclass berdasarkan nilai atribut dalam definisi kelasnya misalnya contoh mainan
bisa memiliki metaclass yang menghasilkan metode di kelas itu dengan properti khusus berdasarkan "halo" (ucapkan metode yang menambahkan "halo" ke akhir string). Ini bisa menjadi hal yang baik untuk pemeliharaan untuk memastikan Anda tidak perlu menulis metode di setiap subkelas yang Anda buat sebagai gantinya yang harus Anda definisikan adalah method_maker_value.
Kebutuhan untuk ini sangat jarang dan hanya mengurangi sedikit pengetikan yang tidak benar-benar layak dipertimbangkan kecuali Anda memiliki basis kode yang cukup besar.
sumber
Anda tidak pernah benar - benar perlu menggunakan metaclass, karena Anda selalu dapat membuat kelas yang melakukan apa yang Anda inginkan menggunakan pewarisan atau agregasi kelas yang ingin Anda ubah.
Meskipun demikian, akan sangat berguna di Smalltalk dan Ruby untuk dapat memodifikasi kelas yang sudah ada, tetapi Python tidak suka melakukannya secara langsung.
Ada artikel DeveloperWorks yang luar biasa tentang metaclassing dengan Python yang mungkin bisa membantu. The Artikel Wikipedia ini juga cukup bagus.
sumber
Beberapa perpustakaan GUI mengalami masalah saat beberapa utas mencoba berinteraksi dengannya.
tkinter
adalah salah satu contohnya; dan sementara seseorang dapat secara eksplisit menangani masalah dengan kejadian dan antrian, itu bisa jauh lebih sederhana untuk menggunakan perpustakaan dengan cara yang mengabaikan masalah sama sekali. Lihatlah - keajaiban metaclasses.Mampu menulis ulang seluruh pustaka secara dinamis dengan mulus sehingga berfungsi dengan baik seperti yang diharapkan dalam aplikasi multithread bisa sangat membantu dalam beberapa keadaan. The safetkinter modul melakukan itu dengan bantuan sebuah metaclass disediakan oleh threadbox modul - acara dan antrian tidak diperlukan.
Satu aspek rapi
threadbox
adalah tidak peduli kelas apa yang dikloningnya. Ini memberikan contoh bagaimana semua kelas dasar dapat disentuh oleh metaclass jika diperlukan. Manfaat lebih lanjut yang datang dengan metaclass adalah bahwa mereka juga berjalan di kelas yang diturunkan. Program yang menulis sendiri - mengapa tidak?sumber
Satu-satunya kasus penggunaan yang sah dari metaclass adalah untuk mencegah pengembang usil lainnya menyentuh kode Anda. Setelah developer yang usil menguasai metaclass dan mulai mencoba-coba dengan milik Anda, masukkan satu atau dua level lagi untuk mencegahnya. Jika itu tidak berhasil, mulailah menggunakan
type.__new__
atau mungkin beberapa skema menggunakan metaclass rekursif.(menulis lidah di pipi, tetapi saya telah melihat kebingungan semacam ini dilakukan. Django adalah contoh sempurna)
sumber
Metaclasses tidak menggantikan pemrograman! Itu hanyalah trik yang dapat mengotomatiskan atau membuat beberapa tugas menjadi lebih elegan. Contoh bagusnya adalah pustaka penyorotan sintaks Pygments . Ini memiliki sebuah kelas yang disebut
RegexLexer
yang memungkinkan pengguna mendefinisikan sekumpulan aturan lexing sebagai ekspresi reguler di kelas. Metaclass digunakan untuk mengubah definisi menjadi parser yang berguna.Mereka seperti garam; mudah digunakan terlalu banyak.
sumber
Cara saya menggunakan metaclass adalah memberikan beberapa atribut ke kelas. Ambil contoh:
akan meletakkan atribut name pada setiap kelas yang akan memiliki metaclass disetel untuk menunjuk ke NameClass.
sumber
Ini adalah penggunaan kecil, tapi ... satu hal yang saya temukan berguna untuk metaclass adalah memanggil fungsi setiap kali subclass dibuat. Saya mengkodifikasi ini ke dalam metaclass yang mencari
__initsubclass__
atribut: setiap kali subclass dibuat, semua kelas induk yang mendefinisikan metode tersebut dipanggil__initsubclass__(cls, subcls)
. Ini memungkinkan pembuatan kelas induk yang kemudian mendaftarkan semua subkelas dengan beberapa registri global, menjalankan pemeriksaan invarian pada subkelas kapan pun mereka didefinisikan, melakukan operasi pengikatan akhir, dll ... semua tanpa harus memanggil fungsi atau membuat metaclass kustom yang melakukan masing-masing tugas terpisah ini.Pikiran Anda, saya perlahan-lahan menyadari keajaiban implisit dari perilaku ini agak tidak diinginkan, karena tidak terduga jika melihat definisi kelas di luar konteks ... jadi saya telah beralih dari menggunakan solusi itu untuk sesuatu yang serius selain menginisialisasi
__super
atribut untuk setiap kelas dan instance.sumber
Saya baru-baru ini harus menggunakan metaclass untuk membantu secara deklaratif mendefinisikan model SQLAlchemy di sekitar tabel database yang diisi dengan data Sensus AS dari http://census.ire.org/data/bulkdata.html
IRE menyediakan shell database untuk tabel data sensus, yang membuat kolom integer mengikuti konvensi penamaan dari Biro Sensus p012015, p012016, p012017, dll.
Saya ingin a) dapat mengakses kolom-kolom ini menggunakan
model_instance.p012017
sintaks, b) cukup eksplisit tentang apa yang saya lakukan dan c) tidak harus secara eksplisit menentukan lusinan bidang pada model, jadi saya membuat subkelas SQLAlchemyDeclarativeMeta
untuk mengulang melalui rentang kolom dan secara otomatis membuat bidang model yang sesuai dengan kolom:Saya kemudian dapat menggunakan metaclass ini untuk definisi model saya dan mengakses bidang yang disebutkan secara otomatis pada model:
sumber
Tampaknya ada penggunaan yang sah yang dijelaskan di sini - Menulis Ulang Docstrings Python dengan Metaclass.
sumber
Saya harus menggunakannya sekali untuk parser biner agar lebih mudah digunakan. Anda mendefinisikan kelas pesan dengan atribut bidang yang ada di kabel. Mereka perlu diurutkan dengan cara mereka dinyatakan untuk membangun format kawat terakhir darinya. Anda dapat melakukannya dengan metaclasses, jika Anda menggunakan dikt namespace yang diurutkan. Faktanya, ini adalah contoh untuk Metaclasses:
https://docs.python.org/3/reference/datamodel.html#metaclass-example
Tetapi secara umum: Evaluasi dengan sangat hati-hati, jika Anda benar-benar membutuhkan kompleksitas tambahan dari metaclass.
sumber
jawaban dari @Dan Gittik keren
contoh di akhir bisa memperjelas banyak hal, saya rubah menjadi python 3 dan beri penjelasan:
sumber
Kasus penggunaan lainnya adalah ketika Anda ingin dapat mengubah atribut tingkat kelas dan memastikan bahwa itu hanya mempengaruhi objek yang ada. Dalam praktiknya, ini menyiratkan "penggabungan" fase metaclass dan instance class, sehingga mengarahkan Anda untuk menangani hanya instance class dari jenisnya sendiri (unik).
Saya juga harus melakukan itu ketika (untuk masalah readibility dan polimorfisme ) kami ingin secara dinamis mendefinisikan
property
s yang mengembalikan nilai (mungkin) hasil dari perhitungan berdasarkan (sering berubah) atribut tingkat instans, yang hanya dapat dilakukan di tingkat kelas , yaitu setelah instance metaclass dan sebelum instance kelas.sumber