Czym są metaklasy w Pythonie?

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

Author: Veltzer Doron, 2008-09-19

16 answers

Metaklasa jest klasą klasy. Tak jak klasa definiuje zachowanie instancji klasy, tak metaklasa definiuje zachowanie klasy. Klasa jest instancją metaklasy.

Podczas gdy w Pythonie można używać dowolnego wywołania dla metaklasy (jak Jerub pokazuje), bardziej użytecznym podejściem jest uczynienie z niej samej klasy. type jest zwykłą metaklasą w Pythonie. Jeśli się zastanawiasz, tak, {[1] } jest sama w sobie klasą i jest jej własnym typem. Nie będziesz potrafi odtworzyć coś w stylu type czysto w Pythonie, ale Python trochę oszukuje. Aby stworzyć własną metaklasę w Pythonie, musisz po prostu podklasować type.

Metaklasa jest najczęściej używana jako fabryka klas. Podobnie jak tworzenie instancji klasy przez wywołanie klasy, Python tworzy nową klasę (gdy wykonuje polecenie 'class') przez wywołanie metaclass. W połączeniu z normalnymi metodami __init__ i __new__, metaklasy pozwalają więc robić "dodatkowe rzeczy", gdy Tworzenie klasy, jak rejestracja nowej klasy w jakimś rejestrze, lub nawet zastąpienie klasy czymś zupełnie innym.

Gdy instrukcja class jest wykonywana, Python najpierw wykonuje ciało instrukcji class jako zwykły blok kodu. Wynikowa przestrzeń nazw (dict) zawiera atrybuty przyszłej klasy. Metaklasa jest określana przez patrzenie na podstawowe klasy, które mają być (metaklasy są dziedziczone), atrybut __metaclass__ klasy, które mają być (jeśli istnieją) lub __metaclass__ zmienna globalna. Metaklasa jest następnie wywoływana z nazwą, podstawami i atrybutami klasy, aby ją utworzyć.

Jednak metaklasy faktycznie definiują typ klasy, a nie tylko dla niej fabrykę, więc możesz z nimi zrobić znacznie więcej. Można na przykład zdefiniować normalne metody na metaklasie. Te metaklasowe metody są jak methods, ponieważ mogą być wywołane NA klasie bez instancji, ale nie są też jak methods, ponieważ nie mogą być wezwany na instancję klasy. type.__subclasses__() jest przykładem metody na metaklasie type. Można również zdefiniować normalne "magiczne" metody, takie jak __add__, __iter__ i __getattr__, aby zaimplementować lub zmienić zachowanie klasy.

Oto zbiorczy 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__
 2178
Author: Thomas Wouters,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-09-20 14:01:08

Klasy jako obiekty

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

W większości języków, klasy są tylko fragmentami kodu opisującymi sposób tworzenia obiektu. W Pythonie to też prawda:

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

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

Ale klasy to coś więcej w Pythonie. Klasy też są obiektami.

Tak, obiekty.

As soon as you użyj słowa kluczowego class, Python wykonuje je i tworzy obiekt. Instrukcja

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

Tworzy w pamięci obiekt o nazwie "ObjectCreator".

Obiekt ten (klasa) jest sam w sobie zdolny do tworzenia obiektów (instancji), i dlatego jest to klasa .

Ale mimo to jest obiektem, a zatem:

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

Np.:

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

Tworzenie klas dynamicznie

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 to nie jest tak dynamiczne, ponieważ nadal musisz napisać całą klasę samodzielnie.

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

Gdy używasz słowa kluczowego class, Python tworzy ten obiekt automatycznie. Ale jak w przypadku większości rzeczy w Pythonie, daje to sposób, aby zrobić to ręcznie.

Pamiętasz funkcję type? Stara, dobra funkcja, która pozwala wiedzieć, co typ obiektu to:

>>> 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ą zdolność, może również tworzyć klasy w locie. type może przyjmować opis klasy jako parametry, I return a klasy.

(wiem, to głupie, że ta sama funkcja może mieć dwa zupełnie różne zastosowania w zależności od parametrów, które jej przekazujesz. Jest to problem z powodu wstecznego kompatybilność 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)

Np.:

>>> 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 zmienna do przechowywania referencji klasy. Mogą być różne, ale tam to nie powód, by komplikować sprawy.

type akceptuje słownik definiujący atrybuty klasy. Więc:

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

Można przetłumaczyć na:

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

I używane jako klasa normalna:

>>> 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 z niego 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ć 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

I możesz dodać jeszcze więcej metod po dynamicznym utworzeniu klasy, podobnie jak dodawanie 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 tworzyć klasy w locie, dynamicznie.

To jest to, co robi Python, gdy używasz słowa kluczowego class, i robi to za pomocą metaklasy.

Czym są metaklasy (w końcu)

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

Definiujesz klasy w celu tworzenia obiektów, prawda?

Ale dowiedzieliśmy się, że Klasy Pythona są obiektami.

Metaklasy tworzą te obiekty. Są to Klasy, można je sobie wyobrazić w ten sposób:
MyClass = MetaClass()
my_object = MyClass()

Widziałeś, żetype pozwala Ci zrobić coś takiego:

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

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

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

Cóż, to chyba kwestia spójności z str, klasą, która tworzy obiekty łańcuchowe oraz int klasa, która tworzy obiekty całkowite. type jest tylko klasa, która tworzy obiekty klasowe.

Widzisz to sprawdzając atrybut __class__.

Wszystko, i mam na myśli wszystko, jest obiektem w Pythonie. W tym ints, ciągi, funkcje i klasy. Wszystkie są obiektami. I wszystkie z nich mają utworzono 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'>

Jaki jest __class__ z jakiegokolwiek __class__?

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

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

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

type jest wbudowany metaclass Python używa, ale oczywiście, można utworzyć swój własna metaklasa.

The __metaclass__ atrybut

W Pythonie 2 możesz dodać __metaclass__ atrybut podczas pisania klasy (zobacz następną sekcję składni Pythona 3):

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

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

Ostrożnie, to trudne.

Najpierw piszesz class Foo(object), ale obiekt klasy {[52] } nie jest tworzony jeszcze w pamięci.

Python będzie szukał __metaclass__ w definicji klasy. Jeśli znajdzie, użyje go do utworzenia klasy obiektu Foo. Jeśli nie, użyje type aby utworzyć klasy.

Przeczytaj to kilka razy.

Kiedy robisz:

class Foo(Bar):
    pass

