Что такое метаклассы в Python?

Что такое метаклассы и для чего мы их используем?

30 ответов

Решение

Метакласс - это класс класса. Как класс определяет поведение экземпляра класса, метакласс определяет поведение класса. Класс является экземпляром метакласса.

В то время как в Python вы можете использовать произвольные вызовы для метаклассов (как показано на Jerub), более полезный подход заключается в том, чтобы фактически сделать его самим классом. type это обычный метакласс в Python. Если вам интересно, да, type сам по себе класс, и это его собственный тип. Вы не сможете воссоздать что-то вроде type чисто в Python, но Python немного обманывает. Чтобы создать свой собственный метакласс в Python, вы действительно хотите создать подкласс type,

Метакласс чаще всего используется как фабрика классов. Подобно тому, как вы создаете экземпляр класса, вызывая класс, Python создает новый класс (когда он выполняет оператор 'class'), вызывая метакласс. В сочетании с нормальным __init__ а также __new__ методы, метаклассы, следовательно, позволяют вам делать "дополнительные вещи" при создании класса, такие как регистрация нового класса в каком-либо реестре, или даже полностью заменить класс чем-то другим.

Когда class оператор выполняется, Python сначала выполняет тело class оператор как обычный блок кода. Результирующее пространство имен (dict) содержит атрибуты будущего класса. Метакласс определяется путем просмотра базовых классов будущего класса (метаклассы наследуются), __metaclass__ атрибут будущего класса (если есть) или __metaclass__ глобальная переменная. Затем метакласс вызывается с именем, основами и атрибутами класса, чтобы создать его экземпляр.

Тем не менее, метаклассы фактически определяют тип класса, а не просто фабрику для него, так что вы можете сделать с ними гораздо больше. Например, вы можете определить нормальные методы в метаклассе. Эти метакласс-методы похожи на методы класса, в том смысле, что их можно вызывать в классе без экземпляра, но они также не похожи на методы класса в том смысле, что их нельзя вызывать в экземпляре класса. type.__subclasses__() пример метода на type метаклассом. Вы также можете определить обычные "магические" методы, такие как __add__, __iter__ а также __getattr__, чтобы реализовать или изменить поведение класса.

Вот агрегированный пример кусочков:

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__

Классы как объекты

Прежде чем разбираться в метаклассах, вам нужно освоить классы на Python. И у Python очень своеобразное представление о том, что такое классы, заимствованные из языка Smalltalk.

В большинстве языков классы - это просто фрагменты кода, которые описывают, как создать объект. Это также верно и для Python:

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

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

Но классы больше, чем в Python. Классы тоже объекты.

Да, объекты.

Как только вы используете ключевое слово class, Python выполняет его и создает объект. Инструкция

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

создает в памяти объект с именем "ObjectCreator".

Этот объект (класс) сам по себе способен создавать объекты (экземпляры), и именно поэтому он является классом.

Но все же, это объект, и поэтому:

  • Вы можете назначить его переменной
  • Вы можете скопировать это
  • Вы можете добавить атрибуты к нему
  • Вы можете передать его как параметр функции

например:

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

Создание классов динамически

Так как классы являются объектами, вы можете создавать их на лету, как и любой объект.

Во-первых, вы можете создать класс в функции, используя 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>

Но это не так динамично, так как вам все равно придется писать весь класс самостоятельно.

Так как классы являются объектами, они должны быть сгенерированы чем-то.

Когда вы используете class Ключевое слово, Python создает этот объект автоматически. Но, как и большинство вещей в Python, он дает вам возможность сделать это вручную.

Запомнить функцию type? Старая добрая функция, которая позволяет узнать тип объекта:

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

Что ж, type обладает совершенно другой способностью, он также может создавать классы на лету. type может взять описание класса в качестве параметров и вернуть класс.

(Я знаю, что глупо, что одна и та же функция может иметь два совершенно разных применения в зависимости от параметров, которые вы ей передаете. Это проблема из-за обратной совместимости в Python)

type работает так:

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

например:

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

можно создать вручную таким образом:

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

Вы заметите, что мы используем "MyShinyClass" в качестве имени класса и в качестве переменной для хранения ссылки на класс. Они могут быть разными, но нет причин усложнять вещи.

type принимает словарь для определения атрибутов класса. Так:

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

Можно перевести на:

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

И используется как обычный класс:

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

И, конечно, вы можете наследовать от него, так что:

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

было бы:

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

В конце концов вы захотите добавить методы в свой класс. Просто определите функцию с правильной подписью и назначьте ее в качестве атрибута.

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

И вы можете добавить еще больше методов после динамического создания класса, точно так же, как добавление методов в нормально созданный объект класса.

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

Вы видите, куда мы идем: в Python классы являются объектами, и вы можете динамически создавать классы на лету.

Это то, что делает Python, когда вы используете ключевое слово class и делает это с помощью метакласса.

Что такое метаклассы (наконец)

Метаклассы - это "материал", который создает классы.

Вы определяете классы для создания объектов, верно?

Но мы узнали, что классы Python являются объектами.

Ну, метаклассы - это то, что создает эти объекты. Это классы классов, вы можете изобразить их следующим образом:

MyClass = MetaClass()
my_object = MyClass()

Вы видели это type позволяет вам сделать что-то вроде этого:

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

Это потому, что функция type на самом деле метакласс. type является метаклассом, который Python использует для создания всех классов за сценой.

Теперь вы удивляетесь, почему, черт возьми, это написано строчными, а не Type?

Ну, я думаю, что это вопрос согласованности с str, класс, который создает строковые объекты, и int класс, который создает целочисленные объекты. type это просто класс, который создает объекты класса.

Вы видите это, проверяя __class__ приписывать.

Все, и я имею в виду все, является объектом в Python. Это включает в себя целые, строки, функции и классы. Все они являются объектами. И все они были созданы из класса:

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

Теперь, что является __class__ любой __class__?

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

Итак, метакласс - это просто материал, который создает объекты класса.

Вы можете назвать это "фабрикой классов", если хотите.

type Это встроенный метакласс, который использует Python, но, конечно, вы можете создать свой собственный метакласс.

__metaclass__ атрибут

В Python 2 вы можете добавить __metaclass__ Атрибут при написании класса (см. следующий раздел для синтаксиса Python 3):

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

