web-dev-qa-db-ja.com

Pythonカスタム名による動的関数作成

この質問がすでに提起され、回答されている場合はおApび申し上げます。私がしなければならないことは概念が非常に単純ですが、残念ながら私はそれに対する答えをオンラインで見つけることができませんでした。

実行時にカスタム名を使用してPython(Python2.7)で動的関数を作成する必要があります。各関数の本体も実行時に構築する必要がありますが、(ほぼ)すべて同じです関数。

名前のリストから始めます。

func_names = ["func1", "func2", "func3"]

Func_nameリストは任意の名前のリストを保持できるため、名前は単にfunc1、func2、func3、...ではないことに注意してください。

私は結果が欲しいです:

    def func1(*args):
        ...

    def func2(*args):
        ...

    def func3(*args):
        ...

これを行う必要があるのは、各関数名がテストケースに対応し、テストケースが外部から呼び出されるためです。

更新:ユーザー入力はありません。私ははるかに大きなモジュールの両端を結び付けています。一端はテストケースが何であるかを決定し、特にテストケースの名前のリストを作成します。もう一方の端は関数自体であり、テストケースの名前との1:1マッピングが必要です。そのため、テストケースの名前があり、各テストケースで何をしたいかを知っています。テストケースの名前を持つ関数を作成するだけです。テストケースの名前は実行時に決定されるため、これらのテストケースに基づく関数の作成も実行時に行う必要があります。

更新:それが物事をより簡単にするならば、私はクラスでこのカスタム名前付き関数をラップすることもできます。

関数の内容を(ほとんど同じであるため)文字列にハードコーディングするか、以前に定義した基本クラスに基づいて作成できます。このコンテンツを関数に取り込む方法を知る必要があります。

例えば:

    func_content = """
                   for arg in args:
                       print arg
                   """

前もって感謝します、

マフディ

40
mahdiolfat

あなたが説明していることについては、evalやマクロに降りる必要はないと思います。クロージャによる関数インスタンスの作成はうまくいくはずです。例:

def bindFunction1(name):
    def func1(*args):
        for arg in args:
            print arg
        return 42 # ...
    func1.__= name
    return func1

def bindFunction2(name):
    def func2(*args):
        for arg in args:
            print arg
        return 2142 # ...
    func2.__= name
    return func2

ただし、名前でアクセスできるように、これらの関数を名前でスコープに追加することをお勧めします。

>>> print bindFunction1('neat')
<function neat at 0x00000000629099E8>
>>> print bindFunction2('keen')
<function keen at 0x0000000072C93DD8>
48
Shane Holloway

同様の問題の解決策を探しているときにこの質問を見つけたばかりなので、シェーンの答えを拡張します。変数のスコープに注意してください。ジェネレーター関数を使用してスコープを定義することにより、スコープの問題を回避できます。クラスのメソッドを定義する例を次に示します。

class A(object):
    pass

def make_method(name):
    def _method(self):
        print("method {0} in {1}".format(name, self))
    return _method

for name in ('one', 'two', 'three'):
    _method = make_method(name)
    setattr(A, name, _method)

使用中で:

In [4]: o = A()

In [5]: o.one()
method one in <__main__.A object at 0x1c0ac90>

In [6]: o1 = A()

In [7]: o1.one()
method one in <__main__.A object at 0x1c0ad10>

In [8]: o.two()
method two in <__main__.A object at 0x1c0ac90>

In [9]: o1.two()
method two in <__main__.A object at 0x1c0ad10>
16
Paul Whipp

おそらくこの種のことを行うための一種の内省がありますが、私はそれが問題に対するPythonのアプローチだとは思いません。

python第1レベルの市民としての関数の性質を活用する必要があると思います。シェーンホロウェイが指摘したようにクロージャーを使用して、関数の内容を好きなようにカスタマイズします。キーが動的に定義された名前である辞書を使用し、値は関数自体になります。

def function_builder(args):
    def function(more_args):
       #do stuff based on the values of args
    return function

my_dynamic_functions = {}
my_dynamic_functions[dynamic_name] = function_builder(some_dynamic_args)

#then use it somewhere else
my_dynamic_functions[dynamic_name](the_args)

ユースケースに意味があることを願っています。

6
Facundo Olano

eval ;を使用することもできます。 Python各関数の定義(つまり、def func1 ...で始まる複数行の文字列)]を含む文字列を作成し、eval it 。

ただし、このような関数ごとに一意の名前を生成します(例:genfun345)。そのような名前に未チェックのユーザー入力を使用しないでください。名前がPythonの既知の名前と同じである場合、デバッグが困難な障害が発生するためです。

これらの機能が作成される入力を信頼しますか?マルウェアや悪用を気にしますか?

ウィキペディアで hygenic macros について読んでください。

要件を正しく理解している場合、既存の関数に新しい名前または代替名を動的に割り当てたいだけのように思えます。その場合、次の行に沿って何かを行う必要があります。

import sys

testcases = []
def testcase(f):
    """ testcase function decorator """
    testcases.append(f)
    return f

@testcase
def testcase0(*args):
    print 'testcase0 called, args:', args

@testcase
def testcase1(*args):
    print 'testcase1 called, args:', args

@testcase
def testcase2(*args):
    print 'testcase2 called, args:', args

def assign_function_names(func_names, namespace=None):
    if namespace is None:
        namespace = sys._getframe(1).f_globals  # default to caller's globals
    for name, func in Zip(func_names, testcases):
        func.__= name  # optional
        namespace[name] = func

assign_function_names(["funcA", "funcB", "funcC"])

funcA(1, 2, 3)
funcB(4, 5)
funcC(42)
1
martineau

関数を本当に動的に作成するには、 makefun を使用できます。そのために開発しました。生成する署名を定義する3つの方法をサポートしています。

  • 'foo(a, b=1)'などの文字列表現
  • 別の関数でinspect.signatureを使用して手作りまたは派生したSignatureオブジェクト
  • 参照関数。その場合、公開された署名はこの関数の署名と同一になります。

さらに、作成された関数の参照を実装の最初の引数として注入するように指示して、呼び出しの発信元(どのファサード)に応じて動作を少し変更することができます。例えば:

# generic core implementation
def generic_impl(f, *args, **kwargs):
    print("This is generic impl called by %s" % f.__name__)
    # here you could use f.__in a if statement to determine what to do
    if f.__== "func1":
        print("called from func1 !")
    return args, kwargs

my_module = getmodule(generic_impl)

# generate 3 facade functions with various signatures
for f_name, f_params in [("func1", "b, *, a"),
                         ("func2", "*args, **kwargs"),
                         ("func3", "c, *, a, d=None")]:
    # the signature to generate
    f_sig = "%s(%s)" % (f_name, f_params)

    # create the function dynamically
    f = create_function(f_sig, generic_impl, inject_as_first_arg=True)

    # assign the symbol somewhere (local context, module...)
    setattr(my_module, f_name, f)

# grab each function and use it
func1 = getattr(my_module, 'func1')
assert func1(25, a=12) == ((), dict(b=25, a=12))

func2 = getattr(my_module, 'func2')
assert func2(25, a=12) == ((25,), dict(a=12))

func3 = getattr(my_module, 'func3')
assert func3(25, a=12) == ((), dict(c=25, a=12, d=None))

documentation でわかるように、絶対に不可能な場合(func2などのvar-positional signatures)を除き、引数は常にkwargsにリダイレクトされます。

0
smarie