Apa saja kasus penggunaan (konkret) untuk metaclasses?

118

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)

Ali Afshar
sumber
3
Ini pertanyaan yang bagus. Dilihat dari jawaban di bawah, cukup jelas bahwa tidak ada yang namanya penggunaan konkret untuk metaclass.
Marcus Ottosson

Jawaban:

25

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:

class PlottingInteractive:
    add_slice = wrap_pylab_newplot(add_slice)

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:

class _Interactify(type):
    def __init__(cls, name, bases, d):
        super(_Interactify, cls).__init__(name, bases, d)
        for base in bases:
            for attrname in dir(base):
                if attrname in d: continue # If overridden, don't reset
                attr = getattr(cls, attrname)
                if type(attr) == types.MethodType:
                    if attrname.startswith("add_"):
                        setattr(cls, attrname, wrap_pylab_newplot(attr))
                    elif attrname.startswith("set_"):
                        setattr(cls, attrname, wrap_pylab_show(attr))

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.

Matt
sumber
103

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:

  1. Pendaftaran (menambahkan kelas ke struktur data):

    models = {}
    
    class ModelMetaclass(type):
        def __new__(meta, name, bases, attrs):
            models[name] = cls = type.__new__(meta, name, bases, attrs)
            return cls
    
    class Model(object):
        __metaclass__ = ModelMetaclass

    Setiap kali Anda membuat subkelas Model, kelas Anda terdaftar di modelskamus:

    >>> class A(Model):
    ...     pass
    ...
    >>> class B(A):
    ...     pass
    ...
    >>> models
    {'A': <__main__.A class at 0x...>,
     'B': <__main__.B class at 0x...>}

    Ini juga dapat dilakukan dengan dekorator kelas:

    models = {}
    
    def model(cls):
        models[cls.__name__] = cls
        return cls
    
    @model
    class A(object):
        pass

    Atau dengan fungsi registrasi eksplisit:

    models = {}
    
    def register_model(cls):
        models[cls.__name__] = cls
    
    class A(object):
        pass
    
    register_model(A)

    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.

    >>> class B(A):
    ...     pass
    ...
    >>> models
    {'A': <__main__.A class at 0x...> # No B :(
  2. Refactoring (memodifikasi atribut kelas atau menambahkan yang baru):

    class ModelMetaclass(type):
        def __new__(meta, name, bases, attrs):
            fields = {}
            for key, value in attrs.items():
                if isinstance(value, Field):
                    value.name = '%s.%s' % (name, key)
                    fields[key] = value
            for base in bases:
                if hasattr(base, '_fields'):
                    fields.update(base._fields)
            attrs['_fields'] = fields
            return type.__new__(meta, name, bases, attrs)
    
    class Model(object):
        __metaclass__ = ModelMetaclass

    Setiap kali Anda membuat subkelas Modeldan menentukan beberapa Fieldatribut, atribut tersebut akan dimasukkan dengan namanya (untuk pesan kesalahan yang lebih informatif, misalnya), dan dikelompokkan ke dalam _fieldskamus (untuk iterasi yang mudah, tanpa harus melihat semua atribut kelas dan semua kelas dasarnya. atribut setiap waktu):

    >>> class A(Model):
    ...     foo = Integer()
    ...
    >>> class B(A):
    ...     bar = String()
    ...
    >>> B._fields
    {'foo': Integer('A.foo'), 'bar': String('B.bar')}

    Sekali lagi, ini dapat dilakukan (tanpa warisan) dengan dekorator kelas:

    def model(cls):
        fields = {}
        for key, value in vars(cls).items():
            if isinstance(value, Field):
                value.name = '%s.%s' % (cls.__name__, key)
                fields[key] = value
        for base in cls.__bases__:
            if hasattr(base, '_fields'):
                fields.update(base._fields)
        cls._fields = fields
        return cls
    
    @model
    class A(object):
        foo = Integer()
    
    class B(A):
        bar = String()
    
    # B.bar has no name :(
    # B._fields is {'foo': Integer('A.foo')} :(

    Atau secara eksplisit:

    class A(object):
        foo = Integer('A.foo')
        _fields = {'foo': foo} # Don't forget all the base classes' fields, too!

    Meskipun, bertentangan dengan anjuran Anda untuk pemrograman non-meta yang dapat dibaca dan dipelihara, ini jauh lebih rumit, berlebihan, dan rawan kesalahan:

    class B(A):
        bar = String()
    
    # vs.
    
    class B(A):
        bar = String('bar')
        _fields = {'B.bar': bar, 'A.foo': A.foo}

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.

class Metaclass(type):
    def __new__(meta, name, bases, attrs):
        return type.__new__(meta, 'foo', (int,), attrs)

class Baseclass(object):
    __metaclass__ = Metaclass

class A(Baseclass):
    pass

class B(A):
    pass

print A.__name__ # foo
print B.__name__ # foo
print issubclass(B, A)   # False
print issubclass(B, int) # True

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 a dict, sehingga mendukung atribut yang dipesan, atribut yang kelebihan beban, dan hal-hal keren lainnya yang jahat:

import collections

class Metaclass(type):

    @classmethod
    def __prepare__(meta, name, bases, **kwds):
        return collections.OrderedDict()

    def __new__(meta, name, bases, attrs, **kwds):
        print(list(attrs))
        # Do more stuff...

class A(metaclass=Metaclass):
    x = 1
    y = 2

# prints ['x', 'y'] rather than ['y', 'x']

 

class ListDict(dict):
    def __setitem__(self, key, value):
        self.setdefault(key, []).append(value)

class Metaclass(type):

    @classmethod
    def __prepare__(meta, name, bases, **kwds):
        return ListDict()

    def __new__(meta, name, bases, attrs, **kwds):
        print(attrs['foo'])
        # Do more stuff...

class A(metaclass=Metaclass):

    def foo(self):
        pass

    def foo(self, x):
        pass

# prints [<function foo at 0x...>, <function foo at 0x...>] rather than <function foo at 0x...>

Anda mungkin berpendapat bahwa atribut yang dipesan dapat dicapai dengan penghitung pembuatan, dan kelebihan beban dapat disimulasikan dengan argumen default:

import itertools

class Attribute(object):
    _counter = itertools.count()
    def __init__(self):
        self._count = Attribute._counter.next()

class A(object):
    x = Attribute()
    y = Attribute()

A._order = sorted([(k, v) for k, v in vars(A).items() if isinstance(v, Attribute)],
                  key = lambda (k, v): v._count)

 

class A(object):

    def _foo0(self):
        pass

    def _foo1(self, x):
        pass

    def foo(self, x=None):
        if x is None:
            return self._foo0()
        else:
            return self._foo1(x)

Selain jauh lebih jelek, ini juga kurang fleksibel: bagaimana jika Anda ingin atribut literal yang diurutkan, seperti integer dan string? Bagaimana jika Nonenilai yang valid untuk x?

Inilah cara kreatif untuk menyelesaikan masalah pertama:

import sys

class Builder(object):
    def __call__(self, cls):
        cls._order = self.frame.f_code.co_names
        return cls

def ordered():
    builder = Builder()
    def trace(frame, event, arg):
        builder.frame = frame
        sys.settrace(None)
    sys.settrace(trace)
    return builder

@ordered()
class A(object):
    x = 1
    y = 'foo'

print A._order # ['x', 'y']

Dan inilah cara kreatif untuk menyelesaikan yang kedua:

_undefined = object()

class A(object):

    def _foo0(self):
        pass

    def _foo1(self, x):
        pass

    def foo(self, x=_undefined):
        if x is _undefined:
            return self._foo0()
        else:
            return self._foo1(x)

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 meneruskanthismetode, 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.

class MetaMetaclass(type):
    def __new__(meta, name, bases, attrs):
        def __new__(meta, name, bases, attrs):
            cls = type.__new__(meta, name, bases, attrs)
            cls._label = 'Made in %s' % meta.__name__
            return cls 
        attrs['__new__'] = __new__
        return type.__new__(meta, name, bases, attrs)

class China(type):
    __metaclass__ = MetaMetaclass

class Taiwan(type):
    __metaclass__ = MetaMetaclass

class A(object):
    __metaclass__ = China

class B(object):
    __metaclass__ = Taiwan

print A._label # Made in China
print B._label # Made in Taiwan

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 .

Dan Gittik
sumber
5
Itu jawaban yang bagus, terima kasih atas waktu menulisnya dan memberikan banyak contoh
Chen A.
"... keuntungan dari metaclass dalam hal ini adalah warisan, karena mereka bekerja untuk subclass apa pun" - bukan di Python 3, saya kira? Saya pikir ini bekerja di Python 2 hanya karena setiap kelas anak mewarisi __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 ?
ForceBru
2
Ini juga berlaku untuk Python 3, karena kelas B, yang diturunkan dari A, yang metaclassnya adalah M, juga merupakan tipe-M. Jadi, ketika B dievaluasi, M dipanggil untuk membuatnya, dan ini secara efektif memungkinkan Anda untuk "mengerjakan subclass apa pun" (dari A). Karena itu, Python 3.6 memperkenalkan yang lebih sederhana init_subclass, jadi sekarang Anda dapat memanipulasi subclass dalam baseclass, dan tidak lagi membutuhkan metaclass untuk tujuan itu.
Dan Gittik
Ini brilian, saya membaca begitu banyak posting blog tentang metaclass, hanya yang ini yang mengetahui pro dan kontra dan alternatif untuk metaclass.
ospider
36

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:

    class Mp3File(MusicFile):
        extensions = ['.mp3']  # Register this type as a handler for mp3 files
        ...
        # Implementation of mp3 methods go here

    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_foodan _set_foodan 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.

Brian
sumber
3
Ya, melacak subclass. Tetapi mengapa Anda menginginkan itu? Contoh Anda hanyalah implisit untuk register_music_file (Mp3File, ['.mp3']), dan cara eksplisitnya lebih mudah dibaca dan dipelihara. Ini adalah contoh kasus buruk yang saya bicarakan.
Ali Afshar
Tentang kasus ORM, apakah Anda berbicara tentang cara berbasis kelas untuk menentukan tabel, atau metaclass pada objek yang dipetakan. Karena SQLAlchemy dapat (dengan benar) memetakan ke kelas mana pun (dan saya berasumsi bahwa SQLAlchemy tidak menggunakan metaclass untuk aktivitas itu).
Ali Afshar
10
Saya lebih suka gaya yang lebih deklaratif, daripada memerlukan metode registrasi tambahan untuk setiap subclass - lebih baik jika semuanya dibungkus dalam satu lokasi.
Brian
Untuk sqlalchemy, saya kebanyakan memikirkan layer deklaratif, jadi mungkin sqlobject adalah contoh yang lebih baik. Namun metaclass yang digunakan secara internal juga merupakan contoh reinterpretasi serupa dari atribut tertentu untuk menyatakan makna.
Brian
2
Maaf salah satu kesalahan saya hilang dalam skenario batas waktu SO. Saya menemukan kelas untuk deklaratif hampir merupakan kekejian. Saya tahu orang menyukainya, dan ini adalah perilaku yang diterima. Tetapi (dari pengalaman) saya tahu ini tidak dapat digunakan dalam situasi di mana Anda ingin UN menyatakan sesuatu. Membatalkan pendaftaran kelas itu sulit .
Ali Afshar
17

Mari kita mulai dengan kutipan klasik Tim Peter:

Metaclasses adalah keajaiban yang lebih dalam dari yang 99% pengguna harus khawatirkan. Jika Anda bertanya-tanya apakah Anda membutuhkannya, Anda tidak (orang yang benar-benar membutuhkannya tahu dengan pasti bahwa mereka membutuhkannya, dan tidak membutuhkan penjelasan tentang mengapa). Tim Peters (posting clp 2002-12-22)

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.

Peter Rowell
sumber
Ya, saya mengerti maksudnya. Saya tidak bertanya-tanya "bagaimana" atau "mengapa" menggunakan metaclasses, saya bertanya-tanya "siapa" dan "apa". ORM adalah kasus umum di sini. Sayangnya ORM Django sangat buruk dibandingkan dengan SQLAlchemy yang memiliki sihir yang lebih sedikit. Sihir itu buruk, dan metaclass sebenarnya tidak diperlukan untuk ini.
Ali Afshar
9
Membaca kutipan Tim Peters di masa lalu, waktu menunjukkan bahwa pernyataannya agak tidak membantu. Tidak sampai meneliti metaclass Python di sini di StackOverflow menjadi jelas bagaimana menerapkannya. Setelah memaksa diri saya sendiri untuk belajar bagaimana menulis dan menggunakan metaclass, kemampuan mereka membuat saya takjub dan memberi saya pemahaman yang lebih baik tentang bagaimana Python benar-benar bekerja. Kelas dapat menyediakan kode yang dapat digunakan kembali, dan metaclass dapat menyediakan penyempurnaan yang dapat digunakan kembali untuk kelas tersebut.
Noctis Skytower
6

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:

Metaclass yang saya gunakan terutama untuk mendukung semacam gaya pemrograman deklaratif. Misalnya, pertimbangkan skema validasi:

class Registration(schema.Schema):
    first_name = validators.String(notEmpty=True)
    last_name = validators.String(notEmpty=True)
    mi = validators.MaxLength(1)
    class Numbers(foreach.ForEach):
        class Number(schema.Schema):
            type = validators.OneOf(['home', 'work'])
            phone_number = validators.PhoneNumber()

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.

number_validator = [
    v.OneOf('type', ['home', 'work']),
    v.PhoneNumber('phone_number'),
]

validators = [
    v.String('first_name', notEmpty=True),
    v.String('last_name', notEmpty=True),
    v.MaxLength('mi', 1),
    v.ForEach([number_validator,])
]

Itu tidak sempurna, tetapi sudah hampir tidak ada sihir, tidak perlu metaclass, dan keseragaman yang ditingkatkan.

jfs
sumber
Terima kasih untuk ini. Ini adalah contoh yang sangat bagus dari kasus penggunaan yang menurut saya tidak perlu, jelek, dan tidak dapat diatur, yang akan lebih sederhana berdasarkan contoh koleksi sederhana (dengan koleksi bersarang sesuai kebutuhan).
Ali Afshar
1
@Ali A: Anda dipersilakan untuk memberikan contoh konkret perbandingan berdampingan antara sintaks deklaratif melalui metaclass dan pendekatan berdasarkan contoh koleksi sederhana.
jfs
@ Ali A: Anda dapat mengedit jawaban saya di tempat untuk menambahkan contoh gaya koleksi.
jfs
Oke, selesai. Maaf, saya sedikit terburu-buru hari ini, tetapi akan mencoba menjawab pertanyaan apa pun nanti / besok. Selamat berlibur!
Ali Afshar
2
Contoh kedua jelek karena Anda harus mengikat instance validator dengan namanya. Cara yang sedikit lebih baik untuk melakukannya adalah dengan menggunakan kamus alih-alih daftar, tetapi kemudian, di kelas python hanya gula sintaks untuk kamus, jadi mengapa tidak menggunakan kelas? Anda juga mendapatkan validasi nama gratis karena bayi python tidak boleh berisi spasi atau karakter khusus yang bisa dimasukkan string.
Lie Ryan
6

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__=Xjelas 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 normal defdan classpernyataan dalam badan definisi kelas.

pengguna412090
sumber
5

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:

# Both the photo type and the flickr.photos.search API method 
# are generated at "run-time"
for photo in flickr.photos.search(text=balloons):
    print photo.description

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.

Triptych
sumber
2
Anda dapat membuat tipe secara dinamis tanpa menggunakan metaclass. >>> help (ketik)
Ali Afshar
8
Bahkan jika Anda tidak menyadari hal itu, Anda yang menggunakan metaclasses kemudian. jenisnya adalah metaclass, sebenarnya yang paling umum. :-)
Veky
5

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

