在Python中处理灵活的函数参数 [英] Handling flexible function arguments in Python

查看:139
本文介绍了在Python中处理灵活的函数参数的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

TL; TR 寻找成语和模式,根据简单的规范,例如,将位置和关键词参数解压缩到有序序列中。名单清单。这个想法看起来类似于类似于scanf的解析。



我正在包装Python模块的函数,名为 someapi
在大多数情况下, someapi 的功能只能期望位置参数,这在数字中是有困难的。
我想让呼叫者具有如何将参数传递给我的包装器的灵活性。
这是我想允许的包装器调用的示例:

 #foo调用someapi.foo() 
foo(1,2,3,4)
foo(1,2,3,4,5)#,但前进只有第1 4到someapi.foo
foo([1,2 ,3,4])
foo([1,2,3,4,5,6])#,但前进只有第1 4到someapi.foo
foo({'x':1, y':2,'z':3,'r':4})
foo(x = 1,y = 2,z = 3,r = 4)
foo(a = b = 0,x = 1,y = 2,z = 3,r = 4)#但只向前x,y,z,r someapi.foo

我没有看到需要支持混合位置和关键字参数的复杂情况:

 code> foo(3,4,x = 1,y = 2)

我的第一个针对 foo 包装器调用 someapi.foo

  def foo(* args,** kwargs):
#BEGIN参数un / re-packing
a =无
kwa =无
如果len(args)> 1:
#foo(1,2,3,4)
a = args
elif len(args)== 1:
if isinstance(args [0],(list ,元组))和len(args [0])> 1:
#foo([1,2,3,4])
a = args [0]
如果isinstance(args [0],dict):
#foo {'x':1,'y':2,'z':3,'r':4})
kwa = args [0]
else:
#foo = 1,y = 2,z = 3,r = 4)
kwa = kwargs

如果a:
(x,y,z,r)= a
elif kwa:
(x,y,z,r)=(kwa ['x'],kwa ['y'],kwa ['z'],kwa ['r'])
else:
raise ValueError(invalid arguments)
#END参数un / re-packing

#使呼叫转移解压缩参数
someapi.foo x,y,z,r)

它按照预期完成了这项工作,据我所知但是它有两个问题:


  1. 我可以在更多的 Python惯用时尚中做得更好吗? li>
  2. 我有十几个 someapi 函数要包装,所以如何避免在 B之间复制和调整整个块EGIN / END 在每个包装器中标记?

我不知道问题1的答案, / p>

然而,这是我尝试解决问题2。



所以,我定义了一个通用处理程序基于名称的简单规范的参数。
名称指定几件事情,具体取决于实际的包装器调用:




  • * args 中解压缩多少个参数? (参见 len(name) test below)

  • ** kwargs ? (请参见下面的生成器表达式返回元组)



这是新版本:

  def unpack_args名称,* args,** kwargs):
a =无
kwa =无
如果len(args)> = len(名称):
#foo(1,2 ,3,4 ...)
a = args
elif len(args)== 1:
如果isinstance(args [0],(list,tuple))和len(args [ 0])> = len(name):
#foo([1,2,3,4 ...])
a = args [0]
if isinstance(args [0 ],dict):
#foo({'x':1,'y':2,'z':3,'r':4 ...})
kwa = args [0 ]
else:
#foo(x = 1,y = 2,z = 3,r = 4)
kwa = kwargs
如果a:
返回
elif kwa:
如果全部(名称中的名称为kwa.keys()):
return(名称中为n的kwa [n])
else:
raise ValueError(missing keys:,\
[name name in name if name not in kwa.keys()])
else:
raise ValueError(invalid参数)

这允许我以下列方式实现包装函数:

  def bar(* args,** kwargs):
#根据给定的名称
取消/重新包装
zargs = unpack_args(('a','b','c','d','e','f'),* args,** kwargs)
#使呼叫转移解包参数
someapi.bar(* zargs)

我认为我已经实现了所有优于 foo 以上我正在寻找的版本:




  • 使呼叫者具有所要求的灵活性


  • 压缩格式,减少复制粘贴。


  • 灵活位置参数协议: bar 可以用7,8和更多的p调用声明参数或一长串数字,但仅考虑前6项。例如,它将允许迭代处理长列表的数字(例如,考虑几何坐标):




 #meaw预期2个数字
n = [1,2,3,4,5,6,7,8]
对于我在范围(0,len(n),2 ):
meaw(n [i:i + 2])




  • 关键字参数的灵活协议:可以指定比实际使用的更多的关键字,或者字典可以有比使用更多的项目。



获取回到上面的问题1,我可以做得更好,使它变得更加Pythonic吗?



另外,我想要求我的解决方案的审查:你看到任何错误?我忽略了什么吗?如何改进?

