gpt4 book ai didi

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

转载 作者:太空宇宙 更新时间:2023-11-03 15:10:39 25 4
gpt4 key购买 nike

TL;TR 根据简单的规范,寻找习惯用法和模式,将位置参数和关键字参数解包为位置参数的有序序列,例如姓名列表。这个想法似乎类似于 scanf-like 解析。

我正在包装一个名为 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.foofoo 包装器实现此类参数处理:

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)

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

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

我还不知道问题 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 版本的所有优点:

  • 为调用者提供所需的灵 active 。

  • 紧凑的表格,减少复制和粘贴。

  • 位置参数的灵活协议(protocol):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])
  • 关键字参数的灵活协议(protocol):指定的关键字可能比实际使用的更多,或者字典中的项目可能比使用的更多。

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

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

最佳答案

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)

这可能被认为是最 pythonic 的实现方式,它充分利用了 Python 的元编程资源,并允许程序员在他们想要的任何地方使用这个 API,而不依赖于特定的 一些api

注意:这是否是最惯用的方式来做到这一点真的取决于意见。我个人认为这很好地遵循了“The Zen of Python”中提出的哲学,所以对我来说这是非常地道的。

关于python - 在 Python 中处理灵活的函数参数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/27271876/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com