snakeoilsales
snakeoilsales

Reputation: 113

Adding methods to a class programmatically in Python 3+

I have a class that provides a simple interface to an API. I want to define a method on that class for each route.

Since most of the routes are the same, a lot of the functionality can be factored out into a more generic function, with many routes just being a partially-applied version of this function

class MyAPIWrapper:

    def _get_results(self, route, **params):
        # Do the important stuff here
        results = ...
        return results

    def get_customers(self):
        return self._get_results(self, 'customers')

    def get_transactions(self):
        return self._get_results(self, 'transactions')

    # etc, etc, etc

However, it is apparent that this still results in a fair amount of boilerplate in the class definition.

One alternative is to add a new method that adds each route's method programmatically:

import functools

class MyAPIWrapper:

    def __init__(self):
        self._init_route_methods()

    def _get_results(self, route, **params):
        # Do the important stuff here
        results = ...
        return results

    def _init_route_methods(self):
        for route in ['customers', 'transactions', ...]:
            route_fn = functools.partial(self. _get_results, route)
            setattr(self, f'get_{route}', route_fn)

This has the advantage of reducing the amount of boilerplate and makes it easy to add/remove routes. However, adding the methods at initialisation feels somewhat inelegant to me.

Is there a better and/or more idiomatic way to do this?

Upvotes: 1

Views: 292

Answers (1)

gdlmx
gdlmx

Reputation: 6789

You may be surprised that this will do the trick:

class MyAPIWrapper:
    def _get_results(self, route, **params):
        # Do the important stuff here
        return route

    for route in ['customers', 'transactions']:
        exec("""
    def get_{0}(self):
        return self._get_results('{0}')
    """.strip().format(route))
MyAPIWrapper().get_customers()    # return customers
MyAPIWrapper().get_transactions() # return transactions

Pros

  • Good readability
  • Minimal code change

Cons

Please note that exec has a little overhead than setattr(MyAPIWrapper,'get_%s'%route, ...), which only matters if you're going to create millions of methods in the loop.

If you want to do the same thing to many different APIWrapper classes, consider to use a class decorator instead.

Upvotes: 2

Related Questions