Metasınıflar için bazı (somut) kullanım durumları nelerdir?


118

Metasınıfları kullanmayı seven ve düzenli olarak bunları çözüm olarak sunan bir arkadaşım var.

Metasınıf kullanmaya neredeyse hiç ihtiyaç duymadığınız aklıma geldi. Neden? çünkü bir sınıfa böyle bir şey yapıyorsanız, muhtemelen bir nesneye yapmalısınız diye düşündüm. Ve küçük bir yeniden tasarım / yeniden düzenleme sırayla.

Metasınıfları kullanabilmek, birçok yerde birçok insanın sınıfları bir tür ikinci sınıf nesne olarak kullanmasına neden oldu, bu bana felaket gibi görünüyor. Programlamanın yerini meta programlama alacak mı? Sınıf dekoratörlerinin eklenmesi maalesef onu daha da kabul edilebilir hale getirdi.

Bu yüzden lütfen, Python'daki metasınıflar için geçerli (somut) kullanım durumlarınızı bilmek için çaresizim. Veya bazen sınıfları mutasyona uğratmanın, nesneleri mutasyona uğratmaktan daha iyi olduğu konusunda aydınlanmak.

Başlayacağım:

Bazen bir üçüncü taraf kitaplığı kullanırken sınıfı belirli bir şekilde değiştirebilmek yararlıdır.

(Aklıma gelen tek durum bu ve somut değil)


3
Bu harika bir soru. Aşağıdaki cevaplardan yola çıkarak, metasınıfların somut kullanımı diye bir şeyin olmadığı oldukça açık.
Marcus Ottosson

Yanıtlar:


25

Matplotlib için bir ön uç olarak etkileşimli olmayan planlamayı ele alan bir sınıfım var. Ancak, zaman zaman etkileşimli çizim yapmak istenebilir. Yalnızca birkaç işlevle rakam sayısını artırabildiğimi, el ile çizim yapabildiğimi vb.Ancak bunları her çizim çağrısından önce ve sonra yapmam gerekiyordu. Bu nedenle, hem etkileşimli bir çizim sarmalayıcısı hem de ekran dışı bir çizim sarmalayıcısı oluşturmak için, bunu meta sınıflar aracılığıyla yapmanın, uygun yöntemleri sarmanın, aşağıdaki gibi bir şey yapmaktan daha verimli olduğunu buldum:

class PlottingInteractive:
    add_slice = wrap_pylab_newplot(add_slice)

Bu yöntem, API değişikliklerine ve benzerlerine ayak uydurmaz, ancak sınıf özniteliklerini __init__yeniden ayarlamadan önce sınıf özniteliklerini yineleyen yöntem daha verimlidir ve işleri güncel tutar:

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))

Elbette, bunu yapmanın daha iyi yolları olabilir, ancak bunu etkili buldum. Elbette, bu __new__veya içinde de yapılabilir __init__, ancak bu, en kolay bulduğum çözümdü.


103

Geçenlerde aynı soruyu sormuştum ve birkaç cevap buldum. Bahsedilen kullanım durumlarından birkaçını detaylandırmak ve birkaç yenisini eklemek istediğim için bu konuyu yeniden canlandırmanın sorun olmadığını umuyorum.

