22.07.2020       Выпуск 344 (20.07.2020 - 26.07.2020)       Статьи

Динамическое определение класса в Python

Под динамическим определением объекта можно понимать определение во время исполнения. В отличие от статического определения, которое используется в привычном определении класса с помощью ключевого слова class, динамическое определение использует встроенный класс type.

Читать>>




Экспериментальная функция:

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

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

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

class

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

type

.

Метакласс type

Класс type часто используется для получения типа объекта. Например так:

h = "hello"
type(h)
<class 'str'>

Но у него есть другое применение. Он может инициализировать новые типы.

Как известно, всё в Python – объект. Из этого следует, что у всех определений имеются типы, включая классы и объекты. Например:

class A:
    pass
print(type(A))
<class 'type'>

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

type

, в отличие от его экземпляров:

a = A()
print(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:
    print(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()
<__main__.MyClass object at 0x7f8b1d69add8>

С новым классом можно работать как обычно:

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

А вы работали с динамическими объектами? Может быть в других языках?






Разместим вашу рекламу

Пиши: mail@pythondigest.ru

Нашли опечатку?

Выделите фрагмент и отправьте нажатием Ctrl+Enter.

Система Orphus