Как динамически вызывать функции Python - PullRequest
44 голосов
/ 22 ноября 2010

У меня есть этот код:

fields = ['name','email']

def clean_name():
    pass

def clean_email():
    pass

Как я могу вызвать clean_name() и clean_email() динамически?

Например:

for field in fields:
    clean_{field}()

Я использовал фигурные скобки, потому что я делал это в PHP, но, очевидно, не работает.

Как это сделать с Python?

Ответы [ 9 ]

54 голосов
/ 22 ноября 2010

Если вы не хотите использовать globals, vars и не хотите создавать отдельный модуль и / или класс для инкапсуляции функций, которые вы хотите вызывать динамически, вы можете вызывать их как атрибуты текущего модуля:

import sys
...
getattr(sys.modules[__name__], "clean_%s" % fieldname)()
35 голосов
/ 22 ноября 2010

Было бы лучше иметь словарь таких функций, чем искать в globals().

Обычный подход - написать класс с такими функциями:

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

, а затем используйте getattr, чтобы получить к ним доступ:

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

Вы могли бы даже двигаться дальше и сделать что-то вроде этого:

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

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

Затем наследуйте его и объявите ваши clean_<name> методы в унаследованном классе:

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

На самом деле это может быть расширено, чтобы сделать его более чистым. Первым шагом, вероятно, будет добавление проверки с hasattr(), если такой метод существует в вашем классе.

33 голосов
/ 22 ноября 2010

Использование global - очень, очень, плохой способ сделать это.Вы должны сделать это следующим образом:

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

for key in fields:
    fields[key]()

Сопоставьте свои функции со значениями в словаре.

Также неправильно использовать vars()[].

6 голосов
/ 22 ноября 2010

globals() даст вам dict глобального пространства имен. Из этого вы можете получить функцию, которую вы хотите:

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

Тогда назовите это:

f()
4 голосов
/ 22 ноября 2010

Вот еще один способ:

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)()
2 голосов
/ 07 января 2019

Я уже дважды сталкивался с этой проблемой и, наконец, нашел безопасное и не уродливое решение (по моему скромному мнению).

RECAP предыдущих ответов:

globals - это хакерский, быстрый и простой метод, но вы должны быть супер согласованы с именами своих функций, и он может сломаться во время выполнения, еслипеременные перезаписываются.Кроме того, это непитонно, небезопасно, неэтично, Ядда Ядда ...

Словари (т. Е. Карты со строками в функции) безопаснее и проще в использовании ... но меня это раздражаетнет необходимости в том, чтобы мне приходилось распределять словарные назначения по моему файлу, которые легко потерять.

Декораторы сделали решение для словаря объединенным для меня.Декораторы - прекрасный способ добавить побочные эффекты и преобразования к определению функции.

Пример времени

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

Каждый раз, когда вы определяете более чистую функцию, добавьте ее вобъявление:

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

Функции добавляются в словарь, как только они анализируются, и могут вызываться следующим образом:

cleaned_email = cleaners['email'](some_email)

Вы можете добавить эту строку вв нижней части вашего сценария, чтобы убедиться, что вы не забыли один.;)

assert(set(cleaners.keys()).issubset(fields))
2 голосов
/ 22 ноября 2010

Вот еще один способ: определить функции, а затем определить dict с именами в качестве ключей:

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

тогда вы перебираете имена как ключи.

или как насчет этого? Создайте строку и используйте 'eval':

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

затем просто зациклите и создайте строки для eval.

Обратите внимание, что любой метод, который требует построения строки для оценки, считается хитрым.

2 голосов
/ 22 ноября 2010
for field in fields:
    vars()['clean_' + field]()
1 голос
/ 22 ноября 2010

Я бы использовал словарь, который отображал имена полей в функции очистки. Если некоторые поля не имеют соответствующей функции очистки, цикл for, обрабатывающий их, можно сохранить простым, предоставив для этих случаев какую-то функцию по умолчанию. Вот что я имею в виду:

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

Приведенный выше код создает словарь функций динамически, определяя, существует ли соответствующая функция с именем clean_<field> для каждой из них, указанных в списке fields. Скорее всего, вам придется выполнить его только один раз, поскольку он останется таким же, если список полей или доступные функции очистки не изменились.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...