Dynamic class definition in Python

Dynamic object definition can be understood as definition at runtime. Unlike a static definition, which is used in the familiar keyword definition of a class class, a dynamic definition uses an inline class type.



Type metaclass



The type class is often used to get the type of an object. For example like this:



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


But it has other uses. It can initialize new types.As you know, everything in Python is an object. It follows that all definitions have types, including classes and objects. For instance:



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


It may not be entirely clear why a class is assigned a class type type, as opposed to its instances:



a = A()
type(a)
<class '__main__.A'>


The object ais assigned a class as a type. This is how the interpreter treats the object as an instance of the class. The class itself has a class type typebecause it inherits it from the base class object:



A.__bases__
(<class 'object'>,)


Class type object:



type(object)
<class 'type'>


The class is objectinherited by all classes by default, that is:



class A(object):
    pass


Same as:



class A:
    pass


The class being defined inherits the base class as a type. However, this does not explain why the base class objectis of the class type type. The point is, it typeis a metaclass. As you already know, all classes inherit from the base class object, which is of the metaclass type type. Therefore, all classes also have this type, including the metaclass itself type:



type(type)
<class 'type'>


This is the "endpoint of typing" in Python. The type inheritance chain is closed on the class type. The metaclass typeserves as the base for all classes in Python. It is easy to verify this:



builtins = [list, dict, tuple]
for obj in builtins:
    type(obj)
<class 'type'>
<class 'type'>
<class 'type'>


A class is an abstract data type, and instances of it have a class reference as a type.



Initializing new types with the type class



When checking types, the class is typeinitialized with a single argument:



type(object) -> type


In doing so, it returns the type of the object. However, the class implements a different initialization method with three arguments, which returns a new type:



type(name, bases, dict) -> new type


Type initialization parameters



  • name

    A string that defines the name of the new class (type).
  • bases

    A tuple of base classes (classes that the new class will inherit).
  • dict

    Dictionary with the attributes of the future class. Usually with strings in keys and callable types in values.


Dynamic class definition



We initialize the class of the new type, providing all the necessary arguments and call it:



MyClass = type("MyClass", (object, ), dict())
MyClass
<class '__main__.MyClass'>


You can work with the new class as usual:



m = MyClass()
m
<__main__.MyClass object at 0x7f8b1d69acc0>


Moreover, the method is equivalent to the usual class definition:



class MyClass:
    pass


Dynamically defining class attributes



There is little point in an empty class, so the question arises: how to add attributes and methods?



To answer this question, consider the initial initialization code:



MyClass = type(“MyClass”, (object, ), dict())


Typically, attributes are added to a class at the initialization stage as a third argument - a dictionary. You can specify attribute names and values ​​in the dictionary. For example, it could be a variable:



MyClass = type(“MyClass”, (object, ), dict(foo=“bar”)
m = MyClass()
m.foo
'bar'


Dynamic method definition



Callable objects can also be passed to the dictionary, for example, methods:



def foo(self):
    return “bar”
MyClass = type(“MyClass”, (object, ), dict(foo=foo))
m = MyClass()
m.foo
'bar'


This method has one significant drawback - the need to define the method statically (I think that in the context of metaprogramming tasks, this can be considered a drawback). Besides, defining a method with a parameter selfoutside the class body looks strange. Therefore, let's go back to dynamic initialization of a class without attributes:



MyClass = type(“MyClass”, (object, ), dict())


After initializing an empty class, you can add methods to it dynamically, that is, without an explicit static definition:



code = compile('def foo(self): print(“bar”)', "<string>", "exec")


compileIs a built-in function that compiles source code into an object. The code can be executed by functions exec()or eval().



Compile function parameters



  • source

    Source code, can be a link to a module.
  • filename

    The name of the file into which the object will be compiled.
  • mode

    If specified "exec", the function will compile the source code into a module.


The result of the work compileis a class object code:



type(code)
<class 'code'>


The object codeneeds to be converted to a method. Since a method is a function, we start by converting a class codeobject to a class object function. To do this, import the module types:



from types import FunctionType, MethodType


I will import MethodTypeit because I will need it later to convert the function to a class method.



function = FunctionType(code.co_consts[0], globals(), “foo”)


FunctionType initialization method parameters



  • code

    Class object code. code.co_consts[0]Is a call to a co_constsclass descriptor code, which is a tuple with constants in the object's code. Imagine an object codeas a module with a single function that we are trying to add as a class method. 0Is its index, since it is the only constant in the module.
  • globals()

    Dictionary of global variables.
  • name

    Optional parameter specifying the name of the function.


The result is a function:



function
<function foo at 0x7fc79cb5ed90>
type(function)
<class 'function'>


Next, you need to add this function as a class method MyClass:



MyClass.foo = MethodType(function, MyClass)


A fairly simple expression that assigns our function to a class method MyClass.



m = MyClass()
m.foo()
bar


Warning



In 99% of cases, you can get by with static class definitions. However, the concept of metaprogramming is good at revealing the internals of Python. Most likely, it will be difficult for you to find the application of the methods described here, although in my practice there was such a case.



Have you worked with dynamic objects? Maybe in other languages?



Links






All Articles