如何确定传递的关键字参数的顺序?

How to determine the order of passed keyword arguments?

问题:

我想写一个通用函数:

def foo(positional, a=None, b=None, c=None, *, keyword_only=True):
    # ... ?? ... magic_code
    return a_b_c_in_tuple_in_order

那个returns一个保留关键字参数顺序的元组a,b,c:

xx = 'some object'
>>> foo(xx, 1, 2, 3)
(1, 2, 3)
>>> foo(xx, 1, 2)
(1, 2)
>>> foo(xx, a=1, b=2, c=3, keyword_only=False)
(1, 2, 3)
>>> foo(xx, b=2, a=1, c=3)   # <---- key behaviour
(2, 1, 3)
>>> foo(xx, b=2, c=3)
(2, 3)
>>> foo(xx, c=3, a=1)
(3, 1)
>>> foo(xx, a='may be anything', c=range(5), b=[1, 2])
('may be anything', range(0, 5), [1, 2])
>>> foo(xx, b=1)
(1,)    # may be 1 or (1,)

我怎样才能做到这一点?这样的代码是不是 pythonic 的,如果是,我应该用什么来代替?

主要目标是易于使用和可读性。

为什么?

我的目标是使用这样的函数进行单位系统之间的转换(例如 SI <--> imperial,但实际用例更高级),用户可以直观地编写例如

l, (t1, t2) = convert(params, lengths=L, times=(T1, T2), normalized=True)
# or
(t1, t2), l = convert(params, times=(T1, T2), lengths=L, normalized=True)

无论函数如何定义,以及数量是否为浮点数、数组等

极端情况:

不需要很好地应对此类误用,但万无一失是加分项:

>>> foo(b=2, a=1, c=3, positional=xx)
(2, 1, 3)
>>> foo(b=2, positional=xx, a=1, keyword_only=False, c=3)
(2, 1, 3)

这似乎通过了你所有的例子,虽然我不确定 positionalkeyword_only 的行为应该是什么:

def foo(positional, *args, **kwargs):
    if args and kwargs:
        return args + tuple(v for k, v in kwargs.items() if k in {'a', 'b', 'c'})
    if args:
        return args
    if kwargs:
        return tuple(v for k, v in kwargs.items() if k in {'a', 'b', 'c'})

xx = 'obj'

print(foo(xx, 1, 2, 3))
print(foo(xx, 1, 2))
print(foo(xx, a=1, b=2, c=3, keyword_only=False))
print(foo(xx, b=2, a=1, c=3))
print(foo(xx, b=2, c=3))
print(foo(xx, c=3, a=1))
print(foo(xx, a='may be anything', c=range(5), b=[1, 2]))
print(foo(xx, b=1))
print(foo(b=2, a=1, c=3, positional=xx))
print(foo(b=2, positional=xx, a=1, keyword_only=False, c=3))
print(foo(xx, 1, c=2))

结果:

(1, 2, 3)
(1, 2)
(1, 2, 3)
(2, 1, 3)
(2, 3)
(3, 1)
('may be anything', range(0, 5), [1, 2])
(1,)
(2, 1, 3)
(2, 1, 3)
(1, 2)

它依赖于 python3 中的字典是有序的。

@rdas 差一点就搞定了 使用他们的答案作为装饰器可以保留原始函数签名并为您提供所需的数据:

kwargs_to_extract = {'a', 'b', 'c'}

def kwarg_tuple_returner(fn):
    def tuple_extractor(positional, *args, **kwargs):
        _unused_return = fn(positional, *args, **kwargs)
        if args and kwargs:
            return args + tuple(v for k, v in kwargs.items() if k in kwargs_to_extract)
        if args:
            return args
        if kwargs:
            return tuple(v for k, v in kwargs.items() if k in kwargs_to_extract)

    return tuple_extractor

@kwarg_tuple_returner
def foo(positional, a=None, b=None, c=None, *, keyword_only=True):
    # ... ?? ... magic_code
    # nothing below matters because we return our argument value from our decorator
    a = "mangled"
    b = 5
    c = 3.14
    return None

xx = 'obj'

print(foo(xx, 1, 2, 3))
print(foo(xx, 1, 2))
print(foo(xx, a=1, b=2, c=3, keyword_only=False))
print(foo(xx, b=2, a=1, c=3))
print(foo(xx, b=2, c=3))
print(foo(xx, c=3, a=1))
print(foo(xx, a='may be anything', c=range(5), b=[1, 2]))
print(foo(xx, b=1))
print(foo(b=2, a=1, c=3, positional=xx))
print(foo(b=2, positional=xx, a=1, keyword_only=False, c=3))
print(foo(xx, 1, c=2))

结果:

(1, 2, 3)
(1, 2)
(1, 2, 3)
(2, 1, 3)
(2, 3)
(3, 1)
('may be anything', range(0, 5), [1, 2])
(1,)
(2, 1, 3)
(2, 1, 3)
(1, 2)