如何在适当的位置键入转换函数的多个参数?

How can I type convert many arguments of a function in place?

上下文

我使用 CherryPy 来提供一个简单的网页,该网页根据 URL 参数显示不同的内容。具体来说,它采用参数的总和并基于此显示不同的消息。 在 CherryPy 中,网页可以定义为函数,并且 URL 参数作为参数传递给该函数。
由于 explained in this tutorial URL 参数作为字符串传递,因此为了计算总和,我想将每个参数转换为浮点数。我会有很多 URL 个参数,所以一个一个地做这个看起来很麻烦。

如何在适当的位置键入转换(大量)参数?

我试过的

愚蠢

“愚蠢”的方法是简单地获取每个参数并将其重新分配为浮点数:

def dumb(a="0", b="0", c="0", d="0", e="0", f="0", g="0"):
    a = float(a)
    b = float(b)
    c = float(c)
    d = float(d)
    e = float(e)
    f = float(f)
    g = float(g)

    return print(sum([a, b, c, d, e, f, g]))

它是可读的,但相当重复并且不是很“pythonic”。

循环 locals()

我发现的另一个选择是将局部变量重新分配给字典,然后遍历它并调用字典中的值。

def looping_dict(a="0", b="0", c="0", d="0", e="0", f="0", g="0"):
    args = locals()
    for key in args:
        if key in ["a", "b", "c", "d", "e", "f", "g"]:
            args[key] = float(args[key])

    return print(sum([args["a"], args["b"], args["c"], args["d"], args["e"], args["f"], args["g"]] ) )

这有点烦人,因为我每次都得查字典。所以一个简单的引用 d 变成了 args["d"]。对代码可读性也没有帮助。

这是我以前用过的一个 @convert 装饰器(最初灵感来自 ):

import functools, inspect

def convert(*to_convert, to):
    def actual_convert(fn):
        arg_names = inspect.signature(fn).parameters.keys()
        @functools.wraps(fn)
        def wrapper(*args, **kwargs):
            args_converted = [to(arg) if arg_name in to_convert else arg
                               for arg, arg_name in zip(args, arg_names)]
            kwargs_converted = {kw_name: to(val) if kw_name in to_convert else val
                                 for kw_name, val in kwargs.items()}
            return fn(*args_converted, **kwargs_converted)
        return wrapper
    return actual_convert

@convert('a', 'c', 'd', to=str)
def f(a, b, c=5, *, d, e=0):
    return a, b, c, d, e

print(f(1, 2, d=7))

# Output: ('1', 2, 5, '7', 0)
# Passed params `a` and `d` got changed to `str`,
# however `c` used the default value without conversion

它使用 inspect.signature 来获取非关键字参数名称。我不确定 CherryPy 如何传递参数或如何获取名称,但这可能是一个良好的开端。使用 functools.wraps 很重要 - 它确保保留原始签名函数签名,这对于 CherryPy 似乎很重要。

这仅记录在变更日志中,但自 2016 年以来 with cherrypy >= 6.2.0 there is a @cherrypy.tools.params tool 完全按照您的意愿行事(前提是您使用支持类型注释的 Python 3 版本):

import cherrypy


@cherrypy.tools.params()
def your_http_handler(
        a: float = 0, b: float = 0,
        c: float = 0, d: float = 0,
        e: float = 0, f: float = 0,
        g: float = 0,
):
    return str(a + b + c + d + e + f + g)

添加它的 PR 是 PR #1442 — 您可以通过查看那里的测试来探索用法。

如果您的 Python 由于某种原因变旧了,您可以这样做:

import cherrypy


def your_http_handler(**kwargs):
    # Validate that the right query args are present in the HTTP request:
    if kwargs.keys() ^ {'a', 'b', 'c', 'd', 'e', 'f', 'g'}:
        raise cherrypy.HTTPError(400, message='Got invalid args!')

    numbers = (float(num) for num in kwargs.values())  # generator expression won't raise conversion errors here
    try:
        return str(sum(numbers))  # this will actually call those `float()` conversions so we need to catch a `ValueError`
    except ValueError as val_err:
        raise cherrypy.HTTPError(
            400,
            message='All args should be valid numbers: {exc!s}'.format(exc=val_err),
        )

P.S。在您最初的 post 中,您使用 return print(...) 这是错误的。 print() 总是 returns None 所以你会把 "None" 发送回 HTTP 客户端,而 print(arg) 的参数只会在你的终端打印出来你 运行 服务器。