- 14. Dynamically Creating Classes with type
- Behind the scenes: Relationship between Class and type
- OUTPUT:
- OUTPUT:
- OUTPUT:
- Динамическое определение класса в Python
- Метакласс type
- Инициализация новых типов с помощью класса type
- Параметры инициализации класса type
- Динамическое определение класса
- Динамическое определение атрибутов класса
- Динамическое определение методов
- Параметры функции compile
- Параметры метода инициализации класса FunctionType
- Предупреждение
- Ссылки
- How to create a Class dynamically with type() in Python?
- Examples
- 1. Create a class dynamically with given classname and attributes
- 2. Create a class dynamically with a constructor
- 3. Create a class dynamically with a constructor
- Summary
14. Dynamically Creating Classes with type
Behind the scenes: Relationship between Class and type
In this chapter of our tutorial, we will provide you with a deeper insight into the magic happening behind the scenes, when we are defining a class or creating an instance of a class. You may ask yourself: «Do I really have to learn theses additional details on object oriented programming in Python?» Most probably not, or you belong to the few people who design classes at a very advanced level.
First, we will concentrate on the relationship between type and class. While defining classes so far, you may have asked yourself, what is happening «behind the lines». We have already seen, that applying «type» to an object returns the class of which the object is an instance of:
x = [4, 5, 9] y = "Hello" print(type(x), type(y))
OUTPUT:
If you apply type on the name of a class itself, you get the class «type» returned.
OUTPUT:
This is similar to applying type on type(x) and type(y):
x = [4, 5, 9] y = "Hello" print(type(x), type(y)) print(type(type(x)), type(type(y)))
OUTPUT:
A user-defined class (or the class «object») is an instance of the class «type». So, we can see, that classes are created from type. In Python3 there is no difference between «classes» and «types». They are in most cases used as synonyms.
The fact that classes are instances of a class «type» allows us to program metaclasses. We can create classes, which inherit from the class «type». So, a metaclass is a subclass of the class «type».
Instead of only one argument, type can be called with three parameters:
type(classname, superclasses, attributes_dict)
If type is called with three arguments, it will return a new type object. This provides us with a dynamic form of the class statement.
- «classname» is a string defining the class name and becomes the name attribute;
- «superclasses» is a list or tuple with the superclasses of our class. This list or tuple will become the bases attribute;
- the attributes_dict is a dictionary, functioning as the namespace of our class. It contains the definitions for the class body and it becomes the dict attribute.
Let’s have a look at a simple class definition:
class A: pass x = A() print(type(x))
Динамическое определение класса в Python
Под динамическим определением объекта можно понимать определение во время исполнения. В отличие от статического определения, которое используется в привычном определении класса с помощью ключевого слова class , динамическое определение использует встроенный класс type .
Метакласс type
Класс type часто используется для получения типа объекта. Например так:
Но у него есть другое применение. Он может инициализировать новые типы. Как известно, всё в Python – объект. Из этого следует, что у всех определений имеются типы, включая классы и объекты. Например:
Может быть не совсем понятно, почему классу присваивается тип класса type , в отличие от его экземпляров:
Объекту a в качестве типа присваивается класс. Так интерпретатор обрабатывает объект как экземпляр класса. Сам же класс имеет тип класса type потому, что он наследует его от базового класса object :
Класс object наследуют все классы по умолчанию, то есть:
Определяемый класс наследует базовый в качестве типа. Однако, это не объясняет, почему базовый класс object имеет тип класса type . Дело в том, что type – это метакласс. Как это уже известно, все классы наследуют базовый класс object , который имеет тип метакласса type . Поэтому, все классы так же имеют этот тип, включая сам метакласс type :
Это «конечная точка типизации» в Python. Цепочка наследования типов замыкается на классе type . Метакласс type служит базой для всех классов в Python. В этом несложно убедиться:
builtins = [list, dict, tuple] for obj in builtins: type(obj)
Класс – это абстрактный тип данных, а его экземпляры имеют ссылку на класс в качестве типа.
Инициализация новых типов с помощью класса type
При проверке типов класс type инициализируется с единственным аргументом:
При этом он возвращает тип объекта. Однако в классе реализован другой способ инициализации с тремя аргументами, который возвращает новый тип:
type(name, bases, dict) -> new type
Параметры инициализации класса type
- name
Строка, которая определяет имя нового класса (типа). - bases
Кортеж базовых классов (классов, которые унаследует новый класс). - dict
Словарь с атрибутами будущего класса. Обычно со строками в ключах и вызываемых типах в значениях.
Динамическое определение класса
Инициализируем класс нового типа, предоставив все необходимые аргументы и вызываем его:
MyClass = type("MyClass", (object, ), dict()) MyClass
С новым классом можно работать как обычно:
Причём, способ эквивалентен обычному определению класса:
Динамическое определение атрибутов класса
В пустом классе мало смысла, поэтому возникает вопрос: как добавить атрибуты и методы?
Чтобы ответить на этот вопрос, рассмотрим изначальный код инициализации:
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”)', "", "exec")
compile – это встроенная функция, которая компилирует исходный код в объект. Код можно выполнить функциями exec() или eval() .
Параметры функции compile
- source
Исходный код, может быть ссылкой на модуль. - filename
Имя файла, в который скомпилируется объект. - mode
Если указать «exec» , то функция скомпилирует исходный код в модуль.
Объект 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
Необязательный параметр, определяющий название функции.
Далее необходимо добавить эту функцию в качестве метода класса MyClass :
MyClass.foo = MethodType(function, MyClass)
Достаточно простое выражение, которое назначает нашу функцию методом класса MyClass .
Предупреждение
В 99% случаев можно обойтись статическим определением классов. Однако концепция метапрограммирования хорошо раскрывает внутреннее устройство Python. Скорее всего вам будет сложно найти применение описанных здесь методов, хотя в моей практике такой случай, все же, был.
А вы работали с динамическими объектами? Может быть в других языках?
Ссылки
How to create a Class dynamically with type() in Python?
In Python, to create a class dynamically, you can use type() built-in function. This can be used to create user defined classes on the fly, without changing the code when a new class type is required.
Consider the following simple example, where we create a class named MyClass of type object, with attributes: x.
MyClass = type('MyClass', (object,), )
In this tutorial, we will learn how to create a class dynamically, with examples.
Examples
1. Create a class dynamically with given classname and attributes
In this example, we define a function that creates and returns a class. The function has two parameters. First argument is the classname, and the second argument is a dictionary representing the attributes of the class.
Python Program
def createClass(classname, attributes): return type(classname, (object,), attributes) Car = createClass('Car', ) mycar = Car() print(f"Car name: \nCar age: years")
Car name: Audi Car age: 5 years
2. Create a class dynamically with a constructor
In this example, we define a function that creates and returns a class with a constructor.
Python Program
def createClass(classname, attributes): return type(classname, (object,), < '__init__': lambda self, arg1, arg2: setattr(self, 'args', (arg1, arg2)), 'args': attributes >) Car = createClass('Car', ) mycar = Car('Audi R8', 3) print(f"Car name: \nCar age: years")
Car name: Audi R8 Car age: 3 years
3. Create a class dynamically with a constructor
In this example, we consider the function defined in the previous example, and create class types: Car and Student dynamically.
Python Program
def createClass(classname, attributes): return type(classname, (object,), < '__init__': lambda self, arg1, arg2: setattr(self, 'args', (arg1, arg2)), 'args': attributes >) Car = createClass('Car', ) Student = createClass('Student', ) mycar = Car('Audi R8', 3) print(f"Car name: \nCar age: years") student1 = Student('Ram', 12) print(f"Student name: \nStudent age: years")
Car name: Audi R8 Car age: 3 years Student name: Ram Student age: 12 years
Summary
In this tutorial of Python Examples, we learned how to use type() function to create a class type dynamically.