Python wykonuje następujące czynności:

Czy jest atrybut __metaclass__ W Foo?

Jeśli tak, Utwórz w pamięci obiekt klasy (powiedziałem obiekt klasy, Zostań ze mną tutaj) o nazwie Foo używając tego, co jest w __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ą, zasadniczo klasy w starym stylu).

Następnie, jeśli nie może znaleźć żadnego __metaclass__, użyje metaklasy Bar (pierwszego rodzica) do utworzenia obiektu klasy (który może być domyślnym type).

Uważaj, aby atrybut __metaclass__ nie został dziedziczony, metaklasa rodzica (Bar.__class__) będzie. Jeśli Bar użyła atrybutu __metaclass__, który utworzył Bar z type() (a nie type.__new__()), podklasy nie odziedziczą tego zachowania.

Teraz wielki pytanie brzmi, co możesz dodać __metaclass__?

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

A co może stworzyć klasę? type, lub cokolwiek, co go podklasuje lub używa.

Metaklasy w Pythonie 3

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

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

Tzn. atrybut __metaclass__ nie jest już używany, na rzecz argumentu słowa kluczowego na liście klas bazowych.

Zachowanie metaklasy jednak pozostaje w dużej mierze to samo .

Niestandardowe metaklasy

Głównym celem metaklasy jest automatyczna zmiana klasy, kiedy zostanie stworzona.

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

Wyobraź sobie głupi przykład, w którym decydujesz, że wszystkie klasy w Twoim module powinny mieć swoje atrybuty napisane wielkimi literami. Istnieje kilka sposobów na zrobić, ale jednym ze sposobów jest ustawienie __metaclass__ na poziom modułu.

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

Na szczęście, __metaclass__ może być właściwie każdy, nie musi być formalna Klasa (wiem, coś z "klasą" w nazwie nie musi być Klasa, do dzieła... ale to 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_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 w przeciwieństwie do innych klas, metaklasy nie mogą być używane jako 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_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 nie jest tak naprawdę OOP. Wywołujemy type bezpośrednio i nie nadpisujemy 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 nie ma w tym nic specjalnego: __new__ zawsze otrzymuje klasę, w której jest zdefiniowana, jako pierwszy parametr. Podobnie jak masz self dla zwykłych metod, które otrzymują instancję jako pierwszy parametr, lub klasę definiującą dla klasy metody.

Oczywiście nazwy, których tu użyłem są długie dla jasności, ale jak dla self wszystkie argumenty mają konwencjonalne nazwy. 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)
W przeciwieństwie do innych metod, które nie są w pełni kompatybilne z metaklasami, nie można ich używać do dziedziczenia po metaklasach, dziedziczenia po typie.]}
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. Nie ma nic więcej o metaklasach.

The powodem złożoności kodu wykorzystującego metaklasy nie jest to, że z metaklasy, to dlatego, że zwykle używasz metaklasy do robienia pokręconych rzeczy opierając się na introspekcji, manipulowaniu dziedziczeniem, var-ach, takich jak __dict__, itp.

[103]}rzeczywiście, metaklasy są szczególnie przydatne do uprawiania czarnej magii, a zatem skomplikowane sprawy. Jednak same w sobie są proste:
  • przechwycić Tworzenie klasy
  • Modyfikuj klasę
  • return the modified Klasa

Dlaczego mielibyście używać klas metaklasy zamiast funkcji?

Ponieważ __metaclass__ może przyjmować dowolne wywołanie, po co używać klasy skoro to jest bardziej skomplikowane?

Istnieje kilka powodów, aby to zrobić:

    Intencja jest jasna. Kiedy czytasz UpperAttrMetaclass(type), wiesz co będzie dalej
  • możesz użyć OOP. Metaclass może dziedziczyć z metaclass, nadpisywać metody nadrzędne. Metaklasy mogą nawet używać metaklasy.
  • podklasy klasy będą instancjami jej metaclass, jeśli podałeś metaclass-class, ale nie z metaclass-function.
  • możesz lepiej uporządkować swój kod. Nigdy nie używasz metaklasy do czegoś jako trywialne jak powyższy przykład. Zazwyczaj chodzi o coś skomplikowanego. Mając na umiejętność tworzenia kilku metod i grupowania ich w jednej klasie jest bardzo przydatna aby Kod był łatwiejszy do odczytania.
  • you can hook on __new__, __init__ i __call__. Które pozwolą robisz różne rzeczy. Nawet jeśli zwykle można to zrobić w __new__, niektórzy ludzie są po prostu bardziej komfortowe przy użyciu __init__.
  • to się nazywa metaklasy, cholera! To musi coś znaczyć!

Dlaczego miałbyś używać metaklasy?

Teraz wielkie pytanie. Dlaczego używasz jakiejś niejasnej funkcji podatnej na błędy?

Cóż, zazwyczaj nie:

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

Python Guru Tim Peters

Głównym przypadkiem użycia metaklasy jest utworzenie 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:

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

Nie powróci obiekt IntegerField. Zwróci int, a nawet może pobrać go bezpośrednio z bazy danych.

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

Django sprawia, że coś złożonego wygląda prosto, odsłaniając proste API i używając metaklasy, odtwarzając kod z tego API, aby wykonać prawdziwą pracę za kulisami.

Ostatni word

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

W rzeczywistości, klasy same w sobie są instancjami. Metaklasy.
>>> class Foo(object): pass
>>> id(Foo)
142630324

Wszystko jest obiektem w Pythonie i wszystkie są instancjami klas lub instancje metaklasy.

Z wyjątkiem type.

type to właściwie jego własna metaklasa. To nie jest coś, co mógłbyś reprodukować w czystym Pythonie, a odbywa się to poprzez oszukiwanie trochę w realizacja poziom.

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

W 99% przypadków, gdy potrzebujesz zmiany klasy, lepiej je używaj.

Ale w 98% przypadków, wcale nie trzeba zmieniać klasy.

 5873
Author: e-satis,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-09-14 00:23:09

UWAGA, Ta odpowiedź dotyczy Pythona 2.x jak napisano w 2008 roku, metaklasy są nieco inne w 3.x, Zobacz komentarze.

Metaklasy są sekretnym sosem, który sprawia, że "klasa" działa. Domyślna metaklasa dla nowego obiektu stylu nazywa się 'type'.
class type(object)
  |  type(object) -> the object's type
  |  type(name, bases, dict) -> a new type

Metaklasy biorą 3 args. "Nazwa', 'bazy " i "dict "

