Odpowiedzi:
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. type
jest zwykłą metaklasą w Pythonie. type
sam jest klasą i jest swoim własnym typem. Nie będziesz mógł odtworzyć czegoś takiego jak type
czysto 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 class
instrukcja jest wykonywana, Python najpierw wykonuje jej treść class
jako 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 type
metaklasy. 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__
__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.
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:
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>
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 class
sł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óż, type
ma zupełnie inną zdolność, może także tworzyć klasy w locie. type
moż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 klasybases
: krotka klasy nadrzędnej (dla dziedziczenia może być pusta)attrs
: słownik zawierający nazwy i wartości atrybutówna 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ć.
type
akceptuje 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.
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 type
możesz zrobić coś takiego:
MyClass = type('MyClass', (), {})
Jest tak, ponieważ funkcja type
jest w rzeczywistości metaklasą. type
to 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 str
klasą, która tworzy obiekty łańcuchowe i int
klasą, która tworzy obiekty całkowite. type
to 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__
atrybutW 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 Foo
nie 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
type
do 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 Bar
wł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 Bar
użyty __metaclass__
atrybut, który został utworzony za Bar
pomocą 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ę? type
lub cokolwiek, co go podklasuje lub wykorzystuje.
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.
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 self
przypadku 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 type
bezpoś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:
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:
UpperAttrMetaclass(type)
, wiesz, co będzie dalej__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__
.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 IntegerField
obiektu. Zwróci int
i może nawet pobrać go bezpośrednio z bazy danych.
Jest to możliwe, ponieważ models.Model
definiuje __metaclass__
i wykorzystuje magię, która przekształci Person
zdefiniowane 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.
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
.
type
jest 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.
models.Model
nie używa klasy, __metaclass__
lecz class Model(metaclass=ModelBase):
odnosi się do ModelBase
klasy, 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/…
__metaclass__
atrybut nie został odziedziczony, ponieważ będzie to metaklasa elementu nadrzędnego ( Bar.__class__
). Jeśli Bar
używany jest __metaclass__
atrybut, który utworzony Bar
z 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?
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
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 Initialised
zyskuje dzięki metaklasie, init_attributes
nie 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
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ą, MyType
otrzymuje atrybut klasy, _order
który rejestruje kolejność, w której klasy zostały zdefiniowane.
__init__(self)
mówi type(self)._order = MyBase.counter; MyBase.counter += 1
?
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.
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%.
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ę Class
bierze? Jest to przykład metaklasy (również pseudokodu):
>>> Metaclass(...)
Class
W prawdziwym kodzie możemy przekazać domyślną metaklasę, type
wszystko, czego potrzebujemy do utworzenia instancji klasy i otrzymujemy klasę:
>>> type('Foo', (object,), {}) # requires a name, bases, and a namespace
<class '__main__.Foo'>
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.
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 type
za 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ę.)
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__
).
__prepare__
przestrzeń nazwJeś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>})
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, dict
zostanie 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ć int
znak 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
__set_name__(cls, name)
deskryptora ( ValidateType
), aby ustawić nazwę w deskryptorze ( self.name
i 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).
__call__()
podczas tworzenia instancji klasyJeś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ż type
jest Meta_1
to klasa nadrzędna ( type
bę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
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()
Ta type(obj)
funkcja pozwala uzyskać typ obiektu.
type()
Klasy jest jego metaklasa .
Aby użyć metaklasy:
class Foo(object):
__metaclass__ = MyMetaClass
type
jest 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.
type
jest właściwie metaclass
klasą, która tworzy kolejne klasy. Większość metaclass
to podklasy type
. metaclass
Otrzymuje new
klasy 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
.
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.
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ę.
Oprócz opublikowanych odpowiedzi mogę powiedzieć, że metaclass
okreś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ć metaclass
swoją 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ą, metaclass
aby zdefiniować zachowanie dowolnej klasy, która jest tworzona za pomocą twojej klasy.
W tym metaclass
celu 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'
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.
Oto kolejny przykład tego, do czego można go użyć:
metaclass
do 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()
metaclass
Jest 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.
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:
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:
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 .
Metaklasa jest rodzajem klasy, która określa jej zachowanie, lub możemy powiedzieć, że sama klasa jest instancją metaklasy.
class A(type):pass<NEWLINE>class B(type,metaclass=A):pass<NEWLINE>b.__class__ = b