[Road to Python intermediate] Dynamically specify execution method by variable name

Link to summary

https://qiita.com/ganariya/items/fb3f38c2f4a35d1ee2e8

Introduction

In order to study Python, I copied a swarm intelligence library called acopy.

In acopy, many interesting Python grammars and idioms are used, and it is summarized that it is convenient among them.

This time, we will focus on dynamically specifying the execution method by the variable name.

getattr

There is a built-in function called getattr in Python.

You can retrieve that method from an instance with getattr (instance, method name of the instance).

For now, let's try a simple example.


class A:

    def __init__(self, msg):
        self.msg = msg

    def hello(self):
        print("hello ", self.msg)

    def add(self, x, y):
        return x + y


'''
hello  world
7
'''
a = A("world")
getattr(a, 'hello')()
print(getattr(a, 'add')(2, 5))

Let's write a simple source code like the one above. Instance a is generated from class A I'm getting the method of the hello attribute of instance a with getattr (a,'hello').

Then, by adding () after that, the extracted method is actually executed.

Advanced version

Let's take a look at the actual application of getattr.

This time you decided to create a class library called Library. (Fastball) In the library, some unique processing is executed by a function called run, and internal information is stored in a member variable called library_information for the time being.

At this point, you also decided to distribute a ** plugin ** that gives you access to the internal information of the Library for other users who want to use the Library.

Since it is a Library you created, only you can implement the main process, but by preparing a process to execute the plugin in the run function, users who want to use the internal information of the Library can create their own plugins It can be expanded.

Let's actually look at the code.

class Library:

    def __init__(self):
        self.plugins = []

        self.library_information = {
            'a': 10,
            'b': 20,
            'c': 30
        }

    def run(self):

        # plugin start
        self.call_plugins('start')

        for cycle in range(3):
            print("Library main process (Implementation omitted)")

            # plugin iteration
            self.call_plugins('iteration')

        # plugin finish
        self.call_plugins('finish')

    def call_plugins(self, hook):
        for plugin in self.plugins:
            plugin(hook, **self.library_information)

    def add_plugin(self, plugin):
        self.plugins.append(plugin)


class Plugin:

    def __init__(self):
        pass

    def __call__(self, hook, **kwargs):
        getattr(self, f'on_{hook}')(**kwargs)

    def on_start(self, **kwargs):
        print(kwargs['a'])

    def on_iteration(self, **kwargs):
        print(kwargs['b'])

    def on_finish(self, **kwargs):
        print(kwargs['c'])


library = Library()
library.add_plugin(Plugin())
library.run()
'''
10
Library main process (Implementation omitted)
20
Library main process (Implementation omitted)
20
Library main process (Implementation omitted)
20
30
'''

Library class

Library is a library that you want to create and publish on PyPI etc. For example, be aware of Numpy and Matplotlib.

I wanted to prepare something called library_information in the internal information of the Library so that it could be used and extended by third-party users. Therefore, ** Prepare a plugin array as a member variable, inherit the Plugin class and pass it to add_plugin so that the plugin can be executed. When the Plugin is executed from the Library, the internal information of the Library is passed, so the user can extend the function. ** **

Plugin class

This time it is implemented by print, but originally it is an abstract class and you create this plugin as well, and the user creates the original plugin by inheriting this plugin further.

By preparing a __call__ function, you can execute it like a function.

Use getattr when calling __call__. As a result, the Library side executes the Plugin instance in the run and passes the Library information at that time. Then, the Plugin instance is getattr, which brings out the function that matches the appropriate start, finish, iteration hook in the run and uses it.

In the function run, you can also delete getattr like call_plugins_start, call_plugins_finish and write all cases separately, but this allows you to write in a unified way.

Library extensibility

By preparing a plugin class with the library and distributing it to users

--Pass the information in the library from the library side to the plugin --Inherit the plug-in and let the user create it to extend the library with the plug-in and wear it --Getattr can reduce the amount of description

There are benefits. However, keep in mind that the risk of run-time errors increases because you do not know if it is correct until you run it.

Recommended Posts

[Road to Python intermediate] Dynamically specify execution method by variable name
A road to intermediate Python
[Road to Intermediate] Understanding Python Properties
[Road to intermediate Python] Use ternary operators
[Road to intermediate Python] Use lambda expressions
[Road to intermediate Python] Article link summary
To dynamically replace the next method in python
[Road to Intermediate] Python seems to be all objects
How to get the variable name itself in python
[Road to Python Intermediate] Define __getattr__ function in class
[Road to intermediate Python] Define in in your own class
[Road to intermediate Python] Install packages in bulk with pip
[Python] How to set variable names dynamically and speed comparison
[Road to intermediate Python] Use if statement in list comprehension
[Road to intermediate Python] Enables comparison operations with original classes
How to override a user-defined method generated by python swig
[Road to Python Intermediate] Call a class instance like a function with __call__
Execution by subshell (and variable update)
[Python] Road to snake charmer (3) Python class
Road to Linux Intermediate: Network Edition
Python (from first time to execution)