2014-12-03 74 views
3

TL; TR寻找成语和模式来解压缩位置和关键字参数到有序的位置参数序列,基于简单的说明书中,例如名单列表。这个想法似乎相似,类似scanf解析。灵活处理函数参数在Python

我包装一个Python模块的功能,称为someapi。的someapi 功能只期望位置参数,这是在疼痛数字在大多数情况下。 我想能够与他们如何将参数传递给我的包装的灵活性来电。 下面是我想使包装的调用示例:

# 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 

我看不出有任何需要支持的混合位置和关键字参数错综复杂的情况下:

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

这里是我的在实施这样的论点处理为foo包装调用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) 

它的工作为e xpected,据我所知道的,但它有两个问题:

  1. 我可以做更多的Python的成语方式更好?
  2. 我有12打someapi函数来包装,所以如何避免复制和调整整个块之间的BEGIN/END标记在每个包装?

我不知道这个问题1的答案,但。

然而,这里是我试图解决2

所以,我定义的基础上,names简单的规格参数的通用处理的问题。 的names指定几件事情,这取决于实际的包装调用:

  • 多少个参数从*args解压? (见下文len(names)测试)
  • 预计**kwargs什么关键字参数?(见下文generator expression返回数组)

这里是新版本:

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") 

这让我实现以下面的方式包装功能:

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) 

我觉得我已经实现了与我正在寻找的foo版本相比的所有优点:

  • 以请求的灵活性启用呼叫者。

  • 紧凑的形式,减少了复制和粘贴。

  • 灵活的位置参数协议:bar可以用7,8个以上的位置参数或一长串数字来调用,但只考虑前6个参数。例如,它将允许迭代处理数字的长列表(例如认为几何坐标):

# 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]) 
  • 灵活的协议为关键字参数:多个关键字可能超过指定实际使用或字典可以有更多的项目比使用。

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

此外,我想问我的解决方案的审查:你看到任何错误?我忽视了什么?如何改善它?

+6

这是一个奇怪的想要的行为。作为调用者,如果我用'foo(1,2,3,4,5)'调用一个函数'我会惊讶地发现我的一个论点被忽略了。例如,我不会想到调用'foo(1,2,3)'并且有第4个默认参数,但删除参数很奇怪。你为什么期望这个API的用户使用不正确的参数数目调用函数? – CoryKramer 2014-12-03 12:39:40

+0

@Cyber​​我理解你的推理。两件事:1)这样的扩展协议是次要优势2),但如果可用的话,它有用例。我用'meaw'函数添加了一个例子来说明一个这样的用例。所以,这不是关于无视论证,而是我更多地将其视为隐式切片。 Ceratinly,它必须记录下来,以便我的包装的用户知道这种功能。 – mloskot 2014-12-03 13:14:16

+1

如果用户添加位置和关键字参数,会发生什么情况? “无效论据”我猜? – KurzedMetal 2014-12-03 13:25:01

回答

4

Python是一种非常强大的语言,它允许您以任何想要的方式操纵代码,但理解您正在做的事情非常困难。为此,您可以使用inspect模块。所以如何在someapi中包装一个函数的例子。 我只会在这个例子中考虑位置参数,你可以直觉如何进一步扩展它。你可以这样说:

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) 

这将检测的给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) 

     if 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'] 

# 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) 

这可能被认为是最Python的实现这个方式,它充分利用Python的元编程资源,允许程序员,到处都希望在不使用该API取决于具体的someapi

注意:是否这是最惯用方式做到这一点真的取决于意见。我个人认为,这遵循了“Python的禅宗”中阐述的哲学,并且对我来说非常地道。

+0

我很高兴接受你的回答。您提出的解决方案对我来说相当新颖,尤其是在这种情况下使用检查。当然,我认为它是我提出的优雅和Pythonic替代品。如果它是惯用的,我没有足够的经验来判断,但我很满意。谢谢! – mloskot 2014-12-08 15:30:36