How to call Python functions dynamically

一世执手 提交于 2019-12-17 06:31:20

问题


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?


回答1:


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



回答2:


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.




回答3:


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.




回答4:


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



回答5:


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 function will add stuff into the dictionary
def add_cleaner(key):
    # this is a parametered decorator, it returns the actual decorator

    def actual_decorator(func):
        # add func to the dictionary
        cleaners[key] = func
        return func

    return actual_decorator

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 they are parsed, and can be called like this:

cleaned_email = cleaners['email'](some_email)

You might want to add this line at the bottom of your script, to make sure you didn't forget one. ;)

assert(set(cleaners.keys()).issubset(fields))



回答6:


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



回答7:


for field in fields:
    vars()['clean_' + field]()



回答8:


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.




回答9:


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.



来源:https://stackoverflow.com/questions/4246000/how-to-call-python-functions-dynamically

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!