Co to są metaklasy w Pythonie?

Odpowiedzi:

2869

Metaklasa jest klasą klasy. Klasa określa zachowanie instancji klasy (tj. Obiektu), a metaklasa - zachowanie klasy. Klasa jest instancją metaklasy.

Podczas gdy w Pythonie możesz używać dowolnych wywołań dla metaklas (takich jak Jerub ), lepszym podejściem jest uczynienie z niej samej klasy. typejest zwykłą metaklasą w Pythonie. typesam jest klasą i jest swoim własnym typem. Nie będziesz mógł odtworzyć czegoś takiego jak typeczysto w Pythonie, ale Python trochę oszukuje. Aby utworzyć własną metaklasę w Pythonie, naprawdę chcesz po prostu podklasę type.

Metaklasa jest najczęściej używana jako fabryka klas. Kiedy tworzysz obiekt przez wywołanie klasy, Python tworzy nową klasę (kiedy wykonuje instrukcję „class”) przez wywołanie metaklasy. W połączeniu z normalną __init__i __new__metodami, metaklasy pozwalają zatem robić „dodatkowe rzeczy” podczas tworzenia klasy, takie jak rejestracja nowej klasy w jakimś rejestrze lub zamiana klasy na coś zupełnie innego.

Kiedy classinstrukcja jest wykonywana, Python najpierw wykonuje jej treść classjako normalny blok kodu. Powstała przestrzeń nazw (dykt) zawiera atrybuty przyszłej klasy. Metaklasę określa się, patrząc na klasy podstawowe przyszłej klasy (metaklasy są dziedziczone), na podstawie __metaclass__atrybutu przyszłej klasy (jeśli istnieje) lub __metaclass__zmiennej globalnej. Metaklasa jest następnie wywoływana z nazwą, zasadami i atrybutami klasy w celu jej utworzenia.

Jednak metaklasy faktycznie określają typ klasy, a nie tylko jej fabrykę, dzięki czemu możesz zrobić z nimi znacznie więcej. Możesz na przykład zdefiniować normalne metody metaklasy. Te metody metaklasy są jak metody klasowe, ponieważ można je wywoływać w klasie bez wystąpienia, ale nie są one również podobne do metod klasowych, ponieważ nie można ich wywoływać w instancji klasy. type.__subclasses__()jest przykładem metody typemetaklasy. Można również zdefiniować sposoby na normal „Magic”, jak __add__, __iter__i __getattr__, aby wdrożyć lub zmienić sposób zachowuje się klasa.

Oto zagregowany przykład bitów i kawałków:

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__
Thomas Wouters
źródło
13
class A(type):pass<NEWLINE>class B(type,metaclass=A):pass<NEWLINE>b.__class__ = b
pppery
20
ppperry miał na myśli oczywiście, że nie można odtworzyć tekstu bez użycia samego typu jako metaklasy. Co jest sprawiedliwe, aby powiedzieć.
Holle van
3
Czy funkcja unregister () nie powinna być wywoływana przez instancję klasy Example?
Ciasto piekarz
5
Zauważ, że __metaclass__nie jest obsługiwane w Pythonie 3. W użyciu w Pythonie 3 class MyObject(metaclass=MyType), zobacz python.org/dev/peps/pep-3115 i odpowiedź poniżej.
BlackShift
2
Dokumentacja opisuje sposób wyboru metaklasy . Metaklasa nie jest dziedziczona tak bardzo, jak pochodzi. Jeśli podasz metaklasę, musi to być podtyp każdej metaklasy klasy bazowej; w przeciwnym razie użyjesz metaklasy klasy bazowej, która jest podtypem każdej innej metaklasy klasy bazowej. Pamiętaj, że możliwe jest, że nie można znaleźć prawidłowej metaklasy, a definicja zakończy się niepowodzeniem.
chepner
6814

Klasy jako obiekty

Zanim zrozumiesz metaklasy, musisz opanować klasy w języku Python. Python ma bardzo osobliwe pojęcie o klasach, zapożyczone z języka Smalltalk.

W większości języków klasy to tylko fragmenty kodu opisujące sposób tworzenia obiektu. Tak jest również w Pythonie:

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

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

Ale klasy są czymś więcej niż w Pythonie. Klasy też są przedmiotami.

Tak, przedmioty.

Jak tylko użyjesz słowa kluczowego class, Python wykonuje go i tworzy OBIEKT. Instrukcja

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

tworzy w pamięci obiekt o nazwie „ObjectCreator”.

Ten obiekt (klasa) może sam tworzyć obiekty (instancje) i dlatego jest klasą .

Ale nadal jest to obiekt, a zatem:

  • możesz przypisać ją do zmiennej
  • możesz to skopiować
  • możesz dodać do niego atrybuty
  • możesz przekazać go jako parametr funkcji

na przykład:

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

Dynamiczne tworzenie klas

Ponieważ klasy są obiektami, możesz je tworzyć w locie, jak każdy obiekt.

Po pierwsze, możesz utworzyć klasę w funkcji, używając 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>

Ale to nie jest tak dynamiczne, ponieważ wciąż musisz sam napisać całą klasę.

Ponieważ klasy są obiektami, muszą być przez coś wygenerowane.

Kiedy używasz classsłowa kluczowego, Python tworzy ten obiekt automatycznie. Ale tak jak w przypadku większości rzeczy w Pythonie, daje to możliwość zrobienia tego ręcznie.

