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

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

16 ответов


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

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

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

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

однако метаклассы фактически определяют тип класса, а не только фабрику, так что вы можете сделать гораздо больше с ними. Например, можно определить обычные методы в метаклассе. Эти методы metaclass похожи на classmethods, поскольку они могут вызываться в классе без экземпляр, но они также не похожи на classmethods в том, что они не могут быть вызваны на экземпляре класса. 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 - это metaclass Python использует для создания всех классов за кулисами.

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

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

вы видите это, проверяя .

все, и я имею в виду все, является объектом в 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__ в определении класса. Если он найдет его, он будет использовать его для создания класса object Foo. Если это не так, он будет использовать type создать класс.

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

если вы:

class Foo(Bar):
    pass

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

есть на 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):
    [...]

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

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

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

основной целью метакласса является автоматическое изменение класса, когда он будет создан.

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

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

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

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

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

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

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

причина сложности кода с использованием метаклассов заключается не в том, что из метаклассов, это потому, что вы обычно используете метаклассы, чтобы делать скрученные вещи опираясь на интроспекцию, манипулируя наследованием, vars, такие как __dict__, etc.

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

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

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

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

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

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

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

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

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

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

Python Гуру Тим Питерс

основным вариантом использования метакласса является создание 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)

он не вернет


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

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

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

метаклассы принимают 3 args. 'имя','баз' и 'дикт'

вот где начинается секрет. Ищите где название, базы и dict исходят из этого определения класса примера.

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'

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

def init_attributes(name, bases, dict):
    if 'attributes' in dict:
        for attr in dict['attributes']:
            dict[attr] = None

    return type(name, bases, dict)

class Initialised(object):
    __metaclass__ = init_attributes
    attributes = ['foo', 'bar', 'baz']

print 'foo =>', Initialised.foo
# output=>
foo => None

отметить, что магические поведение прибыли, что Initalised, имея метакласс 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

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

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

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

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


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

class MyMeta(type):

    counter = 0

    def __init__(cls, name, bases, dic):
        type.__init__(cls, name, bases, dic)
        cls._order = MyMeta.counter
        MyMeta.counter += 1

class MyType(object):              # Python 2
    __metaclass__ = MyMeta

class MyType(metaclass=MyMeta):    # Python 3
    pass

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


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

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)

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

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

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

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

для чего их можно использовать? От docs:

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

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

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

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

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__ для экземпляров класса нашего типа class предоставляет нам демонстрацию, которая может улучшить значение по умолчанию __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__ эхо в командной строке обеспечивает гораздо менее уродливое зрелище, чем по умолчанию:

>>> 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 is используемый.

__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__() там metaclass'__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 is 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

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

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

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

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

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


версия tl; dr

на type(obj) функция возвращает тип объекта.

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

для использования метакласса:

class Foo(object):
    __metaclass__ = MyMetaClass

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

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

class foo:
    ...

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

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

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

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


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

источник: segmentfault.com/a/1190000011447445

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

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

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

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

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

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

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

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

предложение 2: Кто я? Откуда я взялся? Куда мне идти?

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

правда типа

один-метакласс (metaclass, или генератор классов)

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

три экземпляра (пример)

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

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

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

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



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

Hello = Hello()


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

Hello.say_hello() 

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

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" 

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

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

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

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

приходите! Вместе с принципами Daosheng, Yishengyou, Bishengsan, Sanshengwu, войдите жизненный цикл типа Yuan!

# Tao Shengyi: incoming type

class SayMetaClass(type):



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

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

         # Create "talent"

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

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

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



# Lifetime 2: Create class

class Hello ( object ,metaclass = SayMetaClass):
     pass



# two students three: create a real column

Hello = Hello ()



# Three things: call the instance method

hello.say_Hello('world!') 

выход

Hello, world! 

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

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

теперь, сохраняя метакласс неизменным, мы можем продолжать создавать класс 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, "объектно-реляционное отображение", объектно-реляционное отображение, называемое ORM.

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

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

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

Задача 1: Создайте ORM с помощью Metaclass

подготовка к созданию поля класс!--42-->

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. Они будут связаны с частной собственностью Филда. Если вы хотите преобразовать поле в строку, оно вернет "Field:XXX". XXX передается. Имя name.

подготовка: создание 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 )) 

если вы создаете пользователя подкласса из 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') является экземпляром подкласса поля, метакласса новая автоматически запускается, поэтому 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 и печать, на самом деле, введя инструкцию 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 

обход пакетной обработки агент!--42-->

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 учебник: генератор

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

немного

три вещи: прохождение каждого 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 () может возвращать тип объекта или создавать новый тип,

например, мы можем создать класс 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() для динамического создания классов вы можете управлять поведением создания класса и использовать metaclass.

согласно объектной модели Python, класс является объектом, поэтому класс должен быть экземпляром другого некоторый класс. По умолчанию класс Python является экземпляром класса type. То есть 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']

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


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

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

print(MyClass.__metaclass__)

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

class 'type'

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

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