【问题标题】:Programmatically create function specification以编程方式创建功能规范
【发布时间】:2014-11-18 05:42:30
【问题描述】:

为了我自己的娱乐,我想知道如何实现以下目标:

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 - 我很乐意使用通常的装饰器技术创建函数体。

对于那些感兴趣的人,我正在尝试以编程方式创建如下所示的类。同样,困难在于生成带有编程参数的__init__ 方法——类的其余部分使用装饰器或元类看起来很简单。

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 python-3.x


【解决方案1】:

您可以使用exec 从包含 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'>)])

如果您想要更多功能(例如,默认参数值),只需调整包含代码的字符串并让它代表所需的函数签名。

免责声明:如下所述,验证parameters 的内容以及生成的Python 代码字符串可以安全地传递给exec 非常重要。您应该自己构造parameters 或设置限制以防止用户为parameters 构造恶意值。

【讨论】:

  • 很好,请记住不要将它与用户输入一起使用,否则可能会像这样使用它:make_fun(['):\n import sys\n sys.exit()\n if (True'])() 并使您的代码爆炸:P
  • @kroolik:是的,这是一个很好的观点。添加免责声明以确保。确保构造的代码可以安全执行确实很重要。
  • 接受,因为这是最简单的解决方案 - 也是获得与手动定义函数相同的行为的唯一可靠方法。
【解决方案2】:

使用类的可能解决方案之一:

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)

【讨论】:

  • 我喜欢将其作为处理 *arg 和 **kwarg 的一般方法,但 IMO 使我正在寻找的用例过于复杂。虽然...构造和执行一个字符串确实有点笨拙!
【解决方案3】:

这是使用functools.wrap 的另一种方法,它至少在python 3 中保留签名和文档字符串。诀窍是在永远不会被调用的虚拟函数中创建签名和文档。这里有几个例子。

基本示例

import functools

def wrapper(f):
    @functools.wraps(f)
    def template(common_exposed_arg, *other_args, common_exposed_kwarg=None, **other_kwargs):
        print("\ninside template.")
        print("common_exposed_arg: ", common_exposed_arg, ", common_exposed_kwarg: ", common_exposed_kwarg)
        print("other_args: ", other_args, ",  other_kwargs: ", other_kwargs)
    return template

@wrapper
def exposed_func_1(common_exposed_arg, other_exposed_arg, common_exposed_kwarg=None):
    """exposed_func_1 docstring: this dummy function exposes the right signature"""
    print("this won't get printed")

@wrapper
def exposed_func_2(common_exposed_arg, common_exposed_kwarg=None, other_exposed_kwarg=None):
    """exposed_func_2 docstring"""
    pass

exposed_func_1(10, -1, common_exposed_kwarg='one')
exposed_func_2(20, common_exposed_kwarg='two', other_exposed_kwarg='done')
print("\n" + exposed_func_1.__name__)
print(exposed_func_1.__doc__)

结果是:

>> 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) 仍会返回template 的签名。至少,如果您在 template 的定义中添加任何您想要创建的所有函数共有的参数,它们就会出现。

如果由于某种原因这是一个坏主意,请告诉我!

更复杂的例子

通过在更多的包装器中分层并在内部函数中定义更多不同的行为,您可以得到比这更复杂的东西:

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

这有点冗长,但关键是在使用它来创建函数时,来自您(程序员)的动态输入的位置有很大的灵活性,因此暴露的输入(来自用户函数)被使用。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-08-18
    • 1970-01-01
    • 1970-01-01
    • 2010-10-02
    • 2010-09-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多