Если вы это сделаете, Python будет использовать метакласс для создания класса Foo,

Осторожно, это сложно.

Ты пишешь class Foo(object) во-первых, но объект класса Foo еще не создан в памяти.

Python будет искать __metaclass__ в определении класса. Если он найдет его, он будет использовать его для создания класса объекта Foo, Если это не так, он будет использовать type создать класс.

Прочитайте это несколько раз.

Когда вы делаете:

class Foo(Bar):
    pass

Python делает следующее:

Есть ли __metaclass__ приписывать Foo?

Если да, создайте в памяти объект класса (я сказал объект класса, оставайтесь со мной здесь) с именем Foo используя то, что в __metaclass__,

Если Python не может найти __metaclass__, он будет искать __metaclass__ на уровне МОДУЛЯ, и попробуйте сделать то же самое (но только для классов, которые ничего не наследуют, в основном классы старого стиля).

Тогда, если он не может найти __metaclass__ вообще, он будет использовать Bar собственный (первый родительский) метакласс (который может быть по умолчанию type) создать объект класса.

Будьте осторожны, что __metaclass__ атрибут не будет наследоваться, метакласс родительского (Bar.__class__) будет. Если Bar использовал __metaclass__ атрибут, который создал Bar с type() (и не type.__new__()), подклассы не будут наследовать это поведение.

Теперь большой вопрос, что вы можете положить в __metaclass__?

Ответ: что-то, что может создать класс.

А что может создать класс? type или что-нибудь, что подклассы или использует это.

Метаклассы в Python 3

Синтаксис для установки метакласса был изменен в Python 3:

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

то есть __metaclass__ атрибут больше не используется, в пользу ключевого аргумента в списке базовых классов.

Поведение метаклассов, однако, остается в основном таким же.

Пользовательские метаклассы

Основное назначение метакласса - автоматическое изменение класса при его создании.

Вы обычно делаете это для API, где вы хотите создать классы, соответствующие текущему контексту.

Представьте себе глупый пример, когда вы решаете, что все классы в вашем модуле должны иметь свои атрибуты, написанные в верхнем регистре. Есть несколько способов сделать это, но один из них - установить __metaclass__ на уровне модуля.

Таким образом, все классы этого модуля будут созданы с использованием этого метакласса, и нам просто нужно указать метаклассу, чтобы все атрибуты были заглавными.

К счастью, __metaclass__ на самом деле это может быть любой вызываемый объект, он не должен быть формальным классом (я знаю, что-то с именем 'class' в названии не обязательно должно быть классом, поймите, что... но это полезно).

Итак, мы начнем с простого примера, используя функцию.

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

Теперь давайте сделаем точно так же, но используя реальный класс для метакласса:

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

Но это не совсем ООП. Мы называем type напрямую, и мы не переопределяем или не вызываем родителя __new__, Давай сделаем это:

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)

Возможно, вы заметили дополнительный аргумент upperattr_metaclass, В этом нет ничего особенного: __new__ всегда получает класс, в котором он определен, в качестве первого параметра. Так же, как у вас self для обычных методов, которые получают экземпляр в качестве первого параметра, или определяющий класс для методов класса.

Конечно, имена, которые я здесь использовал, длинны для ясности, но для self все аргументы имеют условные имена. Таким образом, настоящий производственный метакласс будет выглядеть так:

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)

Мы можем сделать его еще чище, используя super, что облегчит наследование (потому что да, у вас могут быть метаклассы, наследование от метаклассов, наследование от типа):

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)

Вот и все. В метаклассах больше ничего нет.

Причиной сложности кода, использующего метаклассы, является не метаклассы, а то, что вы обычно используете метаклассы для скрученных вещей, полагаясь на самоанализ, манипулирование наследованием, такие переменные, как __dict__, так далее.

Действительно, метаклассы особенно полезны для черной магии и, следовательно, для сложных вещей. Но сами по себе они просты

  • перехватить создание класса
  • изменить класс
  • вернуть измененный класс

Почему вы используете классы метаклассов вместо функций?

поскольку __metaclass__ может принять любой вызываемый, зачем вам использовать класс, потому что он явно более сложный?

Для этого есть несколько причин:

  • Намерение ясно. Когда ты читаешь UpperAttrMetaclass(type) Вы знаете, что будет следовать
  • Вы можете использовать ООП. Метакласс может наследовать от метакласса, переопределять родительские методы. Метаклассы могут даже использовать метаклассы.
  • Подклассы класса будут экземплярами его метакласса, если вы указали метакласс-класс, но не с метакласс-функцией.
  • Вы можете лучше структурировать свой код. Вы никогда не используете метаклассы для чего-то столь же тривиального, как приведенный выше пример. Это обычно для чего-то сложного. Возможность сделать несколько методов и сгруппировать их в один класс очень полезна для облегчения чтения кода.
  • Вы можете зацепить __new__, __init__ а также __call__, Что позволит вам делать разные вещи. Даже если обычно вы можете сделать все это в __new__ некоторым людям просто удобнее пользоваться __init__,
  • Это называется метаклассы, черт побери! Это должно что-то значить!

Почему вы используете метаклассы?

Теперь большой вопрос. Зачем вам использовать некоторые скрытые ошибки?

Ну, обычно вы этого не делаете:

Метаклассы - это более глубокое волшебство, о котором 99% пользователей никогда не должны беспокоиться. Если вам интересно, нужны ли они вам, вам это не нужно (люди, которые действительно нуждаются в них, знают с уверенностью, что они им нужны, и не нуждаются в объяснении того, почему).

Питон Гуру Тим Питерс

Основным вариантом использования метакласса является создание API. Типичным примером этого является Django ORM.

Это позволяет вам определить что-то вроде этого:

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

Но если вы сделаете это:

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

Это не вернет IntegerField объект. Это вернет int и даже может взять его прямо из базы данных.

Это возможно, потому что models.Model определяет __metaclass__ и он использует магию, которая превратит Person Вы просто определили с помощью простых операторов в сложный крюк к полю базы данных.

Django делает что-то сложное простым, предоставляя простой API и используя метаклассы, воссоздавая код из этого API для реальной работы за кулисами.

Последнее слово

