Soru Python'da metaclass nelerdir?


Metaclass nedir ve bunları ne için kullanıyoruz?


4584
2017-09-19 06:10


Menşei




Cevaplar:


Bir meta sınıf bir sınıfın sınıfıdır. Sınıf gibi, sınıfın bir örneğinin nasıl davrandığını tanımlar, bir meta sınıf bir sınıfın nasıl davrandığını tanımlar. Bir sınıf bir meta sınıfın bir örneğidir.

metaclass diagram

Python'dayken metasınıflar için keyfi callables kullanabilirsiniz. Jerub gösterir), daha kullanışlı bir yaklaşım aslında onu gerçek bir sınıfın kendisi yapmaktır. type Python'daki her zamanki metaclass. Merak ediyorsan, evet, type kendisi bir sınıf ve kendi türü. Gibi bir şeyleri yeniden oluşturamayacaksınız type sadece Python'da, ama Python biraz hile yapıyor. Python'da kendi metaclass'ınızı yaratmak için gerçekten sadece alt sınıflara geçmek istiyorsunuz type.

Bir metaclass en çok bir sınıf-fabrika olarak kullanılır. Sınıfı çağırarak sınıfın bir örneğini oluşturduğunuz gibi, Python metaclassı çağırarak yeni bir sınıf oluşturur ('sınıf' ifadesini yürüttüğünde). Normal ile birlikte __init__ ve __new__ Bu nedenle, metasınıflar, bir sınıf oluştururken, yeni bir sınıfa bazı kayıtlarla kaydolmak, hatta sınıfı tamamen başka bir şeyle değiştirmek gibi 'ekstra şeyler' yapmanıza izin verir.

Ne zaman class deyim yürütülür, Python ilk önce class normal kod bloğu olarak ifade edilir. Ortaya çıkan ad alanı (bir dict), sınıfın öznitelik özelliklerini taşır. Meta sınıfı, sınıfın sınıfının temel sınıflarına bakılarak belirlenir (metasınıflar devralınır). __metaclass__ sınıfın varlığını (varsa) veya __metaclass__ küresel değişken. Meta sınıfı daha sonra sınıfın ismini, tabanlarını ve özniteliklerini çağrıştırmak için çağrılır.

Ancak, meta sınıfları aslında tip Bir sınıf, sadece bir fabrika değil, onlarla daha fazlasını yapabilirsiniz. Örneğin, metaclass üzerindeki normal yöntemleri tanımlayabilirsiniz. Bu meta sınıf metotları, sınıflar üzerinde bir örnek olmaksızın çağrılabilmeleri gibi sınıf yöntemleri gibidir, fakat aynı zamanda sınıfın bir örneğinde çağrılmayacakları gibi sınıflandırmalar gibi de değildirler. type.__subclasses__() bir yöntem örneğidir type Metaclass. Ayrıca, normal 'sihirli' yöntemleri de tanımlayabilirsiniz. __add__, __iter__ ve __getattr__Sınıfın nasıl davrandığını uygulamak veya değiştirmek.

İşte bitlerin ve parçaların toplu bir örneği:

def make_hook(f):
    """Decorator to turn 'foo' method into '__foo__'"""
    f.is_hook = 1
    return f

class MyType(type):
    def __new__(mcls, name, bases, attrs):

        if name.startswith('None'):
            return None

        # Go over attributes and see if they should be renamed.
        newattrs = {}
        for attrname, attrvalue in attrs.iteritems():
            if getattr(attrvalue, 'is_hook', 0):
                newattrs['__%s__' % attrname] = attrvalue
            else:
                newattrs[attrname] = attrvalue

        return super(MyType, mcls).__new__(mcls, name, bases, newattrs)

    def __init__(self, name, bases, attrs):
        super(MyType, self).__init__(name, bases, attrs)

        # classregistry.register(self, self.interfaces)
        print "Would register class %s now." % self

    def __add__(self, other):
        class AutoClass(self, other):
            pass
        return AutoClass
        # Alternatively, to autogenerate the classname as well as the class:
        # return type(self.__name__ + other.__name__, (self, other), {})

    def unregister(self):
        # classregistry.unregister(self)
        print "Would unregister class %s now." % self

class MyObject:
    __metaclass__ = MyType


class NoneSample(MyObject):
    pass

# Will print "NoneType None"
print type(NoneSample), repr(NoneSample)

class Example(MyObject):
    def __init__(self, value):
        self.value = value
    @make_hook
    def add(self, other):
        return self.__class__(self.value + other.value)

# Will unregister the class
Example.unregister()

inst = Example(10)
# Will fail with an AttributeError
#inst.unregister()

print inst + inst
class Sibling(MyObject):
    pass

ExampleSibling = Example + Sibling
# ExampleSibling is now a subclass of both Example and Sibling (with no
# content of its own) although it will believe it's called 'AutoClass'
print ExampleSibling
print ExampleSibling.__mro__

2087
2017-09-19 07:01



"Sadece Python'da bir şey gibi bir şeyleri yeniden oluşturamayacaksınız, ama Python biraz aldatır" doğru değildir. - ppperry
class A(type):pass<NEWLINE>class B(type,metaclass=A):pass<NEWLINE>b.__class__ = b - ppperry


Nesneler olarak sınıflar

Meta sınıfları anlamadan önce, Python'da derslere ihtiyacınız var. Ve Python, Smalltalk dilinden ödünç alınan sınıfların ne olduğu konusunda çok tuhaf bir fikre sahip.

Çoğu dilde, sınıflar bir nesneyi nasıl üretileceğini açıklayan kod parçalarıdır. Python'da da bu doğru:

>>> class ObjectCreator(object):
...       pass
...

>>> my_object = ObjectCreator()
>>> print(my_object)
<__main__.ObjectCreator object at 0x8974f2c>