Pamiętasz funkcję type? Dobra stara funkcja, która pozwala wiedzieć, jakiego typu jest obiekt:

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

Cóż, typema zupełnie inną zdolność, może także tworzyć klasy w locie. typemoże przyjąć opis klasy jako parametry i zwrócić klasę.

(Wiem, to głupie, że ta sama funkcja może mieć dwa zupełnie różne zastosowania w zależności od przekazanych parametrów. Jest to problem związany z kompatybilnością wsteczną w Pythonie)

type działa w ten sposób:

type(name, bases, attrs)

Gdzie:

  • name: nazwa klasy
  • bases: krotka klasy nadrzędnej (dla dziedziczenia może być pusta)
  • attrs: słownik zawierający nazwy i wartości atrybutów

na przykład:

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

można utworzyć ręcznie w ten sposób:

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

Zauważysz, że używamy „MyShinyClass” jako nazwy klasy i jako zmiennej do przechowywania odwołania do klasy. Mogą być różne, ale nie ma powodu, aby to komplikować.

typeakceptuje słownik w celu zdefiniowania atrybutów klasy. Więc:

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

Można przetłumaczyć na:

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

I używany jako normalna klasa:

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

I oczywiście możesz po nim dziedziczyć, więc:

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

byłoby:

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

W końcu będziesz chciał dodać metody do swojej klasy. Wystarczy zdefiniować funkcję z odpowiednim podpisem i przypisać ją jako atrybut.

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

Możesz dodać jeszcze więcej metod po dynamicznym utworzeniu klasy, podobnie jak dodawanie metod do normalnie tworzonego obiektu klasy.

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

Widzisz, dokąd zmierzamy: w Pythonie klasy są obiektami i możesz tworzyć klasy w locie, dynamicznie.

To właśnie robi Python, gdy używasz słowa kluczowego class, i robi to za pomocą metaklasy.

Co to są metaklasy (wreszcie)

Metaklasy to „rzeczy”, które tworzą klasy.

Definiujesz klasy, aby tworzyć obiekty, prawda?

Dowiedzieliśmy się jednak, że klasy Python są obiektami.

Cóż, metaklasy tworzą te obiekty. Są to zajęcia klas, które możesz przedstawić w następujący sposób:

MyClass = MetaClass()
my_object = MyClass()

Widziałeś, że typemożesz zrobić coś takiego:

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

Jest tak, ponieważ funkcja typejest w rzeczywistości metaklasą. typeto metaklasa, której używa Python do tworzenia wszystkich klas za kulisami.

Teraz zastanawiasz się, dlaczego, do cholery, jest napisane małymi literami, a nie Type?

Cóż, myślę, że jest to kwestia spójności z strklasą, która tworzy obiekty łańcuchowe i intklasą, która tworzy obiekty całkowite. typeto tylko klasa, która tworzy obiekty klasowe.

Widzisz to, sprawdzając __class__atrybut.

Wszystko, i mam na myśli wszystko, jest przedmiotem w Pythonie. Obejmuje inty, ciągi znaków, funkcje i klasy. Wszystkie są przedmiotami. Wszystkie zostały utworzone z klasy:

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

Teraz, co jest __class__z dowolnego __class__?

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

Tak więc metaklasa jest tylko tym, co tworzy obiekty klasowe.

Jeśli chcesz, możesz to nazwać „klasową fabryką”.

type jest wbudowaną metaklasą, której używa Python, ale oczywiście możesz stworzyć własną metaklasę.

__metaclass__atrybut

W Python 2 możesz dodać __metaclass__atrybut podczas pisania klasy (zobacz następną sekcję dotyczącą składni Python 3):

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

Jeśli to zrobisz, Python użyje metaklasy do utworzenia klasy Foo.

Ostrożnie, to trudne.

class Foo(object)Najpierw piszesz , ale obiekt klasy Foonie jest jeszcze tworzony w pamięci.

Python będzie szukał __metaclass__w definicji klasy. Jeśli go znajdzie, użyje go do utworzenia klasy obiektu Foo. Jeśli nie, użyje go typedo utworzenia klasy.

Przeczytaj to kilka razy.

Kiedy to zrobisz:

class Foo(Bar):
    pass

Python wykonuje następujące czynności:

Czy jest w tym jakiś __metaclass__atrybut Foo?

Jeśli tak, stwórz w pamięci obiekt klasy (powiedziałem obiekt klasy, zostań ze mną tutaj) o nazwie Foo, używając tego, co jest w środku __metaclass__.

Jeśli Python nie może znaleźć __metaclass__, będzie szukał __metaclass__na poziomie MODUŁU i spróbuje zrobić to samo (ale tylko dla klas, które niczego nie dziedziczą, w zasadzie klas w starym stylu).

Następnie, jeśli w ogóle nie może znaleźć __metaclass__, użyje Barwłasnej metaklasy (pierwszego rodzica) (która może być domyślna type), aby utworzyć obiekt klasy.

Uważaj tutaj, aby __metaclass__atrybut nie został odziedziczony, ponieważ będzie to metaklasa elementu nadrzędnego ( Bar.__class__). Jeśli zostanie Barużyty __metaclass__atrybut, który został utworzony za Barpomocą type()(i nie type.__new__()), podklasy nie odziedziczą tego zachowania.

Najważniejsze pytanie brzmi: co możesz włożyć __metaclass__?

Odpowiedź brzmi: coś, co może stworzyć klasę.

