Введение в язык Питон

773123a3

Метаклассы: решение, требующее проблемы?


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



Методы (классов), как и обычные функции, могут возвращать объекты. В этом смысле очевидно, что фабрики классов столь же могут быть классами, как и функциями. В частности, Python 2.2+ предоставляет специальный класс, называемый type, который именно и есть такая фабрика классов. Разумеется, читатели узнают в type() менее претенциозную встроенную функцию более ранних версий Python - к счастью, поведение старой функции type() поддерживается классом type (другими словами, type(obj) возвращает тип/класс объекта obj). Новый класс работает в качестве фабрики классов точно так же, как прежде делала функция new.classobj:



Листинг 3. type в качестве метакласса фабрики классов

>>> X = type('X',(),{'foo':lambda self:'foo'}) >>> X, X().foo() (<class '__main__.X'>, 'foo')

Но поскольку теперь type - это (мета)класс, вы можете создать от него производный класс:



Листинг 4. Потомок type как фабрика классов

>>> class ChattyType(type): ... def __new__(cls, name, bases, dct): ... print "Allocating memory for class", name ... return type.__new__(cls, name, bases, dct) ... def __init__(cls, name, bases, dct): ... print "Init'ing (configuring) class", name ... super(ChattyType, cls).__init__(name, bases, dct) ... >>> X = ChattyType('X',(),{'foo':lambda self:'foo'}) Allocating memory for class X Init'ing (configuring) class X >>> X, X().foo() (<class '__main__.X'>, 'foo')

Магические методы .__new__() и .__init__() являются специальными, но концептуально они такие же, как и у любого другого класса. Метод .__init__() позволяет конфигурировать созданный объект; метод .__new__() разрешает настраивать его создание. Последний, разумеется, не используется широко, но существует для каждого класса нового стиля Python 2.2 (обычно наследуется, а не подменяется).


У потомков type есть одно свойство, которое необходимо учитывать; на нем ловятся все, кто впервые использует метаклассы. Первый аргумент в методах обычно называется cls, а не self, поскольку эти методы обрабатывают созданный класс, а не метакласс. На самом деле, в этом нет ничего особенного; все методы связываются со своимиэкземплярами, а экземпляр метакласса является классом. Неспециальное имя делает это более очевидным:

Листинг 5. Прикрепление методов класса к созданным классам

>>> class Printable(type): ... def whoami(cls): print "I am a", cls.__name__ ... >>> Foo = Printable('Foo',(),{}) >>> Foo.whoami() I am a Foo >>> Printable.whoami() Traceback (most recent call last): TypeError: unbound method whoami() [...]

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

Листинг 6. Задание метакласса с атрибутом класса

>>> class Bar: ... __metaclass__ = Printable ... def foomethod(self): print 'foo' ... >>> Bar.whoami() I am a Bar >>> Bar().foomethod() foo


Содержание раздела