Gördüğüm metasınıfların çoğu iki şeyden birini yapıyor:

  1. Kayıt (bir veri yapısına sınıf ekleme):

    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

    Ne zaman alt Modelsınıf yaparsanız , sınıfınız modelssözlüğe kaydedilir:

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

    Bu, sınıf dekoratörleri ile de yapılabilir:

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

    Veya açık bir kayıt işlevi ile:

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

    Aslında, bu hemen hemen aynıdır: sınıf dekoratörlerinden olumsuz bir şekilde bahsediyorsunuz, ancak bu gerçekten bir sınıftaki bir işlev çağrısı için sözdizimsel şekerden başka bir şey değil, dolayısıyla bu konuda sihir yok.

    Her neyse, bu durumda meta sınıfların avantajı, herhangi bir alt sınıf için çalıştıkları için kalıtımdır, oysa diğer çözümler yalnızca açıkça dekore edilmiş veya kayıtlı alt sınıflar için çalışır.

    >>> class B(A):
    ...     pass
    ...
    >>> models
    {'A': <__main__.A class at 0x...> # No B :(
  2. Yeniden düzenleme (sınıf özniteliklerini değiştirme veya yenilerini ekleme):

    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

    ModelBazı Fieldöznitelikleri alt sınıfa ayırıp tanımladığınızda , bunlar adlarıyla birlikte enjekte edilir (örneğin, daha bilgilendirici hata mesajları için) ve bir _fieldssözlüğe (kolay yineleme için, tüm sınıf özniteliklerine ve tüm temel sınıflarına bakmak zorunda kalmadan ) gruplanır. her seferinde öznitelikler):

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

    Yine, bu bir sınıf dekoratörüyle yapılabilir (kalıtım olmadan):

    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')} :(

    Veya açıkça:

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

    Okunabilir ve sürdürülebilir meta olmayan programlama savunuculuğunuzun aksine, bu çok daha külfetli, gereksiz ve hataya açık:

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

En yaygın ve somut kullanım durumlarını göz önünde bulundurarak, metasınıf kullanmak zorunda olduğunuz tek durum, sınıf adını veya temel sınıflar listesini değiştirmek istediğiniz zamandır, çünkü tanımlandıktan sonra bu parametreler sınıfa eklenir ve dekoratör yoktur. veya işlev onları çözebilir.

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

Bu, benzer adlara sahip sınıflar veya eksik miras ağaçları tanımlandığında uyarılar yayınlamak için çerçevelerde yararlı olabilir, ancak bu değerleri gerçekten değiştirmek için trollemenin yanı sıra bir neden düşünemiyorum. Belki David Beazley yapabilir.

Her neyse, Python 3'te, metasınıflar ayrıca __prepare__sınıf gövdesini a haricinde bir eşlemede değerlendirmenize izin veren bir yönteme sahiptir dict, böylece sıralı öznitelikleri, aşırı yüklenmiş öznitelikleri ve diğer harika harika şeyleri destekler:

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

Sıralı özniteliklerin oluşturma sayaçları ile elde edilebileceğini ve aşırı yüklemenin varsayılan bağımsız değişkenlerle simüle edilebileceğini iddia edebilirsiniz:

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)

Çok daha çirkin olmasının yanı sıra, aynı zamanda daha az esnektir: ya tamsayılar ve dizeler gibi sıralı değişmez nitelikler istiyorsanız? Ya Noneiçin geçerli bir değerse x?

İşte ilk sorunu çözmenin yaratıcı bir yolu:

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']

Ve işte ikincisini çözmenin yaratıcı bir yolu:

_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)

Ancak bu, basit bir metasınıftan çok, ÇOK büyücü (özellikle beyninizi gerçekten eriten ilki). Demek istediğim, meta sınıflara alışılmadık ve sezgisel olarak bakıyorsunuz, ancak bunlara programlama dillerinde bir sonraki evrim adımı olarak da bakabilirsiniz: sadece zihniyetinizi ayarlamanız gerekir. Sonuçta, işlev işaretçileriyle bir yapı tanımlamak ve onu işlevlerine ilk argüman olarak geçirmek dahil, muhtemelen C'de her şeyi yapabilirsiniz. C ++ 'ı ilk kez gören bir kişi, "Bu sihir nedir? Derleyici neden örtük olarak geçiyor?thisyöntemlere, ancak düzenli ve statik işlevlere değil mi? Tartışmalarınız hakkında açık ve ayrıntılı olmak daha iyidir. "Ama sonra, nesne yönelimli programlama çok daha güçlüdür; ve bu da, uh ... yarı yön odaklı programlama, sanırım. Metasınıfları anlayın, aslında çok basitler, öyleyse neden uygun olduğunda onları kullanmayasınız?

Ve son olarak, metasınıflar radikaldir ve programlama eğlenceli olmalıdır. Standart programlama yapılarını ve tasarım kalıplarını her zaman kullanmak sıkıcı ve sönüktür ve hayal gücünüzü engeller. Biraz yaşa! İşte size bir metameta sınıfı.

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