A co może stworzyć klasę? typelub cokolwiek, co go podklasuje lub wykorzystuje.

Metaklasy w Pythonie 3

Składnia ustawiania metaklasy została zmieniona w Pythonie 3:

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

tzn. __metaclass__atrybut nie jest już używany, na korzyść argumentu słowa kluczowego na liście klas podstawowych.

Zachowanie metaklas pozostaje jednak w dużej mierze takie samo .

Jedną z rzeczy dodanych do metaklas w Pythonie 3 jest to, że możesz również przekazywać atrybuty jako argumenty-słowa kluczowe do metaklasy, tak jak:

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

Przeczytaj poniższą sekcję, w jaki sposób Python sobie z tym radzi.

Niestandardowe metaklasy

Głównym celem metaklasy jest automatyczna zmiana klasy po jej utworzeniu.

Zwykle robisz to dla interfejsów API, w których chcesz tworzyć klasy pasujące do bieżącego kontekstu.

Wyobraź sobie głupi przykład, w którym decydujesz, że wszystkie klasy w module powinny mieć atrybuty zapisane wielkimi literami. Można to zrobić na kilka sposobów, ale jednym ze sposobów jest ustawienie __metaclass__na poziomie modułu.

W ten sposób wszystkie klasy tego modułu zostaną utworzone przy użyciu tej metaklasy, a my musimy tylko powiedzieć metaklasie, aby wszystkie atrybuty zamieniały na wielkie litery.

Na szczęście __metaclass__może być w ogóle dowolna, nie musi to być formalna klasa (wiem, że coś z „klasą” w jej nazwie nie musi być klasą, idź do figury ... ale to jest pomocne).

Zaczniemy więc od prostego przykładu, używając funkcji.

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

Sprawdźmy:

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

Teraz zróbmy dokładnie to samo, ale używając prawdziwej klasy dla metaklasy:

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

Przepiszmy powyższe, ale z krótszymi i bardziej realistycznymi nazwami zmiennych, skoro wiemy, co one oznaczają:

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)

Być może zauważyłeś dodatkowy argument cls. Nie ma w tym nic specjalnego: __new__zawsze otrzymuje klasę, w której jest zdefiniowany, jako pierwszy parametr. Tak jak w selfprzypadku zwykłych metod, które otrzymują instancję jako pierwszy parametr lub klasę definiującą dla metod klasowych.

Ale to nie jest właściwe OOP. Dzwonimy typebezpośrednio i nie zastępujemy ani nie dzwonimy do rodziców __new__. Zróbmy to zamiast tego:

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)

Możemy sprawić, że będzie jeszcze czystszy, używając super, co ułatwi dziedziczenie (ponieważ tak, możesz mieć metaklasy, dziedziczenie po metaklasach, dziedziczenie po typie):

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)

Aha, aw Pythonie 3, jeśli wykonujesz to wywołanie z argumentami słów kluczowych, to tak:

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

Przekłada się to na metaklasę, aby go użyć:

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

Otóż ​​to. Naprawdę nie ma nic więcej o metaklasach.

Powodem złożoności kodu używającego metaklas nie jest to, że metaklasy są używane, ponieważ zwykle używasz metaklas do robienia skręconych rzeczy w oparciu o introspekcję, manipulację dziedziczeniem, zmienne takie jak __dict__itp.

Rzeczywiście, metaklasy są szczególnie przydatne do robienia czarnej magii, a zatem skomplikowanych rzeczy. Ale same w sobie są proste:

  • przechwytuje tworzenie klasy
  • zmodyfikować klasę
  • zwraca zmodyfikowaną klasę

Dlaczego miałbyś używać klas metaklas zamiast funkcji?

Ponieważ __metaclass__można zaakceptować dowolne wywołanie, dlaczego miałbyś skorzystać z zajęć, ponieważ jest to oczywiście bardziej skomplikowane?

Istnieje kilka powodów:

  • Zamiar jest jasny. Kiedy czytasz UpperAttrMetaclass(type), wiesz, co będzie dalej
  • Możesz użyć OOP. Metaklasa może dziedziczyć po metaklasie, zastępując metody nadrzędne. Metaklasy mogą nawet używać metaklas.
  • Podklasy klasy będą instancjami jej metaklasy, jeśli podasz klasę metaklasy, ale nie z funkcją metaklasy.
  • Możesz lepiej ustrukturyzować swój kod. Nigdy nie używasz metaklas dla czegoś tak trywialnego jak powyższy przykład. Zazwyczaj jest to coś skomplikowanego. Możliwość tworzenia kilku metod i grupowania ich w jedną klasę jest bardzo przydatna, aby ułatwić czytanie kodu.
  • Można zahaczyć o __new__, __init__i __call__. Co pozwoli ci robić różne rzeczy. Nawet jeśli zwykle możesz to wszystko zrobić __new__, niektórzy ludzie są po prostu bardziej komfortowi w użyciu __init__.
  • Są to tak zwane metaklasy, do cholery! To musi coś znaczyć!

Dlaczego miałbyś używać metaklasy?

Teraz wielkie pytanie. Dlaczego miałbyś skorzystać z niejasnej, podatnej na błędy funkcji?

Zazwyczaj nie:

Metaklasy to głębsza magia, o którą 99% użytkowników nigdy nie powinno się martwić. Jeśli zastanawiasz się, czy ich potrzebujesz, nie robisz tego (ludzie, którzy ich naprawdę potrzebują, wiedzą z pewnością, że ich potrzebują i nie potrzebują wyjaśnienia, dlaczego).