Tutaj zaczyna się tajemnica. Sprawdź, skąd pochodzą nazwy, bazy i dict w tej przykładowej klasie definicja.
class ThisIsTheName(Bases, Are, Here):
    All_the_code_here
    def doesIs(create, a):
        dict

Zdefiniujmy metaklasę, która zademonstruje, jak' class: ' ją nazywa.

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'

I teraz, przykład, który rzeczywiście coś znaczy, automatycznie ustawi zmienne na liście "atrybuty" na klasie i ustawi na None.

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 "Initalized" zyskuje dzięki metaklasie init_attributes, nie jest przekazywane do podklasy Initalized.

Oto jeszcze bardziej konkretny przykład, pokazujący, jak can podklasa "type" tworzy metaklasę, która wykonuje akcję podczas tworzenia klasy. To jest 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
 321
Author: Jerub,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-04-07 16:21:39

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

Na przykład, jeśli spojrzysz na modele Django, ich definicja wygląda nieco myląco. 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 obiektów Person są wypełnione różnego rodzaju użytecznymi metodami. Zobacz źródło dla niesamowitej metaklasy.

 131
Author: Antti Rasinen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-09-19 06:45:40

Inni wyjaśniali, jak działają metaklasy i jak pasują do systemu typu Python. Oto przykład, do czego można je wykorzystać. W frameworku testowym, który napisałem, chciałem śledzić kolejność, w jakiej klasy zostały zdefiniowane, aby później móc tworzyć ich instancje 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 podklasą MyType otrzymuje atrybut klasy _order, który zapisuje kolejność, w jakiej klasy zostały zdefiniowane.

 124
Author: kindall,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-11-28 18:04:41

Myślę, że wprowadzenie ONLamp do programowania metaclass 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 (zarchiwizowane w https://web.archive.org/web/20080206005253/http://www.onlamp.com/pub/a/python/2003/04/17/metaclasses.html)

W skrócie: klasa jest schematem tworzenia instancji, metaklasa jest schematem dla utworzenie klasy. Łatwo zauważyć, że w Pythonie klasy muszą być obiektami pierwszej klasy, aby umożliwić takie zachowanie.

Nigdy nie pisałem, ale myślę, że jedno z najładniejszych zastosowań metaklasy można zobaczyć w Framework Django . Klasy modelu wykorzystują podejście metaklasowe, aby umożliwić deklaratywny styl pisania nowych modeli lub klas formularzy. Podczas gdy metaclass tworzy klasę, wszyscy członkowie mają możliwość dostosowania klasy siebie.

Jeśli nie wiesz, czym są metaklasy, prawdopodobieństwo, że nie będziesz ich potrzebował wynosi 99%.
 92
Author: Matthias Kestenholz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-08-13 04:53:15

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

TLDR: metaclass tworzy instancje i definiuje zachowanie dla klasy, podobnie jak Klasa tworzy instancje i definiuje zachowanie dla instancji.

Pseudokod:

>>> Class(...)
instance

Powyższe powinno wyglądać znajomo. Skąd się bierze? Jest to instancja metaklasu (także pseudokodu):

>>> Metaclass(...)
Class

W kodzie rzeczywistym możemy przekazać domyślną metaklasę, type, wszystko, czego potrzebujemy, aby utworzyć instancję a klasa i otrzymujemy klasę:

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

Mówiąc inaczej

  • Klasa jest instancją, tak jak metaklasa jest klasą.

    Kiedy tworzymy instancję obiektu, otrzymujemy instancję:
    >>> object()                          # instantiation of class
    <object object at 0x7f9069b4e0b0>     # instance
    

    Podobnie, gdy definiujemy klasę jawnie z domyślną metaclass, type, tworzymy jej instancję:

    >>> type('Object', (object,), {})     # instantiation of metaclass
    <class '__main__.Object'>             # instance
    
  • Mówiąc inaczej, klasa jest instancją metaklasy:

    >>> isinstance(object, type)
    True
    
  • Po trzecie, metaklasa jest Klasa to klasa.

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

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

Tak jak możemy używać definicji klas do zmiany zachowania niestandardowych instancji obiektów, możemy użyć definicji klasy metaclass do zmiany sposobu zachowania obiektu klasy.

Do czego mogą być użyte? Z docs :

Potencjalne zastosowania metaklasy 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/synchronizacja zasobów.

Niemniej jednak zazwyczaj zaleca się użytkownikom unikanie stosowania metaklasy, chyba że jest to absolutnie konieczne.

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

Kiedy piszesz definicję klasy, na przykład, jak to,

class Foo(object): 
    'demo'

Tworzysz instancję obiektu klasy.

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

Jest to to samo, co funkcjonalne wywołanie type z odpowiednimi argumentami i przypisanie 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. przestrzeń nazw:

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

Metaklasaobiektu, który stworzyliśmy, w obu przypadkach jest type.

(dodatkowa informacja o zawartości klasy __dict__: __module__ czy jest ponieważ klasy muszą wiedzieć, gdzie są zdefiniowane, a __dict__ i __weakref__ są tam, ponieważ nie definiujemy __slots__ - jeśli zdefiniujemy __slots__ zaoszczędzimy trochę miejsca w instancjach, ponieważ możemy wyłączyć __dict__ i __weakref__, wykluczając je. 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ślne __repr__ klasy:

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

Jedną z najcenniejszych rzeczy, które możemy zrobić przez domyślnie w pisaniu obiektu Pythona jest zapewnienie mu dobrego __repr__. Kiedy wywołujemy help(repr), dowiadujemy się, że istnieje dobry test na __repr__, który wymaga również testu na równość - obj == eval(repr(obj)). Poniższa prosta implementacja __repr__ i __eq__ dla instancji klas naszego typu zapewnia nam demonstrację, która może poprawić domyślną __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__)

Więc teraz, kiedy tworzymy obiekt z tą metaklasą, __repr__ Echo w linii poleceń zapewnia dużo 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 ładnym __repr__ zdefiniowanym dla instancji klasy, mamy większą zdolność debugowania naszego kodu. Jednak znacznie dalsze sprawdzanie za pomocą eval(repr(Class)) jest mało prawdopodobne (ponieważ funkcje byłyby raczej niemożliwe do oceny z ich domyślnych __repr__).

Oczekiwane 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 dict jako przestrzeń nazw klasy. My zrobi to z __prepare__, który zwraca dict przestrzeni nazw dla klasy, jeśli jest zaimplementowany 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

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

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

Uwaga, Ten przykład został zaadaptowany z dokumentacji - robi to nowe enum w bibliotece standardowej.

Więc to, co zrobiliśmy, to utworzenie metaklasy, tworząc klasy. Możemy również traktować metaklasę 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ż 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>})
 79