Во-первых, вы знаете, что классы - это объекты, которые могут создавать экземпляры.

Ну, на самом деле, классы сами по себе являются экземплярами. Метаклассов.

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

В Python все является объектом, и все они являются экземплярами классов или экземплярами метаклассов.

За исключением type,

type на самом деле его собственный метакласс. Это не то, что вы могли бы воспроизвести на чистом Python, и это делается путем обмана на уровне реализации.

Во-вторых, метаклассы сложны. Возможно, вы не захотите использовать их для очень простых изменений класса. Вы можете изменить классы, используя два разных метода:

В 99% случаев вам нужно изменить класс, лучше использовать их.

Но в 98% случаев вам вообще не нужно менять класс.

Обратите внимание, что этот ответ для Python 2.x, как он был написан в 2008 году, метаклассы немного отличаются в 3.x, см. Комментарии.

Метаклассы - это секретный соус, который заставляет работать "класс". Метакласс по умолчанию для нового объекта стиля называется "тип".

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

Метаклассы занимают 3 аргумента. "имя", "основания" и "дикт"

Вот где секрет начинается. Ищите, откуда взято имя, основания и диктат в этом примере определения класса.

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

Давайте определим метакласс, который продемонстрирует, каккласс: называет его.

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'

А теперь, пример, который на самом деле что-то значит, это автоматически сделает переменные в списке "атрибуты" установленными в классе и установит "Нет".

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

Обратите внимание, что магическое поведение, которое "инициализирует", приобретает наличие метакласса. init_attributes не передается на подкласс Initalised.

Вот еще более конкретный пример, показывающий, как вы можете создать подкласс 'type' для создания метакласса, который выполняет действие при создании класса. Это довольно сложно:

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

Другие объяснили, как работают метаклассы и как они вписываются в систему типов Python. Вот пример того, для чего они могут быть использованы. В тестовой среде, которую я написал, я хотел отслеживать порядок, в котором были определены классы, чтобы впоследствии я мог их создать в этом порядке. Я нашел, что проще всего сделать это с помощью метакласса.

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

Все, что является подклассом MyType затем получает атрибут класса _order это записывает порядок, в котором были определены классы.

Одно из применений метаклассов - автоматическое добавление новых свойств и методов в экземпляр.

Например, если вы посмотрите на модели Django, их определение выглядит немного запутанным. Похоже, вы определяете только свойства класса:

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

Однако во время выполнения объекты Person заполняются всевозможными полезными методами. Смотрите источник для некоторой удивительной метаклассерии.

Я думаю, что введение ONLamp в программирование метаклассов хорошо написано и дает действительно хорошее введение в тему, хотя ему уже несколько лет.

http://www.onlamp.com/pub/a/python/2003/04/17/metaclasses.html (архивируется по адресу https://web.archive.org/web/20080206005253/http://www.onlamp.com/pub/a/python/2003/04/17/metaclasses.html)

Вкратце: класс - это план создания экземпляра, метакласс - это план создания класса. Легко видеть, что в классах Python должны быть объекты первого класса, чтобы включить это поведение.

Я никогда не писал сам, но я думаю, что одно из самых хороших применений метаклассов можно увидеть в структуре Django. Классы моделей используют метаклассовый подход, чтобы включить декларативный стиль написания новых моделей или классов форм. Пока метакласс создает класс, все члены получают возможность настраивать сам класс.

Осталось сказать следующее: если вы не знаете, что такое метаклассы, вероятность того, что они вам не понадобятся, составляет 99%.

Что такое метаклассы? Для чего вы их используете?

TLDR: метакласс создает и определяет поведение для класса так же, как класс создает и определяет поведение для экземпляра.

псевдокод:

>>> Class(...)
instance

Выше должно выглядеть знакомо. Ну где же Class родом из? Это экземпляр метакласса (также псевдокода):

>>> Metaclass(...)
Class

В реальном коде мы можем передать метакласс по умолчанию, type, все, что нам нужно для создания экземпляра класса, и мы получаем класс:

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

Говоря по-другому

  • Класс относится к экземпляру, а метакласс - к классу.

    Когда мы создаем экземпляр объекта, мы получаем экземпляр:

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

    Аналогично, когда мы явно определяем класс с метаклассом по умолчанию, typeМы создаем это:

    >>> type('Object', (object,), {})     # instantiation of metaclass
    <class '__main__.Object'>             # instance
    
  • Другими словами, класс является экземпляром метакласса:

    >>> isinstance(object, type)
    True
    
  • Иными словами, метакласс - это класс класса.

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

Когда вы пишете определение класса и Python выполняет его, он использует метакласс для создания экземпляра объекта класса (который, в свою очередь, будет использоваться для создания экземпляров этого класса).

Так же, как мы можем использовать определения классов, чтобы изменить поведение пользовательских объектов, мы можем использовать определение класса метакласса, чтобы изменить поведение объекта класса.

Для чего они могут быть использованы? Из документов:

Потенциальное использование метаклассов безгранично. Некоторые идеи, которые были исследованы, включают ведение журнала, проверку интерфейса, автоматическое делегирование, автоматическое создание свойств, прокси-серверы, платформы и автоматическую блокировку / синхронизацию ресурсов.

Тем не менее, пользователям обычно рекомендуется избегать использования метаклассов, если в этом нет крайней необходимости.

Вы используете метакласс каждый раз, когда создаете класс:

Когда вы пишете определение класса, например, как это,

class Foo(object): 
    'demo'

Вы создаете объект класса.

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

Это так же, как функциональный вызов type с соответствующими аргументами и присвоением результата переменной с таким именем:

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

Обратите внимание, что некоторые вещи автоматически добавляются в __dict__пространство имен:

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

Метакласс созданного нами объекта в обоих случаях type,

(Примечание о содержании класса __dict__: __module__ там, потому что классы должны знать, где они определены, и __dict__ а также __weakref__ там, потому что мы не определяем __slots__ - если мы определим__slots__ мы сэкономим немного места в экземплярах, так как мы можем запретить __dict__ а также __weakref__ исключив их. Например:

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

... но я отвлекся.)

Мы можем продлить type как и любое другое определение класса:

Вот по умолчанию __repr__ классов:

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

