Явное наследование от 'type' для реализации метакласса в python3.x

Я пытался получить некоторую интуицию о метаклассе в Python. Я пробовал и на Python2.7, и на Python3.5. В Python3.5 я обнаружил, что каждый класс, который мы определяем, имеет тип <class 'type'> наследуем ли мы явно или нет. Но если он не унаследован от типа, мы не можем использовать этот класс в качестве метакласса для другого класса.

>>> class foo:
    pass

>>> class Metafoo(type):
    pass

>>> foo
<class '__main__.foo'>
>>> Metafoo
<class '__main__.Metafoo'>
>>> type(foo)
<class 'type'>
>>> type(Metafoo)
<class 'type'>
>>> 
>>> class foocls1(metaclass=foo):
    pass

Делая выше, я получаю следующую ошибку:

Traceback (most recent call last):
  File "<pyshell#52>", line 1, in <module>
    class foocls1(metaclass=foo):
TypeError: object() takes no parameters

Но это не так при использовании Metafoo в качестве метакласса для нового класса

>>> class foocls3(metaclass=Metafoo):
    pass

>>> foocls3
<class '__main__.foocls3'>
>>> type(foocls3)
<class '__main__.Metafoo'>

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

1 ответ

Решение

"тип" - это базовый класс для всех объектов класса в Python 3 и в Python 2 после версии 2.2. (Просто в Python 2 вы должны наследовать от объекта. Классы, которые не наследуются явно от "объекта" в Python 2, назывались "классами старого стиля" и хранились в целях обратной совместимости, но были мало полезны.)

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

Метакласс - это скорее "класс, из которого построен ваш класс", и, хотя он может служить другим целям, он чаще всего используется для изменения шага построения ваших классов. Поиск вокруг, и вы увидите метаклассы, в основном реализующие __new__ а также __init__ методы. (Хотя это нормально, когда есть другие методы, но вы должны знать, что вы делаете)

Бывает, что для создания класса необходимы некоторые действия, которые не требуются для создания нормальных объектов. Эти действия выполняются на уровне нативного кода (C в CPython) и даже не воспроизводятся в чистом коде Python, как заполнение слотов специальных методов класса - указателей на функции, которые реализуют __add__, __eq__ и такие методы. Единственный класс, который делает это в CPython - это "тип". Поэтому любой код, который вы хотите использовать для создания класса, то есть как метакласс, должен будет в какой-то момент вызвать type.__new__ метод. (Подобно тому, как любая вещь, которую вы хотите создать в Python, будет вызывать код в object.__new__.).

Ваша ошибка произошла не потому, что Python проверяет, сделал ли вы прямой или косвенный вызов type.__new__ досрочно. Ошибка: "TypeError: object() не принимает параметров" просто из-за того, что __new__ Метод метакласса передается 3 параметра (имя, базы, пространство имен), а тот же метод в object Передано без параметров. (И те, и другие получают дополнительные "cls", также эквивалентные self, но это не считается).

Вы можете использовать любой вызываемый в качестве метакласса, даже обычную функцию. Просто для этого нужно будет принять 3 явных параметра. Независимо от этого вызываемого возврата используется как класс с этого момента, но если в какой-то момент вы не вызываете type.__new__ (даже косвенно), у вас нет действительного класса для возврата.

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

def dictclass(name, bases, namespace):
    return namespace

class mydict(metaclass=dictclass):
    a = 1
    b = 2
    c = 3

mydict["a"] 

Итак, один интересный факт со всем, что это type это свой собственный метакласс. (это жестко закодировано в реализации Python). Но type Сам также наследует от объекта:

In [21]: type.__class__
Out[21]: type

In [22]: type.__mro__
Out[22]: (type, object)

И чтобы закончить это: можно создавать классы без вызова type.__new__ и объекты без вызова object.__new__, но не всегда из чистого кода Python, поскольку структуры данных на уровне C-API должны заполняться для обоих действий. Можно было бы сделать реализацию функций в собственном коде, чтобы сделать это, или взломать это с помощью ctypes.

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