Author: Aaron Hall,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-08-30 03:19:14

Python 3 update

W metaklasie istnieją (w tym momencie) dwie kluczowe metody:]}
  • __prepare__, oraz
  • __new__

__prepare__ umożliwia dostarczenie niestandardowego mapowania (takiego jak OrderedDict), które będzie używane jako przestrzeń nazw podczas tworzenia klasy. Musisz zwrócić instancję dowolnej przestrzeni nazw, którą wybierzesz. Jeśli nie zaimplementujesz __prepare__, używany jest zwykły dict.

__new__ odpowiada za rzeczywiste stworzenie / modyfikację finalnego klasy.

Nagi-kości, nie-nic-extra metaklass chciałby:

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:

Powiedz, że chcesz, aby jakiś prosty kod weryfikacyjny działał na Twoich atrybutach - tak jak zawsze musi to być int lub str. Bez metaklasy twoja klasa wyglądałaby tak:

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

Jak widzisz, musisz powtórzyć nazwę atrybutu dwa razy. Dzięki temu możliwe są literówki wraz z irytującymi błędami.

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

class Person(metaclass=Validator):
    weight = ValidateType(int)
    age = ValidateType(int)
    name = ValidateType(str)
Nie jest to konieczne, ponieważ nie jest to konieczne, ponieważ metaklasy nie są potrzebne.]}
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 na tyle prosty, że można go było również wykonać za pomocą dekoratora klasy, ale prawdopodobnie rzeczywisty metaklass zrobiłby znacznie więcej.

Klasa "ValidateType"dla odniesienia:

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
 58
Author: Ethan Furman,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-03-01 19:48:34

Rola metody metaclass ' __call__() podczas tworzenia instancji klasy

Jeśli zajmujesz się programowaniem w Pythonie przez więcej niż kilka miesięcy, w końcu natkniesz się na kod, który wygląda tak:]}

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

Ta ostatnia jest możliwa, gdy zaimplementujesz __call__() magiczną metodę na klasie.

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

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

Metoda __call__() jest wywoływana, gdy instancja klasy jest używana jako wywołanie. Ale jak widzieliśmy z poprzednich odpowiedzi sama klasa jest instancją metaklasy, więc kiedy używamy klasy jako możliwej do wywołania (tzn. kiedy tworzymy jej instancję), w rzeczywistości wywołujemy jej metaclass' __call__(). W tym momencie większość programistów Pythona jest nieco zdezorientowana, ponieważ powiedziano im, że podczas tworzenia instancji takiej jak ta instance = SomeClass() wywołujesz jej metodę __init__(). Niektórzy, którzy kopali trochę głębiej, wiedzą, że wcześniej __init__() jest __new__(). Cóż, dzisiaj ujawnia się kolejna warstwa prawdy, przed __new__() jest metaklasa' __call__().

Przestudiujmy wywołanie metody łańcuch z perspektywy tworzenia instancji klasy.

Jest to metaklasa, która rejestruje dokładnie moment przed utworzeniem instancji i moment, 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 używa 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 przekazuje rzeczywistą pracę rodzicowi implementacji, zachowując w ten sposób domyślne zachowanie. Ponieważ type jest klasą nadrzędną Meta_1 (type jest domyślną metaklasą nadrzędną) i biorąc pod uwagę kolejność sekwencji wyjściowej 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 metoda metaclass' __call__() jest tą, która jest wywoływana jako pierwsza. Następnie deleguje tworzenie instancji do metody __new__() klasy i inicjalizację do instancji __init__(). To także ten to ostatecznie zwraca instancję.

Z powyższego wynika, że metaklasa " __call__() ma również możliwość podjęcia decyzji, czy ostatecznie zostanie wykonane wezwanie do Class_1.__new__() lub Class_1.__init__(). W trakcie wykonywania może zwrócić obiekt, który nie został dotknięty żadną z tych metod. Weźmy na przykład takie podejście do wzoru Singletona:

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

Obserwujmy, co się dzieje, gdy wielokrotnie próbuje się 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
 45
Author: Michael Ekoka,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-08-27 17:21:01

Metaclass jest klasą, która mówi jak (niektóre) inne klasy powinny być tworzone.

Jest to przypadek, w którym postrzegałem metaclass 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, gdzie komentarze w module przewyższają ilość kodu, który został napisany. Tutaj 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()
 43
Author: Craig,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-01-25 20:08:37

type w rzeczywistości jest metaclass -- klasą, która tworzy kolejne klasy. Większość metaclass to podklasy type. metaclass otrzymuje klasę new jako swój pierwszy argument i zapewnia dostęp do obiektu klasy ze szczegółami wymienionymi 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 tworzenia klasy wywołał wykonanie metaclass.

 29
Author: Mushahid Khan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-08-29 05:23:15

Wersja TL;dr

Funkcja type(obj) wyświetla typ obiektu.

type() klasy jest jej metaklasą .

Aby użyć metaklasy:

class Foo(object):
    __metaclass__ = MyMetaClass
 25
Author: noɥʇʎԀʎzɐɹƆ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-12-30 17:28:40

Klasy Pythona same w sobie są obiektami - jak w przypadku instancji-swojej meta-klasy.

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

class foo:
    ...

Meta klasy są używane do zastosowania pewnej reguły do całego zestawu klas. Załóżmy na przykład, że budujesz ORM, aby uzyskać dostęp do bazy danych i chcesz, aby rekordy z każdej tabeli były klasą zmapowaną do tej tabeli (na podstawie pól, reguł biznesowych itp..,), możliwe użycie metaclass to np. połączenie logika puli, która jest współdzielona przez wszystkie klasy rekordów ze wszystkich tabel. Innym zastosowaniem jest logika do obsługi kluczy obcych, która obejmuje wiele klas rekordów.

Kiedy definiujesz metaclass, wpisujesz podklasę i możesz nadpisać następujące magiczne Metody wstawiania logiki.

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 hookami. metaclassing jest potężny, a powyżej nie ma nigdzie w pobliżu i wyczerpującej listy zastosowań dla metaclassingu.

 15
Author: Xingzhou Liu,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-07-13 08:18:22

[[37]}dwa zdania do opanowania najtrudniejszego punktu wiedzy Pythona: Metaclass [38]}

