98

I have this code:

fields = ['name','email']

def clean_name():
    pass

def clean_email():
    pass

How can I call clean_name() and clean_email() dynamically?

For example:

for field in fields:
    clean_{field}()

I used the curly brackets because it's how I used to do it in PHP but obviously doesn't work.

How to do this with Python?

the Tin Man
  • 158,662
  • 42
  • 215
  • 303
nemesisdesign
  • 8,159
  • 12
  • 58
  • 97

11 Answers11

83

If don't want to use globals, vars and don't want make a separate module and/or class to encapsulate functions you want to call dynamically, you can call them as the attributes of the current module:

import sys
...
getattr(sys.modules[__name__], "clean_%s" % fieldname)()
the Tin Man
  • 158,662
  • 42
  • 215
  • 303
khachik
  • 28,112
  • 9
  • 59
  • 94
  • Note that this does not work in the IPython shell, though… (but it does work in the Python shell). – Eric O. Lebigot Nov 22 '10 at 15:45
  • @EOL Thanks, interesting. What is `__name__` in iPython's repl? – khachik Nov 22 '10 at 15:47
  • @khachik: `__name__` is '__main__' both in the Python and the IPython shells. However, `sys.modules[__name__]` does not contain the variables defined in the IPython shell, but does contain those defined in the Python one. I remember seeing some documentation about this… This is not such a big deal though, but this difference of behavior between both shells is worth knowing… – Eric O. Lebigot Nov 22 '10 at 20:20
  • @EOL, Sure. I've never used ipython, i should try it, thanks. – khachik Nov 22 '10 at 20:51
  • @khachik: IPython is much nicer than the Python shell, which I already appreciate, since Perl was my previous scripting language of choice. :) – Eric O. Lebigot Nov 23 '10 at 09:33
  • 4
    well dear Jakob_B, I chosed this as accepted answer because taught me something new that your answer didn't. Don't take it personal though. – nemesisdesign Nov 24 '10 at 09:48
  • coundn't find anything else, I was using an eval(module) instead. Thanks :) – Cesar Jan 11 '11 at 08:27
  • 15
    This answer is ridiculous. "If you don't want to use globals()": the reason to not use globals is the implicitness of it. This does nothing to solve that, it just provides a more complicated way to get to the globals... :( – Ned Batchelder Oct 25 '14 at 20:24
  • Python3 `getattr(sys.modules[__name__], f"clean_{fieldname}")()` – James Bellaby Jan 25 '21 at 16:36
68

Using global is a very, very, bad way of doing this. You should be doing it this way:

fields = {'name':clean_name,'email':clean_email}

for key in fields:
    fields[key]()

Map your functions to values in a dictionary.

Also using vars()[] is wrong too.

the Tin Man
  • 158,662
  • 42
  • 215
  • 303
Jakob Bowyer
  • 33,878
  • 8
  • 76
  • 91
  • I like your fields-`dict`, but could you elaborate on what's wrong with `globals()` and `vars()`? – Magnus Hoff Nov 22 '10 at 13:53
  • 6
    Because vars() and globals() should be avoided, they are considered un-pythonic and they are generally something that requires string manipulation or something that over complicates the problem. This is the simplest answer to your problem. – Jakob Bowyer Nov 22 '10 at 13:55
  • 4
    Using globals() will expose everything within that scope and above it, potentially creating a security risk allowing anyone who can gain access to the dictionary returned by globals() to manipulate its contents. To gain access to a few members of a scope you will be exposing everything where if you explicitly define the dictionary to contain only the functions necessary it will be more secure and easier for other programmers to understand. And vars() is only slightly better because it does not expose everything above your scope only what is in the present scope. – snarkyname77 Nov 22 '10 at 14:09
  • I don't see how this solves the question, which I understand means "how to automatically map a string to a function name?". – Eric O. Lebigot Nov 22 '10 at 15:39
  • @lewisblackfan: Your comment about security sounds interesting. Could you elaborate on how anybody could "gain access to the globals() dictionary"? through keys that are not originally intended to be used? – Eric O. Lebigot Nov 22 '10 at 15:52
  • Point is this code is neater and nicer than any of the other alternatives. Show this to anyone on #python @ freenode and they will choose this one over any alternatives. Tbh do you really want to be calling up everything in the global namespace to get to two different functions that can just as easily be mapped in a function dictionary where you can call the string name of the function as the key and then just run the parentasis – Jakob Bowyer Nov 22 '10 at 16:40
  • wanted to know something more advanced than this – nemesisdesign Nov 22 '10 at 17:53
  • 1
    @EOL: has a point, even with globals() you would not be exposing any attributes that where not intended to be accessed otherwise. The dictionary will however contain many more items than it needs to and if iteration over those keys was not constrained to only those functions you want to call then... I'd hate to have to find that bug. I code with consideration for those who will have to read and add to my code in the future, only return those items which you intended to, using something like globals() to get at even a dozen functions dynamically is excessive and in my opinion insecure. – snarkyname77 Nov 23 '10 at 14:59
  • 1
    I agree with lewisblackfan, code should always be written as if the person who is maintaining it is a psycho who knows where you live bad code = crazy man wanting to kill you – Jakob Bowyer Nov 23 '10 at 15:09
  • 3
    and how to pass parameters? – Volatil3 Nov 23 '16 at 09:34
52

It would be better to have a dictionary of such functions than to look in globals().

The usual approach is to write a class with such functions:

class Cleaner(object):
    def clean_name(self):
        pass

and then use getattr to get access to them:

cleaner = Cleaner()
for f in fields:
    getattr(cleaner, 'clean_%s' % f)()

You could even move further and do something like this:

class Cleaner(object):
    def __init__(self, fields):
        self.fields = fields

    def clean(self):
        for f in self.fields:
            getattr(self, 'clean_%s' % f)()

Then inherit it and declare your clean_<name> methods on an inherited class:

cleaner = Cleaner(['one', 'two'])
cleaner.clean()

Actually this can be extended even further to make it more clean. The first step probably will be adding a check with hasattr() if such method exists in your class.

the Tin Man
  • 158,662
  • 42
  • 215
  • 303
Alexander Solovyov
  • 1,526
  • 1
  • 13
  • 21
16

I have come across this problem twice now, and finally came up with a safe and not ugly solution (in my humble opinion).

RECAP of previous answers:

globals is the hacky, fast & easy method, but you have to be super consistent with your function names, and it can break at runtime if variables get overwritten. Also it's un-pythonic, unsafe, unethical, yadda yadda...

Dictionaries (i.e. string-to-function maps) are safer and easy to use... but it annoys me to no end, that i have to spread dictionary assignments across my file, that are easy to lose track of.

Decorators made the dictionary solution come together for me. Decorators are a pretty way to attach side-effects & transformations to a function definition.

Example time

fields = ['name', 'email', 'address']

# set up our function dictionary
cleaners = {}

# this is a parametered decorator
def add_cleaner(key):
    # this is the actual decorator
    def _add_cleaner(func):
        cleaners[key] = func
        return func
    return _add_cleaner

Whenever you define a cleaner function, add this to the declaration:

@add_cleaner('email')
def email_cleaner(email):
    #do stuff here
    return result
    

The functions are added to the dictionary as soon as their definition is parsed and can be called like this:

cleaned_email = cleaners['email'](some_email)

Alternative proposed by PeterSchorn:

def add_cleaner(func):
    cleaners[func.__name__] = func
    return func

@add_cleaner
def email():
   #clean email

This uses the function name of the cleaner method as its dictionary key. It is more concise, though I think the method names become a little awkward. Pick your favorite.

  • 2
    It would be better to access the name of the function by calling `func.__name__`. This way, you don't have to pass the name of the function into the decorator and ensure that you don't make any typos. – Peter Schorn Apr 18 '20 at 10:48
11

globals() will give you a dict of the global namespace. From this you can get the function you want:

f = globals()["clean_%s" % field]

Then call it:

f()
Magnus Hoff
  • 21,529
  • 9
  • 63
  • 82
7

Here's another way:

myscript.py:

def f1():
    print 'f1'

def f2():
    print 'f2'

def f3():
    print 'f3'

test.py:

import myscript

for i in range(1, 4):
    getattr(myscript, 'f%d' % i)()
the Tin Man
  • 158,662
  • 42
  • 215
  • 303
ceth
  • 44,198
  • 62
  • 180
  • 289
5

I had a requirement to call different methods of a class in a method of itself on the basis of list of method names passed as input (for running periodic tasks in FastAPI). For executing methods of Python classes, I have expanded the answer provided by @khachik. Here is how you can achieve it from inside or outside of the class:

>>> class Math:
...   def add(self, x, y):
...     return x+y
...   def test_add(self):
...     print(getattr(self, "add")(2,3))
... 
>>> m = Math()
>>> m.test_add()
5
>>> getattr(m, "add")(2,3)
5

Closely see how you can do it from within the class using self like this:

getattr(self, "add")(2,3)

And from outside the class using an object of the class like this:

m = Math()
getattr(m, "add")(2,3)
Muhammad Shahbaz
  • 319
  • 1
  • 3
  • 10
3

Here's another way: define the functions then define a dict with the names as keys:

>>> z=[clean_email, clean_name]
>>> z={"email": clean_email, "name":clean_name}
>>> z['email']()
>>> z['name']()

then you loop over the names as keys.

or how about this one? Construct a string and use 'eval':

>>> field = "email"
>>> f="clean_"+field+"()"
>>> eval(f)

then just loop and construct the strings for eval.

Note that any method that requires constructing a string for evaluation is regarded as kludgy.

Spacedman
  • 92,590
  • 12
  • 140
  • 224
2
for field in fields:
    vars()['clean_' + field]()
Matt Joiner
  • 112,946
  • 110
  • 377
  • 526
2

In case if you have a lot of functions and a different number of parameters.

class Cleaner:
    @classmethod
    def clean(cls, type, *args, **kwargs):
        getattr(cls, f"_clean_{type}")(*args, **kwargs)

    @classmethod
    def _clean_email(cls, *args, **kwargs):
        print("invoked _clean_email function")

    @classmethod
    def _clean_name(cls, *args, **kwargs):
        print("invoked _clean_name function")


for type in ["email", "name"]:
    Cleaner.clean(type)

Output:

invoked _clean_email function
invoked _clean_name function
Vlad Bezden
  • 83,883
  • 25
  • 248
  • 179
1

I would use a dictionary which mapped field names to cleaning functions. If some fields don't have corresponding cleaning function, the for loop handling them can be kept simple by providing some sort of default function for those cases. Here's what I mean:

fields = ['name', 'email', 'subject']

def clean_name():
    pass
def clean_email():
    pass

# (one-time) field to cleaning-function map construction
def get_clean_func(field):
    try:
        return eval('clean_'+field)
    except NameError:
        return lambda: None  # do nothing
clean = dict((field, get_clean_func(field)) for field in fields)

# sample usage
for field in fields:
    clean[field]()

The code above constructs the function dictionary dynamically by determining if a corresponding function named clean_<field> exists for each one named in the fields list. You likely would only have to execute it once since it would remain the same as long as the field list or available cleaning functions aren't changed.

martineau
  • 119,623
  • 25
  • 170
  • 301
  • 1
    `eval()` is heavy, as it involves interpreting Python code. The `globals()` or `sys.modules[__name__]` approaches are more direct. – Eric O. Lebigot Nov 22 '10 at 15:38
  • @EOL: The `eval()` is only used to construct the dictionary -- typically not more than once or that often if done more than once I would expect. One advantage to using `eval()` over the two alternatives you mention is that it does the name lookup in the same places and order that Python normally does, namely in `locals()`, `globals()`, and the `__builtin__` module. So I think down-voting my answer because of that (if it was you) was unfair. – martineau Nov 22 '10 at 17:14
  • @EOL: To clarify, I meant `eval()` was only used to construct the dictionary and that shouldn't happen very frequently -- however doing *that* would likely involve calling `eval()` more than once. – martineau Nov 22 '10 at 17:55
  • You're right, your `eval()` is indeed not so "heavy", being only called upon dictionary construction. Thank you for pointing this out. :) – Eric O. Lebigot Nov 22 '10 at 20:15