Düzenle

Bu oldukça eski bir soru, ancak hala artı oylar alıyor, bu yüzden daha kapsamlı bir cevaba bağlantı ekleyeceğimi düşündüm. Metasınıflar ve kullanımları hakkında daha fazla okumak isterseniz, burada bununla ilgili bir makale yayınladım .


5
Bu harika bir cevap, yazdığınız ve birden fazla örnek verdiğiniz için teşekkürler
Chen A.

"... bu durumda meta sınıfların avantajı, alt sınıflar için çalıştıkları için kalıtımdır" - Python 3'te değil, sanırım? Python 2'de çalıştığını düşünüyorum çünkü herhangi bir alt sınıf __metaclass__özniteliği miras alıyor , ancak bu öznitelik artık Python 3'te özel değil. Bu "çocuk sınıfları da ebeveynin meta sınıfı tarafından oluşturulur" şeyini Python 3'te çalıştırmanın bir yolu var mı? ?
ForceBru

2
Bu Python 3 için de geçerlidir, çünkü meta sınıfı M olan A'dan miras alan bir B sınıfı da bir M türüdür. Dolayısıyla, B değerlendirildiğinde, onu oluşturmak için M çağrılır ve bu size etkili bir şekilde izin verir. (A) "herhangi bir alt sınıf üzerinde çalışmak". Bunu söyledikten sonra, Python 3.6 çok daha basit init_subclassolanı sundu, bu nedenle artık bir temel sınıfta alt sınıfları işleyebilirsiniz ve artık bu amaç için bir meta sınıfa ihtiyacınız kalmaz.
Dan Gittik

Bu harika, meta sınıflarla ilgili pek çok blog yazısı okudum, sadece bu, artıları, eksileri ve meta sınıfa alternatifleri öğreniyor.
ospider

36

Metasınıfların amacı sınıf / nesne ayrımını metasınıf / sınıfla değiştirmek değildir - sınıf tanımlarının (ve dolayısıyla örneklerinin) davranışını bir şekilde değiştirmektir. Etkili bir şekilde, sınıf ifadesinin davranışını, belirli etki alanınız için varsayılandan daha yararlı olabilecek şekillerde değiştirmektir. Onları kullandım şeyler:

  • Genellikle işleyicileri kaydetmek için alt sınıfları izleme. Bu, belirli bir şey için bir işleyiciyi yalnızca alt sınıflara ayırarak ve birkaç sınıf özelliğini ayarlayarak kaydetmek istediğiniz bir eklenti stili kurulumu kullanırken kullanışlıdır. Örneğin. çeşitli müzik formatları için bir işleyici yazdığınızı varsayalım, burada her sınıf kendi türüne uygun yöntemleri (çalma / etiketleri alma vb.) uygular. Yeni bir tür için bir işleyici eklemek şu hale gelir:

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

    Metasınıf daha sonra bir {'.mp3' : MP3File, ... }vb. Sözlüğü tutar ve bir fabrika işlevi aracılığıyla bir işleyici talep ettiğinizde uygun türde bir nesne oluşturur.

  • Davranışı değiştirme. Belirli niteliklere özel bir anlam eklemek isteyebilirsiniz, bu da mevcut olduklarında değişen davranışlarla sonuçlanır. Örneğin, adıyla yöntemleri için bakmak isteyebilirsiniz _get_foove _set_foove şeffaf onları özelliklerine dönüştürmek. Gerçek dünyadan bir örnek olarak, burada daha fazla C benzeri yapı tanımları vermek için yazdığım bir tarif var . Metasınıf, bildirilen öğeleri bir struct format dizgisine dönüştürmek, kalıtımı yönetmek vb. Ve bununla başa çıkabilecek bir sınıf üretmek için kullanılır.

    Diğer gerçek dünya örnekleri için, sqlalchemy'nin ORM'si veya sqlobject'i gibi çeşitli ORM'lere bir göz atın . Yine, amaç tanımları (burada SQL sütun tanımları) belirli bir anlamla yorumlamaktır.


