Python'da metasınıflar nelerdir?


Yanıtlar:


2869

Metasınıf bir sınıfın sınıfıdır. Sınıf, sınıfın bir örneğinin (yani bir nesnenin) nasıl davrandığını, bir metasınıf ise bir sınıfın nasıl davrandığını tanımlar. Sınıf bir metasınıfın örneğidir.

Python'da metasınıflar için ( Jerub'un gösterdiği gibi ) keyfi callables kullanabiliyorsanız , daha iyi yaklaşım onu ​​gerçek bir sınıf haline getirmektir. typePython'daki olağan metasınıftır. typekendisi bir sınıftır ve kendi tipidir. typeTamamen Python'da olduğu gibi bir şey yaratamazsınız , ancak Python biraz hile yapar. Python'da kendi metasınıfınızı oluşturmak için gerçekten sadece alt sınıf yapmak istiyorsunuz type.

Metasınıf en yaygın olarak sınıf fabrikası olarak kullanılır. Sınıfı çağırarak bir nesne oluşturduğunuzda, Python metasınıfı çağırarak yeni bir sınıf oluşturur ('class' ifadesini çalıştırdığında). Normal __init__ve __new__yöntemlerle birleştiğinde, metasınıflar bir sınıf oluştururken yeni sınıfı bir kayıt defterine kaydettirmek veya sınıfı tamamen başka bir şeyle değiştirmek gibi 'ekstra şeyler' yapmanıza izin verir.

İfade classyürütüldüğünde, Python önce classifadenin gövdesini normal bir kod bloğu olarak yürütür . Sonuçta elde edilen ad alanı (bir diksiyon), alınacak sınıfın niteliklerini içerir. Metasınıf, olması gereken sınıfın (metasınıflar devralınır) __metaclass__temel sınıflarına, olması gereken sınıfın (varsa) özelliğine veya __metaclass__global değişkene bakarak belirlenir . Daha sonra metasınıfı, sınıfın adı, tabanı ve öznitelikleri ile somutlaştırmak için çağrılır.

Bununla birlikte, metasınıflar aslında sadece bir fabrika değil, bir sınıfın türünü tanımlar , böylece onlarla daha fazlasını yapabilirsiniz. Örneğin, metasınıfta normal yöntemleri tanımlayabilirsiniz. Bu metasınıf yöntemleri, sınıf olmadan örnek üzerinde çağrılabildikleri için sınıf yöntemleri gibidir, ancak sınıf örneğinde çağrılamayacakları gibi sınıf yöntemleri gibi de değildirler. metasınıfta type.__subclasses__()bir yöntem örneğidir type. Ayrıca, gibi normal 'Sihirli' yöntemleri tanımlayabilir __add__, __iter__ve __getattr__uygulamak veya değiştirmek nasıl sınıf davranacağını için.

İş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__

13
class A(type):pass<NEWLINE>class B(type,metaclass=A):pass<NEWLINE>b.__class__ = b
pppery

20
ppperry açıkçası tipini bir metasınıf olarak kullanmadan türü yeniden yaratamayacağınız anlamına geliyordu. Bu söyleyecek kadar adil.
Holle van

3
Unregister (), Örnek sınıf örneği tarafından çağrılmamalı mı?
Ciasto piekarz

5
Not __metaclass__Python 3. ayında Python 3 kullanımda desteklenmez class MyObject(metaclass=MyType), bkz python.org/dev/peps/pep-3115 ve aşağıda cevabını.
BlackShift

2
Belgeler , metasınıfın nasıl seçildiğini açıklar . Metaclass, türetildiği kadar miras alınmaz. Bir metasınıf belirtirseniz, her bir temel sınıf metasınıfının bir alt türü olmalıdır; aksi takdirde, temel sınıf metasınıfının bir alt türü olan bir temel sınıf metasınıfını kullanırsınız. O mümkün olduğunu unutmayın hiçbir geçerli metaclass bulunabilir ve tanımı başarısız olur.
chepner

6819

Nesne olarak sınıflar

Metasınıfları anlamadan önce Python'da derslerde ustalaşmanız gerekir. Ve Python, Smalltalk dilinden ödünç alınan sınıfların ne olduğu konusunda çok tuhaf bir fikre sahiptir.

Çoğu dilde, sınıflar yalnızca bir nesnenin nasıl üretileceğini açıklayan kod parçalarıdır. Bu Python'da da doğrudur:

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

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

Ama sınıflar Python'dakinden daha fazla. Sınıflar da nesnedir.

Evet, nesneler.

Anahtar kelimeyi kullandığınız anda classPython anahtar kelimeyi yürütür ve bir NESNE oluşturur. Talimat

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

bellekte "ObjectCreator" adlı bir nesne oluşturur.

Bu nesnenin (sınıf) kendisi, nesneler (örnekler) oluşturabilmektedir ve bu yüzden bir sınıftır .

Ama yine de, bu bir nesne ve bu nedenle:

  • bir değişkene atayabilirsiniz
  • kopyalayabilirsin
  • buna özellikler ekleyebilirsiniz
  • bir işlev 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>

Sınıfları dinamik olarak oluşturma

Sınıflar nesne olduğundan, bunları herhangi bir nesne gibi anında oluşturabilirsiniz.

İlk olarak, aşağıdakileri kullanarak bir işlevde 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ı hala kendiniz yazmak zorundasınız.

Sınıflar nesne olduğundan, bir şey tarafından üretilmeleri gerekir.

classAnahtar kelimeyi kullandığınızda , Python bu nesneyi otomatik olarak oluşturur. Ancak Python'daki çoğu şeyde olduğu gibi, bunu manuel olarak yapmanın bir yolunu sunar.

İşlevi hatırlıyor typemusunuz? Bir nesnenin ne tür olduğunu bilmenizi sağlayan eski işlev:

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

Eh, typebu da anında sınıf oluşturabileceğini bambaşka bir yeteneği vardır. typebir sınıfın tanımını parametre olarak alabilir ve bir sınıfı döndürebilir.

