Как определить порядок переданных аргументов ключевых слов? - PullRequest
1 голос
/ 14 июля 2020

Проблема:

Я хочу написать функцию общего назначения:

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

, которая возвращает кортеж с сохранением порядка аргументов ключевого слова 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,)

Как я могу этого добиться? Является ли такой код unpythoni c, и если да, то что мне следует использовать вместо него?

Основные цели - простота использования и удобочитаемость.

Почему?

Моя цель - использовать такую ​​функцию для преобразования между системами единиц измерения (например, СИ <--> имперские, но реальный вариант использования более сложен), где пользователь сможет интуитивно написать, например,

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)

независимо от того, как функция определена, и если количества являются числами с плавающей запятой, массивами и т. д. c.

Угловые случаи:

Хорошо реагировать на такое неправильное использование не требуется, но защита от ошибок - это бонус:

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

Ответы [ 2 ]

2 голосов
/ 16 июля 2020

@ 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)
1 голос
/ 14 июля 2020

Кажется, это соответствует всем вашим примерам, хотя я не уверен, каким должно быть поведение positional & keyword_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 заказаны.

...