解决方案

Python是一种非常强大的语言,允许您以任何方式操作代码,但了解什么你在做是很难的为此,您可以使用 inspect 模块。所以一个如何在 someapi 中包装函数的例子。 我只想在这个例子中考虑位置参数,你可以直观地说明如何进一步扩展。你可以这样做:

  import inspect 
import someapi

def foo(args *):
argspec = inspect.getargspec(someapi.foo)

如果len(args)> len(argspec.args):
args = args [:len(argspec.args)]

返回someapi.foo(* args)

这将检测给出 foo 的参数数量是否太多,如果是这样将摆脱过多的论据。另一方面,如果参数太少,那么只会执行任何操作,并让 foo 处理错误。



现在让它变得更多的是pythonic。使用相同模板封装许多函数的理想方法是使用装饰器语法(假设您熟悉此主题,如果您想了解更多信息,请参阅 http://www.python.org/doc )。虽然由于装饰器语法主要用于开发中的功能而不是封装另一个API,但我们将制作一个装饰器,但将其作为工厂(工厂模式)用于我们的API。要使这个工厂,我们将利用 functools 模块来帮助我们(因此,包装功能看起来应该)。所以我们可以将我们的例子变成:

  import inspect 
import functools
import someapi

def my_wrapper_maker(func):
@ functools.wraps(func)
def wrapper(args *):
argspec = inspect.getargspec(func)

如果len(args)> len(argspec.args):
args = args [:len(argspec.args)]

return func(* args)
return wrapper

foo = my_wrapper_maker(someapi.foo)

最后,如果 someapi 有一个相对较大的API可能会在版本之间改变(或者我们只是想让我们的源文件更加模块化,因此它可以包装任何API),那么我们可以自动执行 my_wrapper_maker 由模块导出的所有内容 someapi 。我们会这样做:

  __ all__ = ['my_wrapper_maker'] 

#添加整个API的someapi到我们的程序。
在someapi中的func .__ all__:
#只添加绑定功能。
如果可调用(getattr(someapi,func)):
globals()[func] = my_wrapper_maker(getattr(someapi,func))
__all __。append(func)

这可能被认为是实现这一点的最大的pythonic 方式,它充分利用了Python的meta-编程资源,并允许程序员在他们想要的地方使用这个API,而不依赖于具体的 someapi



注意:这是否是最常用的方式是真正的意见。我个人认为,这符合Python的禅中阐述的理念,所以对我来说这是非常惯用的。


TL;TR Looking for idioms and patterns to unpack positional and keyword arguments into ordered sequence of positional arguments, based on simple specification, e.g. a list of names. The idea seems similar to scanf-like parsing.

I'm wrapping functions of a Python module, called someapi. Functions of someapi only expect positional arguments, which are in pain numbers in most cases. I'd like to enable callers with flexibility of how they can pass arguments to my wrappers. Here are examples of the wrappers invocations I'd like to allow:

# foo calls someapi.foo()
foo(1, 2, 3, 4)
foo(1, 2, 3, 4, 5) # but forward only 1st 4 to someapi.foo
foo([1, 2, 3, 4])
foo([1, 2, 3, 4, 5, 6]) # but forward only 1st 4 to someapi.foo
foo({'x':1, 'y':2, 'z':3, 'r':4})
foo(x=1, y=2, z=3, r=4)
foo(a=0, b=0, x=1, y=2, z=3, r=4) # but forward only x,y,z,r someapi.foo

I don't see any need to support convoluted case of mixed positional and keyword arguments:

foo(3, 4, x=1, y=2)

Here is my first stab at implementing such arguments handling for the foo wrapper calling someapi.foo:

def foo(*args, **kwargs):
    # BEGIN arguments un/re-packing
    a = None
    kwa = None
    if len(args) > 1:
        # foo(1, 2, 3, 4)
        a = args
    elif len(args) == 1:
        if isinstance(args[0], (list, tuple)) and len(args[0]) > 1:
            # foo([1, 2, 3, 4])
            a = args[0]
        if isinstance(args[0], dict):
            # foo({'x':1, 'y':2, 'z':3, 'r':4})
            kwa = args[0]
    else:
        # foo(x=1, y=2, z=3, r=4)
        kwa = kwargs

    if a:
        (x, y, z, r) = a
    elif kwa:
        (x, y, z, r) = (kwa['x'], kwa['y'], kwa['z'], kwa['r'])
    else:
        raise ValueError("invalid arguments")
    # END arguments un/re-packing

    # make call forwarding unpacked arguments 
    someapi.foo(x, y, z, r)

It does the job as expected, as far as I can tell, but it there are two issues:

  1. Can I do it better in more Python idiomatic fashion?
  2. I have dozen(s) of someapi functions to wrap, so how to avoid copying and adjusting the whole block between BEGIN/END marks in every wrapper?

I don't know the answer for the question 1, yet.

Here, however, is my attempt to address the issue 2.

So, I defined a generic handler for arguments based on the simple specification of names. The names specify a couple of things, depending on the actual wrapper invocation:

  • How many arguments to unpack from *args? (see len(names) test below)
  • What keyword arguments are expected in **kwargs? (see generator expression returning tuple below)

Here is new version:

def unpack_args(names, *args, **kwargs):
    a = None
    kwa = None
    if len(args) >= len(names):
        # foo(1, 2, 3, 4...)
        a = args
    elif len(args) == 1:
        if isinstance(args[0], (list, tuple)) and len(args[0]) >= len(names):
            # foo([1, 2, 3, 4...])
            a = args[0]
        if isinstance(args[0], dict):
            # foo({'x':1, 'y':2, 'z':3, 'r':4...})
            kwa = args[0]
    else:
        # foo(x=1, y=2, z=3, r=4)
        kwa = kwargs
    if a:
        return a
    elif kwa:
        if all(name in kwa.keys() for name in names):
            return (kwa[n] for n in names)
        else:
            raise ValueError("missing keys:", \
                [name for name in names if name not in kwa.keys()])
    else:
        raise ValueError("invalid arguments")

This allows me to implement the wrapper functions in the following way:

def bar(*args, **kwargs):
    # arguments un/re-packing according to given of names
    zargs = unpack_args(('a', 'b', 'c', 'd', 'e', 'f'), *args, **kwargs)
    # make call forwarding unpacked arguments 
    someapi.bar(*zargs)

I think I have achieved all the advantages over the foo version above that I was looking for:

  • Enable callers with the requested flexibility.

  • Compact form, cut down on copy-and-paste.

  • Flexible protocol for positional arguments: bar can be called with 7, 8 and more positional arguments or a long list of numbers, but only first 6 are taken into account. For example, it would allow iterations processing long list of numbers (e.g. think of geometry coordinates):

    # meaw expects 2 numbers
    n = [1,2,3,4,5,6,7,8]
    for i in range(0, len(n), 2):
        meaw(n[i:i+2])

  • Flexible protocol for keyword arguments: more keywords may be specified than actually used or dictionary can have more items than used.

Getting back to the question 1 above, can I do better and make it more Pythonic?

Also, I'd like to ask for review of my solution: you see any bugs? have I overlooked anything? how to improve it?

解决方案

Python is a very powerful language that allows you manipulate code in any way you want, but understanding what you're doing is hard. For this you can use the inspect module. So an example of how to wrap a function in someapi. I'll only consider positional arguments in this example, you can intuit how to extend this further. You can do it like this:

import inspect
import someapi

def foo(args*):
    argspec = inspect.getargspec(someapi.foo)

    if len(args) > len(argspec.args):
        args = args[:len(argspec.args)]

    return someapi.foo(*args)

This will detect if the number of arguments given to foo is too many and if so, it will get rid of the excess arguments. On the other hand, if there are too few arguments then it will just do nothing and let foo handle the errors.

Now to make it more pythonic. The ideal way to wrap many functions using the same template is to use decorator syntax (familiarity with this subject is assumed, if you want to learn more then see the docs at http://www.python.org/doc). Although since decorator syntax is mostly used on functions that are in development rather than wrapping another API, we'll make a decorator but just use it as a factory (the factory pattern) for our API. To make this factory we'll make use of the functools module to help us out (so the wrapped function looks as it should). So we can turn our example into:

import inspect
import functools
import someapi

def my_wrapper_maker(func):
    @functools.wraps(func)
    def wrapper(args*):
        argspec = inspect.getargspec(func)

        if len(args) > len(argspec.args):
            args = args[:len(argspec.args)]

        return func(*args)
    return wrapper

foo = my_wrapper_maker(someapi.foo)

Finally, if someapi has a relatively large API that could change between versions (or we just want to make our source file more modular so it can wrap any API) then we can automate the application of my_wrapper_maker to everything exported by the module someapi. We'll do this like so:

__all__ = ['my_wrapper_maker']

# Add the entire API of someapi to our program.
for func in someapi.__all__:
    # Only add in bindings for functions.
    if callable(getattr(someapi, func)):
        globals()[func] = my_wrapper_maker(getattr(someapi, func))
        __all__.append(func)

This probably considered the most pythonic way to implement this, it makes full use of Python's meta-programming resources and allows the programmer to use this API everywhere they want without depending on a specific someapi.

Note: Whether this is most idiomatic way to do this is really up to opinion. I personally believe that this follows the philosophy set out in "The Zen of Python" quite well and so to me it is very idiomatic.

这篇关于在Python中处理灵活的函数参数的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