Pytanie Czym są metaclasses w Pythonie?


Czym są metaclasy i do czego ich używamy?


4584
2017-09-19 06:10


pochodzenie




Odpowiedzi:


Metaclass jest klasą klasy. Podobnie jak klasa określa zachowanie klasy, metaclass określa sposób zachowania klasy. Klasa jest instancją metaklasy.

metaclass diagram

Podczas gdy w Pythonie możesz używać dowolnych kalamburów dla metaclasses (np Jerub pokazuje), tym bardziej użytecznym podejściem jest uczynienie go klasą samą w sobie. type jest zwykle metaklasem w Pythonie. Jeśli zastanawiasz się, tak, type sam jest klasą i jest to jej własny typ. Nie będzie można odtworzyć czegoś takiego type czysto w Pythonie, ale Python oszukuje trochę. Aby utworzyć własny metaclass w Pythonie, naprawdę chcesz tylko podklasę type.

Metaclass jest najczęściej używany jako klasa-fabryka. Tak jak tworzysz instancję klasy, wywołując klasę, Python tworzy nową klasę (gdy wykonuje instrukcję "klasa"), wywołując metaclass. W połączeniu z normalnym __init__ i __new__ metody, metaclasy pozwalają zatem na wykonywanie "dodatkowych czynności" podczas tworzenia klasy, na przykład rejestrowanie nowej klasy przy użyciu jakiegoś rejestru, a nawet zastąpienie klasy czymś zupełnie innym.

Kiedy class instrukcja jest wykonywana, Python najpierw wykonuje ciało class instrukcja jako normalny blok kodu. Wynikowa przestrzeń nazw (dyktatura) zawiera atrybuty przyszłej klasy. Metaclass określa się, patrząc na klasy podstawowe przyszłych klas (metaclasy są dziedziczone), __metaclass__ atrybut przyszłej klasy (jeśli istnieje) lub __metaclass__ zmienna globalna. Metaclass jest następnie wywoływany z nazwą, zasadami i atrybutami klasy, aby utworzyć instancję.

Jednak metaclasy faktycznie definiują rodzaj klasy, a nie tylko fabryka, więc możesz z nimi zrobić o wiele więcej. Możesz na przykład zdefiniować normalne metody na metaklasy. Te metody metaclass są jak metody klasowe, ponieważ można je wywoływać w klasie bez instancji, ale nie są również metodami klasowymi, ponieważ nie można ich wywołać w instancji klasy. type.__subclasses__() jest przykładem metody na type metaclass. Możesz także zdefiniować normalne "magiczne" metody, takie jak __add__, __iter__ i __getattr__, aby zaimplementować lub zmienić zachowanie klasy.

Oto zagregowany przykład bitów i elementó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__

2087
2017-09-19 07:01



"Nie można odtworzyć czegoś podobnego do typu czysto w Pythonie, ale Python trochę oszukuje" nie jest prawdą. - ppperry
class A(type):pass<NEWLINE>class B(type,metaclass=A):pass<NEWLINE>b.__class__ = b - ppperry


Klasy jako obiekty

Zanim zrozumiesz metaclasy, musisz opanować klasy w Pythonie. A Python ma bardzo osobliwe pojęcie o tym, jakie klasy są zapożyczone z języka Smalltalk.

W większości języków klasy są po prostu fragmentami kodu opisującymi sposób tworzenia obiektu. Tak samo jest w Pythonie:

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

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

Ale zajęcia są czymś więcej niż w Pythonie. Klasy również są obiektami.

Tak, obiekty.

Zaraz po użyciu 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) sam jest zdolny do tworzenia obiektów (instancji), i dlatego jest to klasa.

Ale wciąż jest to obiekt, a zatem:

  • możesz przypisać go do zmiennej
  • możesz go 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.

Najpierw 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 nie jest tak dynamiczny, ponieważ musisz napisać całą klasę samemu.

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

Kiedy używasz class słowo kluczowe, Python automatycznie tworzy ten obiekt. Ale jako z większością rzeczy w Pythonie daje ci to możliwość zrobienia tego ręcznie.

Zapamiętaj tę funkcję type? Stara, dobra funkcja, która pozwala wiedzieć co wpisz obiekt:

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

Dobrze, type ma zupełnie inną umiejętność, potrafi także tworzyć zajęcia w locie. type może przyjąć opis klasy jako parametry, i zwróć klasę.

(Wiem, to głupie, że ta sama funkcja może mieć dwa całkowicie różne zastosowania w zależności od parametrów, które do niej przekazujesz. zgodność w Pythonie)