Python Guru Tim Peters

Głównym przypadkiem użycia metaklasy jest utworzenie interfejsu API. Typowym tego przykładem jest Django ORM. Pozwala zdefiniować coś takiego:

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

Ale jeśli to zrobisz:

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

Nie zwróci IntegerFieldobiektu. Zwróci inti może nawet pobrać go bezpośrednio z bazy danych.

Jest to możliwe, ponieważ models.Modeldefiniuje __metaclass__i wykorzystuje magię, która przekształci Personzdefiniowane przed chwilą proste instrukcje w złożony zaczep do pola bazy danych.

Django sprawia, że ​​coś złożonego wygląda na proste, ujawniając prosty interfejs API i używając metaklas, odtwarzając kod z tego interfejsu API, aby wykonać prawdziwą pracę za kulisami.

Ostatnie słowo

Po pierwsze, wiesz, że klasy to obiekty, które mogą tworzyć instancje.

Cóż, w rzeczywistości same klasy są instancjami. Metaklas.

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

W Pythonie wszystko jest przedmiotem i wszystkie one są instancjami klas lub instancjami metaklas.

Z wyjątkiem type.

typejest właściwie własną metaklasą. Nie jest to coś, co można by odtworzyć w czystym języku Python, a odbywa się to poprzez oszukiwanie trochę na poziomie implementacji.

Po drugie, metaklasy są skomplikowane. Możesz nie chcieć ich używać do bardzo prostych zmian klas. Możesz zmienić klasy, używając dwóch różnych technik:

W 99% przypadków, gdy potrzebujesz zmiany klasy, lepiej skorzystaj z nich.

Ale w 98% przypadków wcale nie potrzebujesz zmiany klasy.

e-satis
źródło
30
Wygląda na to, że w Django models.Modelnie używa klasy, __metaclass__lecz class Model(metaclass=ModelBase):odnosi się do ModelBaseklasy, która następnie wykonuje wyżej wspomnianą magię metaklasy. Wspaniały post! Oto źródło Django: github.com/django/django/blob/master/django/db/models/…
Max Goodridge
15
<< Uważaj tutaj, aby __metaclass__atrybut nie został odziedziczony, ponieważ będzie to metaklasa elementu nadrzędnego ( Bar.__class__). Jeśli Barużywany jest __metaclass__atrybut, który utworzony Barz type()(i nie type.__new__()), podklasy nie posiądą że zachowanie >>. - Czy mógłby pan / ktoś proszę wyjaśnić nieco głębiej ten fragment?
petrux
15
@MaxGoodridge To jest składnia języka Python 3 dla metaklas. Zobacz Model danych Python 3.6 VS Model danych Python 2.7
TBBle
2
Now you wonder why the heck is it written in lowercase, and not Type?- cóż, ponieważ jest zaimplementowany w C - to ten sam powód, dla którego defaultdict ma małe litery, podczas gdy OrDERDict (w python 2) jest normalnym CamelCase
Mr_and_Mrs_D
15
Jest to odpowiedź wiki społeczności (więc ci, którzy komentowali poprawki / ulepszenia, mogą rozważyć edytowanie swoich komentarzy w odpowiedzi, jeśli są pewni, że są poprawne).
Brōtsyorfuzthrāx
402

Uwaga: ta odpowiedź dotyczy Pythona 2.x, ponieważ została napisana w 2008 r., Metaklasy różnią się nieco w 3.x.

Metaklasy to sekretny sos, który sprawia, że ​​praca „klasowa”. Domyślna metaklasa dla nowego obiektu o nazwie „typ”.

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

Metaklasy przyjmują 3 argumenty. „ name ”, „ bases ” i „ dict

Tutaj zaczyna się sekret. Poszukaj nazwy, podstaw i dykta w tej przykładowej definicji klasy.

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

Zdefiniujmy metaklasę, która pokaże, jak nazywa ją „ klasa: ”.

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'

A teraz przykład, który tak naprawdę coś znaczy, spowoduje to, że zmienne na liście „atrybuty” zostaną ustawione w klasie i ustawione na Brak.

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

Zauważ, że magiczne zachowanie, które Initialisedzyskuje dzięki metaklasie, init_attributesnie jest przenoszone na podklasę Initialised.

Oto jeszcze bardziej konkretny przykład pokazujący, jak można podklasować „typ”, aby utworzyć metaklasę, która wykonuje akcję podczas tworzenia klasy. Jest to dość trudne:

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
Jerub
źródło
169

Inni wyjaśnili, jak działają metaklasy i jak pasują do systemu typu Python. Oto przykład, do czego można ich użyć. W ramach testów, które napisałem, chciałem śledzić kolejność, w jakiej klasy zostały zdefiniowane, aby móc później utworzyć ich instancję w tej kolejności. Najłatwiej było to zrobić za pomocą metaklasy.

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

Wszystko, co jest wówczas podklasą, MyTypeotrzymuje atrybut klasy, _orderktóry rejestruje kolejność, w której klasy zostały zdefiniowane.