Oryginalne źródło: segmentfault.com/a/1190000011447445

Przetłumaczone i poprawione przeze mnie.

Autor tego artykułu zachowuje wszystkie prawa, jednak zadania tłumaczenia nadal nie mogą być ignorowane

Jeśli są jakieś błędy lub jakiś format przeciwko PEP8, proszę pomóż mi to poprawić. Dzięki!!!

na na początku są przykłady z tradycyjnej kultury chińskiej (Nie jestem Chińczykiem, ale mam na ten temat pewną wiedzę. Jeśli ci się podoba, to jest dobre. A jeśli nie, zignoruj to. Zrozumienie metaclass jest najważniejsze)

Jest to krótkie wprowadzenie Metaclass w Pythonie z praktycznym i użytecznym przykładem. Szkoda, że ci się spodoba.

Nie bój się takiej retoryki jak tzw. " cecha, że metaklasu nie używa 99% z Programistów Pythona."Ponieważ każda osoba jest naturalnym użytkownikiem.

Aby zrozumieć metaklasy, musisz znać tylko dwa zdania:]}

Zdanie 1: jeden przyszedł z prawdy, dwa z jednego, trzy z dwóch, wszystkie rzeczy przyszły z trzech

Zdanie 2: Kim jestem? Skąd pochodzę? Gdzie mam iść?

W świecie Pythona istnieje wieczna prawda, to znaczy "Typ", proszę pamiętać, że typ jest prawdą. Ekosystem Pythona to jest tak rozległe jest produkowane przez typ.

Jeden przyszedł z prawdy, dwa z jednego, trzy z dwóch, wszystkie rzeczy przyszły z trzech:]}

Prawda jest typem

Jest to pierwszy generator klasy (metaclass, czyli metaclass).]} Jest to klasa (klasa lub generator instancji)

Druga to klasa (class, or instance generator)

Trzy to instancja (przykład)

Wszystko jest różnymi atrybutami i metodami instancji. Kiedy używamy Pythona, nazywamy je.

Prawda i jedna to propozycje, które dziś omawiamy. Druga, trzecia i wszystkie rzeczy to Klasy, instancje, atrybuty i metody, których często używamy. Jako przykład używamy hello world:

# Create a Hello class that has the attributes say_hello ---- Second Origin

class Hello () :
     def say_hello(self ,name= 'world'):
         print('Hello, %s.' % name)



# Create an instance hello from the Hello class ---- Two students three

Hello = Hello()


# Use hello to call the method say_hello ---- all three things

Hello.say_hello() 

Efekt wyjściowy:

Hello, world. 

Jest to standardowy proces "trzy pochodziły z dwóch, wszystkie rzeczy pochodziły z trzech". Od klasy do metod, które możemy wywołać, te dwa kroki są używane.

Więc nie możemy pomóc z głównego pytania, skąd pochodzi Klasa? Wróć do pierwszej linii kod.

Klasa Hello jest w rzeczywistości "semantycznym skrótem" funkcji, aby ułatwić zrozumienie kodu. Inny sposób zapisu to:

def  fn(self ,name='world') :  # If we have a function called fn
     print ('Hello, %s.' % name)


Hello = type ('Hello',(object,), dict(say_hello=fn))
# Create Hello class by type ---- Mysterious "Truth", you can change everything, this time we directly from the "Truth" gave birth to "2" 

Ten typ zapisu jest dokładnie taki sam jak poprzednia klasa Hello writing. Możesz spróbować utworzyć instancję i wywołać ją.

# Create an instance of hello from the Hello class.

hello = Hello()


# Use hello call method say_hello ---- all three things, exactly the same

Hello.say_hello() 

Efekt wyjściowy:

Hello, world. ---- The result of the call is exactly the same. 

Spojrzeliśmy wstecz na najbardziej ekscytujące miejsce. Droga urodziła bezpośrednio dwoje:

Hello = type('Hello', (object,), dict(say_hello=fn)) 

To jest "prawda", pochodzenie świat pytona. Możesz się tym zachwycić.

Zwróć uwagę na jego trzy parametry! Trzy odwieczne propozycje, które pokrywają się z ludzkością: kim jestem, skąd pochodzę, dokąd idę?

The first parameter: who I am. Here, I need a name that distinguishes everything else. The above example names me "Hello."

The second parameter: where do I come from. Here, I need to know where I come from, which is my "parent". In my example above, my parent is "object" - a very primitive class in Python.

The third parameter: Where do I go? Here, we include the methods and properties that we need to call into a dictionary and pass them as parameters. In the above example, we have a say_hello method packed into a dictionary. 

Warto zauważyć, że trzy główne odwieczne propozycje to wszystkie klasy, Wszystkie instancje, a nawet wszystkie właściwości instancji i metody. Jak powinno być, ich" stwórcy", prawda oraz jeden, mianowicie typ i metaklasa, też mają te trzy parametry. Ale zwykle trzy wieczne propozycje klasy nie są przekazywane jako parametry, ale są przekazywane w następujący sposób

class Hello(object):{

After class # statement "Who Am I?"

# In the parentheses declare "where do I come from"

# In curly brackets declare "Where do I go?"

     def say_hello ():{



     }

} 


The Creator can create a single person directly, but this is a hard labor. The Creator will first create the species "human" and then create a specific individual in batches. And pass on the three eternal propositions.

"Truth" can produce "2" directly, but it will produce "1" and then make "2" in batches.

Type can directly generate a class, but it can also be a metaclass and then use a metaclass to customize a class. 

Metaclass-jeden przyszedł z prawdy, dwa przyszedł z jednego.

Ogólnie rzecz biorąc, metaklasy są nazywane przyrostkiem Metaklasy. Wyobraź sobie, że potrzebujemy metaklasy, która może automatycznie się przywitać. Metody klasowe w nim, czasami potrzebują say_Hello, czasami say_Hi, czasami say_Sayolala, a czasami say_Nihao.

Jeśli każda wbudowana say_xxx musi być zadeklarowana raz w klasie, jak bardzo to będzie ciężka praca! Lepiej jest użyć metaklasy, aby rozwiązać problem.

Poniżej znajduje się kod meta klasy do tworzenia specjalnego "powitania":

class SayMetaClass(type):



     def __new__ (cls, Name ,Bases ,Attrs) :

         attrs[ 'say_' + name ] = lambda   self, value , saying = name : print ( saying + ',' + value + '!' )

         Return   Type . __new__ ( cls ,name, bases ,   attrs) 

