DEV Community

Chris White
Chris White

Posted on

Advanced Function Calling in Python

Introduction

Python has a few interesting ways to call functions. This guide will show a few interesting ways to do so that's outside of a standard function call. These techniques can be used for more advanced cases, especially dynamic calling.

Partials

Partials are essentially templates for how a function is called. Take for example a common json dumps I use to give indented JSON output:

from json import dumps

test_dictionary = {'a': 'foo', 'b': 'bar'}
print(dumps(test_dictionary, indent=2, default=str))
Enter fullscreen mode Exit fullscreen mode

Now the thing is I'm always going to be calling it this way. It indents by 2 spaces making it easier to read, and default=str defaults to using the str() construct when it can't parse normally (this happens frequently for some timestamp related values). Calling it this way is a bit verbose, so we can use a partial instead:

from functools import partial
from json import dumps

test_dictionary = {'a': 'foo', 'b': 'bar'}
indented_json = partial(dumps, indent=2, default=str)
print(indented_json(test_dictionary))
Enter fullscreen mode Exit fullscreen mode

This is nice because it prevents the need to write function just for the purpose of calling other functions, especially if there's a lot of arguments going with it. Note that any required arguments not present in the partial will need to be filled in during the call.

Dynamic Keyword Args

Python has an interesting mechanism for passing in keyword args as a dictionary of name-> value pairs:

from json import dumps
values = {'a': 1, 'b': 2, 'c': 3}
keyword_args = {
  'indent': 2,
  'default': str
}

print(dumps(values, **keyword_args))
Enter fullscreen mode Exit fullscreen mode

The ** operator is often referred to as splat, or unpacking as well. This is the equivalent call wise to:

dumps(values, indent=2, default=str)
Enter fullscreen mode Exit fullscreen mode

This can be useful in some boto calls where you're building up values to pass on. DynamoDB in particular can utilize this to build up filters and query expressions.

Function Mapping

In cases of calling a function via a string, a mapping using something like a dictionary makes this possible:

def double_values(value):
  return value**2

function_mapping = {
  'double': double_values
}

print(function_mapping['double'](2))
Enter fullscreen mode Exit fullscreen mode

I prefer this to eval() usage as it essentially lets me create an approved list of functions to call.

Dynamic Method Calling

Now methods are essentially attributes of a class or class instance. For static methods bound to the class itself (not reliant on self variables), getattr can simply be used with the class and method name:

class Test:
  @staticmethod
  def hello_world():
    return "Hello, World"


hello_method = getattr(Test, 'hello_world')
print(hello_method())
Enter fullscreen mode Exit fullscreen mode

Now if the class name is also dynamic, you need to either access it via globals() like so:

class Test:
  @staticmethod
  def hello_world():
    return "Hello, World"


hello_method = getattr(globals()['Test'], 'hello_world')
print(hello_method())
Enter fullscreen mode Exit fullscreen mode

or if it's part of the module then getattr can be used on the namespace declaration to obtain it:

import csv

writer_class = getattr(csv, 'DictWriter')
Enter fullscreen mode Exit fullscreen mode

This could then be passed to getattr again to obtain method in DictWriter. For this to work on class instances (ie. keeping self type values isolated) then getattr will need to be done on the class instance assignment itself:

class Math:
  def __init__(self, value=2):
    self.value = value

  def double(self):
    return self.value ** 2


math_instance = Math()
math_instance2 = Math(4)
double_method = getattr(math_instance, 'double')
double_method2 = getattr(math_instance2, 'double')
print(double_method())
print(double_method2())
Enter fullscreen mode Exit fullscreen mode

Note that if you're using this for something like dynamic plugin calls consider something like Abstract Base Class to to ensure a consistent method name exists to be executed (run_plugin for example).

Conclusion

That's it for this lesson on advanced ways to do function (and method) calls in Python. If you like what you see I'm available for hire.

Top comments (0)