type działa w ten sposób:

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

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 zmienną do przechowywania odwołania do klasy. Mogą być różne, ale nie ma powodu do komplikowania rzeczy.

type akceptuje słownik, aby zdefiniować atrybuty klasy. Więc:

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

Można przetłumaczyć na:

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

I używane 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żna go 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. Po prostu zdefiniuj funkcję z odpowiednim podpisem i przypisz go 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 w przypadku dodawania metod do normalnie utworzonego 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 dynamicznie tworzyć klasę w locie.

Tak właśnie działa Python, gdy używasz słowa kluczowego classi robi to za pomocą metaklasy.

Czym są metaclasy (ostatecznie)

Metaclasses to "rzeczy", które tworzą klasy.

Zdefiniujesz klasy w celu tworzenia obiektów, prawda?

Ale dowiedzieliśmy się, że klasy Python są obiektami.

Cóż, metaclasses są tym, co tworzy te obiekty. Są to klasy klas, możesz je sobie wyobrazić w ten sposób:

MyClass = MetaClass()
my_object = MyClass()

Widziałeś to type pozwala zrobić coś takiego:

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

To dlatego, że funkcja type w rzeczywistości jest metaclass. type jest metaclass Python używa do tworzenia wszystkich klas za kulisami.

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

Cóż, myślę, że to kwestia spójności str, klasa, która tworzy ciągi obiektów, oraz intklasa, która tworzy obiekty integer. type jest tylko klasa, która tworzy obiekty klasowe.

Widzisz to, zaznaczając __class__ atrybut.

Wszystko, a mam na myśli wszystko, jest obiektem w Pythonie. Obejmuje to ints, łańcuchy, funkcje i klasy. Wszystkie są obiektami. I wszyscy mają 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 to jest __class__ o żadnym __class__ ?

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

Tak więc metaclass jest po prostu materiałem, który tworzy obiekty klasowe.

Możesz to nazwać "fabryką klas", jeśli chcesz.

type to wbudowana metaklasa używa Pythona, ale oczywiście możesz stworzyć swoją własny metaclass.

The __metaclass__ atrybut

Możesz dodać __metaclass__ atrybut podczas pisania klasy:

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

Jeśli to zrobisz, Python użyje metaclass do stworzenia klasy Foo.

Ostrożnie, to jest trudne.

Ty piszesz class Foo(object) pierwszy, ale obiekt klasy Foo nie jest tworzony w pamięci jeszcze.

Python będzie szukać __metaclass__ w definicji klasy. Jeśli to znajdzie, użyje go do stworzenia klasy obiektu Foo. Jeśli nie, użyje type stworzyć klasę.

Przeczytaj to kilka razy.

Kiedy to zrobisz:

class Foo(Bar):
    pass

Python wykonuje następujące czynności:

Czy istnieje __metaclass__ atrybut w Foo?

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

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

Następnie, jeśli nie może znaleźć żadnego __metaclass__ w ogóle będzie używał Bar's (pierwszy nadrzędny) własny metaclass (który może być domyślny type), aby utworzyć obiekt klasy.

Uważaj tutaj, że __metaclass__ atrybut nie zostanie odziedziczony, metaclass rodzica (Bar.__class__) będzie. Gdyby Bar używane a __metaclass__ atrybut, który został utworzony Bar z type() (i nie type.__new__()), podklasy nie odziedziczą tego zachowania.

Teraz 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 je podklasy lub używa.

Niestandardowe metaclasy

Głównym celem metaklasu jest automatyczna zmiana klasy, kiedy jest tworzony.

Zwykle robisz to dla API, gdzie chcesz stworzyć klasy pasujące do obecny kontekst.

Wyobraź sobie głupi przykład, w którym zdecydujesz, że wszystkie klasy w twoim module powinny mieć swoje atrybuty napisane wielkimi literami. Istnieje kilka sposobów zrób to, ale jednym ze sposobów jest ustawienie __metaclass__ na poziomie modułu.

W ten sposób wszystkie klasy tego modułu będą tworzone przy użyciu tego metaklasu, i musimy po prostu powiedzieć metaclassowi, aby wszystkie atrybuty były wielkie.

Na szczęście, __metaclass__ w rzeczywistości może być dowolnie wywoływalny, nie musi być klasa formalna (wiem, że coś z "klasą" w nazwie nie musi być klasa, idź figura ... ale to jest pomocne).

Zaczniemy 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_attr):
    """
      Return a class object, with the list of its attribute turned
      into uppercase.
    """

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

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

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

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

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

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

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

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

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

        return type(future_class_name, future_class_parents, uppercase_attr)

Ale to naprawdę nie jest OOP. Nazywamy type bezpośrednio i nie zastępujemy lub zadzwoń do rodzica __new__. Zróbmy to:

