Под динамическим определением объекта можно понимать определение во время исполнения. В отличие от статического определения, которое используется в привычном определении класса с помощью ключевого слова
Класс type часто используется для получения типа объекта. Например так:
Но у него есть другое применение. Он может инициализировать новые типы. Как известно, всё в Python – объект. Из этого следует, что у всех определений имеются типы, включая классы и объекты. Например:
Может быть не совсем понятно, почему классу присваивается тип класса
Объекту
Тип класса
Класс
Тоже самое, что:
Определяемый класс наследует базовый в качестве типа. Однако, это не объясняет, почему базовый класс
Это «конечная точка типизации» в Python. Цепочка наследования типов замыкается на классе
Класс – это абстрактный тип данных, а его экземпляры имеют ссылку на класс в качестве типа.
При проверке типов класс
При этом он возвращает тип объекта. Однако в классе реализован другой способ инициализации с тремя аргументами, который возвращает новый тип:
Инициализируем класс нового типа, предоставив все необходимые аргументы и вызываем его:
С новым классом можно работать как обычно:
Причём, способ эквивалентен обычному определению класса:
В пустом классе мало смысла, поэтому возникает вопрос: как добавить атрибуты и методы?
Чтобы ответить на этот вопрос, рассмотрим изначальный код инициализации:
Обычно, атрибуты добавляются в класс на стадии инициализации в качестве третьего аргумента – словаря. В словаре можно указать имена атрибутов и значения. Например, это может быть переменная:
В словарь можно передать и вызываемые объекты, например методы:
У этого способа есть один существенный недостаток – необходимость определять метод статически (думаю, что в контексте задач метапрограммирования, это можно рассматривать как недостаток). Кроме этого, определение метода с параметром
После инициализации пустого класса, можно добавить в него методы динамически, то есть, без явного статического определения:
Результатом работы
Объект
Я импортирую
В результате получилась функция:
Далее необходимо добавить эту функцию в качестве метода класса
Достаточно простое выражение, которое назначает нашу функцию методом класса
В 99% случаев можно обойтись статическим определением классов. Однако концепция метапрограммирования хорошо раскрывает внутреннее устройство Python. Скорее всего вам будет сложно найти применение описанных здесь методов, хотя в моей практике такой случай, все же, был.
А вы работали с динамическими объектами? Может быть в других языках?
class
, динамическое определение использует встроенный класс type
.Метакласс type
Класс type часто используется для получения типа объекта. Например так:
h = "hello"
type(h)
<class 'str'>
Но у него есть другое применение. Он может инициализировать новые типы. Как известно, всё в Python – объект. Из этого следует, что у всех определений имеются типы, включая классы и объекты. Например:
class A:
pass
type(A)
<class 'type'>
Может быть не совсем понятно, почему классу присваивается тип класса
type
, в отличие от его экземпляров:a = A()
type(a)
<class '__main__.A'>
Объекту
a
в качестве типа присваивается класс. Так интерпретатор обрабатывает объект как экземпляр класса. Сам же класс имеет тип класса type
потому, что он наследует его от базового класса object
:A.__bases__
(<class 'object'>,)
Тип класса
object
:type(object)
<class 'type'>
Класс
object
наследуют все классы по умолчанию, то есть:class A(object):
pass
Тоже самое, что:
class A:
pass
Определяемый класс наследует базовый в качестве типа. Однако, это не объясняет, почему базовый класс
object
имеет тип класса type
. Дело в том, что type
– это метакласс. Как это уже известно, все классы наследуют базовый класс object
, который имеет тип метакласса type
. Поэтому, все классы так же имеют этот тип, включая сам метакласс type
:type(type)
<class 'type'>
Это «конечная точка типизации» в Python. Цепочка наследования типов замыкается на классе
type
. Метакласс type
служит базой для всех классов в Python. В этом несложно убедиться:builtins = [list, dict, tuple]
for obj in builtins:
type(obj)
<class 'type'>
<class 'type'>
<class 'type'>
Класс – это абстрактный тип данных, а его экземпляры имеют ссылку на класс в качестве типа.
Инициализация новых типов с помощью класса type
При проверке типов класс
type
инициализируется с единственным аргументом:type(object) -> type
При этом он возвращает тип объекта. Однако в классе реализован другой способ инициализации с тремя аргументами, который возвращает новый тип:
type(name, bases, dict) -> new type
Параметры инициализации класса type
name
Строка, которая определяет имя нового класса (типа).bases
Кортеж базовых классов (классов, которые унаследует новый класс).dict
Словарь с атрибутами будущего класса. Обычно со строками в ключах и вызываемых типах в значениях.
Динамическое определение класса
Инициализируем класс нового типа, предоставив все необходимые аргументы и вызываем его:
MyClass = type("MyClass", (object, ), dict())
MyClass
<class '__main__.MyClass'>
С новым классом можно работать как обычно:
m = MyClass()
m
<__main__.MyClass object at 0x7f8b1d69acc0>
Причём, способ эквивалентен обычному определению класса:
class MyClass:
pass
Динамическое определение атрибутов класса
В пустом классе мало смысла, поэтому возникает вопрос: как добавить атрибуты и методы?
Чтобы ответить на этот вопрос, рассмотрим изначальный код инициализации:
MyClass = type(“MyClass”, (object, ), dict())
Обычно, атрибуты добавляются в класс на стадии инициализации в качестве третьего аргумента – словаря. В словаре можно указать имена атрибутов и значения. Например, это может быть переменная:
MyClass = type(“MyClass”, (object, ), dict(foo=“bar”)
m = MyClass()
m.foo
'bar'
Динамическое определение методов
В словарь можно передать и вызываемые объекты, например методы:
def foo(self):
return “bar”
MyClass = type(“MyClass”, (object, ), dict(foo=foo))
m = MyClass()
m.foo
'bar'
У этого способа есть один существенный недостаток – необходимость определять метод статически (думаю, что в контексте задач метапрограммирования, это можно рассматривать как недостаток). Кроме этого, определение метода с параметром
self
вне тела класса выглядит странно. Поэтому вернёмся к динамической инициализации класса без атрибутов:MyClass = type(“MyClass”, (object, ), dict())
После инициализации пустого класса, можно добавить в него методы динамически, то есть, без явного статического определения:
code = compile('def foo(self): print(“bar”)', "<string>", "exec")
compile
– это встроенная функция, которая компилирует исходный код в объект. Код можно выполнить функциями exec()
или eval()
.Параметры функции compile
- source
Исходный код, может быть ссылкой на модуль. - filename
Имя файла, в который скомпилируется объект. - mode
Если указать"exec"
, то функция скомпилирует исходный код в модуль.
Результатом работы
compile
является объект класса code
:type(code)
<class 'code'>
Объект
code
нужно преобразовать в метод. Так как метод – это функция, то начнём с преобразования объекта класса code
в объект класса function
. Для этого импортируем модуль types
:from types import FunctionType, MethodType
Я импортирую
MethodType
, так как он понадобится в дальнейшем для преобразования функции в метод класса.function = FunctionType(code.co_consts[0], globals(), “foo”)
Параметры метода инициализации класса FunctionType
code
Объект классаcode
.code.co_consts[0]
– это обращение к дискрипторуco_consts
классаcode
, который представляет из себя кортеж с константами в коде объекта. Представьте себе объектcode
как модуль с одной единственной функцией, которую мы пытаемся добавить в качестве метода класса.0
– это её индекс, так как она единственная константа в модуле.globals()
Словарь глобальных переменных.name
Необязательный параметр, определяющий название функции.
В результате получилась функция:
function
<function foo at 0x7fc79cb5ed90>
type(function)
<class 'function'>
Далее необходимо добавить эту функцию в качестве метода класса
MyClass
:MyClass.foo = MethodType(function, MyClass)
Достаточно простое выражение, которое назначает нашу функцию методом класса
MyClass
.m = MyClass()
m.foo()
bar
Предупреждение
В 99% случаев можно обойтись статическим определением классов. Однако концепция метапрограммирования хорошо раскрывает внутреннее устройство Python. Скорее всего вам будет сложно найти применение описанных здесь методов, хотя в моей практике такой случай, все же, был.
А вы работали с динамическими объектами? Может быть в других языках?