Advanced Function Calling in Python
Tags: python, programming
Published: 2023-04-10 21:10:37

## 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:

```python
from json import dumps

test_dictionary = {'a': 'foo', 'b': 'bar'}
print(dumps(test_dictionary, indent=2, default=str))
```

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:

```python
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))
```

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:

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

print(dumps(values, **keyword_args))
```

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

```python
dumps(values, indent=2, default=str)
```

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:

```python
def double_values(value):
  return value**2

function_mapping = {
  'double': double_values
}

print(function_mapping['double'](2))
```

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:

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


hello_method = getattr(Test, 'hello_world')
print(hello_method())
```

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

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


hello_method = getattr(globals()['Test'], 'hello_world')
print(hello_method())
```

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

```python
import csv

writer_class = getattr(csv, 'DictWriter')
```

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:

```python
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())
```

Note that if you're using this for something like dynamic plugin calls consider something like [Abstract Base Class](https://docs.python.org/3/library/abc.html) 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](https://www.linkedin.com/in/chrischriswhite/).