Zapamiętaj dwie rzeczy:

Metaclasses are derived from "type", so the parent class needs to pass in the type. [Taosheng 1, so one must include Tao]

Metaclass operations are done in __new__. The first parameter is the class that will be created. The following parameters are the three eternal propositions: Who am I, where do I come from, and where do I go. The objects it returns are also the three eternal propositions. Next, these three parameters will always be with us.

W nowym , wykonałem tylko jedną operację.

Attrs['say_'+name] = lambda self,value,saying=name: print(saying+','+value+'!') 

Tworzy metodę klasy o nazwie klasy. Na przykład klasa, którą stworzyliśmy z metaclass nazywa się "Hello". Gdy zostanie utworzona, automatycznie będzie miała metodę klasy o nazwie "say_Hello". Następnie użyje nazwy klasy "Hello" jako domyślnego parametru do powiedzenia i przekaże ją do metody. Następnie przekaż w wywołaniu metody hello jako wartość i na koniec wydrukuj ją.

Jak więc metaklasa przechodzi od stworzenia do inwokacji? Chodź! Wraz z zasadami Daosheng, Yishengyou, Bishengsan, Sanshengwu, wchodzimy w cykl życia klasy Yuan!
# Tao Shengyi: incoming type

class SayMetaClass(type):



     # Incoming three eternal propositions: class name, parent class, attribute

     def __new__(cls ,name ,bases ,attrs):

         # Create "talent"

         attrs[ 'say_' + name ] = lambda   self, value , saying = name : print( saying + ',' + value + '!' )

         # Three eternal propositions: class name, parent class, attribute

         return type . __new__ ( cls ,name ,bases ,attrs )



# Lifetime 2: Create class

class Hello ( object ,metaclass = SayMetaClass):
     pass



# two students three: create a real column

Hello = Hello ()



# Three things: call the instance method

hello.say_Hello('world!') 

Wyjście to

Hello, world! 

Uwaga: Klasa stworzona przez metaclass, pierwsza parametr jest klasą nadrzędną, drugim parametrem jest metaclass

[39]}zwykli ludzie nie będą w stanie mówić po urodzeniu, ale niektórzy ludzie powiedzą cześć, "cześć" i "sayolala", gdy się urodzą. To jest siła talentu. To da nam programowanie obiektowe, aby zaoszczędzić niezliczone kłopoty.

Teraz, zachowując metaklasę bez zmian, możemy nadal tworzyć klasę Sayolala, Nihao, w następujący sposób:]}

# Two came from one: Create class

class Sayolala ( object ,metaclass = SayMetaClass ) :
    pass



# three came from two: create a real column

s = Sayolala ()



# all things came from two: call the instance method

s.say_Sayolala ( 'japan!' ) 

Wyjście

Sayolala, japan! 

Może też mówić Chiński

# Two came from one: Create class

class Nihao(object ,metaclass = SayMetaClass ) :
    pass



# two students three: create a real column

n = Nihao()



# Three things: call the instance method

n.say_Nihao ( '中 中华!' ) 

Wyjście

Nihao, China! 

Kolejny mały przykład:

# one came from truth.

class ListMetaclass (type) :

     def   __new__ ( cls ,name, bases ,   attrs) :

         # Talent: Bind values ​​by the add method

         attrs[ 'add' ] = lambda   self, value: self.append(value)

         return type . __new__ ( cls ,name ,bases ,attrs )



# One lifetime

class MyList ( list ,   Metaclass = ListMetaclass ) :
    pass



# Two students three

L = MyList ()



# Three things

L.add( 1 ) 

Teraz drukujemy L

print(L)



>>> [ 1 ] 

Lista zwykła nie posiada metody add ()

L2 = list ()

L2 . add ( 1 )



>>> AttributeError : 'list'   Object   Has no attribute   'add' 

Awesome! Nauczyłeś się tutaj, czy doświadczyłeś radości Stwórcy?

Everything in the python world is at your fingertips. 

Młody Stwórco, proszę za mną, aby stworzyć nowy świat.

Wybieramy dwa obszary, jeden jest podstawową ideą Django, "Object Relational Mapping", mapowanie obiektowo-relacyjne, określane jako ORM.

Jest to duża trudność Django, ale po nauczeniu się metaklasy, wszystko staje się jasne. Twoje zrozumienie Django będzie jeszcze lepsze!

Inny obszar to gady (hakerzy), automatyczne wyszukiwanie dostępnych agentów w sieci, a następnie zmiana IP, aby złamać ograniczenia anty-crawler innych ludzi.

Te dwie umiejętności są bardzo przydatne i bardzo zabawne!

Wyzwanie 1: Utwórz ORM przez Metaclass]}

Przygotowanie do utworzenia pola Klasa

class Field ( object ) :
     def __init__ ( self, name, column_type ) :

         Self.name = name

         Self.column_type = column_type



     def   __str__ ( self ) :

         return   '<%s:%s>' % ( self . __class__ . __name__ ,   self. name ) 

Jego rola to

Gdy Klasa pola zostanie utworzona, otrzyma dwa parametry, name I column_type. Będą przywiązani do prywatnej własności Fielda. Jeśli chcesz przekonwertować pole na ciąg znaków, zwróci ono "Field: XXX". XXX zostało przekazane. Imię nazwisko.

Przygotowanie: Utwórz StringField i IntergerField

class StringField ( Field ) :



    def   __init__ ( self ,   name ) :

         super( StringField ,   self). __init__ ( name ,   'varchar(100)' )



class IntegerField ( Field ) :
     def   __init__ ( self ,name) :

         super( IntegerField ,   self). __init__ ( name ,   'bigint' ) 

Jego rola to

Gdy instancja StringField, IntegerField jest inicjalizowana, metoda inicjalizacji rodzica jest wywoływany automatycznie.

One came from the truth

class ModelMetaclass ( type ) :



     def __new__ ( cls ,name, bases ,   attrs) :

         Ifname== 'Model' :

             Return   Type . __new__ ( cls ,name, bases ,   attrs)

         print( 'Found model: %s' % name )

         Mappings = dict ()

         for k ,   v   In   attrs. items () :

             If   Isinstance ( v ,   Field ) :

                 print( 'Found mapping: %s ==> %s' % ( k ,   v ))

                 Mappings [ k ] = v

         for k   In   Mappings . keys () :

             attrs. pop ( k )

         attrs[ '__mappings__' ] = mappings   # Save the mapping between attributes and columns

         attrs[ '__table__' ] = name   # Assume that the table name and class name are the same

         Return   Type . __new__ ( cls ,name, bases ,   attrs) 