(Biliyorum, aynı işlevin, ilettiğiniz parametrelere göre tamamen farklı iki kullanıma sahip olması saçma. Python'daki geriye dönük uyumluluk nedeniyle bir sorun)

type şu şekilde çalışır:

type(name, bases, attrs)

Nerede:

  • name: sınıfın adı
  • bases: ana sınıfın tuple'i (kalıtım için boş olabilir)
  • attrs: özellik adları ve değerleri içeren sözlük

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

Sınıfın adı ve sınıf referansını tutmak için değişken olarak "MyShinyClass" kullandığımızı fark edeceksiniz. Farklı olabilirler, ancak işleri karmaşıklaştırmak için bir neden yoktur.

typesınıfın özelliklerini tanımlamak için bir sözlüğü 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, ondan 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 isteyeceksiniz. Sadece uygun imzalı bir işlev tanımlayın 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

Sınıfı dinamik olarak oluşturduktan sonra, normalde oluşturulan bir sınıf nesnesine yöntem eklemek gibi, 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 dinamik olarak bir sınıf oluşturabilirsiniz.

Anahtar kelimeyi kullandığınızda Python classbunu yapar ve bunu bir metasınıf kullanarak yapar.

Metaclasses nedir (sonunda)

Metasınıflar sınıflar yaratan 'şeyler'dir.

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

Ancak Python sınıflarının nesne olduğunu öğrendik.

Bu nesneleri yaratan metasınıflar. Bunlar sınıfların sınıflarıdır, onları şu şekilde hayal edebilirsiniz:

MyClass = MetaClass()
my_object = MyClass()

Bunun typeböyle bir şey yapmanıza izin verdiğini gördünüz :

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

Çünkü fonksiyon typeaslında bir metasınıf. typePython'un perde arkasındaki tüm sınıfları oluşturmak için kullandığı metasınıf.

Şimdi merak ediyorum neden küçük harfle yazılıyor, değil Typemi?

Sanırım bu bir tutarlılık meselesi, strdizeler nesneleri intyaratan sınıf ve tamsayı nesneler yaratan sınıf. typesadece sınıf nesneleri oluşturan sınıftır.

Bu __class__özelliği kontrol ederek görürsünüz .

Her şey ve her şey demek istediğim Python'da bir nesne. Buna ints, stringler, fonksiyonlar ve sınıflar dahildir. Hepsi nesneler. Ve hepsi bir sınıftan yaratıldı:

>>> 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, __class__herhangi biri __class__nedir?

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

Yani, bir metasınıf sadece sınıf nesneleri yaratan şeydir.

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

type Python'un kullandığı yerleşik metasınıftır, ancak elbette kendi metasınıfınızı oluşturabilirsiniz.

__metaclass__nitelik

Python 2'de, __metaclass__bir sınıf yazarken bir öznitelik ekleyebilirsiniz (Python 3 sözdizimi için sonraki bölüme bakın):

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

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

Dikkatli, zor.

Önce yazıyorsunuz class Foo(object), ancak sınıf nesnesi Foohenüz bellekte oluşturulmamış.

Python __metaclass__sınıf tanımında arayacaktır . Bulursa, nesne sınıfını oluşturmak için kullanır Foo. Yoksa, typesınıfı oluşturmak için kullanılır .

Bunu birkaç kez okuyun.

Yaptığınızda:

class Foo(Bar):
    pass

Python aşağıdakileri yapar:

İçinde bir __metaclass__özellik var Foomı?

Evet ise, bellekte bir sınıf nesnesi oluşturun (bir sınıf nesnesi dedim, burada benimle kal), içinde Foone olduğunu kullanarak adla __metaclass__.

Python bulamazsa __metaclass__, __metaclass__MODÜL seviyesinde bir arar ve aynısını yapmaya çalışır (ancak yalnızca hiçbir şey miras almayan sınıflar, temelde eski stil sınıfları için).

Sonra herhangi bulamazsa __metaclass__hiç bu kullanacağız Bar's (birinci ebeveyn) (varsayılan olabilir kendi metaclass typesınıf nesnesi oluşturmak için).

Burada __metaclass__özelliğin miras alınmayacağına, üst öğenin ( Bar.__class__) metasınıfına dikkat edin . Eğer Barkullanılmış bir __metaclass__yaratılmış olduğunu niteliği Barile type()(ve type.__new__()), alt sınıfları davranış olduğunu devralmaz.

Şimdi asıl soru, ne koyabilirsiniz __metaclass__?

Cevap: sınıf yaratabilecek bir şey.

Ve ne sınıf oluşturabilir? typeveya alt sınıfları kullanan veya kullanan herhangi bir şey.

Metasınıflar in Python 3

Meta sınıfı ayarlamak için sözdizimi Python 3'te değiştirildi:

class Foo(object, metaclass=something):
    ...

yani __metaclass__öznitelik artık temel sınıflar listesindeki bir anahtar kelime argümanı lehine kullanılmaz.

Ancak metasınıfların davranışı büyük ölçüde aynı kalır .

Python 3'teki metasınıflara eklenen bir şey, öznitelikleri anahtar kelime bağımsız değişkenleri olarak bir metasınıfa da aktarabilmenizdir:

class Foo(object, metaclass=something, kwarg1=value1, kwarg2=value2):
    ...

Python'un bunu nasıl ele aldığını öğrenmek için aşağıdaki bölümü okuyun.

Özel metasınıflar

Metasınıfın temel amacı sınıfı oluşturulduğunda otomatik olarak değiştirmektir.

Bunu genellikle geçerli bağlamla eşleşen sınıflar oluşturmak istediğiniz API'lar için yaparsınız.

Modülünüzdeki tüm sınıfların niteliklerinin büyük harfle yazılması gerektiğine karar verdiğiniz aptal bir örnek düşünün. Bunu yapmanın birkaç yolu vardır, ancak bir yol __metaclass__modül düzeyinde ayarlamaktır .

Bu şekilde, bu modülün tüm sınıfları bu metasınıf kullanılarak oluşturulacak ve sadece metaslass'a tüm nitelikleri büyük harfe çevirmesini söylemeliyiz.

Neyse ki, __metaclass__aslında herhangi bir çağrılabilir olabilir, resmi bir sınıf olması gerekmez (biliyorum, 'sınıf' olan bir şey bir sınıf olmak zorunda değildir, şekil gidin ... ama yararlıdır).

Yani basit bir örnekle, bir fonksiyon kullanarak 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_attrs):
    """
      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_attrs = {
        attr if attr.startswith("__") else attr.upper(): v
        for attr, v in future_class_attrs.items()
    }

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

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

Hadi kontrol edelim:

>>> hasattr(Foo, 'bar')
False
>>> hasattr(Foo, 'BAR')
True
>>> Foo.BAR
'bip'

Şimdi aynısını yapalım, ancak bir metasınıf 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_attrs):
        uppercase_attrs = {
            attr if attr.startswith("__") else attr.upper(): v
            for attr, v in future_class_attrs.items()
        }
        return type(future_class_name, future_class_parents, uppercase_attrs)

Yukarıdakileri yeniden yazalım, ancak şimdi ne anlama geldiğini bildiğimiz için daha kısa ve daha gerçekçi değişken isimleriyle:

class UpperAttrMetaclass(type):
    def __new__(cls, clsname, bases, attrs):
        uppercase_attrs = {
            attr if attr.startswith("__") else attr.upper(): v
            for attr, v in attrs.items()
        }
        return type(clsname, bases, uppercase_attrs)

Ekstra argümanı fark etmiş olabilirsiniz cls. Bu konuda özel bir şey yok: __new__her zaman tanımlandığı sınıfı ilk parametre olarak alır. Tıpkı selfilk parametre olarak örneği alan sıradan yöntemlere veya sınıf yöntemleri için tanımlayıcı sınıfa sahip olduğunuz gibi.

Ancak bu uygun bir OOP değil. Biz aradığınız typedoğrudan ve biz geçersiz kılma veya ebeveynin demiyorsun __new__. Bunun yerine yapalım:

class UpperAttrMetaclass(type):
    def __new__(cls, clsname, bases, attrs):
        uppercase_attrs = {
            attr if attr.startswith("__") else attr.upper(): v
            for attr, v in attrs.items()
        }
        return type.__new__(cls, clsname, bases, uppercase_attrs)

Bunu kullanarak daha da temiz hale getirebiliriz super, bu da kalıtımı kolaylaştırır (çünkü evet, metasınıflardan miras, metasınıflardan miras, tipten miras alabilirsiniz):

class UpperAttrMetaclass(type):
    def __new__(cls, clsname, bases, attrs):
        uppercase_attrs = {
            attr if attr.startswith("__") else attr.upper(): v
            for attr, v in attrs.items()
        }
        return super(UpperAttrMetaclass, cls).__new__(
            cls, clsname, bases, uppercase_attrs)

Oh ve python 3'te bu çağrıyı aşağıdaki gibi anahtar kelime bağımsız değişkenleriyle yaparsanız:

class Foo(object, metaclass=MyMetaclass, kwarg1=value1):
    ...

Bunu kullanmak için metasınıfta buna çevirir:

class MyMetaclass(type):
    def __new__(cls, clsname, bases, dct, kwargs1=default):
        ...

Bu kadar. Metaclasses hakkında daha fazla bir şey yok.

Metaclasses kullanan kodun karmaşıklığının ardındaki neden, metasınıflar nedeniyle değildir, çünkü genellikle introspeksiyona dayanan, mirası manipüle eden __dict__, vb.

Gerçekten de, metasınıflar özellikle kara büyü ve dolayısıyla karmaşık şeyler yapmak için kullanışlıdır. Ancak kendi başlarına basittirler:

  • sınıf oluşturmayı engelleme
  • sınıfı değiştir
  • değiştirilmiş sınıfı döndür

Neden fonksiyonlar yerine metasınıf sınıfları kullanasınız?

Yana __metaclass__herhangi çağrılabilir kabul edebilir açıkça daha karmaşıktır çünkü, neden bir sınıf kullanmak istiyorsunuz?

Bunu yapmanın birkaç nedeni vardır:

  • Niyet açık. Okuduğunuzda UpperAttrMetaclass(type), neyi takip edeceğinizi biliyorsunuz
  • OOP kullanabilirsiniz. Metaclass, metaclass'tan miras alabilir, üst yöntemleri geçersiz kılabilir. Metaclasses bile metaclasses kullanabilir.
  • Bir metasınıf sınıfını belirttiyseniz, ancak bir metasınıf işleviyle değil, bir sınıfın alt sınıfları metasınıfının örnekleri olacaktır.
  • Kodunuzu daha iyi yapılandırabilirsiniz. Yukarıdaki örnek kadar önemsiz bir şey için asla metasınıf kullanmazsınız. Genellikle karmaşık bir şey için. Birkaç yöntem yapma ve bunları bir sınıfta gruplama yeteneğine sahip olmak, kodun okunmasını kolaylaştırmak için çok yararlıdır.
  • Üzerinde kanca __new__, __init__ve __call__. Hangi farklı şeyler yapmanıza izin verecektir. Genellikle her şeyi yapabilseniz bile __new__, bazı insanlar kullanmak daha rahattır __init__.
  • Bunlara metasınıf denir, lanet olsun! Bir şey ifade etmeli!

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

Şimdi büyük soru. Neden bazı hataya yatkın özellikler kullanıyorsunuz?

Genellikle şunları yapmazsınız:

Metaclasses, kullanıcıların% 99'unun asla endişelenmemesi gereken daha derin bir sihirdir. Onlara ihtiyacınız olup olmadığını merak ediyorsanız, (gerçekten ihtiyaç duyan insanlar onlara ihtiyaç duyduklarını kesin olarak bilirler ve neden hakkında bir açıklamaya ihtiyaç duymazlar).

Python Guru Tim Peters

Metasınıfın ana kullanım durumu bir API oluşturmaktır. Bunun tipik bir örneği Django ORM'dir. Bunun gibi bir şey tanımlamanızı sağlar:

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

Ancak bunu yaparsanız:

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

Bir IntegerFieldnesne döndürmez . Bir döndürür intve hatta doğrudan veritabanından alabilir.

Bu mümkündür çünkü models.Modeltanımlar __metaclass__ve Personbasit ifadelerle tanımladığınız bir sihirbazı kullanarak karmaşık bir kancaya veritabanı alanına dönüşür.

Django, basit bir API'yi açığa çıkararak ve metasınıfları kullanarak, sahne arkasındaki gerçek işi yapmak için bu API'dan kod yeniden oluşturarak karmaşık bir şeyi basitleştirir.

Son kelime

İlk olarak, sınıfların örnek oluşturabilen nesneler olduğunu bilirsiniz.

Aslında, sınıfların kendileri örneklerdir. Metasınıfların.

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

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

Dışında type.

typeaslında kendi metasınıfıdır. Bu, saf Python'da çoğaltabileceğiniz bir şey değildir ve uygulama düzeyinde biraz hile yaparak yapılır.

İkincisi, metasınıflar karmaşıktır. Bunları çok basit sınıf değişiklikleri için kullanmak istemeyebilirsiniz. İki farklı teknik kullanarak sınıfları değiştirebilirsiniz:

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

Ama zamanın% 98'inde sınıf değişikliğine hiç ihtiyacınız yok.


30
Görünüşe göre Django'da, daha önce yukarıda bahsedilen metasınıf büyüsünü yapan bir sınıfa başvurmak yerine bunu models.Modelkullanmıyor . Harika gönderi! İşte Django kaynağı: github.com/django/django/blob/master/django/db/models/…__metaclass__class Model(metaclass=ModelBase):ModelBase
Max Goodridge

15
<< Burada __metaclass__özniteliğin devralınmayacağına, üst öğenin ( Bar.__class__) metasınıfına dikkat edin . Eğer Barkullanılmış bir __metaclass__yaratılmış olduğunu niteliği Barile type()(ve type.__new__(). Eğer / birileri biraz daha derin bu pasaj açıklayabilir misiniz -), alt sınıfları davranış >> o miras değil mi?
petrux

15
@MaxGoodridge Bu metasınıflar için Python 3 sözdizimi. Bkz. Python 3.6 Veri modeli VS Python 2.7 Veri modeli
TBBle

2
Now you wonder why the heck is it written in lowercase, and not Type?- C dilinde uygulandığı için - OrderedDict (python 2'de) normal CamelCase iken defaultdict küçük harfle aynı nedendir
Mr_and_Mrs_D

15
Bu bir topluluk wiki yanıtıdır (bu nedenle, düzeltmeler / iyileştirmelerle yorum yapanlar doğru olduklarından emin olduklarında yorumlarını yanıtta düzenlemeyi düşünebilirler).
Brōtsyorfuzthrāx

403

Not, bu cevap 2008'de yazıldığı gibi Python 2.x içindir, metasınıflar 3.x'te biraz farklıdır.

Metaclasses, 'sınıf' iş yapan gizli sostur. Yeni bir stil nesnesinin varsayılan metasınıfına 'tip' denir.

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

Metasınıflar 3 argüman alır. ' name ', ' base ' ve ' dict '

İşte sır burada başlıyor. Bu örnek sınıf tanımında adın, tabanların ve diktenin nereden geldiğine bakın.

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

' Class: ' kelimesinin nasıl çağırdığını gösteren bir metasınıf tanımlayalım .

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 otomatik olarak sınıfta ayarlanan "öznitelikler" listesindeki değişkenleri 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

InitialisedMetaclass'a sahip olarak kazanılan sihir davranışının init_attributesbir alt sınıfına aktarılmadığını unutmayın Initialised.

Sınıf oluşturulduğunda bir eylem gerçekleştiren bir metasınıf yapmak için 'tip' alt sınıfını nasıl yapabileceğ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

169

Diğerleri, metasınıfların nasıl çalıştığını ve Python tipi sisteme nasıl uyduklarını açıkladı. İşte ne için kullanılabileceğine dair bir örnek. Yazdığım bir test çerçevesinde, sınıfların tanımlandığı sırayı takip etmek istedim, böylece daha sonra bunları bu sırayla başlatabilirdim. Bunu bir metasınıf kullanarak yapmanın en kolay yolunu 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

Alt sınıfı olan her şey , sınıfların tanımlandığı sırayı kaydeden MyTypebir sınıf niteliği alır _order.


Örnek için teşekkürler. Neden bu kimin, MyBase devralmasını daha kolay buldunuz __init__(self)diyor type(self)._order = MyBase.counter; MyBase.counter += 1?
Michael Gundlach

1
Sınıfların kendi örneklerinin değil kendilerinin numaralandırılmasını istedim.
tür

Doğru, duh. Teşekkürler. Kodum, her örneklemede MyType özniteliğini sıfırlar ve hiçbir MyType örneği oluşturulmamışsa özniteliği hiçbir zaman ayarlamaz. Hata. (Ve bir sınıf özelliği de işe yarayabilir, ancak metasınıfın aksine sayacı saklamak için belirgin bir yer
sunmaz

1
Bu çok ilginç bir örnektir, çünkü en azından bir kişi, belirli bir zorluğa bir çözüm sağlamak için bir metasınıfın neden gerekli olduğunu gerçekten görebilir. OTOH Herkesin sınıflarının tanımlandığı sırayla nesneleri somutlaştırması gerektiğine ikna olmak için mücadele ediyorum: Sanırım bunun için sadece sözünüzü almak zorundayız :).
mike kemirgen

159

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

Örneğin, Django modellerine bakarsanız, tanımları biraz kafa karıştırıcı görünüyor. Sadece sınıf özelliklerini tanımlıyormuşsunuz gibi görünüyor:

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

Bununla birlikte, çalışma zamanında Person nesneleri her türlü yararlı yöntemle doldurulur. Bazı şaşırtıcı metasınıflar için kaynağa bakın .


6
Meta sınıfların kullanımı, bir sınıfa değil , bir sınıfa yeni özellikler ve yöntemler ekliyor mu? Anladığım kadarıyla meta sınıfı sınıfın kendisini değiştirir ve sonuç olarak örnekler değiştirilmiş sınıf tarafından farklı şekilde inşa edilebilir. Bir meta sınıfın doğasını almaya çalışan insanlar için biraz yanıltıcı olabilir. Örnekler üzerinde yararlı yöntemlere sahip olmak, normal kalıtımla sağlanabilir. Bununla birlikte, örnek olarak Django koduna referans iyidir.
trixn

119

Metasınıf programlamaya ONLamp girişinin iyi yazılmış olduğunu ve zaten birkaç yaşında olmasına rağmen konuya gerçekten iyi bir giriş yaptığını düşünüyorum.

http://www.onlamp.com/pub/a/python/2003/04/17/metaclasses.html ( https://web.archive.org/web/20080206005253/http://www.onlamp adresinde arşivlendi . com.tr / pub / a / python / 2003/04/17 / metaclasses.html )

Kısacası: Sınıf, bir örneğin oluşturulması için bir taslaktır, metasınıf ise bir sınıfın oluşturulması için bir taslaktır. Bu davranışı sağlamak için Python sınıflarında da birinci sınıf nesneler olması gerektiği kolayca görülebilir.

Kendimi hiç yazmadım, ancak bence metasınıfların en güzel kullanımlarından biri Django çerçevesinde görülebilir . Model sınıfları, yeni modeller veya form sınıfları yazma konusunda açıklayıcı bir stil sağlamak için bir metasınıf yaklaşımı kullanır. Metaclass sınıfı oluştururken, tüm üyeler sınıfın kendisini özelleştirme olanağına sahiptir.

Söylenecek olan şey şu: Eğer metasınıfların ne olduğunu bilmiyorsanız, onlara ihtiyacınız olmayacak olasılığı % 99'dur.


109

Metaclasses nedir? Onları ne için kullanıyorsun?

TLDR: Bir metasınıf, bir sınıfın davranışını, tıpkı bir sınıfın örneğini başlatır ve tanımlar gibi tanımlar.

pseudocode:

>>> Class(...)
instance

Yukarıdakiler tanıdık gelmelidir. Peki, nereden Classgeliyor? Bu bir metasınıf örneği (ayrıca sözde kod):

>>> Metaclass(...)
Class

Gerçek kodda, varsayılan metasınıfı geçebiliriz, typebir sınıfı başlatmak için ihtiyacımız olan her şey ve bir sınıf alırız:

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

Farklı bir şekilde koymak

  • Sınıf örneğe, metasınıf ise sınıfa aittir.

    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 metasınıfla açıkça tanımladığımızda type, bunu başlatırız:

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

    >>> isinstance(object, type)
    True
  • Üçüncü bir deyişle, metasınıf bir sınıftır.

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

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

Özel nesne örneklerinin davranış biçimini değiştirmek için sınıf tanımlarını kullanabildiğimiz gibi, bir sınıf nesnesinin davranış biçimini değiştirmek için bir metasınıf sınıf tanımı kullanabiliriz.

Ne için kullanılabilirler? Gönderen docs :

Metasınıfların potansiyel kullanımları sınırsızdır. Keşfedilen bazı fikirler arasında günlük kaydı, arayüz denetimi, otomatik temsilci seçme, otomatik özellik oluşturma, proxy'ler, çerçeveler ve otomatik kaynak kilitleme / eşitleme bulunur.

Bununla birlikte, kullanıcıların kesinlikle gerekli olmadıkça metasınıf kullanmaktan kaçınmaları teşvik edilir.

Her sınıf oluşturduğunuzda bir metasınıf 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ırsınız.

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

İşlevsel olarak typeuygun argümanlarla çağrı yapmak ve sonucu bu adın bir değişkenine atamakla aynıdır :

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

Not, bazı şeyler otomatik olarak __dict__ad alanına eklenir :

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

Her iki durumda da yarattığımız nesnenin metasınıfıtype .

(Sınıfın içeriğiyle ilgili bir yan not __dict__: __module__sınıflar nerede tanımlandıklarını bilmeleri gerektiğinden __dict__ve __weakref__orada tanımlanmadığımız için varlar __slots__- eğer tanımlarsak__slots__ , örneklerde biraz yer tasarrufu yapacağız. izin vermeyebilir __dict__ve __weakref__hariç tutabiliriz.

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

... ama konuya giriyorum.)

typeDiğer sınıf tanımları gibi genişletebiliriz :

__repr__Sınıfların varsayılanı :

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

Bir Python nesnesi yazarken varsayılan olarak yapabileceğimiz en değerli şeylerden biri, ona bir iyi sağlamaktır __repr__. Aradığımızda , eşitlik testi de gerektiren help(repr)iyi bir test olduğunu öğreniyoruz . Tür sınıfımızın sınıf örneklerinin ve örneklerinin aşağıdaki basit uygulaması bize sınıfların varsayılan değerlerini artırabilecek bir gösterim sağlar :__repr__obj == eval(repr(obj))__repr____eq____repr__

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 metasınıfla bir nesne oluşturduğumuzda __repr__, komut satırındaki yankı varsayılandan çok daha az çirkin bir görüş sağlar:

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

__repr__Sınıf örneği için iyi tanımlanmış bir kodla, kodumuzu hata ayıklamak için daha güçlü bir yeteneğe sahibiz. Bununla birlikte, daha fazla kontrol eval(repr(Class))yapılması olası değildir (fonksiyonların varsayılan değerlerinden değerlendirilmesi imkansız olacaktır __repr__).

Beklenen bir kullanım: __prepare__bir ad alanı

Örneğin, bir sınıfın yöntemlerinin hangi sırada oluşturulduğunu bilmek istiyorsak, sınıfın ad alanı olarak sıralı bir diksiyon sağlayabiliriz. Biz bu yapardın __prepare__hangi Python 3'te uygulanırsa sınıf için ad dicti 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 özniteliklerinin) yaratılma sırasının bir kaydı var:

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

Not, bu örnek dokümantasyondan uyarlanmıştır - standart kütüphanedeki yeni numaralandırma bunu yapar.

Yani yaptığımız şey bir sınıf oluşturarak bir metasınıf başlatmaktı. Ayrıca metasınıfı başka bir sınıf gibi davranabiliriz. Bir yöntem çözümleme sırası vardır:

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

Ve yaklaşık olarak doğru repr(işlevlerimizi temsil etmenin bir yolunu bulamadıkça artık 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>})

78

Python 3 güncellemesi

Bir metasınıfta (bu noktada) iki anahtar yöntem vardır:

  • __prepare__, ve
  • __new__

__prepare__OrderedDictsınıf oluşturulurken ad alanı olarak kullanılacak özel bir eşleme (an gibi ) sağlamanıza olanak tanır . Seçtiğiniz ad alanının bir örneğini döndürmelisiniz. Uygulamazsanız __prepare__normal dictkullanılır.

__new__ final sınıfının asıl yaratılmasından / değiştirilmesinden sorumludur.

Çıplak kemikler, ekstra hiçbir şey yapmayan metasınıf:

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:

Özelliklerinizde bazı basit doğrulama kodlarının çalışmasını istediğinizi varsayalım - her zaman bir intveya a olması gerekir str. Metaclass olmasaydı, sınıfınız şöyle görünecektir:

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

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

Basit bir metasınıf bu sorunu çözebilir:

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

Metaclass böyle görünecektir (gerekli olmadığından kullanmamak __prepare__):

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ü ile de gerçekleştirilebilecek kadar basittir, ancak muhtemelen gerçek bir metasınıf çok daha fazlasını yapacaktır.

Başvuru 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

Vay canına, bu Python 3'te var olduğunu bilmediğim harika yeni bir özellik.
Rich Lysakowski Doktora

Piton 3.6, kullanabilirsiniz beri bu Not __set_name__(cls, name)açıklayıcıda ( ValidateType) açıklayıcıda adını ayarlamak için ( self.nameve ayrıca bu durumda self.attr). Bu, bu özel ortak kullanım durumu için metasınıflara dalmak zorunda kalmamak için eklenmiştir (bakınız PEP 487).
Lars

68

__call__()Sınıf örneği oluştururken metasınıfın yönteminin rolü

Birkaç aydan uzun bir süredir Python programlaması yaptıysanız, sonunda aşağıdaki gibi görünen kodlara rastlayacaksınız:

# define a class
class SomeClass(object):
    # ...
    # some definition here ...
    # ...

# create an instance of it
instance = SomeClass()

# then call the object as if it's a function
result = instance('foo', 'bar')

İkincisi, __call__()sınıfta sihirli yöntemi uyguladığınızda mümkündür .

class SomeClass(object):
    # ...
    # some definition here ...
    # ...

    def __call__(self, foo, bar):
        return bar + foo

__call__()Bir sınıfın bir örneği, bir çağrılabilir olarak kullanıldığı zaman, yöntem, çağrılır. Ancak önceki cevaplardan da gördüğümüz gibi, bir sınıfın kendisi bir metasınıfın örneğidir, bu yüzden sınıfı bir çağrılabilir olarak kullandığımızda (yani bir örneğini oluşturduğumuzda) aslında onun metasınıfı __call__()yöntemini çağırıyoruz . Bu noktada, Python programcılarının çoğu biraz karışıktır, çünkü böyle bir örnek oluştururken yöntemini instance = SomeClass()çağırdığınızı söylediler __init__(). Biraz daha derine kazılmış ettik kim Bazı önce biliyoruz __init__()var __new__(). Peki, bugün __new__()metasınıf olmadan önce başka bir gerçeklik katmanı ortaya çıkıyor __call__().

Yöntem çağırma zincirini, özellikle bir sınıf örneği oluşturma perspektifinden inceleyelim.

Bu, bir örnek oluşturulmadan hemen önce ve onu döndürmek üzere olan anı günlüğe kaydeden bir metasınıftır.

class Meta_1(type):
    def __call__(cls):
        print "Meta_1.__call__() before creating an instance of ", cls
        instance = super(Meta_1, cls).__call__()
        print "Meta_1.__call__() about to return instance."
        return instance

Bu metasınıfı kullanan bir sınıf

class Class_1(object):

    __metaclass__ = Meta_1

    def __new__(cls):
        print "Class_1.__new__() before creating an instance."
        instance = super(Class_1, cls).__new__(cls)
        print "Class_1.__new__() about to return instance."
        return instance

    def __init__(self):
        print "entering Class_1.__init__() for instance initialization."
        super(Class_1,self).__init__()
        print "exiting Class_1.__init__()."

Şimdi bir örnek oluşturalım Class_1

instance = Class_1()
# Meta_1.__call__() before creating an instance of <class '__main__.Class_1'>.
# Class_1.__new__() before creating an instance.
# Class_1.__new__() about to return instance.
# entering Class_1.__init__() for instance initialization.
# exiting Class_1.__init__().
# Meta_1.__call__() about to return instance.

Yukarıdaki kodun aslında görevleri günlüğe kaydetmekten başka bir şey yapmadığını gözlemleyin. Her yöntem asıl çalışmayı ebeveyninin uygulamasına devreder ve böylece varsayılan davranışı korur. Yana typeolduğunu Meta_1'ın üst sınıf ( typevarsayılan ebeveyn metaclass olmak üzere) ve yukarıdaki çıkışın sipariş dizisini göz önüne alındığında artık sözde uygulaması ne olacağını bir ipucu var type.__call__():

class type:
    def __call__(cls, *args, **kwarg):

        # ... maybe a few things done to cls here

        # then we call __new__() on the class to create an instance
        instance = cls.__new__(cls, *args, **kwargs)

        # ... maybe a few things done to the instance here

        # then we initialize the instance with its __init__() method
        instance.__init__(*args, **kwargs)

        # ... maybe a few more things done to instance here

        # then we return it
        return instance

Metaclass ' __call__()yönteminin ilk denilen yöntem olduğunu görebiliriz . Daha sonra, örneğin oluşturulmasını sınıfın __new__()yöntemine ve başlatmayı örneğe uygular __init__(). Nihayetinde örneği döndüren de odur.

Üstündeki Metaclass' olduğunu kaynaklanıyor itibaren __call__()de edip etmeme çağrısı karar için fırsat verilir Class_1.__new__()veya Class_1.__init__()sonunda yapılacaktır. Yürütülmesi sırasında, bu yöntemlerden herhangi biri tarafından dokunulmamış bir nesneyi döndürebilir. Örneğin, singleton modeline bu yaklaşımı ele alalım:

class Meta_2(type):
    singletons = {}

    def __call__(cls, *args, **kwargs):
        if cls in Meta_2.singletons:
            # we return the only instance and skip a call to __new__()
            # and __init__()
            print ("{} singleton returning from Meta_2.__call__(), "
                   "skipping creation of new instance.".format(cls))
            return Meta_2.singletons[cls]

        # else if the singleton isn't present we proceed as usual
        print "Meta_2.__call__() before creating an instance."
        instance = super(Meta_2, cls).__call__(*args, **kwargs)
        Meta_2.singletons[cls] = instance
        print "Meta_2.__call__() returning new instance."
        return instance

class Class_2(object):

    __metaclass__ = Meta_2

    def __new__(cls, *args, **kwargs):
        print "Class_2.__new__() before creating instance."
        instance = super(Class_2, cls).__new__(cls)
        print "Class_2.__new__() returning instance."
        return instance

    def __init__(self, *args, **kwargs):
        print "entering Class_2.__init__() for initialization."
        super(Class_2, self).__init__()
        print "exiting Class_2.__init__()."

Tekrar tekrar bir tür nesne yaratmaya çalışırken neler olduğunu gözlemleyelim Class_2

a = Class_2()
# Meta_2.__call__() before creating an instance.
# Class_2.__new__() before creating instance.
# Class_2.__new__() returning instance.
# entering Class_2.__init__() for initialization.
# exiting Class_2.__init__().
# Meta_2.__call__() returning new instance.

b = Class_2()
# <class '__main__.Class_2'> singleton returning from Meta_2.__call__(), skipping creation of new instance.

c = Class_2()
# <class '__main__.Class_2'> singleton returning from Meta_2.__call__(), skipping creation of new instance.

a is b is c # True

Bu, daha önce onaylanan "kabul edilen cevap" a iyi bir ektir. Ara kodlayıcıların çiğnemesi için örnekler sağlar.
Rich Lysakowski Doktora

56

Metasınıf, başka bir sınıfın nasıl (bazı) yaratılması gerektiğini anlatan bir sınıftır.

Bu, metasınıfı problemime bir çözüm olarak gördüğüm bir durum: Muhtemelen farklı bir şekilde çözülebilecek gerçekten karmaşık bir sorunum vardı, ancak bir metasınıf kullanarak çözmeyi seçtim. Karmaşıklık nedeniyle, modüldeki yorumların yazılan kod miktarını aştığı birkaç modülden biridir. İşte burada...

#!/usr/bin/env python

# Copyright (C) 2013-2014 Craig Phillips.  All rights reserved.

# This requires some explaining.  The point of this metaclass excercise is to
# create a static abstract class that is in one way or another, dormant until
# queried.  I experimented with creating a singlton on import, but that did
# not quite behave how I wanted it to.  See now here, we are creating a class
# called GsyncOptions, that on import, will do nothing except state that its
# class creator is GsyncOptionsType.  This means, docopt doesn't parse any
# of the help document, nor does it start processing command line options.
# So importing this module becomes really efficient.  The complicated bit
# comes from requiring the GsyncOptions class to be static.  By that, I mean
# any property on it, may or may not exist, since they are not statically
# defined; so I can't simply just define the class with a whole bunch of
# properties that are @property @staticmethods.
#
# So here's how it works:
#
# Executing 'from libgsync.options import GsyncOptions' does nothing more
# than load up this module, define the Type and the Class and import them
# into the callers namespace.  Simple.
#
# Invoking 'GsyncOptions.debug' for the first time, or any other property
# causes the __metaclass__ __getattr__ method to be called, since the class
# is not instantiated as a class instance yet.  The __getattr__ method on
# the type then initialises the class (GsyncOptions) via the __initialiseClass
# method.  This is the first and only time the class will actually have its
# dictionary statically populated.  The docopt module is invoked to parse the
# usage document and generate command line options from it.  These are then
# paired with their defaults and what's in sys.argv.  After all that, we
# setup some dynamic properties that could not be defined by their name in
# the usage, before everything is then transplanted onto the actual class
# object (or static class GsyncOptions).
#
# Another piece of magic, is to allow command line options to be set in
# in their native form and be translated into argparse style properties.
#
# Finally, the GsyncListOptions class is actually where the options are
# stored.  This only acts as a mechanism for storing options as lists, to
# allow aggregation of duplicate options or options that can be specified
# multiple times.  The __getattr__ call hides this by default, returning the
# last item in a property's list.  However, if the entire list is required,
# calling the 'list()' method on the GsyncOptions class, returns a reference
# to the GsyncListOptions class, which contains all of the same properties
# but as lists and without the duplication of having them as both lists and
# static singlton values.
#
# So this actually means that GsyncOptions is actually a static proxy class...
#
# ...And all this is neatly hidden within a closure for safe keeping.
def GetGsyncOptionsType():
    class GsyncListOptions(object):
        __initialised = False

    class GsyncOptionsType(type):
        def __initialiseClass(cls):
            if GsyncListOptions._GsyncListOptions__initialised: return

            from docopt import docopt
            from libgsync.options import doc
            from libgsync import __version__

            options = docopt(
                doc.__doc__ % __version__,
                version = __version__,
                options_first = True
            )

            paths = options.pop('<path>', None)
            setattr(cls, "destination_path", paths.pop() if paths else None)
            setattr(cls, "source_paths", paths)
            setattr(cls, "options", options)

            for k, v in options.iteritems():
                setattr(cls, k, v)

            GsyncListOptions._GsyncListOptions__initialised = True

        def list(cls):
            return GsyncListOptions

        def __getattr__(cls, name):
            cls.__initialiseClass()
            return getattr(GsyncListOptions, name)[-1]

        def __setattr__(cls, name, value):
            # Substitut option names: --an-option-name for an_option_name
            import re
            name = re.sub(r'^__', "", re.sub(r'-', "_", name))
            listvalue = []

            # Ensure value is converted to a list type for GsyncListOptions
            if isinstance(value, list):
                if value:
                    listvalue = [] + value
                else:
                    listvalue = [ None ]
            else:
                listvalue = [ value ]

            type.__setattr__(GsyncListOptions, name, listvalue)

    # Cleanup this module to prevent tinkering.
    import sys
    module = sys.modules[__name__]
    del module.__dict__['GetGsyncOptionsType']

    return GsyncOptionsType

# Our singlton abstract proxy class.
class GsyncOptions(object):
    __metaclass__ = GetGsyncOptionsType()

43

Tl; dr sürümü

type(obj)Fonksiyon size bir nesnenin türünü alır.

type()Bir sınıfın onun olduğu metaclass .

Metasınıf kullanmak için:

class Foo(object):
    __metaclass__ = MyMetaClass

typekendi metasınıfıdır. Bir sınıfın sınıfı bir metasınıftır - bir sınıfın gövdesi, sınıfı oluşturmak için kullanılan metaclass'a iletilen argümanlardır.

Burada sınıf yapımını özelleştirmek için metasınıfların nasıl kullanılacağı hakkında bilgi edinebilirsiniz.


42

typeaslında metaclassbaşka bir sınıf oluşturan bir sınıftır. Çoğu metaclassalt sınıflarıdır type. metaclassAldığında newilk argüman olarak sınıf ve aşağıda belirtilen ayrıntıları ile sınıf nesnesi erişim sağlar:

>>> class MetaClass(type):
...     def __init__(cls, name, bases, attrs):
...         print ('class name: %s' %name )
...         print ('Defining class %s' %cls)
...         print('Bases %s: ' %bases)
...         print('Attributes')
...         for (name, value) in attrs.items():
...             print ('%s :%r' %(name, value))
... 

>>> class NewClass(object, metaclass=MetaClass):
...    get_choch='dairy'
... 
class name: NewClass
Bases <class 'object'>: 
Defining class <class 'NewClass'>
get_choch :'dairy'
__module__ :'builtins'
__qualname__ :'NewClass'

Note:

Sınıfın hiçbir zaman somutlaştırılmadığına dikkat edin; sınıf yaratmanın basit eylemi metaclass.


27

Python sınıflarının kendileri - örneğin, meta sınıflarının nesneleridir.

Sınıfları şu şekilde belirlediğinizde uygulanan varsayılan metasınıf:

class foo:
    ...

meta sınıfı, sınıfların tümüne bir kural uygulamak için kullanılır. Örneğin, bir veritabanına erişmek için bir ORM oluşturduğunuzu ve her tablodaki kayıtların o tabloyla eşleştirilmiş bir sınıfta olmasını istediğinizi varsayalım (alanlar, iş kuralları vb. Temel alınarak), metasınıfın olası kullanımı örneğin, tüm tablolardan tüm kayıt sınıfları tarafından paylaşılan bağlantı havuzu mantığıdır. Başka bir kullanım, birden fazla kayıt sınıfını içeren yabancı anahtarları desteklemek için mantıktır.

metasınıfı tanımladığınızda, alt sınıf türünü kullanırsınız ve mantığınızı eklemek için aşağıdaki sihirli yöntemleri geçersiz kılabilirsiniz.

class somemeta(type):
    __new__(mcs, name, bases, clsdict):
      """
  mcs: is the base metaclass, in this case type.
  name: name of the new class, as provided by the user.
  bases: tuple of base classes 
  clsdict: a dictionary containing all methods and attributes defined on class

  you must return a class object by invoking the __new__ constructor on the base metaclass. 
 ie: 
    return type.__call__(mcs, name, bases, clsdict).

  in the following case:

  class foo(baseclass):
        __metaclass__ = somemeta

  an_attr = 12

  def bar(self):
      ...

  @classmethod
  def foo(cls):
      ...

      arguments would be : ( somemeta, "foo", (baseclass, baseofbase,..., object), {"an_attr":12, "bar": <function>, "foo": <bound class method>}

      you can modify any of these values before passing on to type
      """
      return type.__call__(mcs, name, bases, clsdict)


    def __init__(self, name, bases, clsdict):
      """ 
      called after type has been created. unlike in standard classes, __init__ method cannot modify the instance (cls) - and should be used for class validaton.
      """
      pass


    def __prepare__():
        """
        returns a dict or something that can be used as a namespace.
        the type will then attach methods and attributes from class definition to it.

        call order :

        somemeta.__new__ ->  type.__new__ -> type.__init__ -> somemeta.__init__ 
        """
        return dict()

    def mymethod(cls):
        """ works like a classmethod, but for class objects. Also, my method will not be visible to instances of cls.
        """
        pass

her neyse, bu ikisi en sık kullanılan kancalardır. metaclassing güçlüdür ve yukarıda metaclassing için hiçbir yere yakın ve kapsamlı bir liste yoktur.


21

Type () işlevi bir nesnenin türünü döndürebilir veya yeni bir tür oluşturabilir,

örneğin, type () işleviyle bir Hi sınıfı oluşturabiliriz ve Hi (object) sınıfıyla bu yolu kullanmamız gerekmez:

def func(self, name='mike'):
    print('Hi, %s.' % name)

Hi = type('Hi', (object,), dict(hi=func))
h = Hi()
h.hi()
Hi, mike.

type(Hi)
type

type(h)
__main__.Hi

Sınıfları dinamik olarak oluşturmak için type () işlevine ek olarak, sınıfın oluşturma davranışını denetleyebilir ve metaclass kullanabilirsiniz.

Python nesne modeline göre, sınıf nesnedir, bu nedenle sınıf başka bir sınıfın örneği olmalıdır. Varsayılan olarak, bir Python sınıfı type sınıfının bir örneğidir. Yani, tür yerleşik sınıfların çoğunun metasınıfı ve kullanıcı tanımlı sınıfların metasınıfıdır.

class ListMetaclass(type):
    def __new__(cls, name, bases, attrs):
        attrs['add'] = lambda self, value: self.append(value)
        return type.__new__(cls, name, bases, attrs)

class CustomList(list, metaclass=ListMetaclass):
    pass

lst = CustomList()
lst.add('custom_list_1')
lst.add('custom_list_2')

lst
['custom_list_1', 'custom_list_2']

Metasınıfta anahtar kelime argümanlarını geçtiğimizde sihir etkili olacak, Python yorumlayıcısını ListMetaclass aracılığıyla CustomList oluşturduğunu gösteriyor. new (), bu noktada, örneğin sınıf tanımını değiştirebilir ve yeni bir yöntem ekleyebilir ve sonra düzeltilmiş tanımı döndürebiliriz.


11

Yayınlanan cevaplara ek olarak, metaclassbir sınıfın davranışını tanımladığını söyleyebilirim . Böylece, metasınıfınızı açıkça ayarlayabilirsiniz. Ne zaman Python bir anahtar kelime alırsa classo zaman metaclass. Bulunmazsa - sınıfın nesnesini oluşturmak için varsayılan metasınıf türü kullanılır. __metaclass__Özniteliği kullanarak metaclasssınıfınızı ayarlayabilirsiniz :

class MyClass:
   __metaclass__ = type
   # write here other method
   # write here one more method

print(MyClass.__metaclass__)

Çıkışı şöyle üretecektir:

class 'type'

Ve elbette, metaclasssınıfınızı kullanarak oluşturulan herhangi bir sınıfın davranışını tanımlamak için kendinizinkini oluşturabilirsiniz .

Bunu yapmak için, varsayılan metaclassyazım sınıfı, bu ana sınıf olduğu için miras alınmalıdır metaclass:

class MyMetaClass(type):
   __metaclass__ = type
   # you can write here any behaviour you want

class MyTestClass:
   __metaclass__ = MyMetaClass

Obj = MyTestClass()
print(Obj.__metaclass__)
print(MyMetaClass.__metaclass__)

Çıktı şöyle olacaktır:

class '__main__.MyMetaClass'
class 'type'

4

Nesne yönelimli programlamada, metasınıf, örnekleri sınıf olan bir sınıftır. Tıpkı sıradan bir sınıfın belirli nesnelerin davranışını tanımlaması gibi, bir metasınıf da belirli sınıfın davranışlarını ve örneklerini tanımlar Metaclass terimi basitçe sınıflar oluşturmak için kullanılan bir şey anlamına gelir. Başka bir deyişle, bir sınıfın sınıfıdır. Metaclass, nesneyi bir sınıf örneği gibi sınıf oluşturmak için kullanılır, sınıf bir metaclass örneğidir. Python sınıflarında da nesneler olarak kabul edilir.


Kitap örnekleri tanımlamak yerine, bazı örnekler ekleseydiniz daha iyi olurdu. Cevabınızın ilk satırı Wikipedia'nın Metaclasses girişinden kopyalanmış gibi görünüyor.
verisimilitude

@verisimilitude Ben de öğreniyorum deneyimlerinizden bazı pratik örnekler sağlayarak bu cevabı geliştirmeme yardımcı olabilir misiniz ??
Venu Gopal Tewari

2

İşte bunun için kullanılabilecek başka bir örnek:

  • metaclassÖrneğinin (sınıf) işlevini değiştirmek için tuşunu kullanabilirsiniz .
class MetaMemberControl(type):
    __slots__ = ()

    @classmethod
    def __prepare__(mcs, f_cls_name, f_cls_parents,  # f_cls means: future class
                    meta_args=None, meta_options=None):  # meta_args and meta_options is not necessarily needed, just so you know.
        f_cls_attr = dict()
        if not "do something or if you want to define your cool stuff of dict...":
            return dict(make_your_special_dict=None)
        else:
            return f_cls_attr

    def __new__(mcs, f_cls_name, f_cls_parents, f_cls_attr,
                meta_args=None, meta_options=None):

        original_getattr = f_cls_attr.get('__getattribute__')
        original_setattr = f_cls_attr.get('__setattr__')

        def init_getattr(self, item):
            if not item.startswith('_'):  # you can set break points at here
                alias_name = '_' + item
                if alias_name in f_cls_attr['__slots__']:
                    item = alias_name
            if original_getattr is not None:
                return original_getattr(self, item)
            else:
                return super(eval(f_cls_name), self).__getattribute__(item)

        def init_setattr(self, key, value):
            if not key.startswith('_') and ('_' + key) in f_cls_attr['__slots__']:
                raise AttributeError(f"you can't modify private members:_{key}")
            if original_setattr is not None:
                original_setattr(self, key, value)
            else:
                super(eval(f_cls_name), self).__setattr__(key, value)

        f_cls_attr['__getattribute__'] = init_getattr
        f_cls_attr['__setattr__'] = init_setattr

        cls = super().__new__(mcs, f_cls_name, f_cls_parents, f_cls_attr)
        return cls


class Human(metaclass=MetaMemberControl):
    __slots__ = ('_age', '_name')

    def __init__(self, name, age):
        self._name = name
        self._age = age

    def __getattribute__(self, item):
        """
        is just for IDE recognize.
        """
        return super().__getattribute__(item)

    """ with MetaMemberControl then you don't have to write as following
    @property
    def name(self):
        return self._name

    @property
    def age(self):
        return self._age
    """


def test_demo():
    human = Human('Carson', 27)
    # human.age = 18  # you can't modify private members:_age  <-- this is defined by yourself.
    # human.k = 18  # 'Human' object has no attribute 'k'  <-- system error.
    age1 = human._age  # It's OK, although the IDE will show some warnings. (Access to a protected member _age of a class)

    age2 = human.age  # It's OK! see below:
    """
    if you do not define `__getattribute__` at the class of Human,
    the IDE will show you: Unresolved attribute reference 'age' for class 'Human'
    but it's ok on running since the MetaMemberControl will help you.
    """


if __name__ == '__main__':
    test_demo()

metaclassOnunla, ama bu yalnızca size biliniyor olabilir dikkatli olun (örneğin maymun sihirli gibi) pek çok şey vardır, güçlüdür.


2

Python'daki bir sınıf bir nesnedir ve diğer tüm nesneler gibi "şey" in bir örneğidir. Bu "bir şey" Metaclass olarak adlandırılan şeydir. Bu metasınıf, diğer sınıfın nesnelerini oluşturan özel bir sınıf türüdür. Bu nedenle, metasınıf yeni sınıflar yapmaktan sorumludur. Bu, programcının sınıfların oluşturulma şeklini özelleştirmesini sağlar.

Bir metasınıf oluşturmak için genellikle new () ve init () yöntemlerinin geçersiz kılınması yapılır. new () nesnelerin oluşturulma şeklini değiştirmek için geçersiz kılınırken, init () ise nesnenin başlatılma şeklini değiştirmek için geçersiz kılınabilir. Metaclass birkaç yolla oluşturulabilir. Yollardan biri type () işlevini kullanmaktır. type () işlevi, 3 parametre ile çağrıldığında bir metasınıf oluşturur. Parametreler: -

  1. Sınıf adı
  2. Sınıfa miras alınan temel sınıflara sahip grup
  3. Tüm sınıf yöntemlerine ve sınıf değişkenlerine sahip bir sözlük

Metasınıf oluşturmanın başka bir yolu da 'metaclass' anahtar kelimesinden oluşur. Metasınıfı basit bir sınıf olarak tanımlayın. Devralınan sınıfın parametrelerinde, metaclass = metaclass_name iletin

Metaclass özellikle aşağıdaki durumlarda kullanılabilir: -

  1. tüm alt sınıflara belirli bir efekt uygulanması gerektiğinde
  2. Sınıfın otomatik olarak değiştirilmesi (oluşturma sırasında) gereklidir
  3. API geliştiricileri tarafından

2

Python 3.6'da, metasınıflar __init_subclass__(cls, **kwargs)için birçok yaygın kullanım durumunun yerini almak üzere yeni bir dunder yönteminin sunulduğunu unutmayın. Tanımlayıcı sınıfın bir alt sınıfı oluşturulduğunda Is çağrılır. Bkz. Python belgeleri .


-3

Metaclass, sınıfın nasıl davranacağını tanımlayan bir tür sınıftır ya da A sınıfının kendisinin bir metasınıf örneği olduğunu söyleyebiliriz.

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.