3
Evet, alt sınıfları takip etmek. Ama neden bunu isteyesin ki? Örneğiniz sadece register_music_file (Mp3File, ['.mp3']) için örtüktür ve açık yol daha okunabilir ve bakımı yapılabilir. Bu bahsettiğim kötü vakalara bir örnek.
Ali Afshar

ORM durumu hakkında, tabloları tanımlamanın sınıf temelli yolundan mı yoksa eşlenen nesnelerdeki meta sınıflardan mı bahsediyorsunuz? Çünkü SQLAlchemy herhangi bir sınıfa (haklı olarak) eşleyebilir (ve bu aktivite için bir metasınıf kullanmadığını varsayıyorum).
Ali Afshar

10
Her alt sınıf için fazladan kayıt yöntemleri gerektirmektense daha açıklayıcı stili tercih ederim - her şey tek bir konuma sarılmışsa daha iyi.
Brian

Sqlalchemy için, çoğunlukla bildirim katmanını düşünüyorum, bu yüzden belki de sqlobject daha iyi bir örnek. Bununla birlikte, dahili olarak kullanılan metasınıflar, aynı zamanda, anlamı bildirmek için belirli özniteliklerin benzer yeniden yorumlanmasına da örnektir.
Brian

2
Özür dilerim, kontrollerimden biri SO zaman aşımı senaryosunda kayboldu. Beyanname için sınıfları neredeyse iğrenç buluyorum. İnsanların onu sevdiğini biliyorum ve bu kabul gören bir davranış. Ama (deneyimlerden) Bir şeyleri BM deklare etmek istediğiniz bir durumda kullanılamaz olduğunu biliyorum. Bir sınıfın kaydını silmek zordur .
Ali Afshar

17

Tim Peter'ın klasik sözüyle başlayalım:

Metasınıflar, kullanıcıların% 99'unun endişelenmesi gerekenden daha derin bir sihirdir. Onlara ihtiyacınız olup olmadığını merak ediyorsanız, yok (gerçekten ihtiyacı olan insanlar onlara ihtiyaç duyduklarını kesin olarak bilirler ve nedenleriyle ilgili bir açıklamaya ihtiyaç duymazlar). Tim Peters (2002-12-22 sonrası clp)

Bunu söyledikten sonra, metasınıfların gerçek kullanımlarıyla (periyodik olarak) karşılaştım. Akla gelen, tüm modellerinizin modellerden miras aldığı Django'da. Model, sırayla, DB modellerinizi Django'nun ORM iyiliğiyle kaplamak için ciddi bir sihir yapıyor. Bu sihir, metasınıflar aracılığıyla gerçekleşir. Her türden istisna sınıfları, yönetici sınıfları vb. Oluşturur.

Öykünün başlangıcı için django / db / models / base.py, ModelBase () sınıfına bakın.


Evet, asıl noktayı anlıyorum. Metasınıfları "nasıl" veya "neden" kullanacağımı merak etmiyorum, "kim" ve "ne" yi merak ediyorum. ORM'ler burada yaygın bir durumdur. Ne yazık ki Django'nun ORM'si, daha az sihire sahip olan SQLAlchemy ile karşılaştırıldığında oldukça zayıf. Büyü kötüdür ve bunun için metasınıflar gerçekten gerekli değildir.
Ali Afshar

9
Geçmişte Tim Peters'ın alıntısını okuduktan sonra, zaman, açıklamasının pek yararsız olduğunu gösterdi. Python meta sınıflarını StackOverflow'da araştırana kadar, bunların nasıl uygulanacağı bile belli olmadı. Metasınıfları yazmayı ve kullanmayı öğrenmeye zorladıktan sonra yetenekleri beni şaşırttı ve Python'un gerçekten nasıl çalıştığını daha iyi anlamamı sağladı. Sınıflar yeniden kullanılabilir kod sağlayabilir ve metasınıflar bu sınıflar için yeniden kullanılabilir geliştirmeler sağlayabilir.
Noctis Skytower

