web-dev-qa-db-ja.com

Python関数を動的に呼び出す方法

私はこのコードを持っています:

_fields = ['name','email']

def clean_name():
    pass

def clean_email():
    pass
_

clean_name()およびclean_email()を動的に呼び出すにはどうすればよいですか?

例えば:

_for field in fields:
    clean_{field}()
_

PHPでそれを行う方法だったが、明らかに機能しないため、中括弧を使用しました。

Pythonでこれを行う方法は?

46
nemesisdesign

globals, varsを使用したくない場合、および動的に呼び出す関数をカプセル化するために別のモジュールやクラスを作成したくない場合は、それらを現在のモジュールの属性として呼び出すことができます。

import sys
...
getattr(sys.modules[__name__], "clean_%s" % fieldname)()
55
khachik

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()でチェックを追加することです。

34

globalを使用することは、これを行う非常に悪い方法です。次のようにしてください。

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

for key in fields:
    fields[key]()
_

関数を辞書の値にマップします。

vars()[]の使用も間違っています。

33
Jakob Bowyer

globals()は、グローバル名前空間のdictを提供します。これから、必要な機能を取得できます。

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

次に呼び出します:

f()
6
Magnus Hoff

別の方法を次に示します。

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

私はこの問題に2度遭遇し、最終的にsafenotいではないの解決策を思いつきました(私の謙虚な意見です)。

[〜#〜] recap [〜#〜]以前の回答:

globalsはハッキング、高速かつ簡単な方法ですが、関数名と非常に一貫性がなければならず、変数が上書きされると実行時に壊れる可能性があります。また、それは非Pythonicで、安全でなく、非倫理的です。

辞書(つまり、文字列から関数へのマップ)はより安全で使いやすいです...私のファイル全体で辞書の割り当て、それは簡単に失われます。

Decorators辞書ソリューションを私のためにまとめました。デコレータは、副作用と変換を関数定義に添付するための非常に良い方法です。

例の時間

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))
3
for field in fields:
    vars()['clean_' + field]()
2
Matt Joiner

別の方法は次のとおりです。関数を定義してから、名前をキーとして辞書を定義します。

>>> 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の文字列を作成します。

評価のために文字列を作成する必要があるメソッドは、kludgyと見なされることに注意してください。

2
Spacedman

フィールド名をクリーニング関数にマッピングする辞書を使用します。いくつかのフィールドに対応するクリーニング関数がない場合、それらを処理する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リストで指定された名前ごとに存在します。フィールドリストまたは使用可能なクリーニング機能が変更されない限り、同じままであるため、おそらく1回実行するだけで済みます。

1
martineau