class UpperAttrMetaclass(type):

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

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

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

Być może zauważyłeś dodatkowy argument upperattr_metaclass. Jest nic specjalnego w tym: __new__ zawsze przyjmuje klasę, w której jest zdefiniowany, jako pierwszy parametr. Tak jak ty self dla zwykłych metod, które otrzymują instancję jako pierwszy parametr lub definiującą klasę dla metod klasowych.

Oczywiście nazwy, których tu użyłem, są długie ze względu na jasność, ale na przykład dla selfwszystkie argumenty mają konwencjonalne nazwy. A więc prawdziwa produkcja metaclass wyglądałby tak:

class UpperAttrMetaclass(type):

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

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

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

Możemy sprawić, że będzie jeszcze czystszy przy użyciu super, co ułatwi dziedziczenie (ponieważ tak, możesz mieć metaclasses, dziedzicząc z metaclasses, dziedzicząc z type):

class UpperAttrMetaclass(type):

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

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

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

to jest to! Naprawdę nie ma nic więcej na temat metaclasses.

Przyczyną złożoności kodu przy użyciu metaclasses nie jest, ponieważ metaclasses, ponieważ zazwyczaj używasz metaclasses do robienia skręconych rzeczy polegając na introspekcji, manipulując dziedziczeniem, vars takich jak __dict__itp.

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

  • przechwycić tworzenie klasy
  • zmodyfikuj klasę
  • zwróć zmodyfikowaną klasę

Dlaczego używałbyś klas metacyt zamiast funkcji?

Od __metaclass__ Potrafię zaakceptować każdą możliwość wywołania, dlaczego używałbyś klasy ponieważ jest to oczywiście bardziej skomplikowane?

Istnieje kilka powodów, aby to zrobić:

  • Intencja jest jasna. Kiedy czytasz UpperAttrMetaclass(type), wiesz co dalej
  • Możesz użyć OOP. Metaclass może dziedziczyć z metaklasu, nadpisując metody macierzyste. Metaclasses mogą nawet używać metaclasses.
  • Podklasy klasy będą instancjami jej metaklasy, jeśli podasz klasę metaclass, ale nie z funkcją metaclass.
  • Możesz lepiej skonstruować swój kod. Nigdy nie używasz metaclasses do czegoś takiego jak banalny jak powyższy przykład. Zazwyczaj jest to coś skomplikowanego. Posiadanie umiejętność tworzenia kilku metod i grupowania ich w jedną klasę jest bardzo przydatna aby kod był łatwiejszy do odczytania.
  • Możesz podłączyć się __new__, __init__ i __call__. Co pozwoli robić różne rzeczy. Nawet jeśli zazwyczaj możesz to wszystko zrobić __new__, niektórzy ludzie są po prostu wygodniejsi w użyciu __init__.
  • Nazywane są metaclasses, do cholery! To musi coś znaczyć!

Dlaczego miałbyś używać metaclasses?

Teraz najważniejsze pytanie. Dlaczego używałbyś jakiejś niejasnej funkcji podatnej na błędy?

Zazwyczaj nie:

Metaclasses to głębsza magia   99% użytkowników nigdy nie powinno się martwić.   Jeśli zastanawiasz się, czy ich potrzebujesz,   nie robisz (ludzie, którzy faktycznie   potrzebować ich z pewnością, że   potrzebują ich i nie potrzebują   wyjaśnienie, dlaczego).

Guru Pythona Tim Peters

Głównym zastosowaniem metaclass jest stworzenie API. Typowym tego przykładem jest ORM Django.

To pozwala ci zdefiniować coś takiego:

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

Ale jeśli to zrobisz:

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

To nie zwróci IntegerField obiekt. Zwróci wartość int, a nawet może pobrać je bezpośrednio z bazy danych.

Jest to możliwe, ponieważ models.Model definiuje __metaclass__ i używa jakiejś magii, która obróci Person właśnie zdefiniowałeś za pomocą prostych instrukcji w złożony hak do pola bazy danych.

Django sprawia, że ​​coś skomplikowanego wygląda na prostego, odsłaniając prosty interfejs API i używając metaclasses, odtwarzając kod z tego API, aby wykonać prawdziwą pracę za kulisami.

Ostatnie słowo

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

Właściwie klasy są same instancjami. Metaclasses.

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

Wszystko jest obiektem w Pythonie i wszystkie są instancjami klas lub wystąpienia metaclasses.

Z wyjątkiem type.

type jest w rzeczywistości jego własnym metaclass. To nie jest coś, co możesz rozmnażaj się w czystym Pythonie, a dzieje się to przez oszukiwanie przy implementacji poziom.