6

Meta sınıfları Python'da Etki Alanına Özgü Dillerin oluşturulması için kullanışlı olabilir. Somut örnekler, SQLObject'in veritabanı şemalarının bildirime dayalı sözdizimi olan Django'dur.

Ian Bicking'in A Conservative Metaclass'tan temel bir örneği :

Kullandığım metasınıflar, öncelikle bir tür bildirimsel programlama tarzını desteklemek içindi. Örneğin, bir doğrulama şeması düşünün:

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()

Diğer bazı teknikler: Python'da DSL Oluşturmak için Malzemeler (pdf).

Düzenleme (Ali tarafından): Koleksiyonları ve örnekleri kullanarak bunu yapmanın bir örneği tercih ederim. Önemli olan, size daha fazla güç veren ve metasınıf kullanma nedenini ortadan kaldıran örneklerdir. Ayrıca, örneğinizin bir sınıflar ve örnekler karışımı kullandığını da belirtmek gerekir ki bu kesinlikle hepsini metasınıflarla yapamayacağınızın bir göstergesidir. Ve bunu yapmanın gerçekten tek tip olmayan bir yolunu yaratır.

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,])
]

Mükemmel değil, ama zaten neredeyse sıfır sihir var, metasınıflara gerek yok ve geliştirilmiş tekdüzelik var.


Bunun için teşekkürler. Bu, gereksiz, çirkin ve yönetilemez olduğunu düşündüğüm bir kullanım senaryosunun çok iyi bir örneğidir.
Ali Afshar

1
@Ali A: Metasınıflar aracılığıyla bildirimsel sözdizimi ve basit koleksiyon örneğine dayalı bir yaklaşım arasında yan yana karşılaştırmanın somut bir örneğini sunabilirsiniz.
jfs

@Ali A: Bir koleksiyon stili örneği eklemek için cevabımı yerinde düzenleyebilirsiniz.
jfs

Tamam yaptım. Üzgünüm bugün biraz acelem var, ancak herhangi bir soruyu daha sonra / yarın yanıtlamaya çalışacağım. Mutlu tatiller!
Ali Afshar

2
İkinci örnek, doğrulayıcı örneğini adıyla bağlamanız gerektiği için çirkin. Bunu yapmanın biraz daha iyi bir yolu, liste yerine sözlük kullanmaktır, ancak o zaman, python sınıflarında sözlük için sadece sözdizimi şekeri vardır, öyleyse neden sınıfları kullanmayasınız? Ücretsiz isim doğrulaması da alırsınız çünkü python bebekler boşluklar veya bir dizenin yapabileceği özel karakterler içeremez.
Lie Ryan

6

Makul bir meta sınıf kullanımı kalıbı, aynı sınıf her somutlaştırıldığında tekrar tekrar yapmak yerine, bir sınıf tanımlandığında bir şeyler yapmaktır.

Birden çok sınıf aynı özel davranışı paylaştığında, __metaclass__=Xyineleme, özel amaçlı kodu tekrarlamaktan ve / veya geçici paylaşılan üst sınıflar sunmaktan açıkça daha iyidir.

Ancak tek bir özel sınıfla ve öngörülebilir bir uzantı olmadan __new__ve __init__bir meta sınıfla bile, sınıf değişkenlerini veya diğer genel verileri başlatmak için, özel amaçlı kod ile normal defve classsınıf tanımlama gövdesindeki ifadeleri karıştırmaktan daha temiz bir yoldur .


5

Python'da meta sınıfları kullandığım tek zaman Flickr API'si için bir sarmalayıcı yazarken oldu.

Amacım, flickr'ın api sitesini kazımak ve dinamik olarak Python nesnelerini kullanarak API erişimine izin vermek için eksiksiz bir sınıf hiyerarşisi oluşturmaktı:

# 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

Dolayısıyla bu örnekte, Python Flickr API'sinin tamamını web sitesinden oluşturduğum için, çalışma zamanında sınıf tanımlarını gerçekten bilmiyorum. Türleri dinamik olarak üretebilmek çok faydalı oldu.