class test(baseclass_with_metaclass):
    method_maker_value = "hello"

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.

David Raznick
sumber
5

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.

Charlie Martin
sumber
1
Anda juga tidak memerlukan objek untuk melakukan pemrograman berorientasi objek — Anda dapat melakukannya dengan fungsi kelas satu. Jadi Anda tidak perlu menggunakan objek. Tapi mereka ada di sana untuk kenyamanan. Jadi saya tidak yakin poin apa yang ingin Anda sampaikan di paragraf pertama.
Tyler Crompton
1
Lihat kembali pertanyaannya.
Charlie Martin
4

Beberapa perpustakaan GUI mengalami masalah saat beberapa utas mencoba berinteraksi dengannya. tkinteradalah 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 threadboxadalah 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?

Noctis Skytower
sumber
4

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)

Mike A
sumber
7
Saya tidak yakin motivasinya sama di Django.
Ali Afshar
3

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 RegexLexeryang 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.

Benjamin Peterson
sumber
Menurut pendapat saya, casing Pygments itu tidak perlu. Mengapa tidak hanya memiliki koleksi biasa seperti dikt, mengapa memaksa kelas untuk melakukan ini?
Ali Afshar
4
Karena kelas yang bagus merangkum gagasan Lexer dan memiliki metode berguna lainnya seperti guess_filename (), dll.
Benjamin Peterson
3