kindall
źródło
Dzięki za przykład. Dlaczego uważasz, że ta łatwiejsza niż dziedziczenie z MyBase, której __init__(self)mówi type(self)._order = MyBase.counter; MyBase.counter += 1?
Michael Gundlach
1
Chciałem, aby same klasy, a nie ich wystąpienia, były ponumerowane.
kindall
Racja. Dzięki. Mój kod resetuje atrybut MyType przy każdej instancji i nigdy nie ustawia tego atrybutu, jeśli instancja MyType nigdy nie została utworzona. Ups (I własność klasy mogłaby również działać, ale w przeciwieństwie do metaklasy nie oferuje oczywistego miejsca do przechowywania licznika.)
Michael Gundlach
1
Jest to niezwykle interesujący przykład, zwłaszcza dlatego, że naprawdę można zrozumieć, dlaczego metaklasa może być potrzebna do tego, aby zapewnić rozwiązanie określonej trudności. OTOH Z trudem przekonuję się, że każdy naprawdę będzie musiał utworzyć instancję obiektów w kolejności, w jakiej zdefiniowane były ich klasy: Chyba musimy po prostu uwierzyć ci na słowo :).
Mike Rodent
159

Jednym z zastosowań metaklas jest automatyczne dodawanie nowych właściwości i metod do instancji.

Na przykład, jeśli spojrzysz na modele Django , ich definicja wydaje się nieco myląca. Wygląda na to, że definiujesz tylko właściwości klasy:

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

Jednak w czasie wykonywania obiekty Person są wypełniane różnego rodzaju przydatnymi metodami. Zobacz źródło niesamowitych metaclassery.

Antti Rasinen
źródło
6
Czy użycie meta klas do dodawania nowych właściwości i metod do klasy nie jest instancją? O ile rozumiem, meta klasa zmienia samą klasę, w wyniku czego instancje mogą być skonstruowane inaczej przez zmienioną klasę. Może to być nieco mylące dla osób, które próbują uzyskać charakter meta-klasy. Posiadanie użytecznych metod w instancjach można osiągnąć przez normalne działanie. Odniesienie do kodu Django jako przykładu jest jednak dobre.
trixn
119

Myślę, że wprowadzenie ONLamp do programowania w metaklasach jest dobrze napisane i daje naprawdę dobre wprowadzenie do tematu, mimo że ma już kilka lat.

http://www.onlamp.com/pub/a/python/2003/04/17/metaclasses.html (zarchiwizowany pod adresem https://web.archive.org/web/20080206005253/http://www.onlamp. com / pub / a / python / 2003/04/17 / metaclasses.html )

W skrócie: Klasa jest planem tworzenia instancji, metaklasa jest planem tworzenia klasy. Łatwo można zauważyć, że w klasach Python również muszą być obiektami pierwszej klasy, aby umożliwić takie zachowanie.

Nigdy sam tego nie napisałem, ale myślę, że jedno z najpiękniejszych zastosowań metaklas można zobaczyć w środowisku Django . Klasy modeli używają metaklasy, aby umożliwić deklaratywny styl pisania nowych modeli lub klas formularzy. Podczas gdy metaklasa tworzy klasę, wszyscy członkowie mają możliwość dostosowania samej klasy.

Pozostaje do powiedzenia: jeśli nie wiesz, co to są metaklasy, prawdopodobieństwo, że nie będziesz ich potrzebować, wynosi 99%.

Matthias Kestenholz
źródło
109

Co to są metaklasy? Do czego ich używasz?

TLDR: metaklasa tworzy instancję i definiuje zachowanie klasy tak jak klasa tworzy instancję i definiuje zachowanie instancji.

Pseudo kod:

>>> Class(...)
instance

Powyższe powinno wyglądać znajomo. Skąd się Classbierze? Jest to przykład metaklasy (również pseudokodu):

>>> Metaclass(...)
Class

W prawdziwym kodzie możemy przekazać domyślną metaklasę, typewszystko, czego potrzebujemy do utworzenia instancji klasy i otrzymujemy klasę:

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

Mówiąc inaczej

  • Klasa jest dla instancji tak jak metaklasa dla klasy.

    Kiedy tworzymy instancję obiektu, otrzymujemy instancję:

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

    Podobnie, gdy jawnie definiujemy klasę za pomocą domyślnej metaklasy type, tworzymy jej instancję:

    >>> type('Object', (object,), {})     # instantiation of metaclass
    <class '__main__.Object'>             # instance
  • Innymi słowy, klasa jest instancją metaklasy:

    >>> isinstance(object, type)
    True
  • Mówiąc inaczej, metaklasa jest klasą klasy.

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

Kiedy piszesz definicję klasy, a Python ją wykonuje, używa metaklasy do tworzenia instancji obiektu klasy (który z kolei będzie używany do tworzenia instancji tej klasy).

Tak jak możemy użyć definicji klasy, aby zmienić zachowanie niestandardowych instancji obiektów, możemy użyć definicji klasy metaklasy, aby zmienić sposób zachowania obiektu klasy.

Do czego można je wykorzystać? Z dokumentów :

Potencjalne zastosowania metaklas są nieograniczone. Niektóre pomysły, które zostały zbadane, obejmują rejestrowanie, sprawdzanie interfejsu, automatyczne delegowanie, automatyczne tworzenie właściwości, serwery proxy, struktury oraz automatyczne blokowanie / synchronizacja zasobów.

Niemniej jednak zwykle zachęca się użytkowników, aby unikali używania metaklas, chyba że jest to absolutnie konieczne.

Używasz metaklasy za każdym razem, gdy tworzysz klasę:

Kiedy piszesz na przykład definicję klasy, taką jak ta,

class Foo(object): 
    'demo'