2
Metasınıfları kullanmadan dinamik olarak türler oluşturabilirsiniz. >>> yardım (tür)
Ali Afshar

8
Eğer bunun farkında olmasalar bile, sen olan o metaclasses kullanarak. type bir metasınıftır, aslında en yaygın olanıdır. :-)
Veky

5

Ben de dün aynı şeyi düşünüyordum ve tamamen aynı fikirdeydim. Kodda, onu daha bildirimsel hale getirme girişimlerinin neden olduğu komplikasyonlar, bana göre kod tabanının sürdürülmesini zorlaştırır, okumayı zorlaştırır ve daha az pythonik yapar. Ayrıca normalde çok fazla copy.copy () oluşturma gerektirir (kalıtımı korumak ve sınıftan örneğe kopyalamak için) ve neler olup bittiğini görmek için birçok yere bakmanız gerektiği anlamına gelir (her zaman metasınıfdan yukarı bakarsınız). ayrıca python tahıl. Formencode ve sqlalchemy kodunu, böyle bir bildirge stilinin buna değip değmediğini ve açıkça değersiz olup olmadığını görmek için seçiyordum. Bu tarz, tanımlayıcılara (özellik ve yöntemler gibi) ve değişmez verilere bırakılmalıdır. Ruby'nin bu tür bildirime dayalı stillere daha iyi desteği var ve çekirdek python dilinin bu yoldan gitmemesine sevindim.

Hata ayıklama için kullanımlarını görebiliyorum, daha zengin bilgi almak için tüm temel sınıflarınıza bir meta sınıf ekleyebiliyorum. Bazı standart kodlardan kurtulmak için yalnızca (çok) büyük projelerde kullanımlarını görüyorum (ancak netlik kaybıyla). Örneğin sqlalchemy , sınıf tanımlarındaki bir öznitelik değerine göre tüm alt sınıflara belirli bir özel yöntem eklemek için bunları başka bir yerde kullanır, örneğin bir oyuncak örneği

class test(baseclass_with_metaclass):
    method_maker_value = "hello"

bu sınıfta "merhaba" temelli özel özelliklere sahip bir yöntem oluşturan bir meta sınıfa sahip olabilir (örneğin, bir dizenin sonuna "merhaba" ekleyen bir yöntem). Yaptığınız her alt sınıfta bir yöntem yazmak zorunda kalmadığınızdan emin olmak sürdürülebilirlik açısından iyi olabilir, bunun yerine tanımlamanız gereken tek şey method_maker_value.

Buna duyulan ihtiyaç çok nadirdir ve sadece bir miktar yazmayı azaltır ki, yeterince büyük bir kod tabanınız yoksa, gerçekten dikkate almaya değmez.


5

Kesinlikle hiç gerek her zaman değiştirmek istediğiniz sınıfın miras veya toplama kullanarak istediğiniz yapar bir sınıf oluşturabiliriz beri bir metaclass kullanmak.

Bununla birlikte, Smalltalk ve Ruby'de mevcut bir sınıfı değiştirebilmek çok kullanışlı olabilir, ancak Python bunu doğrudan yapmaktan hoşlanmaz.

Python'da meta sınıflandırma hakkında yardımcı olabilecek mükemmel bir DeveloperWorks makalesi var. Wikipedia makalesi de oldukça iyidir.


1
Nesne yönelimli programlama yapmak için nesnelere de ihtiyacınız yoktur - bunu birinci sınıf işlevlerle yapabilirsiniz. Yani nesneleri kullanmanıza gerek yok . Ama kolaylık sağlamak için oradalar. Bu yüzden ilk paragrafta hangi noktaya değinmeye çalıştığından emin değilim.
Tyler Crompton

1
Soruya tekrar bakın.
Charlie Martin

4

Bazı GUI kitaplıkları, birden çok iş parçacığı onlarla etkileşim kurmaya çalıştığında sorun yaşar. tkinterböyle bir örnektir; ve olaylarla ve kuyruklarla sorun açıkça çözülebilirken, kitaplığı sorunu tamamen görmezden gelecek şekilde kullanmak çok daha kolay olabilir. Hani - metasınıfların büyüsü.