Po drugie metaclasy są skomplikowane. Możesz nie chcieć ich używać bardzo proste zmiany klasowe. Możesz zmieniać klasy za pomocą dwóch różnych technik:

99% czasu potrzebujesz zmiany klasy, lepiej jest z nich korzystać.

Ale 98% czasu, w ogóle nie potrzebujesz zmiany klasy.


5763
2017-09-19 06:26



Wygląda na to, że w Django models.Model nie używa __metaclass__ ale raczej class Model(metaclass=ModelBase): odwołać się do a ModelBaseklasa, która następnie wykonuje wyżej wspomnianą magię metaklasu. Wspaniały post! Oto źródło Django: github.com/django/django/blob/master/django/db/models/... - Max Goodridge
<< Uważaj tutaj, że __metaclass__ atrybut nie zostanie odziedziczony, metaclass rodzica (Bar.__class__) będzie. Gdyby Bar używane a __metaclass__ atrybut, który został utworzony Bar z type() (i nie type.__new__()), podklasy nie odziedziczą tego zachowania. >> - Czy mógł / proszę wyjaśnić nieco głębiej ten fragment? - petrux
@MaxGoodridge To jest składnia Pythona 3 dla metaclasses. Widzieć Python 3.6 Model danych VS Python 2.7 Model danych - TBBle
Jest to wspólna odpowiedź wiki (więc ci, którzy komentowali poprawki / ulepszenia, mogą rozważyć edytowanie swoich komentarzy w odpowiedzi, jeśli są pewni, że są poprawne). - Shule
O których częściach tej odpowiedzi chodzi python2 i które około pythono3? - styrofoam fly


Uwaga: ta odpowiedź dotyczy Pythona 2.x, ponieważ została napisana w 2008 roku, metaclasses są nieco inne w 3.x, zobacz komentarze.

Metaclasses to sekretny sos, który sprawia, że ​​praca jest "klasowa". Domyślna metaklasa dla nowego obiektu stylu nazywa się "typ".

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

Metaclasses przyjmują 3 argumenty. "Nazwa","podstawy' i 'dyktować"

Tutaj zaczyna się sekret. Poszukaj, gdzie nazwa, podstawy i dyktat pochodzą z tej przykładowej definicji klasy.

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

Pozwala zdefiniować metaclass, który pokaże jak "klasa:"nazywa to.

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 faktycznie oznacza coś, to automatycznie ustawi zmienne na liście "atrybuty" ustawione na klasie i ustawi 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 'Initalised' zyskuje dzięki metaklassowi init_attributes nie jest przekazywane do podklasy Initalised.

Oto jeszcze bardziej konkretny przykład pokazujący, jak można podklasować "typ", aby utworzyć metaklas, który 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

312
2017-09-19 06:45





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

Na przykład, jeśli spojrzysz Modele Djangoich definicja wydaje się nieco dezorientują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 środowisku wykonawczym obiekty Person są wypełnione różnego rodzaju użytecznymi metodami. Zobacz źródło za niesamowitą metaclassery.


125
2018-06-21 16:30



Nie jest używanie klas meta dodanie nowych właściwości i metod do klasa a nie instancja? O ile zrozumiałem, klasa meta modyfikuje samą klasę, w wyniku czego instancje mogą być konstruowane inaczej przez zmienioną klasę. Może być nieco mylące dla osób, które próbują uzyskać charakter klasy meta. Posiadanie użytecznych metod w instancjach może zostać osiągnięte przez normalną inherentność. Odniesienie do kodu Django jako przykładu jest jednak dobre. - trixn


Inni wyjaśnili, jak działają metaclasy i jak pasują do systemu typu Python. Oto przykład tego, do czego można ich użyć. W ramach testowania napisałem, chciałem śledzić kolejność, w której klasy zostały zdefiniowane, dzięki czemu mogłem później utworzyć ich w tej kolejności. Zauważyłem, że najłatwiej to zrobić przy użyciu metaclass.

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

Cokolwiek, co jest podklasą MyType następnie dostaje atrybut klasy _order rejestruje kolejność, w której klasy zostały zdefiniowane.


117
2017-09-19 06:32





Wydaje mi się, że wstęp ONLamp do programowania metaklasu jest dobrze napisany i daje dobre wprowadzenie do tematu, mimo że ma już kilka lat.

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

W skrócie: klasa jest planem tworzenia instancji, metaclass jest planem tworzenia klasy. Łatwo zauważyć, że klasy Pythona również muszą być obiektami najwyższej klasy, aby umożliwić to zachowanie.