Fakat sınıflar Python'dakinden daha fazladır. Sınıflar da nesnelerdir.

Evet, nesneler.

Anahtar kelimeyi kullandığınız anda class, Python yürütür ve oluşturur bir obje. Talimat

>>> class ObjectCreator(object):
...       pass
...

bellekte "ObjectCreator" adıyla bir nesne oluşturur.

Bu nesne (sınıf) kendi başına nesneleri (örnekleri) oluşturabilir, ve bu yüzden bu bir sınıf.

Ama yine de, bu bir nesne ve bu nedenle:

  • onu bir değişkene atayabilirsiniz
  • kopyalayabilirsiniz
  • ona nitelik ekleyebilirsin
  • bir fonksiyon parametresi olarak iletebilirsiniz

Örneğin.:

>>> print(ObjectCreator) # you can print a class because it's an object
<class '__main__.ObjectCreator'>
>>> def echo(o):
...       print(o)
...
>>> echo(ObjectCreator) # you can pass a class as a parameter
<class '__main__.ObjectCreator'>
>>> print(hasattr(ObjectCreator, 'new_attribute'))
False
>>> ObjectCreator.new_attribute = 'foo' # you can add attributes to a class
>>> print(hasattr(ObjectCreator, 'new_attribute'))
True
>>> print(ObjectCreator.new_attribute)
foo
>>> ObjectCreatorMirror = ObjectCreator # you can assign a class to a variable
>>> print(ObjectCreatorMirror.new_attribute)
foo
>>> print(ObjectCreatorMirror())
<__main__.ObjectCreator object at 0x8997b4c>

Dinamik olarak sınıf oluşturma

Sınıflar nesne olduklarından, onları herhangi bir nesne gibi anında yaratabilirsiniz.

İlk olarak, bir işlevi kullanarak bir sınıf oluşturabilirsiniz. class:

>>> def choose_class(name):
...     if name == 'foo':
...         class Foo(object):
...             pass
...         return Foo # return the class, not an instance
...     else:
...         class Bar(object):
...             pass
...         return Bar
...
>>> MyClass = choose_class('foo')
>>> print(MyClass) # the function returns a class, not an instance
<class '__main__.Foo'>
>>> print(MyClass()) # you can create an object from this class
<__main__.Foo object at 0x89c6d4c>

Ama o kadar dinamik değil, çünkü tüm sınıfı kendiniz yazmanız gerekiyor.

Sınıflar nesne olduklarından, bir şey tarafından oluşturulmaları gerekir.

Kullandığınızda class anahtar sözcüğü, Python bu nesneyi otomatik olarak oluşturur. Ancak Python'daki çoğu şeyle, el ile yapmanın bir yolunu sunar.

Fonksiyonu hatırla type? Neyi bilmene izin veren iyi eski işlev bir nesne yazmak:

>>> print(type(1))
<type 'int'>
>>> print(type("1"))
<type 'str'>
>>> print(type(ObjectCreator))
<type 'type'>
>>> print(type(ObjectCreator()))
<class '__main__.ObjectCreator'>

İyi, type tamamen farklı bir yeteneği vardır, aynı zamanda sinek üzerinde sınıfları oluşturabilir. type Bir sınıfın açıklamasını parametre olarak alabilir, ve bir sınıf döndürün.