Çok iş parçacıklı bir uygulamada beklendiği gibi düzgün çalışması için tüm bir kitaplığı sorunsuz bir şekilde dinamik olarak yeniden yazabilmek, bazı durumlarda son derece yararlı olabilir. Safetkinter modülü yapar tarafından sağlanan bir metaclass yardımıyla threadbox modülü - olaylar ve sıraları gerekli değildir.

Güzel bir yönü, threadboxhangi sınıfı klonladığının umurunda olmamasıdır. Gerekirse tüm temel sınıflara bir meta sınıf tarafından nasıl dokunulabileceğine dair bir örnek sağlar. Metasınıflarla birlikte gelen bir başka avantaj da, sınıfları miras alarak çalıştırmalarıdır. Kendi kendine yazan programlar - neden olmasın?


4

Bir metasınıfın tek meşru kullanım durumu, diğer meraklı geliştiricilerin kodunuza dokunmasını engellemektir. Meraklı bir geliştirici meta sınıflarda ustalaştığında ve sizinkiyle uğraşmaya başladığında, onları uzak tutmak için başka bir veya iki seviye atın. Bu işe yaramazsa, kullanmaya başlayın type.__new__veya belki de yinelemeli bir metasınıf kullanarak bir düzen kullanın.

(yanakta yazılı dil, ancak bu tür bir gizleme yapıldığını gördüm. Django mükemmel bir örnek)


7
Django'da da motivasyonun aynı olduğundan emin değilim.
Ali Afşar

3

Metasınıflar programlamanın yerini almaz! Bunlar, bazı görevleri otomatikleştirebilen veya daha zarif hale getirebilen bir numara. Buna güzel bir örnek, Pygments sözdizimi vurgulama kitaplığıdır. Bu adında bir sınıfa sahip RegexLexerkullanıcı bir sınıf üzerinde normal ifadeler olarak kurallarını Lexing bir dizi tanımlamanızı sağlar. Tanımları kullanışlı bir ayrıştırıcıya dönüştürmek için bir meta sınıf kullanılır.

Tuz gibiler; kullanımı çok kolay.


Bence Pygments davası gereksiz. Neden bir dikte gibi sade bir koleksiyona sahip olmuyorsunuz, neden bir sınıfı bunu yapmaya zorlayasınız?
Ali Afshar

4
Çünkü iyi bir sınıf, Lexer fikrini özetliyor ve tahmin_filename () gibi başka yararlı yöntemler de içeriyor
Benjamin Peterson

3

Metasınıfları kullanma şeklim, sınıflara bazı nitelikler sağlamaktı. Örneğin alın:

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

koyacağız adı NameClass işaret edecek metaclass dizi olacak her sınıf üzerinde niteliği.


5
Evet, bu çalışıyor. En azından açık ve kod olarak takip edilebilir olan bir üst sınıf da kullanabilirsiniz. İlgisiz, bunu ne için kullandın?
Ali Afshar

2

Bu küçük bir kullanım, ama ... metasınıflar için yararlı bulduğum bir şey, bir alt sınıf oluşturulduğunda bir işlevi çağırmaktır. Bunu bir __initsubclass__öznitelik arayan bir meta sınıfa kodladım : bir alt sınıf oluşturulduğunda, bu yöntemi tanımlayan tüm ebeveyn sınıfları ile çağrılır __initsubclass__(cls, subcls). Bu, daha sonra tüm alt sınıfları bazı genel kayıtlarla kaydeden, tanımlandıklarında alt sınıflarda değişmez kontroller çalıştıran, geç bağlama işlemleri gerçekleştiren vb. Tüm alt sınıfları manuel olarak işlev çağırmaya veya özel metasınıflar oluşturmaya gerek kalmadan oluşturmaya izin verir. bu ayrı görevlerin her birini yerine getirir.

