我想定義一個make_function回傳新函式的函式。它將arg_names新函式的引數名稱串列和要在新函式inner_func定義中使用的函式作為引數。inner_func在這個簡單的例子中,新函式只會將 5 添加到 的輸出中。
我嘗試使用eval():
def make_function(args_names: List[str], inner_func: Callable):
args_str = str.join(", ", args_names)
expr: str = (
"lambda "
args_str
": "
inner_func.__name__
"("
args_str
") 5"
)
return eval(expr)
這行得通。但eval不推薦使用。這一次,除錯起來并不容易。此外,在我的用例中,我需要從不可用的地方呼叫新函式inner_func,這會引發錯誤。還有其他選擇嗎?
uj5u.com熱心網友回復:
也許exec實際上是最好的方法。eval/如果輸入字串是任意/不可信的exec,通常不推薦使用。但是,如果最終傳遞給編譯器的代碼字串也是由您生成的,而不是由用戶直接提供的,那就可以了。有一些使用這種方法的 stdlib 示例:使用和使用,沒有人知道如何利用它們(還)。namedtupleevaldataclassexec
現在,我認為在您的情況下,只需驗證args_names傳入的確實是 arg 名稱串列,而不是一些任意 Python 代碼,就可以很容易地進行安全的代碼生成 exec。
from textwrap import dedent
from typing import List, Callable
def make_function(args_names: List[str], inner_func: Callable):
for arg_name in args_names:
if not arg_name.isidentifier():
raise Exception(f"Invalid arg name: {arg_name}")
args = ', '.join(args_names)
code_str = dedent(f"""\
def outer_func({args}):
return inner_func({args}) 5
""")
scope = {"inner_func": inner_func}
exec(code_str, scope)
return scope["outer_func"]
演示:
>>> def orig(a, b):
... return a b 1
...
>>> func = make_function(args_names=["foo", "bar"], inner_func=orig)
>>> func(2, 3)
11
>>> func(foo=2, bar=3)
11
>>> func(foo=2, bar=3, baz=4)
TypeError: outer_func() got an unexpected keyword argument 'baz'
>>> func(foo=2)
TypeError: outer_func() missing 1 required positional argument: 'bar'
根據需要,即使對 的本地參考inner_func不再可用,它也會繼續作業,因為我們確保該參考在代碼生成期間可用:
>>> del orig
>>> func(foo=2, bar=3)
11
不允許使用惡意的“引數名稱”:
>>> make_function(["foo", "bar", "__import__('os')"], orig)
Exception: Invalid arg name: __import__('os')
對于不使用代碼生成的方法,也可以types.FunctionType直接實體化。為此,您需要向它傳遞一個types.CodeType實體,這很難手動創建。這些是公開的/記錄在案的,但代碼型別的檔案字串甚至試圖嚇跑你:
>>> ((lambda: None).__code__.__doc__)
'Create a code object. Not for the faint of heart.'
如果您想嘗試它,請參閱如何在 python 中創建代碼物件?但我想你會發現使用eval, execorcompile更有說服力。
轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/526793.html
