1

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?

3
  • 1
    These aren't methods. These are simply function objects that are instance attributes. Methods belong to the class. But this seems to be what you want to do, no? So what exactly is wrong with it? But if you want actual methods, don't do the loop in __init__, just do a loop outside the class definition adding the methods to the class Commented Mar 20, 2019 at 18:13
  • Ah, I see what you mean. Yes that would be a bit better (I do want these to be methods rather than attributes) Commented Mar 20, 2019 at 18:24
  • 1
    Yes, however, an instance attribute that is essentially the function partially applied with the instance as the first attribute is essentially the same, unless you are making many instances (in which case, you are avoiding the inherent flyweight pattern aspect of real methods. It would likely be a slightly faster, note, pyhton method calls create a method object on each invocation Commented Mar 20, 2019 at 18:30

1 Answer 1

2

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.

Sign up to request clarification or add additional context in comments.

5 Comments

This seems like a perfectly fine suggestion, why wouldn't you suggest it?
This will be slower at runtime than simply setattr(MyAPIWrapper,'get_%s'%route, ...). But it require minimal code change.
Probably not a concern, are they going to be creating a millions of attributes in a tight loop? Probably not. A one time slightly slower creation of a dozen (or even a hundreds) methods is not going to materially affect the performance. This has the added advantage of keeping all the nice thigns, like fully qualified names for the functions for better debugging.
Good point. Thanks for the comment. I've edit the answer accordingly.
Thanks for your answer. This works, and I (mostly) agree on the pros. Although, I do think you lose some things like syntax highlighting and code completion which offsets the readability a little. Also, (and I know it's not a particularly robust criticism but) metaprogramming with strings feels an awful lot like a bad code "smell" to me, and I think I prefer the other answer. Am I wrong for thinking this?

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.