编程语言
首页 > 编程语言> > 在Python中处理灵活的函数参数

在Python中处理灵活的函数参数

作者:互联网

TL; TR根据简单的规范,例如,寻找习语和模式,以将位置和关键字参数解压缩为位置参数的有序序列名称列表.这个想法似乎类似于类似scanf的解析.

我包装了一个名为someapi的Python模块的函数.
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)

这是我为调用someapi.foo的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)

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

>我能以更多的Python惯用方式做得更好吗?
>我有几十个要包装的someapi函数,那么如何避免在每个包装器中复制和调整BEGIN / END标记之间的整个块?

我不知道问题1的答案.

但是,这是我要解决的问题2.

因此,我基于简单的名称规范为参数定义了通用处理程序.
这些名称根据实际的包装程序调用指定了几件事:

>要从* args解压缩多少个参数? (请参阅下面的len(名称)测试)
>在** 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吗?

另外,我想请我对解决方案进行审查:您看到任何错误吗?我有没有忽略什么?如何改善呢?

解决方法:

Python是一种非常强大的语言,它允许您以所需的任何方式来操作代码,但是要了解自己的工作很困难.为此,您可以使用检查模块.下面是一个如何在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的禅宗》中提出的哲学,因此对我来说这是非常习惯的.

标签:python-3-x,arguments,idioms,python,design-patterns
来源: https://codeday.me/bug/20191028/1955891.html