Одна из самых ценных вещей, которые мы можем сделать по умолчанию при написании объекта Python, - это предоставить ему хороший __repr__, Когда мы звоним help(repr) мы узнаем, что есть хороший тест для __repr__ что также требует проверки на равенство - obj == eval(repr(obj)), Следующая простая реализация __repr__ а также __eq__ для экземпляров класса нашего класса типов предоставляет нам демонстрацию, которая может улучшить по умолчанию __repr__ классов:

class Type(type):
    def __repr__(cls):
        """
        >>> Baz
        Type('Baz', (Foo, Bar,), {'__module__': '__main__', '__doc__': None})
        >>> eval(repr(Baz))
        Type('Baz', (Foo, Bar,), {'__module__': '__main__', '__doc__': None})
        """
        metaname = type(cls).__name__
        name = cls.__name__
        parents = ', '.join(b.__name__ for b in cls.__bases__)
        if parents:
            parents += ','
        namespace = ', '.join(': '.join(
          (repr(k), repr(v) if not isinstance(v, type) else v.__name__))
               for k, v in cls.__dict__.items())
        return '{0}(\'{1}\', ({2}), {{{3}}})'.format(metaname, name, parents, namespace)
    def __eq__(cls, other):
        """
        >>> Baz == eval(repr(Baz))
        True            
        """
        return (cls.__name__, cls.__bases__, cls.__dict__) == (
                other.__name__, other.__bases__, other.__dict__)

Так что теперь, когда мы создаем объект с этим метаклассом, __repr__ echoed в командной строке обеспечивает гораздо менее уродливый вид, чем по умолчанию:

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

С хорошим __repr__ определенный для экземпляра класса, у нас есть более сильная способность отлаживать наш код. Тем не менее, гораздо больше проверять с eval(repr(Class)) маловероятно (так как функции было бы довольно невозможно оценить по умолчанию __repr__"S).

Ожидаемое использование: __prepare__ пространство имен

Если, например, мы хотим знать, в каком порядке создаются методы класса, мы можем предоставить упорядоченный dict в качестве пространства имен класса. Мы бы сделали это с __prepare__ который возвращает dict пространства имен для класса, если он реализован в Python 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

И использование:

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

И теперь у нас есть запись порядка, в котором были созданы эти методы (и другие атрибуты класса):

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

Обратите внимание, этот пример был адаптирован из документации - это делает новый enum в стандартной библиотеке.

Итак, мы сделали экземпляр метакласса, создав класс. Мы также можем обращаться с метаклассом так же, как и с любым другим классом. У него есть порядок разрешения метода:

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

И это примерно правильно repr (который мы больше не сможем оценить, если не сможем найти способ представить наши функции.):

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

Обновление Python 3

На данный момент есть два ключевых метода в метаклассе:

  • __prepare__, а также
  • __new__

__prepare__ позволяет предоставить пользовательское сопоставление (например, OrderedDict) для использования в качестве пространства имен при создании класса. Вы должны вернуть экземпляр любого пространства имен, которое вы выберете. Если вы не реализуете __prepare__ нормальный dict используется.

__new__ отвечает за фактическое создание / модификацию финального класса.

Метаклассу "голые", "ничего не делать" хотелось бы:

class Meta(type):

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

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

Простой пример:

Допустим, вы хотите, чтобы на ваших атрибутах выполнялся простой проверочный код - как будто он всегда должен быть int или str, Без метакласса ваш класс будет выглядеть примерно так:

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

Как видите, вы должны повторить имя атрибута дважды. Это делает возможным опечатки наряду с раздражающими ошибками.

Простой метакласс может решить эту проблему:

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

Вот как будет выглядеть метакласс (без использования __prepare__ так как это не нужно)

class Validator(type):
    def __new__(metacls, cls, bases, clsdict):
        # search clsdict looking for ValidateType descriptors
        for name, attr in clsdict.items():
            if isinstance(attr, ValidateType):
                attr.name = name
                attr.attr = '_' + name
        # create final class and return it
        return super().__new__(metacls, cls, bases, clsdict)

Примерный прогон:

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

производит:

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

Примечание. Этот пример достаточно прост, его также можно было бы выполнить с помощью декоратора классов, но, по-видимому, реальный метакласс сделал бы гораздо больше.

Класс ValidateType для справки:

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

Роль метакласса __call__() метод при создании экземпляра класса

Если вы занимались программированием на Python более нескольких месяцев, вы в конечном итоге натолкнетесь на код, который выглядит следующим образом:

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

Последнее возможно при реализации __call__() магический метод на уроке.

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

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

__call__() Метод вызывается, когда экземпляр класса используется как вызываемый. Но, как мы видели из предыдущих ответов, сам класс является экземпляром метакласса, поэтому, когда мы используем класс в качестве вызываемого (т.е. когда мы создаем его экземпляр), мы фактически вызываем его метакласс ' __call__() метод. На данный момент большинство программистов на Python немного смущены, потому что им сказали, что при создании экземпляра, подобного этому instance = SomeClass() ты звонишь __init__() метод. Те, кто копали немного глубже, знают это раньше __init__() есть __new__(), Ну, сегодня еще один слой истины раскрывается, прежде чем __new__() есть метакласс __call__(),

Давайте изучим цепочку вызовов метода с точки зрения создания экземпляра класса.

Это метакласс, который регистрирует ровно момент перед созданием экземпляра и момент, когда он собирается его вернуть.

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

Это класс, который использует этот метакласс

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

А теперь давайте создадим экземпляр 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.

Обратите внимание, что приведенный выше код на самом деле не делает ничего, кроме регистрации задач. Каждый метод делегирует фактическую работу реализации его родителя, сохраняя поведение по умолчанию. поскольку type является Meta_1Родительский класс (type являясь родительским метаклассом по умолчанию) и учитывая последовательность упорядочения вышеприведенных выходных данных, теперь у нас есть подсказка относительно того, какой будет псевдо-реализация 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

Мы можем видеть, что метакласс __call__() Метод называется первым. Затем он делегирует создание экземпляра классу __new__() метод и инициализация экземпляра __init__(), Это также тот, который в конечном итоге возвращает экземпляр.

Из вышесказанного вытекает, что метакласс __call__() также предоставляется возможность решить, стоит ли звонить Class_1.__new__() или же Class_1.__init__() в конечном итоге будет сделано. За время своего выполнения он мог фактически вернуть объект, который не был затронут ни одним из этих методов. Возьмем для примера такой подход к шаблону синглтона:

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

Давайте посмотрим, что происходит при неоднократных попытках создать объект типа 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

Метакласс - это класс, который сообщает, как (какой-то) другой класс должен быть создан.

Это тот случай, когда я рассматривал метакласс как решение моей проблемы: у меня была действительно сложная проблема, которую, возможно, можно было решить по-другому, но я решил решить ее с помощью метакласса. Из-за сложности, это один из немногих написанных мной модулей, где комментарии в модуле превосходят объем написанного кода. Вот...

#!/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()

Версия tl;dr

type(obj) Функция получает тип объекта.

type()класса является его метаклассом.

Чтобы использовать метакласс:

class Foo(object):
    __metaclass__ = MyMetaClass

type на самом деле metaclass - класс, который создает другие классы. Наиболее metaclass подклассы type, metaclass получает new class в качестве первого аргумента и предоставляет доступ к объекту класса с деталями, как указано ниже:

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

Обратите внимание, что класс не был создан в любое время; простой акт создания класса запускает выполнение metaclass,

Классы Python сами являются объектами - как, например, - их метакласса.

Метакласс по умолчанию, который применяется, когда вы определяете классы как:

class foo:
    ...

метакласс используется для применения некоторого правила ко всему набору классов. Например, предположим, что вы создаете ORM для доступа к базе данных, и вы хотите, чтобы записи из каждой таблицы были класса, сопоставленного с этой таблицей (на основе полей, бизнес-правил и т. Д.,), Возможное использование метакласса например, логика пула соединений, которая является общей для всех классов записей из всех таблиц. Другое использование - логика для поддержки внешних ключей, которая включает несколько классов записей.

когда вы определяете метакласс, вы подклассируете тип и можете переопределить следующие магические методы для вставки вашей логики.

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

во всяком случае, эти два наиболее часто используемые крючки. метаклассирование является мощным, и выше не далеко и исчерпывающий список применений для метаклассирования.

Функция type() может возвращать тип объекта или создавать новый тип,

например, мы можем создать класс Hi с функцией type() и нам не нужно использовать этот способ с классом 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

Помимо использования type() для динамического создания классов, вы можете управлять поведением создания класса и использовать метакласс.

Согласно объектной модели Python, класс является объектом, поэтому класс должен быть экземпляром другого определенного класса. По умолчанию класс Python является экземпляром класса type. То есть тип является метаклассом большинства встроенных классов и метаклассом пользовательских классов.

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

Магия вступит в силу, когда мы передадим аргументы ключевого слова в метаклассе, он указывает интерпретатору Python для создания CustomList через ListMetaclass. new (), на этом этапе мы можем изменить определение класса, например, добавить новый метод и затем вернуть исправленное определение.

В дополнение к опубликованным ответам могу сказать, что metaclass определяет поведение для класса. Таким образом, вы можете явно установить свой метакласс. Всякий раз, когда Python получает ключевое слово class затем начинается поиск metaclass, Если он не найден - тип метакласса по умолчанию используется для создания объекта класса. С использованием __metaclass__ атрибут, вы можете установить metaclass вашего класса:

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

print(MyClass.__metaclass__)

Он выдаст вывод примерно так:

class 'type'

И, конечно же, вы можете создать свой собственный metaclass определить поведение любого класса, созданного с использованием вашего класса.

Для этого по умолчанию metaclass Тип класса должен быть унаследован, так как это основной 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__)