Tworzysz obiekt klasy.

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

Jest to to samo, co funkcjonalne wywoływanie typeza pomocą odpowiednich argumentów i przypisywanie wyniku do zmiennej o tej nazwie:

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

Uwaga: niektóre rzeczy są automatycznie dodawane do __dict__np. Przestrzeni nazw:

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

Metaklasa obiektu stworzyliśmy, w obu przypadkach jesttype .

(Dodatkowa uwaga na temat zawartości klasy __dict__: __module__istnieje, ponieważ klasy muszą wiedzieć, gdzie są zdefiniowane, __dict__i __weakref__są tam, ponieważ nie definiujemy __slots__- jeśli zdefiniujemy__slots__ , zaoszczędzimy trochę miejsca w instancjach, ponieważ możemy zabronić __dict__i __weakref__poprzez ich wykluczenie. Na przykład:

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

... ale dygresję.)

Możemy przedłużyć type tak jak każdą inną definicję klasy:

Oto domyślne __repr__klasy:

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

Jedną z najcenniejszych rzeczy, które możemy zrobić domyślnie, pisząc obiekt Python, jest dostarczenie mu dobra __repr__. Kiedy dzwonimy help(repr), dowiadujemy się, że istnieje dobry test, __repr__który również wymaga testu równości obj == eval(repr(obj)). Poniższa prosta implementacja __repr__i __eq__dla instancji klas naszej klasy typu zapewnia nam demonstrację, która może ulec poprawie w stosunku do domyślnych __repr__klas:

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

Kiedy więc tworzymy obiekt za pomocą tej metaklasy, __repr__echo w wierszu poleceń zapewnia znacznie mniej brzydki widok niż domyślny:

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

Dzięki ładnemu __repr__zdefiniowaniu dla instancji klasy mamy silniejszą zdolność do debugowania naszego kodu. Jednak dalsze sprawdzanie za pomocą eval(repr(Class))jest mało prawdopodobne (ponieważ funkcje byłyby raczej niemożliwe do wyewaluowania z domyślnych__repr__ ).

Oczekiwane użycie: __prepare__przestrzeń nazw

Jeśli na przykład chcemy wiedzieć, w jakiej kolejności są tworzone metody klasy, możemy podać uporządkowany dyktus jako przestrzeń nazw klasy. Chcemy to zrobić z __prepare__którym zwraca dict nazw dla tej klasy, jeżeli jest realizowany w Pythonie 3 :

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

I użycie:

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

A teraz mamy zapis kolejności, w jakiej te metody (i inne atrybuty klas) zostały utworzone:

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

Uwaga: ten przykład został zaadaptowany z dokumentacji - nowego wyliczenia w standardowej bibliotece robi to .

Więc stworzyliśmy metaklasę, tworząc klasę. Metaklasę możemy również traktować jak każdą inną klasę. Ma kolejność rozwiązywania metod:

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

I ma w przybliżeniu poprawną repr(której nie możemy już ewaluować, chyba że znajdziemy sposób na reprezentację naszych funkcji):

>>> 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>})
Aaron Hall
źródło
78

Aktualizacja języka Python 3

Istnieją (w tym momencie) dwie kluczowe metody metaklasy:

  • __prepare__, i
  • __new__

__prepare__umożliwia dostarczenie niestandardowego odwzorowania (takiego jak an OrderedDict), które będzie używane jako przestrzeń nazw podczas tworzenia klasy. Musisz zwrócić instancję dowolnej wybranej przestrzeni nazw. Jeśli nie zaimplementujesz __prepare__normalnego, dictzostanie użyty.

__new__ odpowiada za faktyczne stworzenie / modyfikację końcowej klasy.

Metaklasa typu bare-bone, nic nie rób, chciałaby:

class Meta(type):

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

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

Prosty przykład:

Załóżmy, że chcesz, aby na twoich atrybutach działał prosty kod sprawdzania poprawności - tak jak zawsze musi to być intznak lub str. Bez metaklasy Twoja klasa wyglądałaby mniej więcej tak:

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

Jak widać, musisz dwukrotnie powtórzyć nazwę atrybutu. Umożliwia to literówki i irytujące błędy.

Prosta metaklasa może rozwiązać ten problem:

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

Tak wyglądałaby metaklasa (nie używa, __prepare__ponieważ nie jest potrzebna):

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)

Przykładowy przebieg:

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

produkuje:

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

Uwaga : ten przykład jest dość prosty, można go również osiągnąć za pomocą dekoratora klas, ale przypuszczalnie rzeczywista metaklasa zrobiłaby znacznie więcej.

Klasa „ValidateType” w celach informacyjnych:

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
Ethan Furman
źródło
Wow, to niesamowita nowa funkcja, o której nie wiedziałem, że istnieje w Pythonie 3. Dziękuję za przykład !!
Rich Lysakowski PhD
Zauważ, że od Pythona 3.6 możesz użyć __set_name__(cls, name)deskryptora ( ValidateType), aby ustawić nazwę w deskryptorze ( self.namei w tym przypadku również self.attr). Zostało to dodane, aby nie trzeba było zanurzać się w metaklasy dla tego szczególnego wspólnego zastosowania (patrz PEP 487).
Lars
68

Rola metody metaklasy __call__()podczas tworzenia instancji klasy

Jeśli programujesz w Pythonie przez ponad kilka miesięcy, ostatecznie natkniesz się na kod, który wygląda następująco:

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