Cara saya menggunakan metaclass adalah memberikan beberapa atribut ke kelas. Ambil contoh:

class NameClass(type):
    def __init__(cls, *args, **kwargs):
       type.__init__(cls, *args, **kwargs)
       cls.name = cls.__name__

akan meletakkan atribut name pada setiap kelas yang akan memiliki metaclass disetel untuk menunjuk ke NameClass.

hyperboreean
sumber
5
Ya, ini berhasil. Anda juga bisa menggunakan superclass, yang setidaknya eksplisit, dan dapat diikuti dalam kode. Karena tertarik, Anda menggunakan ini untuk apa?
Ali Afshar
2

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 __superatribut untuk setiap kelas dan instance.

Eli Collins
sumber
1

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.p012017sintaks, b) cukup eksplisit tentang apa yang saya lakukan dan c) tidak harus secara eksplisit menentukan lusinan bidang pada model, jadi saya membuat subkelas SQLAlchemy DeclarativeMetauntuk mengulang melalui rentang kolom dan secara otomatis membuat bidang model yang sesuai dengan kolom:

from sqlalchemy.ext.declarative.api import DeclarativeMeta

class CensusTableMeta(DeclarativeMeta):
    def __init__(cls, classname, bases, dict_):
        table = 'p012'
        for i in range(1, 49):
            fname = "%s%03d" % (table, i)
            dict_[fname] = Column(Integer)
            setattr(cls, fname, dict_[fname])

        super(CensusTableMeta, cls).__init__(classname, bases, dict_)