Nigdy nie napisałem go sam, ale myślę, że jednym z najładniejszych zastosowań metaclasses można zobaczyć w Struktura Django. Klasy modeli używają podejścia metaclass, aby umożliwić deklaratywny styl pisania nowych modeli lub klas postaci. Podczas gdy metaclass tworzy klasę, wszyscy członkowie mają możliwość dostosowania samej klasy.

Rzeczą, która pozostała do powiedzenia jest: Jeśli nie wiesz, jakie są metaclasy, prawdopodobieństwo, że ty nie będą ich potrzebować wynosi 99%.


86
2017-08-10 23:28





Czym są metaclasses? Do czego ich używasz?

TLDR: Metaclass tworzy instancje i definiuje zachowanie dla klasy, tak jak klasa tworzy wystąpienia i definiuje zachowanie dla instancji.

Pseudo kod:

>>> Class(...)
instance

Powyższe powinno wyglądać znajomo. Cóż, gdzie się znajduje Class pochodzić z? Jest to instancja metaclass (również pseudokod):

>>> Metaclass(...)
Class

W prawdziwym kodzie możemy przekazać domyślny metaklass, type, wszystko, czego potrzebujemy do utworzenia instancji klasy i otrzymamy klasę:

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

Mówiąc inaczej

  • Klasa jest dla instancji, ponieważ metaclass jest dla klasy.

    Kiedy tworzymy instancję, otrzymujemy instancję:

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

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

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

    >>> isinstance(object, type)
    True
    
  • Połóż trzeci sposób, metaclass jest klasą klasy.

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

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

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

Do czego można ich użyć? Od docs:

Potencjalne zastosowania metaclasses 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 i automatyczne blokowanie / synchronizowanie zasobów.

Niemniej jednak zaleca się użytkownikom unikanie używania metaclasses, chyba że jest to absolutnie konieczne.

Za każdym razem, gdy tworzysz klasę, używasz metaclass:

Kiedy piszesz definicję klasy, na przykład w ten sposób,

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 type z odpowiednimi argumentami i przypisaniem 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__, tj. przestrzeń nazw:

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

The metaclass obiektu, który stworzyliśmy, w obu przypadkach jest type.

(Nota boczna dotycząca zawartości klasy __dict__: __module__ jest tak, ponieważ klasy muszą wiedzieć, gdzie są zdefiniowane, i __dict__ i __weakref__ są tam, ponieważ nie definiujemy __slots__ - Jeśli my definiować __slots__ zaoszczędzimy trochę miejsca w instancjach, ponieważ możemy zabronić __dict__ i __weakref__ przez wykluczenie ich. Na przykład:

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

... ale dygresję.)

Możemy rozszerzyć type tak jak każda inna definicja klasy:

Oto domyślny __repr__ zajęć:

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

Jedną z najcenniejszych rzeczy, które możemy wykonać domyślnie podczas pisania obiektu w języku Python, jest dostarczenie mu dobrego __repr__. Kiedy dzwonimy help(repr) dowiadujemy się, że istnieje dobry test na __repr__ to także wymaga testu na równość - obj == eval(repr(obj)). Następująca prosta implementacja __repr__ i __eq__ dla wystąpień klas naszej klasy typów zapewnia nam demonstrację, która może poprawić się domyślnie __repr__ zajęć:

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

Tak więc teraz, gdy tworzymy obiekt z tym metaclass, __repr__ powtórzone w linii poleceń zapewnia o wiele 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})

Z miłym __repr__ zdefiniowany dla instancji klasy, mamy silniejszą możliwość debugowania naszego kodu. Jednak o wiele więcej sprawdzania z eval(repr(Class)) jest mało prawdopodobne (ponieważ funkcje byłyby raczej niemożliwe do oszacowania niż domyślne __repr__'s).

Spodziewane użycie: __prepare__ przestrzeń nazw

Jeśli, na przykład, chcemy wiedzieć, w jakiej kolejności tworzone są metody klasy, możemy dostarczyć uporządkowany dykt jako przestrzeń nazw klasy. Zrobilibyśmy to dzięki __prepare__ który zwraca dict obszaru nazw klasy, jeśli jest zaimplementowana 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 rekord porządku, w którym te metody (i inne atrybuty klasy) zostały stworzone:

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

Uwaga, ten przykład został zaadaptowany z dokumentacja - Nowa enum w standardowej bibliotece robi to.

Zrobiliśmy więc utworzenie metaclassu, tworząc klasę. Możemy również traktować metaclass tak 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 prawidłowe repr (których nie możemy już ocenić, chyba że znajdziemy sposób na reprezentowanie 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>})

74
2018-03-01 19:48