Python: Call Functions by String

Recently I was working on a server maintenance project, which involves loads of remote interactions through several different protocols such as ssh, mosh, telnet, snmp, etc. To implement such an application often need let your code branching through several functions. I really hate to write loads of ifs in my code especially in Python, which identify blocks by indentation. My solution is to call each functions by its string-type name. An example is as follows.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def _ssh(hostname, port):
pass

def _telnet(hostname, port):
pass

def _mosh(hostname, port):
pass

protocols = {
'ssh': _ssh,
'mosh': _mosh,
'telnet': _telnet
}

# call your function by string
hostname = 'localhost'
port = '22'
protocol = 'ssh'
result = protocols[protocol](hostname, port)

This solution really helped me beautified by codebase. Thus, I started to search on Google about other ways to implement this kind of dynamic calling. Fortunately, I got some interesting alternatives that I would like to note them here and share them on this blog.

Declaration

The content of this post is produced based on Python3, which means some of the points and code snippets in this post may not be true when appliying them in the Python2 environment.

Approach 1: exec()

exec() is one of the Build-in Functions provided by Python natively. The definition of this built-in function is ciated below.

This function supports dynamic execution of Python code. object must be either a string or a code object. If it is a string, the string is parsed as a suite of Python statements which is then executed (unless a syntax error occurs). If it is a code object, it is simply executed. In all cases, the code that’s executed is expected to be valid as file input (see the section “File input” in the Reference Manual). Be aware that the return and yield statements may not be used outside of function definitions even within the context of code passed to the exec() function. The return value is None

As warned by the official documentation, function exec() could not pass the return value both in the code snippet and out side the code snippet. An example is provided as follows.

1
2
3
4
5
6
def my_func(arg1, arg2):
print(arg1, arg2)
return arg1 + arg2

# The return value of exec() is None
exec('my_func(1, 2)')

The output of the above code blocks:

1
1 2

Apart from this, exec() could take a large block of Python code as input, which definitely includes the full support for for and while loops, expressions and statements. However, it returns None for whatever the input is. Be careful to use it if you do need to return a value.

Approach 2: eval()

The built-in function eval() seems very much like exec() at first glance. They both evaluate the string as Python code, while the arguments of eval() is limited to onlu Python expressions. Check out the Offical Documentation for detailed specification of function eval()

1
2
3
4
5
6
7
8
9
def my_func(arg1, arg2):
print(arg1, arg2)
return arg1 + arg2

# The return value of exec() is None
r = eval('my_func(1, 2)')
print(r)
r = eval('1 == 2')
print(r)

The output of the above code snippets:

1
2
3
1 2 
3
False

Aparently, eval() return the value of the input expression after evaluates the expression. However, the follow examples shows the main difference between exec() and eval().

1
r = eval('a = 1')

An SyntaxError exception will be raised when the code is evaluated by Python interpreter, because a = 1 is an assignment statement rather than an expression.

Approach 3: Synbol Tables

Built-in function locals() and globals() return a dictionary of current local and global symbol table respectively. So you could call a function just like:

1
2
3
4
5
6
def my_func(arg1, arg2):
print(arg1, arg2)
return arg1 + arg2

r = locals()['my_func'](1, 2)
print(r)

The output is:

1
2
1 2
3

This approach is just like the examples provided in the very beginning of this post, in which, I maintained a symbol table by myself and called the function by its string-typed name.