Python 以编程方式创建函数规范
对于我自己的娱乐,我想知道如何实现以下目标:Python 以编程方式创建函数规范,python,python-3.x,Python,Python 3.x,对于我自己的娱乐,我想知道如何实现以下目标: functionA = make_fun(['paramA', 'paramB']) functionB = make_fun(['arg1', 'arg2', 'arg3']) 相当于 def functionA(paramA, paramB): print(paramA) print(paramB) def functionB(arg1, arg2, arg3): print(arg1) print(arg2)
functionA = make_fun(['paramA', 'paramB'])
functionB = make_fun(['arg1', 'arg2', 'arg3'])
相当于
def functionA(paramA, paramB):
print(paramA)
print(paramB)
def functionB(arg1, arg2, arg3):
print(arg1)
print(arg2)
print(arg3)
这意味着需要以下行为:
functionA(3, paramB=1) # Works
functionA(3, 2, 1) # Fails
functionB(0) # Fails
问题的焦点是变量argspec-I,它使用常用的装饰器技术创建函数体
对于那些感兴趣的人,我正在尝试通过编程方式创建如下类。同样,困难在于使用编程参数生成\uuuuu init\uuuuu
方法-使用装饰器或元类,类的其余部分看起来很简单
class MyClass:
def __init__(self, paramA=None, paramB=None):
self._attr = ['paramA', 'paramB']
for a in self._attr:
self.__setattr__(a, None)
def __str__(self):
return str({k:v for (k,v) in self.__dict__.items() if k in self._attributes})
可以使用从包含Python代码的字符串构造函数对象:
def make_fun(parameters):
exec("def f_make_fun({}): pass".format(', '.join(parameters)))
return locals()['f_make_fun']
例如:
>>> f = make_fun(['a', 'b'])
>>> import inspect
>>> print(inspect.signature(f).parameters)
OrderedDict([('a', <Parameter at 0x1024297e0 'a'>), ('b', <Parameter at 0x102429948 'b'>)])
>>f=make_fun(['a','b'])
>>>进口检验
>>>打印(检查签名(f).参数)
订单信息([('a',),('b',)]
如果需要更多功能(例如,默认参数值),则需要调整包含代码的字符串并使其表示所需的函数签名
免责声明:正如下面指出的,验证
参数的内容以及生成的Python代码字符串是否可以安全地传递给exec
非常重要。您应该自己构造参数
或设置限制,以防止用户使用类为参数
构造恶意值:
def make_fun(args_list):
args_list = args_list[:]
class MyFunc(object):
def __call__(self, *args, **kwargs):
if len(args) > len(args_list):
raise ValueError('Too many arguments passed.')
# At this point all positional arguments are fine.
for arg in args_list[len(args):]:
if arg not in kwargs:
raise ValueError('Missing value for argument {}.'.format(arg))
# At this point, all arguments have been passed either as
# positional or keyword.
if len(args_list) - len(args) != len(kwargs):
raise ValueError('Too many arguments passed.')
for arg in args:
print(arg)
for arg in args_list[len(args):]:
print(kwargs[arg])
return MyFunc()
functionA = make_fun(['paramA', 'paramB'])
functionB = make_fun(['arg1', 'arg2', 'arg3'])
functionA(3, paramB=1) # Works
try:
functionA(3, 2, 1) # Fails
except ValueError as e:
print(e)
try:
functionB(0) # Fails
except ValueError as e:
print(e)
try:
functionB(arg1=1, arg2=2, arg3=3, paramC=1) # Fails
except ValueError as e:
print(e)
下面是使用functools.wrap
的另一种方法,它至少在python 3中保留了签名和docstring。诀窍是在从未被调用的伪函数中创建签名和文档。这里有几个例子
基本示例
结果是:
>> inside template.
>> common_exposed_arg: 10 , common_exposed_kwarg: one
>> other_args: (-1,) , other_kwargs: {}
>>
>> inside template.
>> common_exposed_arg: 20 , common_exposed_kwarg: two
>> other_args: () , other_kwargs: {'other_exposed_kwarg': 'done'}
>>
>> exposed_func_1
>> exposed_func_1 docstring: this dummy function exposes the right signature
调用inspect.signature(exposed_func_1).parameters
返回所需的签名。但是,使用inspect.getfullargspec(exposed_func_1)
仍然返回模板的签名。至少,如果您在模板的定义中放置了所有要生成的函数的公共参数,这些参数就会出现
如果出于某种原因这是个坏主意,请让我知道
更复杂的例子
通过在更多包装中分层并在内部函数中定义更多不同的行为,您可以得到比这更复杂的结果:
import functools
def wrapper(inner_func, outer_arg, outer_kwarg=None):
def wrapped_func(f):
@functools.wraps(f)
def template(common_exposed_arg, *other_args, common_exposed_kwarg=None, **other_kwargs):
print("\nstart of template.")
print("outer_arg: ", outer_arg, " outer_kwarg: ", outer_kwarg)
inner_arg = outer_arg * 10 + common_exposed_arg
inner_func(inner_arg, *other_args, common_exposed_kwarg=common_exposed_kwarg, **other_kwargs)
print("template done")
return template
return wrapped_func
# Build two examples.
def inner_fcn_1(hidden_arg, exposed_arg, common_exposed_kwarg=None):
print("inner_fcn, hidden_arg: ", hidden_arg, ", exposed_arg: ", exposed_arg, ", common_exposed_kwarg: ", common_exposed_kwarg)
def inner_fcn_2(hidden_arg, common_exposed_kwarg=None, other_exposed_kwarg=None):
print("inner_fcn_2, hidden_arg: ", hidden_arg, ", common_exposed_kwarg: ", common_exposed_kwarg, ", other_exposed_kwarg: ", other_exposed_kwarg)
@wrapper(inner_fcn_1, 1)
def exposed_function_1(common_exposed_arg, other_exposed_arg, common_exposed_kwarg=None):
"""exposed_function_1 docstring: this dummy function exposes the right signature """
print("this won't get printed")
@wrapper(inner_fcn_2, 2, outer_kwarg="outer")
def exposed_function_2(common_exposed_arg, common_exposed_kwarg=None, other_exposed_kwarg=None):
""" exposed_2 doc """
pass
这有点冗长,但关键是当使用它来创建函数时,您(程序员)的动态输入在哪里,以及暴露的输入(来自函数的用户)在哪里使用,都有很大的灵活性。可能重复,或者我不要求传递可变数量的参数。我想以编程方式创建任何形式的函数规范。装饰器和元类可以用于以编程方式创建函数和类-但是我遇到的每个示例都有硬编码的函数规范。很好,请记住不要将其用于用户输入,否则可以这样使用:make_fun(['):\n import sys\n sys.exit()\n if(True'])()
并使您的代码放大:P@kroolik:是的,这是一个很好的观点。添加了一个免责声明以确保。确保构造的代码可以安全地执行确实很重要。这是可以接受的,因为这是最简单的解决方案,也是获得与手动定义函数相同行为的唯一可靠方法。我喜欢将此作为处理*arg和**kwarg的通用方法,但IMO使我正在寻找的用例过于复杂。虽然。。。构造和执行字符串确实有点笨拙!
import functools
def wrapper(inner_func, outer_arg, outer_kwarg=None):
def wrapped_func(f):
@functools.wraps(f)
def template(common_exposed_arg, *other_args, common_exposed_kwarg=None, **other_kwargs):
print("\nstart of template.")
print("outer_arg: ", outer_arg, " outer_kwarg: ", outer_kwarg)
inner_arg = outer_arg * 10 + common_exposed_arg
inner_func(inner_arg, *other_args, common_exposed_kwarg=common_exposed_kwarg, **other_kwargs)
print("template done")
return template
return wrapped_func
# Build two examples.
def inner_fcn_1(hidden_arg, exposed_arg, common_exposed_kwarg=None):
print("inner_fcn, hidden_arg: ", hidden_arg, ", exposed_arg: ", exposed_arg, ", common_exposed_kwarg: ", common_exposed_kwarg)
def inner_fcn_2(hidden_arg, common_exposed_kwarg=None, other_exposed_kwarg=None):
print("inner_fcn_2, hidden_arg: ", hidden_arg, ", common_exposed_kwarg: ", common_exposed_kwarg, ", other_exposed_kwarg: ", other_exposed_kwarg)
@wrapper(inner_fcn_1, 1)
def exposed_function_1(common_exposed_arg, other_exposed_arg, common_exposed_kwarg=None):
"""exposed_function_1 docstring: this dummy function exposes the right signature """
print("this won't get printed")
@wrapper(inner_fcn_2, 2, outer_kwarg="outer")
def exposed_function_2(common_exposed_arg, common_exposed_kwarg=None, other_exposed_kwarg=None):
""" exposed_2 doc """
pass