To drugie jest możliwe, gdy zastosujesz __call__()magiczną metodę na klasie.

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

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

__call__()Metoda jest wywoływana gdy przykład klasy stosuje się jako wywoływalny. Ale jak widzieliśmy z poprzednich odpowiedzi, sama klasa jest instancją metaklasy, więc kiedy używamy tej klasy jako wywoływalnej (tj. Kiedy tworzymy jej instancję), w rzeczywistości nazywamy __call__()metodę jej metaklasy . W tym momencie większość programistów Pythona jest nieco zdezorientowana, ponieważ powiedziano im, że podczas tworzenia takiej instancji instance = SomeClass()wywołujesz jej __init__()metodę. Niektórzy, którzy byli wykopali nieco głębiej wiedzieć, że zanim __init__()tam __new__(). Cóż, dzisiaj ujawnia się kolejna warstwa prawdy, zanim __new__()pojawi się metaklasa ” __call__().

Przeanalizujmy łańcuch wywołań metod ze szczególnego punktu widzenia tworzenia instancji klasy.

Jest to metaklasa, która rejestruje się dokładnie na chwilę przed utworzeniem instancji i momentem, w którym ma ją zwrócić.

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

Jest to klasa, która korzysta z tej metaklasy

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__()."

A teraz stwórzmy instancję 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.

Zauważ, że powyższy kod nie robi nic więcej niż rejestrowanie zadań. Każda metoda deleguje rzeczywistą pracę do implementacji swojego rodzica, zachowując w ten sposób zachowanie domyślne. Ponieważ typejest Meta_1to klasa nadrzędna ( typebędąca domyślną metaklasą nadrzędną) i biorąc pod uwagę sekwencję porządkowania danych wyjściowych powyżej, mamy teraz wskazówkę, jaka byłaby pseudo-implementacja 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

Widzimy, że __call__()metoda metaklasy jest wywoływana jako pierwsza. Następnie deleguje tworzenie instancji do metody klasy __new__()i inicjalizację do instancji __init__(). Jest to również ten, który ostatecznie zwraca instancję.

Z powyższego wynika, że metaklasa __call__()jest również możliwość decydowania, czy wywołanie Class_1.__new__()lub Class_1.__init__()zostanie ostatecznie wykonany. W trakcie wykonywania może faktycznie zwrócić obiekt, który nie został dotknięty żadną z tych metod. Weźmy na przykład takie podejście do wzorca singletonu:

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__()."

Zobaczmy, co się dzieje, gdy wielokrotnie próbujemy utworzyć obiekt typu 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
Michael Ekoka
źródło
Jest to dobry dodatek do wcześniej zaakceptowanej „akceptowanej odpowiedzi”. Zawiera przykłady koderów pośrednich do żucia.
Rich Lysakowski PhD
56

Metaklasa to klasa, która mówi, w jaki sposób (niektóre) inne klasy powinny zostać utworzone.

Jest to przypadek, w którym widziałem metaklasę jako rozwiązanie mojego problemu: miałem naprawdę skomplikowany problem, który prawdopodobnie mógł zostać rozwiązany inaczej, ale zdecydowałem się rozwiązać go za pomocą metaklasy. Ze względu na złożoność jest to jeden z niewielu modułów, które napisałem, w których komentarze w module przekraczają ilość napisanego kodu. Oto jest ...

#!/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()
Craig
źródło
43

Wersja tl; dr

Ta type(obj)funkcja pozwala uzyskać typ obiektu.

type()Klasy jest jego metaklasa .

Aby użyć metaklasy:

class Foo(object):
    __metaclass__ = MyMetaClass

typejest własną metaklasą. Klasą klasy jest metaklasa - jej ciałem są argumenty przekazywane metaklasie używanej do konstruowania klasy.

Tutaj możesz przeczytać o tym, jak używać metaklas do dostosowywania konstrukcji klas.

noɥʇʎԀʎzɐɹƆ
źródło
42

typejest właściwie metaclassklasą, która tworzy kolejne klasy. Większość metaclassto podklasy type. metaclassOtrzymuje newklasy jako pierwszy argument i zapewnić dostęp do obiektu klasy ze szczegółami, jak podano poniżej:

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

Zauważ, że klasa nie została utworzona w żadnym momencie; prosty akt utworzenia klasy uruchomił wykonanie metaclass.

Mushahid Khan
źródło
27

Klasy Python same w sobie są obiektami - jak na przykład - ich meta-klasy.

Domyślna metaklasa, która jest stosowana, gdy określasz klasy jako:

class foo:
    ...

meta klasa służy do zastosowania jakiejś reguły do ​​całego zestawu klas. Załóżmy na przykład, że budujesz ORM w celu uzyskania dostępu do bazy danych i chcesz, aby rekordy z każdej tabeli były klasy odwzorowanej na tę tabelę (na podstawie pól, reguł biznesowych itp.), Możliwe użycie metaklasy jest na przykład logiką puli połączeń, która jest współużytkowana przez wszystkie klasy rekordów ze wszystkich tabel. Innym zastosowaniem jest logika do obsługi kluczy obcych, która obejmuje wiele klas rekordów.

podczas definiowania metaklasy wpisujesz podklasę i możesz przesłonić następujące magiczne metody, aby wstawić logikę.

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

w każdym razie te dwa są najczęściej używanymi hakami. metaklasowanie jest potężne, a powyżej nie ma nigdzie blisko wyczerpującej listy zastosowań metaklasowania.