Выход будет:

class '__main__.MyMetaClass'
class 'type'

Обратите внимание, что в python 3.6 появился новый метод dunder __init_subclass__(cls, **kwargs)был введен, чтобы заменить множество распространенных вариантов использования метаклассов. Is вызывается при создании подкласса определяющего класса. См. Документацию по python.

Вот еще один пример того, для чего его можно использовать:

  • Вы можете использовать metaclass для изменения функции своего экземпляра (класса).
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 мощно, с ним можно делать много вещей (например, магию обезьян), но будьте осторожны, это может быть известно только вам.

Метаклассы - это не что иное, как простой внутренний класс вашего модельного класса, например: в Django или python

      class Author(models.Model):
    name = models.CharField(max_length=50)
    email = models.EmailField()

    class Meta:
        abstract = True

Здесь, если abstract = True, эта модель будет абстрактным базовым классом. Метакласс изменит поведение вашего базового класса.

В объектно-ориентированном программировании метакласс - это класс, экземпляры которого являются классами. Так же, как обычный класс определяет поведение определенных объектов, метакласс определяет поведение определенного класса и их экземпляров. Термин метакласс просто означает что-то, что используется для создания классов. Другими словами, это класс класса. Метакласс используется для создания класса, поэтому, как объект, являющийся экземпляром класса, класс является экземпляром метакласса. В Python классы также считаются объектами.

Класс в Python - это объект, и, как и любой другой объект, он является экземпляром "чего-то". Это "что-то" называется Метаклассом. Этот метакласс представляет собой особый тип класса, который создает объекты других классов. Следовательно, метакласс отвечает за создание новых классов. Это позволяет программисту настраивать способ создания классов.

Для создания метакласса обычно выполняется переопределение методов new() и init(). new() можно переопределить, чтобы изменить способ создания объектов, а init() можно переопределить, чтобы изменить способ инициализации объекта. Метакласс можно создать несколькими способами. Один из способов - использовать функцию type(). Функция type() при вызове с 3 параметрами создает метакласс. Параметры следующие:-

  1. Имя класса
  2. Кортеж, имеющий базовые классы, унаследованные классом
  3. Словарь, содержащий все методы класса и переменные класса

Другой способ создания метакласса состоит из ключевого слова "метакласс". Определите метакласс как простой класс. В параметрах унаследованного класса передайте metaclass=metaclass_name

Metaclass можно специально использовать в следующих ситуациях:-

  1. когда конкретный эффект должен быть применен ко всем подклассам
  2. Требуется автоматическая смена класса (при создании)
  3. Разработчики API