(Biliyorum, aynı işlevin, ona geçirdiğiniz parametrelere göre iki tamamen farklı kullanımı olabileceği aptalca. Geriye doğru bir sorun var. Python'da uyumluluk)

type bu şekilde çalışır:

type(name of the class,
     tuple of the parent class (for inheritance, can be empty),
     dictionary containing attributes names and values)

Örneğin.:

>>> class MyShinyClass(object):
...       pass

manuel olarak şu şekilde oluşturulabilir:

>>> MyShinyClass = type('MyShinyClass', (), {}) # returns a class object
>>> print(MyShinyClass)
<class '__main__.MyShinyClass'>
>>> print(MyShinyClass()) # create an instance with the class
<__main__.MyShinyClass object at 0x8997cec>

"MyShinyClass" ı sınıfın adı olarak kullandığımızı fark edeceksiniz. ve sınıf referansını tutmak için değişken olarak. Farklı olabilirler, ama işleri zorlaştırmanın bir sebebi yok.

type sınıfın özelliklerini tanımlamak için bir sözlük kabul eder. Yani:

>>> class Foo(object):
...       bar = True

Tercüme edilebilir:

>>> Foo = type('Foo', (), {'bar':True})

Ve normal bir sınıf olarak kullanılır:

>>> print(Foo)
<class '__main__.Foo'>
>>> print(Foo.bar)
True
>>> f = Foo()
>>> print(f)
<__main__.Foo object at 0x8a9b84c>
>>> print(f.bar)
True

Ve elbette, bundan miras alabilirsiniz, yani:

>>>   class FooChild(Foo):
...         pass

olabilir:

>>> FooChild = type('FooChild', (Foo,), {})
>>> print(FooChild)
<class '__main__.FooChild'>
>>> print(FooChild.bar) # bar is inherited from Foo
True

Sonunda sınıfınıza yöntemler eklemek istersiniz. Sadece bir işlevi tanımla uygun imza ile ve bir öznitelik olarak atayın.

>>> def echo_bar(self):
...       print(self.bar)
...
>>> FooChild = type('FooChild', (Foo,), {'echo_bar': echo_bar})
>>> hasattr(Foo, 'echo_bar')
False
>>> hasattr(FooChild, 'echo_bar')
True
>>> my_foo = FooChild()
>>> my_foo.echo_bar()
True

Ayrıca, normal olarak oluşturulmuş bir sınıf nesnesine yöntemler eklemek gibi, sınıfı dinamik olarak oluşturduktan sonra daha da fazla yöntem ekleyebilirsiniz.

>>> def echo_bar_more(self):
...       print('yet another method')
...
>>> FooChild.echo_bar_more = echo_bar_more
>>> hasattr(FooChild, 'echo_bar_more')
True

Nereye gittiğimizi görüyorsunuz: Python'da sınıflar nesnelerdir ve anında sinif olarak bir sınıf yaratabilirsiniz.

Bu, anahtar kelimeyi kullandığınızda Python'un yaptığı şeydir classve bunu bir metaclass kullanarak yapar.

Metaclass nedir (nihayet)

Meta sınıflar, sınıflar oluşturan 'şeyler' dir.

Nesneleri oluşturmak için sınıfları tanımlarsınız, değil mi?

Fakat Python sınıflarının nesneler olduğunu öğrendik.

Eh, metaclass bu nesneleri oluşturan şeydir. Onlar sınıfların sınıfları, onları şu şekilde görüntüleyebilirsiniz:

MyClass = MetaClass()
my_object = MyClass()

Bunu gördün type Böyle bir şey yapmanıza izin verir:

MyClass = type('MyClass', (), {})

Çünkü fonksiyon type aslında bir metaclass. type öyle mi metaclass Python sahnelerin arkasındaki tüm sınıfları oluşturmak için kullanır.

Şimdi neden cehennemde küçük harfle yazıldığını merak ediyorsunuz. Type?

Eh, sanırım bu bir tutarlılık meselesi. str, yaratan sınıf dizeleri nesneleri ve intTamsayı nesneler oluşturan sınıf. type olduğu sadece sınıf nesneleri oluşturan sınıf.

Bunu kontrol ederek görüyorsunuz. __class__ bağlıyor.

Her şey ve ben her şeyi, Python'da bir nesne. Bu ints içerir dizeler, fonksiyonlar ve sınıflar. Hepsi nesne. Ve hepsi var bir sınıftan oluşturuldu:

>>> age = 35
>>> age.__class__
<type 'int'>
>>> name = 'bob'
>>> name.__class__
<type 'str'>
>>> def foo(): pass
>>> foo.__class__
<type 'function'>
>>> class Bar(object): pass
>>> b = Bar()
>>> b.__class__
<class '__main__.Bar'>

Şimdi, ne __class__ herhangi bir __class__ ?

>>> age.__class__.__class__
<type 'type'>
>>> name.__class__.__class__
<type 'type'>
>>> foo.__class__.__class__
<type 'type'>
>>> b.__class__.__class__
<type 'type'>

Yani, bir meta sınıf sadece sınıf nesneleri oluşturan şeydir.

İsterseniz bir 'sınıf fabrikası' diyebilirsiniz.

type Python'un yerleşik meta sınıfı kullanıyor, ama elbette kendi metaclass.

__metaclass__ nitelik

Ekleyebilirsiniz __metaclass__ Bir sınıf yazdığınızda özellik:

class Foo(object):
    __metaclass__ = something...
    [...]

Bunu yaparsanız, Python sınıfı oluşturmak için meta sınıfı kullanacaktır. Foo.

Dikkat et, zor.

Sen yaz class Foo(object) önce ama sınıf nesnesi Foo oluşturulmadı henüz bellekte.

Python arayacak __metaclass__ sınıf tanımında. Eğer bulursa, nesne sınıfı oluşturmak için kullanacaktır Foo. Eğer değilse, kullanacaktır type sınıfı oluşturmak için.

Bunu birkaç kez okuyun.

Yaptığınız zaman:

class Foo(Bar):
    pass

Python aşağıdakileri yapar:

Orada bir __metaclass__ nitelik Foo?

Eğer evet ise, bir sınıf nesnesi (sınıf nesnesi dedim, burada benimle kal) dedim Foo içinde ne kullanarak __metaclass__.

Python bulamıyorsa __metaclass__, bir bakacağız __metaclass__ MODÜL seviyesinde ve aynı şeyi yapmaya çalışın (ancak sadece eski tip sınıflar olan herhangi bir şeyi miras almayan sınıflar için).

O zaman bulamazsa __metaclass__ hiç kullanmaz Bar'(ilk ana) kendi meta sınıfı (bu varsayılan olabilir type) sınıf nesnesini oluşturmak.

Burada dikkatli olun __metaclass__ öznitelik devralınmayacak, ebeveynin meta sınıfı (Bar.__class__) olacak. Eğer Bar kullanılmış __metaclass__ oluşturulan özellik Bar ile type() (ve yok type.__new__()), alt sınıflar bu davranışı devralmayacaktır.

Şimdi büyük soru, ne koyabilirsiniz? __metaclass__ ?

Cevap: bir sınıf oluşturabilecek bir şey.

Ve bir sınıf ne oluşturabilir? typeya da alt sınıfı veya onu kullanan herhangi bir şey.

Özel metaclasses

Bir meta sınıfın temel amacı, dersi otomatik olarak değiştirmektir. oluşturulduğunda.

Bunu genellikle, eşleşen sınıflar oluşturmak istediğiniz API'ler için yaparsınız. mevcut içerik

Modülünüzdeki tüm sınıflara karar vereceğiniz aptal bir örnek düşünün. özniteliklerinin büyük harfle yazılması gerekir. Birkaç yolu vardır Bunu yap, ama bir yolu ayarlamak __metaclass__ modül seviyesinde.

Bu şekilde, bu modülün tüm sınıfları bu meta sınıfı kullanılarak oluşturulacak, ve metaclass'a tüm özellikleri büyük harfe çevirmek zorundayız.

Neyse ki, __metaclass__ aslında herhangi bir kalınabilir olabilir, bir resmi sınıf (Biliyorum, adıyla 'sınıf' olan bir şeyin olması gerekmiyor. Bir sınıf, git rakam ... ama yardımcı olur.

Böylece bir fonksiyon kullanarak basit bir örnekle başlayacağız.

# the metaclass will automatically get passed the same argument
# that you usually pass to `type`
def upper_attr(future_class_name, future_class_parents, future_class_attr):
    """
      Return a class object, with the list of its attribute turned
      into uppercase.
    """

    # pick up any attribute that doesn't start with '__' and uppercase it
    uppercase_attr = {}
    for name, val in future_class_attr.items():
        if not name.startswith('__'):
            uppercase_attr[name.upper()] = val
        else:
            uppercase_attr[name] = val

    # let `type` do the class creation
    return type(future_class_name, future_class_parents, uppercase_attr)

__metaclass__ = upper_attr # this will affect all classes in the module

class Foo(): # global __metaclass__ won't work with "object" though
    # but we can define __metaclass__ here instead to affect only this class
    # and this will work with "object" children
    bar = 'bip'

print(hasattr(Foo, 'bar'))
# Out: False
print(hasattr(Foo, 'BAR'))
# Out: True

f = Foo()
print(f.BAR)
# Out: 'bip'

Şimdi, tam olarak aynısını yapalım ama bir metaclass için gerçek bir sınıf kullanalım:

# remember that `type` is actually a class like `str` and `int`
# so you can inherit from it
class UpperAttrMetaclass(type):
    # __new__ is the method called before __init__
    # it's the method that creates the object and returns it
    # while __init__ just initializes the object passed as parameter
    # you rarely use __new__, except when you want to control how the object
    # is created.
    # here the created object is the class, and we want to customize it
    # so we override __new__
    # you can do some stuff in __init__ too if you wish
    # some advanced use involves overriding __call__ as well, but we won't
    # see this
    def __new__(upperattr_metaclass, future_class_name,
                future_class_parents, future_class_attr):

        uppercase_attr = {}
        for name, val in future_class_attr.items():
            if not name.startswith('__'):
                uppercase_attr[name.upper()] = val
            else:
                uppercase_attr[name] = val

        return type(future_class_name, future_class_parents, uppercase_attr)

Ama bu gerçekten OOP değil. Biz ararız type doğrudan ve geçersiz kılmayız veya ebeveyni arayın __new__. Haydi Yapalım şunu:

class UpperAttrMetaclass(type):

    def __new__(upperattr_metaclass, future_class_name,
                future_class_parents, future_class_attr):

        uppercase_attr = {}
        for name, val in future_class_attr.items():
            if not name.startswith('__'):
                uppercase_attr[name.upper()] = val
            else:
                uppercase_attr[name] = val

        # reuse the type.__new__ method
        # this is basic OOP, nothing magic in there
        return type.__new__(upperattr_metaclass, future_class_name,
                            future_class_parents, uppercase_attr)

Ekstra tartışmayı fark etmiş olabilirsiniz. upperattr_metaclass. Var Bu konuda özel bir şey yok: __new__ her zaman tanımlandığı sınıfı ilk parametre olarak alır. Tıpkı seninki gibi self örneği ilk parametre olarak alan sıradan yöntemler veya sınıf yöntemleri için tanımlayıcı sınıf.

Tabii ki, burada kullandığım isimler netlik için uzun, ama için selfTüm argümanların geleneksel isimleri vardır. Yani gerçek bir üretim metaclass şöyle görünürdü:

class UpperAttrMetaclass(type):

    def __new__(cls, clsname, bases, dct):

        uppercase_attr = {}
        for name, val in dct.items():
            if not name.startswith('__'):
                uppercase_attr[name.upper()] = val
            else:
                uppercase_attr[name] = val

        return type.__new__(cls, clsname, bases, uppercase_attr)

Kullanarak daha da temiz yapabiliriz supermirasını kolaylaştıracak (çünkü evet, meta sınıflar alabilir, meta sınıflardan miras alabilir, türünden miras alabilirsiniz):

class UpperAttrMetaclass(type):

    def __new__(cls, clsname, bases, dct):

        uppercase_attr = {}
        for name, val in dct.items():
            if not name.startswith('__'):
                uppercase_attr[name.upper()] = val
            else:
                uppercase_attr[name] = val

        return super(UpperAttrMetaclass, cls).__new__(cls, clsname, bases, uppercase_attr)

Bu kadar. Meta sınıflar hakkında gerçekten başka bir şey yoktur.

Metaclasses kullanarak kod karmaşıklığının arkasındaki nedeni değildir metaclasses, çünkü genellikle bükülmüş şeyler yapmak için metasınıflar kullanırsınız introspeksiyona dayanarak, miras manipüle ederek, __dict__, vb.

Aslında, metasınıflar kara büyü yapmak için özellikle yararlıdır ve bu nedenle karmaşık şeyler. Fakat kendileri, basittirler:

  • sınıf yaratımını engellemek
  • sınıfı değiştir
  • değiştirilmiş sınıfı döndür

Neden işlevler yerine meta sınıflar sınıfları kullanırsınız?

Dan beri __metaclass__ herhangi bir heceyi kabul edebilir, neden bir sınıfı kullanırdınız Açıkça daha karmaşık olduğu için mi?

Bunu yapmanın birkaç nedeni vardır:

  • Amaç açıktır. Okuduğun zaman UpperAttrMetaclass(type), Bilirsin ne takip edecek
  • OOP kullanabilirsiniz. Meta sınıf meta sınıftan miras alabilir, üst yöntemleri geçersiz kılabilir. Meta sınıfları metasınıflarını bile kullanabilir.
  • Bir sınıfın alt sınıfları, bir metaclass sınıfı belirttiyseniz, ancak bir metaclass işleviyle değil, meta sınıfının örnekleridir.
  • Kodunuzu daha iyi yapılandırabilirsiniz. Asla bir şey için metaclass kullanmazsınız Yukarıdaki örnek olarak önemsiz. Genellikle karmaşık bir şey için. Sahip olmak Birkaç yöntem yapma ve bunları bir sınıfta gruplama becerisi çok yararlıdır kodun okunmasını kolaylaştırmak için
  • Bağlayabilirsiniz __new__, __init__ ve __call__. Hangi izin verecek Farklı şeyler yapmalısın. Genelde hepsini yapabilirseniz bile __new__, bazı insanlar sadece daha rahat kullanıyor __init__.
  • Bu metaclasses denir, lanet olsun! Bir şey ifade etmeli!

Neden metasınıf kullanırsın?

Şimdi büyük soru. Neden bazı belirsiz hata eğilimli özelliği kullanırsınız?

Peki, genellikle yapmazsın:

Meta sınıflar daha derin bir sihirdir   Kullanıcıların% 99'u asla endişelenmemelidir.   İhtiyacın olup olmadığını merak ediyorsan,   yapmıyorsunuz (aslında insanlar   onlara kesin olarak bilmeniz gerekir   Onlara ihtiyaçları var ve bir   neden hakkında açıklama).

Python Guru Tim Peters

Bir metaclass için ana kullanım durumu bir API oluşturmaktır. Bunun tipik bir örneği Django ORM'dir.

Böyle bir şeyi tanımlamanıza olanak tanır:

class Person(models.Model):
    name = models.CharField(max_length=30)
    age = models.IntegerField()

Ama bunu yaparsan:

guy = Person(name='bob', age='35')
print(guy.age)

Bir geri dönmeyecek IntegerField nesne. Bir geri dönecek intve hatta doğrudan veritabanından alabilir.

Bu mümkün çünkü models.Model tanımlar __metaclass__ ve dönecek bazı büyü kullanır Person basit ifadelerle tanımladınız bir veritabanı alanına karmaşık bir kancaya.

Django, basit bir API'yi göstererek karmaşık bir görünümü basitleştirir ve metaclass kullanarak, gerçek işi yapmak için bu API'dan kodun yeniden oluşturulması kamera ARKASI.

Son kelime

Öncelikle, sınıfların örnek oluşturabilecek nesneler olduğunu biliyorsunuz.

Aslında, sınıflar kendileridir. Metasınıfların.

>>> class Foo(object): pass
>>> id(Foo)
142630324

Her şey Python'da bir nesnedir ve hepsi ya sınıfların örnekleridir veya metasınıfların örnekleri.

Dışında type.

type aslında kendi meta sınıfıdır. Bu yapabileceğin bir şey değil Saf Python'da yeniden üretin ve uygulamada biraz aldatarak yapılır seviyesi.

İkincisi, metasınıflar karmaşıktır. Bunları kullanmak istemeyebilirsiniz Çok basit sınıf değişiklikleri. Sınıfları iki farklı teknik kullanarak değiştirebilirsiniz.

Sınıf değişikliğine ihtiyaç duyduğunuz zamanın% 99'u, bunları kullanmaktan daha iyidir.

Ama zamanın% 98'inde, sınıf değişikliği yapmanıza gerek yok.


5763
2017-09-19 06:26



Görünüşe göre Django’da models.Model kullanmaz __metaclass__ daha ziyade class Model(metaclass=ModelBase): referans vermek ModelBase Daha sonra sözü edilen metaclass sihrini yapan sınıf. Harika mesaj! İşte Django kaynağı: github.com/django/django/blob/master/django/db/models/... - Max Goodridge
<< burada dikkat edin __metaclass__ öznitelik devralınmayacak, ebeveynin meta sınıfı (Bar.__class__) olacak. Eğer Bar kullanılmış __metaclass__ oluşturulan özellik Bar ile type() (ve yok type.__new__()), alt sınıflar bu davranışı devralmayacaktır. >> - Birisi bu geçidi biraz daha açıklayabilir misiniz? - petrux
@MaxGoodridge Bu, metasınıflar için Python 3 sözdizimi. Görmek Python 3.6 Veri modeli VS Python 2.7 Veri modeli - TBBle
Bu bir topluluk wiki cevabıdır (bu yüzden düzeltmeler / geliştirmelerle yorum yapanların yorumları doğru olduklarından emin olduklarında yorumları düzenlemeyi düşünebilirler). - Shule
Bu cevabın hangi kısımları hakkında python2 ve hangisi pythono3? - styrofoam fly


Not, bu cevap 2008 yılında yazıldığı gibi Python 2.x içindir, metaclass 3.x'te biraz farklıdır, yorumlara bakın.

Meta sınıflar, 'sınıf' çalışmasını yapan gizli sostur. Yeni bir stil nesnesinin varsayılan meta sınıfı 'type' olarak adlandırılır.

class type(object)
  |  type(object) -> the object's type
  |  type(name, bases, dict) -> a new type

Metaclasses 3 tane alır. 'isim','bazlar' ve 'dict'

İşte sırrın başladığı yer. Bu örnek sınıf tanımında adın, tabanların ve dict'ın nereden geldiğini bulun.

class ThisIsTheName(Bases, Are, Here):
    All_the_code_here
    def doesIs(create, a):
        dict

Nasıl göstereceğini gösteren bir meta sınıfı tanımlarsınıf:'onu çağırır.

def test_metaclass(name, bases, dict):
    print 'The Class Name is', name
    print 'The Class Bases are', bases
    print 'The dict has', len(dict), 'elems, the keys are', dict.keys()

    return "yellow"

class TestName(object, None, int, 1):
    __metaclass__ = test_metaclass
    foo = 1
    def baz(self, arr):
        pass

print 'TestName = ', repr(TestName)

# output => 
The Class Name is TestName
The Class Bases are (<type 'object'>, None, <type 'int'>, 1)
The dict has 4 elems, the keys are ['baz', '__module__', 'foo', '__metaclass__']
TestName =  'yellow'

Ve şimdi, aslında bir şey anlamına gelen bir örnek, bu, sınıfta belirlenen "nitelikler" listesindeki değişkenleri otomatik olarak yapar ve Yok olarak ayarlanır.

def init_attributes(name, bases, dict):
    if 'attributes' in dict:
        for attr in dict['attributes']:
            dict[attr] = None

    return type(name, bases, dict)

class Initialised(object):
    __metaclass__ = init_attributes
    attributes = ['foo', 'bar', 'baz']

print 'foo =>', Initialised.foo
# output=>
foo => None

'Initalised' olan sihirli davranışın meta sınıfına sahip olmasının getirdiğini unutmayın. init_attributes initalised alt sınıfına aktarılmaz.

Sınıf oluşturulduğunda bir eylem gerçekleştiren bir metaclass oluşturmak için 'tip' alt sınıfını nasıl gösterebileceğinizi gösteren daha somut bir örnek. Bu oldukça zor:

class MetaSingleton(type):
    instance = None
    def __call__(cls, *args, **kw):
        if cls.instance is None:
            cls.instance = super(MetaSingleton, cls).__call__(*args, **kw)
        return cls.instance

 class Foo(object):
     __metaclass__ = MetaSingleton

 a = Foo()
 b = Foo()
 assert a is b

312
2017-09-19 06:45





Metasınıflar için bir kullanım otomatik olarak bir örneğe yeni özellikler ve yöntemler eklemektir.

Örneğin, eğer bakarsanız Django modelleriOnların tanımı biraz kafa karıştırıcı görünüyor. Sadece sınıf özelliklerini tanımlıyor gibi görünüyor:

class Person(models.Model):
    first_name = models.CharField(max_length=30)
    last_name = models.CharField(max_length=30)

Ancak, çalışma zamanında Kişi nesneleri, her türlü yararlı yöntemlerle doldurulur. Bakın kaynak Bazı muhteşem metaclassery için.


125
2018-06-21 16:30



Yeni özellikler ve yöntemler ekleyen meta sınıflarının kullanımı sınıf ve bir örnek değil? Anladığım kadarıyla, meta sınıf, sınıfın kendisini değiştirir ve sonuç olarak, değişmiş sınıf tarafından farklı şekilde inşa edilebilir. Bir meta sınıfının doğasını bulmaya çalışan insanlara biraz yanıltıcı olabilir. Örnekler üzerinde yararlı yöntemlere sahip olmak normal kalıtımla sağlanabilir. Bir örnek olarak Django koduna referans olsa da, iyidir. - trixn


Diğerleri metasınıfların nasıl çalıştığını ve Python tipi sisteme nasıl uyduğunu anlattı. İşte bunlar için kullanılabilecekleri bir örnek. Yazdığım bir test çerçevesinde, sınıfların tanımlandığı sırayı takip etmek istedim, böylece bunları daha sonra bu sırayla gerçekleştirebildim. Bunu bir metaclass kullanarak yapmanın en kolayını buldum.

class MyMeta(type):

    counter = 0

    def __init__(cls, name, bases, dic):
        type.__init__(cls, name, bases, dic)
        cls._order = MyMeta.counter
        MyMeta.counter += 1

class MyType(object):              # Python 2
    __metaclass__ = MyMeta

class MyType(metaclass=MyMeta):    # Python 3
    pass

Bir alt sınıfı olan herhangi bir şey MyType sonra bir sınıf özniteliği alır _order Bu, sınıfların tanımlandığı sırayı kaydeder.


117
2017-09-19 06:32





ONLamp metaclass programlamaya giriş iyi yazılmış ve birkaç yaşında olmasına rağmen konuya gerçekten iyi bir giriş sunuyor.

http://www.onlamp.com/pub/a/python/2003/04/17/metaclasses.html

Kısaca: Bir sınıf, bir örneğin yaratılması için bir plandır, bir meta sınıf bir sınıfın oluşturulması için bir plandır. Python sınıflarında bu davranışı sağlamak için de birinci sınıf nesneler olması gerektiği kolayca görülebilir.

Hiç kendim yazmadım, ama metasınıfların en güzel kullanımlarından birinin Django çerçevesi. Model sınıfları, yeni modellerin yazılması veya form sınıfları oluşturmanın bildirimsel stilini sağlamak için bir meta sınıf yaklaşımı kullanır. Metaclass sınıfı yaratırken, tüm üyeler sınıfın kendisini kişiselleştirme olanağına kavuşurlar.

Söylenecek şey şudur: Metaclass'ın ne olduğunu bilmiyorsanız, olasılık sizin onlara gerek olmayacak % 99


86
2017-08-10 23:28





Metaclass nelerdir? Onları ne için kullanıyorsun?

TLDR: Bir meta sınıf, bir sınıfın tıpkı bir sınıf için davranışını tanımlar ve tanımlar ve bir örneğin davranışını tanımlar.

pseudocode:

>>> Class(...)
instance

Yukarıdakiler tanıdık gelmelidir. Peki, nerede Class dan geliyorum? Bir metaclass örneğidir (ayrıca sözde kod):

>>> Metaclass(...)
Class

Gerçek kodda, varsayılan meta sınıfı geçebilir, typeBir ders oluşturmak için ihtiyacımız olan her şey ve bir sınıf elde ederiz:

>>> type('Foo', (object,), {}) # requires a name, bases, and a namespace
<class '__main__.Foo'>

Farklı koyarak

  • Bir sınıf, meta sınıfın bir sınıfa olduğu bir örneğidir.

    Bir nesneyi başlattığımızda, bir örnek alırız:

    >>> object()                          # instantiation of class
    <object object at 0x7f9069b4e0b0>     # instance
    

    Benzer şekilde, bir sınıfı varsayılan metaclass ile açıkça tanımladığımız zaman, type, biz onu başlatalım:

    >>> type('Object', (object,), {})     # instantiation of metaclass
    <class '__main__.Object'>             # instance
    
  • Başka bir deyişle, bir sınıf bir meta sınıfın bir örneğidir:

    >>> isinstance(object, type)
    True
    
  • Üçüncü bir yolla, bir meta sınıf bir sınıfın sınıfıdır.

    >>> type(object) == type
    True
    >>> object.__class__
    <class 'type'>
    

Bir sınıf tanımı yazdığınızda ve Python bunu çalıştırdığında, sınıf nesnesini başlatmak için bir metaclass kullanır (bu da, o sınıfın örneklerini başlatmak için kullanılır).

Özel nesne örneklerinin nasıl davrandığını değiştirmek için sınıf tanımlarını kullanabileceğimiz gibi, bir sınıf nesnesinin davranış şeklini değiştirmek için bir meta sınıf sınıf tanımını kullanabiliriz.

Ne için kullanılabilirler? İtibaren docs:

Metasınıflar için potansiyel kullanımlar sınırsızdır. İncelenen bazı fikirler arasında günlük kaydı, arayüz kontrolü, otomatik yetkilendirme, otomatik özellik oluşturma, proxy'ler, çerçeveler ve otomatik kaynak kilitleme / senkronizasyon bulunmaktadır.

Yine de, kullanıcılara kesinlikle gerekli olmadıkça metasınıfları kullanmaktan kaçınmaları teşvik edilir.

Her sınıf oluşturduğunuzda bir metaclass kullanırsınız:

Örneğin, bir sınıf tanımı yazdığınızda,

class Foo(object): 
    'demo'

Bir sınıf nesnesini başlatıyorsunuz.

>>> Foo
<class '__main__.Foo'>
>>> isinstance(Foo, type), isinstance(Foo, object)
(True, True)

Fonksiyonel olarak aynı şey type Uygun argümanlar ile ve sonucu bu ismin bir değişkenine atayarak:

name = 'Foo'
bases = (object,)
namespace = {'__doc__': 'demo'}
Foo = type(name, bases, namespace)

Not, bazı şeyler otomatik olarak eklenir __dict__yani ad alanı:

>>> Foo.__dict__
dict_proxy({'__dict__': <attribute '__dict__' of 'Foo' objects>, 
'__module__': '__main__', '__weakref__': <attribute '__weakref__' 
of 'Foo' objects>, '__doc__': 'demo'})

metaclass Her iki durumda da yarattığımız nesnenin type.

(Sınıfın içeriği ile ilgili bir yan not __dict__: __module__ çünkü sınıflar nerede tanımlandığını bilmeli ve __dict__ ve __weakref__ var çünkü tanımlamıyoruz __slots__ - Eğer biz tanımlamak __slots__ izin veremeyeceğimiz gibi, örneklerde biraz yer kapatacağız __dict__ ve __weakref__ onları dışarıda bırakarak. Örneğin:

>>> Baz = type('Bar', (object,), {'__doc__': 'demo', '__slots__': ()})
>>> Baz.__dict__
mappingproxy({'__doc__': 'demo', '__slots__': (), '__module__': '__main__'})

... ama ben kazıyorum.

Uzayabiliriz type diğer sınıf tanımları gibi:

İşte varsayılan __repr__ sınıfların

>>> Foo
<class '__main__.Foo'>

Python nesnesini yazarken varsayılan olarak yapabileceğimiz en değerli şeylerden biri, iyi bir şekilde sunmaktır. __repr__. Aradığımızda help(repr) Öğrenmek için iyi bir test olduğunu öğreniyoruz __repr__ Bu da eşitlik için bir test gerektirir - obj == eval(repr(obj)). Aşağıdaki basit uygulama __repr__ ve __eq__ tür sınıfımızın sınıf örnekleri için bize varsayılanı geliştirebilecek bir tanıtım sunar. __repr__ sınıfların

class Type(type):
    def __repr__(cls):
        """
        >>> Baz
        Type('Baz', (Foo, Bar,), {'__module__': '__main__', '__doc__': None})
        >>> eval(repr(Baz))
        Type('Baz', (Foo, Bar,), {'__module__': '__main__', '__doc__': None})
        """
        metaname = type(cls).__name__
        name = cls.__name__
        parents = ', '.join(b.__name__ for b in cls.__bases__)
        if parents:
            parents += ','
        namespace = ', '.join(': '.join(
          (repr(k), repr(v) if not isinstance(v, type) else v.__name__))
               for k, v in cls.__dict__.items())
        return '{0}(\'{1}\', ({2}), {{{3}}})'.format(metaname, name, parents, namespace)
    def __eq__(cls, other):
        """
        >>> Baz == eval(repr(Baz))
        True            
        """
        return (cls.__name__, cls.__bases__, cls.__dict__) == (
                other.__name__, other.__bases__, other.__dict__)

Şimdi bu metaclass ile bir nesne oluşturduğumuzda, __repr__ komut satırında yankılandı, varsayılandan daha az çirkin bir görüntü sağlar:

>>> class Bar(object): pass
>>> Baz = Type('Baz', (Foo, Bar,), {'__module__': '__main__', '__doc__': None})
>>> Baz
Type('Baz', (Foo, Bar,), {'__module__': '__main__', '__doc__': None})

Güzel __repr__ sınıf örneği için tanımlanmış, kodumuzda hata ayıklama konusunda daha güçlü bir yeteneğimiz vardır. Ancak, daha fazla kontrol ile eval(repr(Class)) olası değildir (işlevlerin varsayılan değerlerinden değerlendirmek oldukça imkansız olduğu için) __repr__'Ler).

Beklenen kullanım: __prepare__ ad alanı

Örneğin, bir sınıfın yöntemlerinin hangi sırayla yaratıldığını bilmek istiyorsak, sınıfın ad alanı olarak düzenli bir dict sağlayabiliriz. Bunu yapardık __prepare__ hangi Python 3'te uygulanmışsa, sınıfın ad alanı kuralı döndürür:

from collections import OrderedDict

class OrderedType(Type):
    @classmethod
    def __prepare__(metacls, name, bases, **kwargs):
        return OrderedDict()
    def __new__(cls, name, bases, namespace, **kwargs):
        result = Type.__new__(cls, name, bases, dict(namespace))
        result.members = tuple(namespace)
        return result

Ve kullanımı:

class OrderedMethodsObject(object, metaclass=OrderedType):
    def method1(self): pass
    def method2(self): pass
    def method3(self): pass
    def method4(self): pass

Ve şimdi, bu yöntemlerin (ve diğer sınıf özelliklerinin) yaratıldığı sırayı kaydettik:

>>> OrderedMethodsObject.members
('__module__', '__qualname__', 'method1', 'method2', 'method3', 'method4')

Not, bu örnek, belgeleme - yeni standart kitaplıkta numara bunu yapar.

Yani yaptığımız, bir sınıf oluşturarak bir metaclass oluşturmaktı. Ayrıca, meta sınıfı başka bir sınıfta olduğu gibi ele alabiliriz. Bir yöntem çözüm sırası vardır:

>>> inspect.getmro(OrderedType)
(<class '__main__.OrderedType'>, <class '__main__.Type'>, <class 'type'>, <class 'object'>)

Ve yaklaşık doğru repr (Artık işlevlerimizi temsil etmenin bir yolunu bulamadıkça değerlendiremeyiz.):

>>> OrderedMethodsObject
OrderedType('OrderedMethodsObject', (object,), {'method1': <function OrderedMethodsObject.method1 at 0x0000000002DB01E0>, 'members': ('__module__', '__qualname__', 'method1', 'method2', 'method3', 'method4'), 'method3': <function OrderedMet
hodsObject.method3 at 0x0000000002DB02F0>, 'method2': <function OrderedMethodsObject.method2 at 0x0000000002DB0268>, '__module__': '__main__', '__weakref__': <attribute '__weakref__' of 'OrderedMethodsObject' objects>, '__doc__': None, '__d
ict__': <attribute '__dict__' of 'OrderedMethodsObject' objects>, 'method4': <function OrderedMethodsObject.method4 at 0x0000000002DB0378>})

74
2018-03-01 19:48





Python 3 güncellemesi

Bir meta sınıfta iki temel yöntem vardır (bu noktada):

  • __prepare__, ve
  • __new__

__prepare__ özel bir eşleme sağlamanıza olanak tanır. OrderedDict) sınıf oluştururken isim alanı olarak kullanılacak. Seçtiğiniz herhangi bir adın bir örneğini döndürmelisiniz. Uygulamıyorsanız __prepare__normal dict kullanıldı.

__new__ Son sınıfın gerçek yaratımı / modifikasyonundan sorumludur.

Bir çıplak kemikleri, hiçbir şey yapmadan fazla metaclass şöyle olurdu:

class Meta(type):

    def __prepare__(metaclass, cls, bases):
        return dict()

    def __new__(metacls, cls, bases, clsdict):
        return super().__new__(metacls, cls, bases, clsdict)

Basit bir örnek:

Özellikleriniz üzerinde çalışmak için bazı basit doğrulama kodu istediğinizi varsayalım - her zaman olması gerektiği gibi int ya da str. Bir metaclass olmadan, sınıfınız şöyle bir şey olurdu:

class Person:
    weight = ValidateType('weight', int)
    age = ValidateType('age', int)
    name = ValidateType('name', str)

Gördüğünüz gibi, özniteliğin adını iki kez tekrarlamanız gerekir. Bu, yazım hatalarını tahriş edici hatalarla birlikte mümkün kılar.

Basit bir meta sınıf bu soruna hitap edebilir:

class Person(metaclass=Validator):
    weight = ValidateType(int)
    age = ValidateType(int)
    name = ValidateType(str)

Bu metaclass nasıl görüneceği (kullanmıyor) __prepare__ gerekli olmadığı için):

class Validator(type):
    def __new__(metacls, cls, bases, clsdict):
        # search clsdict looking for ValidateType descriptors
        for name, attr in clsdict.items():
            if isinstance(attr, ValidateType):
                attr.name = name
                attr.attr = '_' + name
        # create final class and return it
        return super().__new__(metacls, cls, bases, clsdict)

Örnek bir çalışma:

p = Person()
p.weight = 9
print(p.weight)
p.weight = '9'

üretir:

9
Traceback (most recent call last):
  File "simple_meta.py", line 36, in <module>
    p.weight = '9'
  File "simple_meta.py", line 24, in __set__
    (self.name, self.type, value))
TypeError: weight must be of type(s) <class 'int'> (got '9')

Not: Bu örnek, bir sınıf dekoratörüyle de gerçekleştirilebilecek kadar basittir, ancak gerçek bir metaclass çok daha fazlasını yapıyor olabilirdi.

Referans için 'ValidateType' sınıfı:

class ValidateType:
    def __init__(self, type):
        self.name = None  # will be set by metaclass
        self.attr = None  # will be set by metaclass
        self.type = type
    def __get__(self, inst, cls):
        if inst is None:
            return self
        else:
            return inst.__dict__[self.attr]
    def __set__(self, inst, value):
        if not isinstance(value, self.type):
            raise TypeError('%s must be of type(s) %s (got %r)' %
                    (self.name, self.type, value))
        else:
            inst.__dict__[self.attr] = value

55
2017-10-13 09:21



Metaclass örneğinde anladım NameError: name 'ValidateType' is not defined. Bunu en iyi şekilde düzeltmek için herhangi bir öneriniz var mı? Python 2 kullanıyorum - Nickpick