Xingzhou Liu
źródło
21

Funkcja type () może zwrócić typ obiektu lub utworzyć nowy typ,

na przykład możemy utworzyć klasę Hi za pomocą funkcji type () i nie musimy używać w ten sposób z klasą Hi (obiekt):

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

Oprócz użycia type () do dynamicznego tworzenia klas, możesz kontrolować zachowanie tworzenia klasy i używać metaklasy.

Według modelu obiektowego Python klasa jest obiektem, więc klasa musi być instancją innej określonej klasy. Domyślnie klasa Python jest instancją klasy typu. Oznacza to, że typ jest metaklasą większości wbudowanych klas i metaklasą klas zdefiniowanych przez użytkownika.

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

Magia zacznie działać, gdy przekażemy argumenty słów kluczowych w metaklasie, oznacza to, że interpreter Pythona tworzy listę niestandardową za pomocą ListMetaclass. new (), w tym momencie możemy zmodyfikować definicję klasy, na przykład dodać nową metodę, a następnie zwrócić poprawioną definicję.

binbjz
źródło
11

Oprócz opublikowanych odpowiedzi mogę powiedzieć, że metaclassokreśla zachowanie klasy. Możesz więc jawnie ustawić swoją metaklasę. Ilekroć Python dostaje słowo kluczowe class, zaczyna szukać metaclass. Jeśli nie zostanie znaleziony - do utworzenia obiektu klasy używany jest domyślny typ metaklasy. Za pomocą tego __metaclass__atrybutu możesz ustawić metaclassswoją klasę:

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

print(MyClass.__metaclass__)

Wyprodukuje takie dane wyjściowe:

class 'type'

I oczywiście możesz stworzyć własną, metaclassaby zdefiniować zachowanie dowolnej klasy, która jest tworzona za pomocą twojej klasy.

W tym metaclasscelu należy odziedziczyć domyślną klasę typów, ponieważ jest to główna 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__)

Dane wyjściowe będą:

class '__main__.MyMetaClass'
class 'type'
Andy
źródło
4

W programowaniu obiektowym metaklasa jest klasą, której instancjami są klasy. Tak jak zwykła klasa definiuje zachowanie niektórych obiektów, tak metaklasa określa zachowanie niektórych klas i ich instancji. Termin metaklasa oznacza po prostu coś, co służy do tworzenia klas. Innymi słowy, jest to klasa klasy. Metaklasa służy do tworzenia klasy, więc podobnie jak obiekt będący instancją klasy, klasa jest instancją metaklasy. W python klasy są również uważane za obiekty.

Venu Gopal Tewari
źródło
Lepiej byłoby, gdybyś podał kilka przykładów, zamiast podawać definicje książkowe. Pierwszy wiersz twojej odpowiedzi wydaje się być skopiowany z wpisu Metaclasses w Wikipedii.
verisimilitude
@verisimilitude Uczę się również, czy możesz pomóc mi poprawić tę odpowiedź, podając kilka praktycznych przykładów z twojego doświadczenia?
Venu Gopal Tewari
2

Oto kolejny przykład tego, do czego można go użyć:

  • Możesz użyć metaclassdo zmiany funkcji jego instancji (klasy).
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()

metaclassJest potężny, istnieje wiele rzeczy (takie jak małpa magii) można z nim zrobić, ale należy uważać, to może być znany tylko Tobie.

Carson
źródło
2

Klasa w Pythonie jest obiektem i podobnie jak każdy inny obiekt jest instancją „czegoś”. To „coś” jest określane jako metaklasa. Ta metaklasa jest specjalnym typem klasy, który tworzy obiekty innych klas. Dlatego metaklasa jest odpowiedzialna za tworzenie nowych klas. Pozwala to programiście dostosować sposób generowania klas.

Aby utworzyć metaklasę, zwykle wykonuje się przesłonięcie metod new () i init (). new () można zastąpić, aby zmienić sposób tworzenia obiektów, natomiast init () można zastąpić, aby zmienić sposób inicjowania obiektu. Metaklasę można utworzyć na wiele sposobów. Jednym ze sposobów jest użycie funkcji type (). Funkcja type (), wywołana z 3 parametrami, tworzy metaklasę. Parametry to:

  1. Nazwa klasy
  2. Krotka posiadająca klasy podstawowe dziedziczone przez klasy
  3. Słownik posiadający wszystkie metody klas i zmienne klas

Innym sposobem tworzenia metaklasy jest słowo kluczowe „metaklasa”. Zdefiniuj metaklasę jako prostą klasę. W parametrach klasy dziedziczonej podaj metaclass = nazwa_metaklasy

Metaklasa może być szczególnie używana w następujących sytuacjach:

  1. kiedy należy zastosować określony efekt do wszystkich podklas
  2. Wymagana jest automatyczna zmiana klasy (podczas tworzenia)
  3. Przez programistów API
Swati Srivastava
źródło
2

Zauważ, że w Pythonie 3.6 wprowadzono nową metodę dundera, __init_subclass__(cls, **kwargs)która zastępuje wiele typowych przypadków użycia metaklas. Jest wywoływany, gdy tworzona jest podklasa klasy definiującej. Zobacz dokumenty Pythona .

Lars
źródło
-3

Metaklasa jest rodzajem klasy, która określa jej zachowanie, lub możemy powiedzieć, że sama klasa jest instancją metaklasy.

AD techniczne
źródło