Русский
Русский
English
Статистика
Реклама

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

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

Абстрактный класс type


Класс type часто используется для получения типа объекта. Например так:
h = "hello"type(h)<class 'str'>

Но у него есть другое применение. Он может инициализировать новые типы. Как известно, всё в Python объект. Из этого следует, что у всех определений имеются типы, включая классы и объекты. Например:
class A:    passprint(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 barMyClass = 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. Скорее всего вам будет сложно найти применение описанных здесь методов, хотя в моей практике такой случай, все же, был.

А вы работали с динамическими объектами? Может быть в других языках?
Источник: habr.com
К списку статей
Опубликовано: 23.07.2020 00:16:58
0

Сейчас читают

Комментариев (0)
Имя
Электронная почта

Python

Ооп

Compile

Class

Object

Dynamic

Types

Functiontype

Methodtype

Категории

Последние комментарии

© 2006-2020, personeltest.ru