Я видел интересный вариант использования метаклассов в пакете под названием classutilities. Он проверяет, все ли переменные класса находятся в формате верхнего регистра (удобно иметь унифицированную логику для классов конфигурации), и проверяет, нет ли в классе методов уровня экземпляра. Еще одним интересным примером для метакласов была деактивация модульных тестов на основе сложных условий (проверка значений нескольких переменных среды).

В Python метакласс - это подкласс подкласса, который определяет поведение подкласса. Класс - это экземпляр другого метакласса. В Python класс определяет, как будет вести себя экземпляр класса.

Поскольку за создание классов отвечают метаклассы, вы можете написать свои собственные метаклассы, чтобы изменить способ создания классов путем выполнения дополнительных действий или внедрения кода. Пользовательские метаклассы не всегда важны, но могут иметь значение.

Что такое метапрограммирование?

В двух словах мы можем сказать, что метапрограммирование — это код, который манипулирует кодом. Python поддерживает форму метапрограммирования для классов, называемую метаклассами.

Когда использовать:

Обычно он используется для чего-то сложного, но есть несколько случаев, когда мы используем метаклассы:

  • метаклассы распространяются вниз по иерархии наследования. Это также повлияет на все подклассы. Если у нас такая ситуация, то мы должны использовать метаклассы.
  • Если мы хотим изменить класс автоматически при его создании, мы используем метаклассы.
  • Для разработки API мы могли бы использовать метаклассы.
  • Далее при создании: логирование и профилирование, проверка интерфейса, регистрация классов во время создания, автоматическое добавление новых методов, автоматическое создание свойств, прокси, автоматический ресурс, блокировка/синхронизация.

Схема для новичков:

Фабрика классов:

Метакласс в основном используется как фабрика классов. Когда вы создаете объект, вызывая класс, Python создает новый класс, вызывая метакласс.

> В сочетании с обычным а также методы, метаклассы, позволяют делать дополнительные действия при создании класса, например регистрировать новый класс в каком-либо реестре или полностью заменять класс чем-то другим.

1- __new__():Это метод, который вызывается перед . Он создает объект и возвращает его. Мы можем переопределить этот метод, чтобы контролировать создание объектов.

2- __init__():Этот метод просто инициализирует созданный объект, переданный в качестве параметра.

Способы определения метаклассов:

1- Способ 1:

      class MyMeta1(type):
    def __new__(cls, name, bases, dict):
        pass

2- Способ 2:

      class MyMeta2(type):
    def __init__(self, name, bases, dict):
        pass

Два предложения, чтобы освоить самую сложную точку знаний Python: метакласс

Первоначальный источник: https://segmentfault.com/a/1190000011447445

переведено и исправлено мной.

Оригинальный автор этой статьи сохраняет все в порядке, однако перевод рабочих мест по-прежнему нельзя игнорировать

Если есть какие-то ошибки или какой-либо формат против PEP8, пожалуйста, помогите мне исправить это. Спасибо!!!

В начале есть несколько примеров из китайской традиционной культуры (я не китайский, но у меня есть кое-какие знания об этом. Если вам это нравится, это хорошо. А если нет, просто игнорируйте это. Понимание метакласса это самое главное)

Это краткое введение в Metaclass в Python с некоторыми практическими и полезными примерами. Желаю вам понравится.

Не пугайтесь такой риторики, как так называемая "особенность, заключающаяся в том, что метакласс не используется 99% программистов Python". Потому что каждый человек - естественный пользователь.

Чтобы понимать метаклассы, вам нужно знать только два предложения:

предложение 1: один пришел от истины, два пришли от одного, три пришли от двух, все вещи пришли от трех

предложение 2: кто я? Откуда я пришел? Куда я иду?

В мире питонов существует вечная истина, то есть "тип", помните, пожалуйста, "тип - это правда". Экосистема питона, которая настолько обширна, производится по типу.

один пришел от истины, два - от одного, три - от двух, все вещи - от трех:

Правда это тип

Одним из них является метакласс (метакласс или генератор классов)

Вторым является класс (класс или генератор экземпляра)

Три это пример (пример)

Все это различные атрибуты и методы экземпляра. Когда мы используем Python, мы вызываем их.

Правда и Один - это предложения, которые мы обсуждаем сегодня. Второе, третье и все остальное - это классы, экземпляры, атрибуты и методы, которые мы часто используем. Мы используем привет мир в качестве примера:

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

Выходной эффект:

Hello, world. 

Это стандартный процесс "три пришли из двух, все пришли из трех". От класса до методов, которые мы можем вызвать, используются эти два шага.

Тогда мы не можем помочь от основного вопроса, откуда приходит класс? Вернитесь к первой строке кода.

Класс Hello на самом деле является "семантическим сокращением" функции, просто чтобы облегчить понимание кода. Другой способ написать это:

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" 

Этот тип записи точно такой же, как и предыдущий класс. Вы можете попытаться создать экземпляр и вызвать его.

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

Выходной эффект:

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

Мы оглянулись на самое захватывающее место. Дорога родила сразу двоих:

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

Это "Истина", происхождение мира питонов. Вы можете удивиться этому.

Обратите внимание на три его параметра! Три вечных суждения, которые совпадают с человечеством: кто я, откуда я, куда я иду?

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. 

Стоит отметить, что три главных вечных предложения - это все классы, все экземпляры и даже все свойства и методы экземпляров. Как и должно быть, их "создатели", "Правда" и "Один", а именно тип и метакласс, также имеют эти три параметра. Но обычно три вечных предложения класса не передаются как параметры, а передаются следующим образом

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. Представьте, что нам нужен метакласс, который может автоматически сказать "привет". Методы класса в нем иногда требуют say_Hello, иногда say_Hi, иногда say_Sayolala, а иногда say_Nihao.

Если каждый встроенный say_xxx должен быть объявлен один раз в классе, как ужасно тяжело это будет! Для решения проблемы лучше использовать метаклассы.

Ниже приведен код мета-класса для создания специального приветствия:

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) 

Помните две вещи:

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.

В новом я выполнил только одну операцию.

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

Он создает метод класса с именем класса. Например, класс, который мы создали из метакласса, называется "Hello". Когда он был создан, он автоматически получал метод класса с именем "say_Hello". Затем он будет использовать имя класса "Hello" в качестве параметра по умолчанию и передать его методу. Затем передайте вызов метода hello в качестве значения и, наконец, распечатайте его.