Robi następujące rzeczy

Create a new dictionary mapping

Each property of the class is traversed through its .items() key-value pair. If the value is a Field class, the key is printed and the key is bound to the mapping dictionary.

Delete the property that was just passed in as the Field class.

Create a special __mappings__ attribute and save the dictionary mapping.

Create a special __table__ attribute and save the name of the passed in class. 

Dwa pochodzą z jednego

class Model ( dict ,   Metaclass = ModelMetaclass ) :



     def __init__ ( self , ** kwarg ) :

         super(model ,   self). __init__ ( ** kwarg )



     def __getattr__ ( self ,   Key ) :

         Try :

             Return   self[ key ]

         except KeyError :

             Raise   AttributeError ( "'Model' object has no attribute '%s'" % key )



     def __setattr__ ( self ,   Key ,   Value ) :

         self[ key ] = value



     # Simulate table creation operation

     def save( self ) :

         Fields = []

         Args = []

         for k ,   v   In   self. __mappings__ . items () :

             Fields . append ( v . name )

             Args . append ( getattr ( self ,   k ,   None ))

         Sql = 'insert into %s (%s) values ​​(%s)' % ( self . __table__ ,   ',' . join ( fields ),   ',' . join ([ str ( i )   for i   In   Args ]))

         print( 'SQL: %s' % sql )

         print( 'ARGS: %s' % str ( args )) 

Jeśli utworzysz użytkownika podklasy z modela:

class User (model ) :

     # Define the mapping of attributes's attributes to columns:

     Id = IntegerField ( 'id' )

  name= StringField ( 'username' )

     Email = StringField ( 'email' )

     Password = StringField ( 'password' ) 

W tym czasie

Id = IntegerField ('id') automatycznie rozwiąże się do:

Model.setattr (self, 'id', IntegerField ('id'))

IntergerField ('id') jest instancją podklasy pola, metaklasa new jest automatycznie uruchamiany, więc IntergerField ('id') jest przechowywany w mappingach i para klucz-wartość jest usuwana.

Dwóch studentów, trzech studentów, wszystko

Po zainicjowaniu instancji i wywołaniu metody save ()

u = User ( id = 12345 ,name= 'Batman' ,   Email = '[email protected]' ,   Password = 'iamback' )

u . save () 

W tym czasie proces dwóch uczniów jest zakończony jako pierwszy:

First call Model.__setattr__ to load key values ​​into private objects

Then call the "genius" of the metaclass, ModelMetaclass.__new__, and private objects in the Model, as long as they are instances of Field, are automatically stored in u.__mappings__. 

Następnym krokiem jest ukończenie trzech rzeczy:

Symulować operacje inwentaryzacji danych za pomocą u. save (). Tutaj robimy tylko trochę traversal mapowanie operacja, wirtualny sql i drukowanie, w rzeczywistości, poprzez wprowadzenie instrukcji sql i bazy danych do uruchomienia.

Wyjście to

Found model : User

Found mapping : name ==> < StringField : username >

Found mapping : password ==> < StringField : password >

Found mapping : id ==> < IntegerField : id >

Found mapping : email ==> < StringField : email >

SQL : insert into User   ( username , password , id , email )   Values   ( Batman , iamback , 12345 , batman @ nasa . org )

ARGS : [ 'Batman' ,   'iamback'   12345 ,   '[email protected]' ] 


    Young Creator, you have experienced with me the great course of evolution of Everything from the Tao, which is also the core principle of the Model section in Django.

    Next, join me in a more fun reptile battle (well, you are now a junior hacker): crawling web agents! 

Wyzwanie II: pełzanie agentów sieci

Przygotuj się na wejście na stronę do gry

Upewnij się, że oba pakiety, requests i pyquery, są zainstalowane.

# File: get_page.py

import Requests



Base_headers = {

     'User-Agent' : 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.71 Safari/537.36' ,

     'Accept-Encoding' : 'gzip, deflate, sdch' ,

     'Accept-Language' : 'zh-CN,zh;q=0.8'

}





def Get_page ( url ) :

     Headers = dict ( base_headers )

     print( 'Getting' ,   Url )

     Try :

         r = requests . get ( url ,   Headers = headers )

         print( 'Getting result' ,   Url ,   r . status_code )

         If   r . status_code == 200 :

             Return   r .

     exceptConnectionError :

         print( 'Crawling Failed' ,   Url )

         Return   None 

Tutaj używamy pakietu request, aby wydostać się z kodu źródłowego Baidu.

Spróbuj spróbować Baidu

Stick this paragraf za get_page.py i spróbuj usunąć

If ( __name__ == '__main__' ) :

     Rs = get_page ( 'https://www.baidu.com' )

     print( 'result: ' ,   Rs ) 

Spróbuj złapać agentów

Przyklej ten akapit za get_page.py i spróbuj usunąć

If ( __name__ == '__main__' ) :

     from Pyquery import   PyQuery as   Pq

     Start_url = 'http://www.proxy360.cn/Region/China'

     print( 'Crawling' ,   Start_url )

     Html = get_page ( start_url )

     If   Html :

         Doc = pq ( html )

         Lines = doc ( 'div[name="list_proxy_ip"]' ). items ()

         for Line in   Lines :

             Ip = line . find ( '.tbBottomLine:nth-child(1)' ). text ()

             Port = line . find ( '.tbBottomLine:nth-child(2)' ). text ()

             print( ip + ':' + port ) 


Next, go to the topic: Use the metaclass batch fetch proxy 

Agent indeksujący przetwarzanie wsadowe

from Getpage import   Get_page

from Pyquery import   PyQuery as   Pq





# one came from truth: Create metaclass of extraction agent

class ProxyMetaclass ( type ) :

     """

Metaclass, added in the FreeProxyGetter class

__CrawlFunc__ and __CrawlFuncCount__

Two parameters, which represent the crawler function and the number of crawler functions, respectively.

"""

     def __new__ ( cls ,name, bases ,   attrs) :

         Count = 0

         attrs[ '__CrawlFunc__' ] = []

         attrs[ '__CrawlName__' ] = []

         for k ,   v   In   attrs. items () :

             If   'crawl_'   In   k :

                 attrs[ '__CrawlName__' ]. append ( k )

                 attrs[ '__CrawlFunc__' ]. append ( v )

                 Count += 1

         for k   In   attrs[ '__CrawlName__' ] :

             attrs. pop ( k )

         attrs[ '__CrawlFuncCount__' ] = count

         Return   Type . __new__ ( cls ,name, bases ,   attrs)





