Use getattr() to get method dynamically in Python

Eva(Tzuni) Hsieh
2 min readJan 25, 2019

I have a handy script which helps me to handle a lot of things, and the structure looks like this:

/lib
|- /arguments_handlers
|---- __init__.py
|---- crawler.py
|---- create.py
|---- delete.py
|- arguments.py

In /arguments_handlers/__init__.py, I have this one line code to do the relative import:

from . import create, crawler, delete

Because I didn’t have many methods to call, I just kept it simple and have this code in my arguments.py.

from . import argument_handlersfuncs = {
'crawler': argument_handlers.crawler.run,
'create': argument_handlers.create.run,
'delete': argument_handlers.delete.run,
}
def parse_args(args):
"""Function for parsing argument then execute the correct handler
Parameters
----------
args : argparse.ArgumentParser()
"""
func = funcs[args.action]
func(args.env, args)

This static function mapping was all I need; clean, simple, and convenient. I put this mapping on the top of my code, and handle the rest of the complicated logic in each argument handler.

It was easy to maintain until the script has grown up like this recently:

/lib
|- /arguments_handlers
|---- __init__.py
|---- crawler.py
|---- create.py
|---- delete.py
|---- fetch.py
|---- generate.py
|---- reset.py
|---- search.py
|---- sync.py
|---- term.py
|- arguments.py

In the __init__.py:

from . import create, crawler, delete, fetch, generate, reset, search, sync, term

And the funcs mapping in my arguments.py looked like this:

from . import argument_handlersfuncs = {
'crawler': argument_handlers.crawler.run,
'create': argument_handlers.create.run,
'delete': argument_handlers.delete.run,
'fetch': argument_handlers.fetch.run,
'generate': argument_handlers.generate.run,
'reset': argument_handlers.reset.run,
'search': argument_handlers.search.run,
'sync': argument_handlers.sync.run,
'term': argument_handlers.term.run,
}

Whenever I need to add a new action function, I had to

  1. Create handler
  2. Include handler module in __init__.py
  3. Update the funcs mappings in arguments.py (always forget this step until I ran the script)

Ugh!

So I decided to make some changes for arguments.py:

from . import argument_handlersfuncs = {
name: getattr(argument_handlers, name).run for name in dir(argument_handlers) if not name.startswith('_')
}

This is using Python dictionary comprehension.

What I am doing in here is using getattr() function to get modules from arguments_handlersdynamically instead of doing so many copy-and-paste.

mod = myobject.first_name

equals to

mod = getattr(myobject 'first_name') # the attribute name must be a string

getattr() is a very handy function for getting attributes/methods of an object dynamically.

Trade-Off: Performance

Everything has trade-offs, you just need to decide if that’s worth to trade.

In this use case, dynamic version is about ~4 times slower than static version, depends on how many modules you have.

But if you look at the actual seconds we got, it’s only about 0.0000144 seconds slower than the original static version.

Which doesn’t look too bad at all!

Using this much time to exchange a better maintenance for my script sounds like a good deal for me.

--

--

Eva(Tzuni) Hsieh

Software Engineer. Love Python and Go; JavaScript/Node.js sometimes.