Так как же метакласс переходит от создания к вызову?

Прийти! Вместе с принципами Даошенг, Ишэнъю, Бишенгсан, Саншенгву, войдите в жизненный цикл класса Юань!

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

Выход

Hello, world! 

Примечание: класс, созданный метаклассом, первый параметр - родительский класс, второй параметр - метакласс

Обычные люди не смогут говорить при рождении, но некоторые люди скажут привет, "привет" и "сайолала", когда они родятся. Это сила таланта. Это даст нам объектно-ориентированное программирование, чтобы избавить от множества проблем.

Теперь, не изменяя метакласс, мы можем продолжить создание класса Sayolala, Nihao, следующим образом:

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

Выход

Sayolala, japan! 

Также может говорить по-китайски

# 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 ( '中 中华!' ) 

Выход

Nihao, China! 

Еще один маленький пример:

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

Теперь мы печатаем L

print(L)



>>> [ 1 ] 

Обычный список не имеет метода add()

L2 = list ()

L2 . add ( 1 )



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

классно! Узнали ли вы здесь, испытали ли вы радость Творца?

Everything in the python world is at your fingertips. 

Молодой Создатель, пожалуйста, следуйте за мной, чтобы создать новый мир.

Мы выбираем две области, одна из которых является основной идеей Django, "Object Relational Mapping", объектно-реляционное отображение, именуемое ORM.

Это серьезная проблема с Django, но после изучения метакласса все становится ясно. Ваше понимание Джанго будет еще лучше!

Другая область - это рептилии (хакеры), автоматический поиск доступных агентов в сети, а затем смена IP-адреса, чтобы нарушить анти-сканерские ограничения других людей.

Эти два навыка очень полезны и очень забавны!

Задача 1: Создать ORM по метаклассу

Подготовьтесь к созданию класса Field

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 ) 

Его роль

Когда создается экземпляр класса Field, он получает два параметра: name и column_type. Они будут связаны с частной собственностью Филда. Если вы хотите преобразовать поле в строку, он вернет "Поле:XXX". XXX передается. Имя имя.

Подготовка: Создание StringField и 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' ) 

Его роль

Когда инициализируется экземпляр StringField, IntegerField, автоматически вызывается метод инициализации родителя.

один пришел из правды

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) 

Это делает следующие вещи

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. 

два пришли из одного

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

Если вы создаете подкласс User из themodel:

class User (model ) :

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

     Id = IntegerField ( 'id' )

  name= StringField ( 'username' )

     Email = StringField ( 'email' )

     Password = StringField ( 'password' ) 

В это время

Id= IntegerField('id') автоматически преобразуется в:

Модель. setattr (self, 'id', IntegerField ('id'))

Поскольку IntergerField('id') является экземпляром подкласса Field, новый метакласс запускается автоматически, поэтому IntergerField('id') сохраняется в отображениях, а пара ключ-значение удаляется.

Два студента, три студента, все вещи

Когда вы инициализируете экземпляр и вызываете метод save()

u = User ( id = 12345 ,name= 'Batman' ,   Email = 'batman@nasa.org' ,   Password = 'iamback' )

u . save () 

На данный момент процесс двух студентов завершается первым:

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

Следующим шагом является выполнение трех вещей:

Имитация операций инвентаризации данных с помощью u.save(). Здесь мы просто выполняем небольшую операцию отображения обхода, виртуальный sql и print, на самом деле, вводя оператор sql и базу данных для запуска.

Выход

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 ,   'batman@nasa.org' ] 


    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! 

Задача II: сканирование сетевых агентов

Приготовьтесь залезть на страницу, чтобы играть

Пожалуйста, убедитесь, что установлены оба пакета, запросы и pyquery.

# 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 

Здесь мы используем пакет запроса, чтобы выбраться из исходного кода Baidu.

Попробуй Baidu

Вставьте этот абзац позади get_page.py и попробуйте удалить

If ( __name__ == '__main__' ) :

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

     print( 'result: ' ,   Rs ) 

Попробуй поймать агентов

Вставьте этот абзац позади get_page.py и попробуйте удалить

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 

Пакетная обработка ползучего агента

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 ) 

один пришел из истины: в новом метаклассе он сделал четыре вещи:

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? 

два пришли из одного: класс определяет метод использования pyquery для захвата элементов страницы

Каждый из агентов, показанных на странице, был просмотрен с трех бесплатных сайтов агентов.

Если вы не знакомы с использованием yield, ознакомьтесь с: Учебник Liao Xuefeng по Python: генератор

три пришли из двух: создать экземпляр объекта Crawler

немного

Три вещи: Обход каждого 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! 

Молодой Создатель, инструмент для создания мира, уже в ваших руках. Пожалуйста, используйте его мощность в полной мере!

Вспомните рот владеющего инструментом:

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 

я хочу добавить немного о том, почемуtype.__new__()над

сначала взгляните на эти классы

      In [1]: class MyMeta(type):
   ...:     def __new__(cls, cls_name, bases, attrs):
   ...:         print(cls, cls_name, bases, attrs)
   ...:         return super().__new__(cls, cls_name, bases, attrs)
   ...:

In [2]: class AClass(metaclass=MyMeta):
   ...:     pass
   ...:
<class '__main__.MyMeta'> AClass () {'__module__': '__main__', '__qualname__': 'AClass'}

In [3]: class BClass:
   ...:     pass
   ...:

In [4]: AClass.__class__
Out[4]: __main__.MyMeta

In [5]: BClass.__class__
Out[5]: type

In [6]: class SubAClass(AClass):
   ...:     pass
   ...:
<class '__main__.MyMeta'> SubAClass (<class '__main__.AClass'>,) {'__module__': '__main__', '__qualname__': 'SubAClass'}

когда мы пытались создать SubAClass, подкласс AClass, Python обращался к метаклассу, который мы определили для использования для создания SubAClass.

и в этом случае мы не передали метакласс для SubAClass, поэтому Python получил None для метакласса.

и Python попытается выбрать первый базовый класс SubAClass, который является AClass, имеет метакласс, который является MyMeta, тогда он вызоветMyMeta.__new__для создания SubAClass