# two came from one: Create an agent to get the class



class ProxyGetter ( object ,   Metaclass = ProxyMetaclass ) :

     def Get_raw_proxies ( self ,   Site ) :

         Proxies = []

         print( 'Site' ,   Site )

         for Func in   self. __CrawlFunc__ :

             If   Func . __name__ == site :

                 This_page_proxies = func ( self )

                 for Proxy in   This_page_proxies :

                     print( 'Getting' ,   Proxy ,   'from' ,   Site )

                     Proxies . append ( proxy )

         Return   Proxies





     def Crawl_daili66 ( self ,   Page_count = 4 ) :

         Start_url = 'http://www.66ip.cn/{}.html'

         Urls = [ start_url . format ( page )   for Page in   Range ( 1 ,   Page_count + 1 )]

         for Url in   Urls :

             print( 'Crawling' ,   Url )

             Html = get_page ( url )

             If   Html :

                 Doc = pq ( html )

                 Trs = doc ( '.containerbox table tr:gt(0)' ). items ()

                 for Tr in   Trs :

                     Ip = tr . find ( 'td:nth-child(1)' ). text ()

                     Port = tr . find ( 'td:nth-child(2)' ). text ()

                     Yield   ':' . join ([ ip ,   Port ])



     def Crawl_proxy360 ( self ) :

         Start_url = 'http://www.proxy360.cn/Region/China'

         print( 'Crawling' ,   Start_url )

         Html = get_page ( start_url )

         If   Html :

             Doc = pq ( html )

             Lines = doc ( 'div[name="list_proxy_ip"]' ). items ()

             for Line in   Lines :

                 Ip = line . find ( '.tbBottomLine:nth-child(1)' ). text ()

                 Port = line . find ( '.tbBottomLine:nth-child(2)' ). text ()

                 Yield   ':' . join ([ ip ,   Port ])



     def Crawl_goubanjia ( self ) :

         Start_url = 'http://www.goubanjia.com/free/gngn/index.shtml'

         Html = get_page ( start_url )

         If   Html :

             Doc = pq ( html )

             Tds = doc ( 'td.ip' ). items ()

             for Td in   Tds :

                 Td . find ( 'p' ). remove ()

                 Yield   Td . text (). replace ( ' ' ,   '' )





If   __name__ == '__main__' :

     # Two students three: Instantiate ProxyGetter

     Crawler = ProxyGetter ()

     print(crawler . __CrawlName__ )

     # Three things

     for Site_label in   Range ( crawler . __CrawlFuncCount__ ) :

         Site = crawler . __CrawlName__ [ site_label ]

         myProxies = crawler . get_raw_proxies ( site ) 

Jeden przyszedł z prawdy: w metaklasie nowy , zrobił cztery rzeczy:

Push the name of the class method that starts with "crawl_" into ProxyGetter.__CrawlName__

Push the class method that starts with "crawl_" itself into ProxyGetter.__CrawlFunc__

Calculate the number of class methods that match "crawl_"

Delete all class methods that match "crawl_" 


how about it? Is it very similar to the __mappings__ process used to create an ORM? 

Dwa pochodzą z jednego: Klasa definiuje metodę użycia pyquery do przechwytywania elementów strony

Każdy z agentów pokazanych na stronie był indeksowany z trzech wolnych agentów miejsca.

Jeśli nie jesteś zaznajomiony z używaniem yield, sprawdź: Liao Xuefeng ' s Python tutorial: generator {]}

Trzy pochodzą z dwóch: Utwórz instancję obiekt crawler

Lekko

Trzy rzeczy: przemierzanie każdego CrawlFunc

Above ProxyGetter.__CrawlName__, get the URL name that can be crawled.

Trigger class method ProxyGetter.get_raw_proxies(site)

Traverse ProxyGetter.__CrawlFunc__, if the method name and URL are the same, then execute this method

Integrate the proxy obtained from each URL into an array output. 


So. . . How to use bulk agents, impact other people's websites, capture other people's passwords, frantically advertise water stickers, and regularly harass customers? Uh! Think it! These self-realization! If you do not realize it, please listen to the next decomposition! 
Młody Stwórca, narzędzie do tworzenia świata, jest już w twoich rękach. Proszę, wykorzystaj jego moc w pełni!

Zapamiętaj usta narzędzia:

One came from truth, two came from one, three came from two, all the thing came from three.

Who am I, where do I come from, where do I go 
 14
Author: pah8J,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-10-01 07:48:51

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

Na przykład, możemy utworzyć klasę Hi Z funkcją type() i nie musimy używać tej metody z klasą Hi(object):

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żywania metody type() do dynamicznego tworzenia klas, możesz kontrolować zachowanie tworzenia klasy i używać metaclass.

Zgodnie z modelem obiektowym Pythona, klasa jest obiektem, więc klasa musi być instancją innej określonej klasy. Domyślnie, a Klasa Python jest instancją klasy type. 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 podamy argumenty słów kluczowych w metaclass, wskazuje interpreter Pythona, aby utworzyć CustomList poprzez ListMetaclass. new (), w tym momencie możemy na przykład zmodyfikować definicję klasy i dodać nową metodę, a następnie zwrócić zmienioną definicję.

 9
Author: binbjz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-01-12 09:30:20

Oprócz opublikowanych odpowiedzi mogę powiedzieć, że metaclass definiuje zachowanie dla klasy. Możesz więc jawnie ustawić swoją metaklasę. Za każdym razem, gdy Python otrzymuje słowo kluczowe class, zaczyna szukać metaclass. Jeśli nie został znaleziony – domyślny typ metaclass jest używany do wytworzenia obiektu klasy. Używając atrybutu __metaclass__, możesz ustawić metaclass swojej klasy:

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

print(MyClass.__metaclass__)

To będzie produkować Wyjście Tak:

class 'type'

I oczywiście możesz stworzyć swój własny metaclass, Aby zdefiniuj zachowanie każdej klasy, która została utworzona przy użyciu twojej klasy.

Aby to zrobić, Twoja domyślna klasa typu metaclass musi być dziedziczona, 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__)

Wyjście będzie:

class '__main__.MyMetaClass'
class 'type'
 1
Author: Gigantic,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-09-15 13:17:29