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

6532
e-satis 19 Сен 2008 в 10:10

16 ответов

Лучший ответ

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

В то время как в 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__
2757
Cameron Savage 4 Мар 2019 в 21:34

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

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

1
Carson Arucard 20 Дек 2019 в 11:03

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

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

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

Метаклассы занимают 3 аргумента. name , base и dict .

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

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

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

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

Обратите внимание, что магическое поведение, которое Initialised приобретает, имея метакласс init_attributes, не передается в подкласс Initialised.

Вот еще более конкретный пример, показывающий, как вы можете создать подкласс '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
386
ralh 6 Ноя 2019 в 07:57

Роль метода метакласса __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__() вызывается, когда экземпляр класса используется как вызываемый. Но, как мы видели из предыдущих ответов, сам класс является экземпляром метакласса, поэтому, когда мы используем класс в качестве вызываемого (то есть когда мы создаем его экземпляр), мы фактически вызываем его метакласс '{{X1} } метод. В этот момент большинство программистов на 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
65
Michael Ekoka 27 Авг 2018 в 17:21

Я думаю, что введение 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%.

113
Yet Another User 13 Авг 2018 в 04:53

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

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

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

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

154
Antti Rasinen 19 Сен 2008 в 06:45

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

20
binbjz 12 Янв 2018 в 09:30

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

3
Venu Gopal Tewari 9 Июл 2019 в 05:45

В дополнение к опубликованным ответам я могу сказать, что 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'
10
Andy 15 Сен 2018 в 13:17

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

159
kindall 28 Ноя 2016 в 18:04

Версия tl; dr

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

type() класса - это его метакласс .

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

class Foo(object):
    __metaclass__ = MyMetaClass

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

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

39
noɥʇʎԀʎzɐɹƆ 5 Дек 2019 в 16:27

type на самом деле metaclass - класс, который создает другие классы. Большинство metaclass являются подклассами type. 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.

39
Chankey Pathak 29 Авг 2017 в 05:23

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

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

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

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

Метакласс может быть специально использован в следующих ситуациях:

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

Обновление 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
74
Ethan Furman 1 Мар 2016 в 19:48

Классы 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

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

25
Xingzhou Liu 13 Июл 2017 в 08:18

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

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

#!/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()
53
3 revs, 2 users 99% 25 Янв 2016 в 20:08