Unutmayın, bu davranışın örtük büyüsünün biraz istenmeyen olduğunu yavaş yavaş fark etmeye başladım, çünkü bağlam dışında bir sınıf tanımına bakmak beklenmedik bir durumdur ... ve bu yüzden bu çözümü başka ciddi bir şey için kullanmaktan uzaklaştım. __superher sınıf ve örnek için bir öznitelik başlatma .


1

Kısa bir süre önce, http://census.ire.org/data/bulkdata.html adresindeki ABD Nüfus Sayımı verileriyle doldurulmuş bir veritabanı tablosu etrafında bir SQLAlchemy modelini bildirimli olarak tanımlamaya yardımcı olmak için bir meta sınıf kullanmak zorunda kaldım

IRE , nüfus sayımı veri tabloları için, p012015, p012016, p012017 vb. Nüfus Sayım Bürosu'ndan bir adlandırma kuralını takiben tam sayı sütunları oluşturan veritabanı kabukları sağlar .

A) bu sütunlara bir model_instance.p012017sözdizimi kullanarak erişebilmek , b) ne yaptığım konusunda oldukça açık olmak ve c) model üzerinde düzinelerce alanı açık bir şekilde tanımlamak zorunda kalmamak istedim, bu yüzden SQLAlchemy'nin DeclarativeMetabir dizi sütunlar ve sütunlara karşılık gelen model alanlarını otomatik olarak oluşturun:

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_)

Daha sonra model tanımım için bu meta sınıfı kullanabilir ve modeldeki otomatik olarak numaralandırılmış alanlara erişebilirim:

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

    ...

1

Meşru bir kullanım tarif var gibi görünüyor burada yeniden yazma Python Docstringler bir metaclass ile -.


0

Kullanımını kolaylaştırmak için bunları bir ikili ayrıştırıcı olarak kullanmak zorunda kaldım. Kabloda mevcut alanların nitelikleriyle bir mesaj sınıfı tanımlarsınız. Ondan nihai kablo formatını oluşturacakları şekilde sipariş edilmeleri gerekiyordu. Sıralı bir ad alanı diktesi kullanıyorsanız, bunu meta sınıflarla yapabilirsiniz. Aslında, Metasınıflar için örneklerde:

https://docs.python.org/3/reference/datamodel.html#metaclass-example

Ancak genel olarak: Metasınıfların ek karmaşıklığına gerçekten ihtiyacınız olup olmadığını çok dikkatli bir şekilde değerlendirin.


0

@Dan Gittik'in cevabı harika

sondaki örnekler birçok şeyi açıklığa kavuşturabilir, onu python 3 olarak değiştirdim ve bazı açıklamalar verdim:

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
  • her şey nesnedir, yani sınıf nesnedir
  • sınıf nesnesi meta sınıf tarafından oluşturulur
  • türden miras alınan tüm sınıflar metasınıftır
  • metasınıf, sınıf oluşturmayı kontrol edebilir
  • metasınıf, metasınıf oluşturmayı da kontrol edebilir (böylece sonsuza kadar döngü yapabilir)
  • bu meta programlama ... tür sistemini çalışma zamanında kontrol edebilirsiniz
  • yine, her şey nesnedir, bu tek tip bir sistemdir, yazım create type ve type create instance

0

Başka bir kullanım durumu, sınıf düzeyinde nitelikleri değiştirebilmek ve yalnızca elinizdeki nesneyi etkilediğinden emin olmak istediğiniz zamandır. Pratikte bu, metasınıfların ve sınıf örneklemelerinin aşamalarının "birleştirilmesi" anlamına gelir, böylece sizi yalnızca kendi türlerinin (benzersiz) sınıf örnekleriyle ilgilenmeye yönlendirir.

Ayrıca, ( okunabilirlik ve çok biçimlilikle ilgili endişeler için ) döndürülen değerlerin (genellikle değişen) örnek düzeyi özniteliklerine dayalı hesaplamalardan kaynaklanabileceğini (olabilecek) dinamik olarak tanımlamak istediğimizde de bunu yapmak zorunda kaldım property, bu yalnızca sınıf düzeyinde yapılabilir , yani metasınıf somutlaştırmasından sonra ve sınıf somutlaştırmasından önce.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.