Saya kemudian dapat menggunakan metaclass ini untuk definisi model saya dan mengakses bidang yang disebutkan secara otomatis pada model:

CensusTableBase = declarative_base(metaclass=CensusTableMeta)

class P12Tract(CensusTableBase):
    __tablename__ = 'ire_p12'

    geoid = Column(String(12), primary_key=True)

    @property
    def male_under_5(self):
        return self.p012003

    ...
Geoffrey Hing
sumber
1

Tampaknya ada penggunaan yang sah yang dijelaskan di sini - Menulis Ulang Docstrings Python dengan Metaclass.

mistermarko
sumber
0

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.

Ya ampun
sumber
0

jawaban dari @Dan Gittik keren

contoh di akhir bisa memperjelas banyak hal, saya rubah menjadi python 3 dan beri penjelasan:

class MetaMetaclass(type):
    def __new__(meta, name, bases, attrs):
        def __new__(meta, name, bases, attrs):
            cls = type.__new__(meta, name, bases, attrs)
            cls._label = 'Made in %s' % meta.__name__
            return cls

        attrs['__new__'] = __new__
        return type.__new__(meta, name, bases, attrs)

#China is metaclass and it's __new__ method would be changed by MetaMetaclass(metaclass)
class China(MetaMetaclass, metaclass=MetaMetaclass):
    __metaclass__ = MetaMetaclass

#Taiwan is metaclass and it's __new__ method would be changed by MetaMetaclass(metaclass)
class Taiwan(MetaMetaclass, metaclass=MetaMetaclass):
    __metaclass__ = MetaMetaclass

#A is a normal class and it's __new__ method would be changed by China(metaclass)
class A(metaclass=China):
    __metaclass__ = China

#B is a normal class and it's __new__ method would be changed by Taiwan(metaclass)
class B(metaclass=Taiwan):
    __metaclass__ = Taiwan


print(A._label)  # Made in China
print(B._label)  # Made in Taiwan
  • semuanya objek, jadi kelas adalah objek
  • objek kelas dibuat oleh metaclass
  • semua kelas yang diwarisi dari tipe adalah metaclass
  • metaclass dapat mengontrol pembuatan kelas
  • metaclass juga bisa mengontrol pembuatan metaclass (sehingga bisa mengulang selamanya)
  • ini metaprograming ... Anda dapat mengontrol sistem tipe pada waktu berjalan
  • sekali lagi, semuanya adalah objek, ini adalah sistem seragam, tipe buat tipe, dan tipe buat contoh
kebodohan
sumber
0

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.

berusaha agar hidup
sumber