для простоты можно сказать

  1. только что присвоил наш метакласс родителю. (AClass.__class__).

    и когда подкласс был определен (SubAClass) оператором класса, Python

    просто использовал быparent.__class__создать этот подкласс, если только

    мы вручную передали метакласс нашему подклассу

  2. видимо, если бы ты позвонилtype()вместоtype.__new__, класс

    будет иметь тип в качестве атрибута.

    это означает, что AClass будет эквивалентен BClass, оба они имеют тип

    как их__class__атрибут

как работает поиск метакласса в коде C?

это работает очень похоже на то, что мы только что упомянули

функцияbuiltin___build_class__будет вызываться, когда вы определяете класс

и код такой простой

      static PyObject *
builtin___build_class__(PyObject *self, PyObject *const *args, Py_ssize_t nargs,
                        PyObject *kwnames){

    if (meta == NULL) {
        /* if there are no bases, use type: */
        if (PyTuple_GET_SIZE(bases) == 0) {
            meta = (PyObject *) (&PyType_Type);
        }
        /* else get the type of the first base */
        else {
            PyObject *base0 = PyTuple_GET_ITEM(bases, 0);
            meta = (PyObject *)Py_TYPE(base0);
        }
        Py_INCREF(meta);
        isclass = 1;  /* meta is really a class */
    }

    PyObject *margs[3] = {name, bases, ns};
    cls = PyObject_VectorcallDict(meta, margs, 3, mkw);
}

по сути,meta = (PyObject *)Py_TYPE(base0);это все, что мы хотим знать

это можно перевести какmeta = MyMeta = AClass.__class__ = Py_TYPE(AClass)

посмотри это:

      Python 3.10.0rc2 (tags/v3.10.0rc2:839d789, Sep  7 2021, 18:51:45) [MSC v.1929 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> class Object:
...     pass
... 
>>> class Meta(type):
...     test = 'Worked!!!'
...     def __repr__(self):
...             return 'This is "Meta" metaclass'
... 
>>> class ObjectWithMetaClass(metaclass=Meta):
...     pass
... 
>>> Object or type(Object())
<class '__main__.Object'>
>>> ObjectWithMetaClass or type(ObjectWithMetaClass())
This is "Meta" metaclass
>>> Object.test
AttributeError: ...
>>> ObjectWithMetaClass.test
'Worked!!!'
>>> type(Object)
<class 'type'>
>>> type(ObjectWithMetaClass)
<class '__main__.Meta'>
>>> type(type(ObjectWithMetaClass))
<class 'type'>
>>> Object.__bases__
(<class 'object'>,)
>>> ObjectWithMetaClass.__bases__
(<class 'object'>,)
>>> type(ObjectWithMetaClass).__bases__
(<class 'type'>,)
>>> Object.__mro__
(<class '__main__.Object'>, <class 'object'>)
>>> ObjectWithMetaClass.__mro__
(This is "Meta" metaclass, <class 'object'>)
>>> 

Другими словами, когда объект не был создан (тип объекта), мы ищем MetaClass.

Определение:
метакласс - это класс, экземпляры которого являются классами. Подобно "обычному" классу, определяющему поведение экземпляров класса, метакласс определяет поведение классов и их экземпляров.

Метаклассы поддерживаются не всеми объектно-ориентированными языками программирования. Те языки программирования, которые поддерживают метаклассы, значительно различаются способами их реализации. Python поддерживает их.

Некоторые программисты рассматривают метаклассы в Python как "решения, ожидающие или ищущие проблему".

Существует множество вариантов использования метаклассов.

logging and profiling
interface checking
registering classes at creation time
automatically adding new methods
automatic property creation
proxies
automatic resource locking/synchronization.

Определение класса Meta:
он распечатает содержимое своих аргументов в новом методе и вернет результаты типа.новый звонок:

class LittleMeta(type):
    def __new__(cls, clsname, superclasses, attributedict):
        print("clsname: ", clsname)
        print("superclasses: ", superclasses)
        print("attributedict: ", attributedict)
        return type.__new__(cls, clsname, superclasses, attributedict)

Мы будем использовать метакласс LittleMeta в следующем примере:

class S:
    pass    
class A(S, metaclass=LittleMeta):
    pass    
a = A()

Выход:

clsname:  A
superclasses:  (<class '__main__.S'>,)
attributedict:  {'__module__': '__main__', '__qualname__': 'A'}

А metaclassв Python - это класс класса, который определяет поведение класса. Класс сам по себе является экземпляромmetaclass. Класс в Python определяет, как будет вести себя экземпляр класса. Мы можем настроить процесс создания класса, передавmetaclassключевое слово в определении класса. Это также можно сделать, унаследовав класс, который уже был передан в этом ключевом слове.

class MyMeta(type):
    pass

class MyClass(metaclass=MyMeta):
    pass

class MySubclass(MyClass):
    pass

Мы видим, что тип MyMeta класс type и что тип MyClass а также MySubClass является MyMeta.

print(type(MyMeta))
print(type(MyClass))
print(type(MySubclass))

<class 'type'>
<class '__main__.MyMeta'>
<class '__main__.MyMeta'>

При определении класса и нет metaclass определяется тип по умолчанию metaclassбудет использовано. Еслиmetaclass дан, и это не пример type(), то он используется непосредственно как metaclass.

Метаклассы могут применяться, среди прочего, для ведения журнала, регистрации классов во время создания и профилирования. Они кажутся довольно абстрактными концепциями, и вам может быть интересно, нужно ли их вообще использовать.

В Python или любом другом языке у нас есть тип для каждой переменной или объекта, который мы объявляем. Для получения типа чего-либо (переменной, объекта и т. д.) в Python мы можем использовать функцию type().

Обойдя ключевое слово метакласса в определении класса, мы можем настроить процесс создания класса.

      class meta(type):
    pass
class baseclass(metaclass=meta): # This is Mestaclass
    pass
class derivedclass(baseclass):
    pass
print(type(meta))
print(type(baseclass))
print(type(derivedclass))

При определении нового класса, если метакласс не определен, используется метакласс типа по умолчанию. Если данный метакласс не является объектом (экземпляром) type(), в этой ситуации он используется непосредственно как метакласс.

Метакласс - это своего рода класс, который определяет, как класс будет вести себя, или мы можем сказать, что класс сам по себе является экземпляром метакласса.

Другие вопросы по тегам