Предположим, что функция query определена так:

def query(how='Here or there'):
    print 'Would you like them\n%s?' % how

Затем, когда (неверное) выражение

query(how='With a mouse', who='Daniel', where='Anywhere')

Оценивается, Python автоматически выдает ошибку

Traceback (most recent call last):
  File "/tmp/seuss.py", line 4, in <module>
    query(how='With a mouse', who='Daniel', where='Anywhere')
TypeError: query() got an unexpected keyword argument 'who'

Теперь предположим, что мне нужно определить функцию reply, которая принимает ноль или более позиционных аргументов, а также необязательный именованный аргумент prefix. Итак, все приведенные ниже выражения должны быть действительными, по крайней мере, синтаксически:

reply('in a box', 'with a fox')
reply('In a house', 'With a mouse', preamble='I would not like them\n')
reply(preamble='I do not like them, Sam-i-am.')

Единственный способ, с помощью которого я знаю, реализовать такую функцию, это подписать (*args, **kwargs); к сожалению, с этой сигнатурой недопустимые выражения, такие как reply('green eggs', wot='and', wotelse='ham'), хороши для Python, а это значит, что мы должны выполнить эту проверку ошибок самостоятельно.

1

# check.py

def unexpected_kwargs(kwargs):
    unexpected = kwargs.keys()
    if len(unexpected) > 0:
        import inspect
        caller = inspect.getframeinfo(inspect.currentframe(1)).function
        raise TypeError("%s() got an unexpected keyword argument '%s'" %
                        (caller, unexpected[0]))

РЕДАКТИРОВАТЬ: Ради этого вопроса, пожалуйста, возьмите такой факторинг кода обработки ошибок, как данное .

Теперь мы можем реализовать reply(*args, **kwargs) следующим образом:

import check

def reply(*args, **kwargs):

    preamble = kwargs.pop('preamble', 'Not ')
    check.unexpected_kwargs(kwargs)

    # rest of function definition

... и оценка reply('green eggs', wot='and', wotelse='ham') результатов в

Traceback (most recent call last):
  File "/tmp/seuss.py", line 14, in <module>
    reply('green eggs', wot='and', wotelse='ham')
  File "/tmp/seuss.py", line 9, in reply
    check.unexpected_kwargs(kwargs)
  File "/tmp/check.py", line 8, in unexpected_kwargs
    (frameinfo.function, unexpected[0]))
TypeError: reply() got an unexpected keyword argument 'wot'

Это похоже на вывод ошибок, показанный ранее, но обратите внимание, что в этом выводе строка с ошибками (query(how='With a mouse', who='Daniel', where='Anywhere')) была напечатана в самом конце трассировки, тогда как во втором выводе сообщение об ошибке выводится несколькими строками выше. Остальная часть обратной трассировки, после ошибочной строки, представляет собой ненужный шум IMO, который служит только для того, чтобы скрыть ошибку, ответственную за сбой.

Итак, чтобы действительно повторить обработку Python неожиданного ключевого слова, вывод ошибки должен выглядеть следующим образом:

Traceback (most recent call last):
  File "/tmp/seuss.py", line 14, in <module>
    reply('green eggs', wot='and', wotelse='ham')
TypeError: reply() got an unexpected keyword argument 'wot'

Конечно, один из неверных способов добиться этого будет выглядеть так:

def unexpected_kwargs_NO_GOOD(kwargs):
    unexpected = kwargs.keys()
    if len(unexpected) > 0:
        import inspect
        import traceback
        import sys

        print >> sys.stderr, ('Traceback (most recent call last):\n%s' %
                              ''.join(traceback.format_stack()[:-2])),

        frameinfo = inspect.getframeinfo(inspect.currentframe(1))

        print >> sys.stderr, ('TypeError: '
                              "%s() got an unexpected keyword argument '%s'" %
                              (frameinfo.function, unexpected[0]))
        sys.exit(1)

Это приводит к правильному выводу, но не вызывает исключения, которое может перехватить исходный код. Вместо этого он безоговорочно завершает выполнение программы.

Есть ли способ получить желаемый результат и в то же время вызвать правильное исключение?


1 Можно выделить больше обработки аргументов, чем просто обработку ошибок, но, поскольку эта дополнительная функциональность является касательной к вопросу поста, я исключил ее из приведенного выше примера кода.

0
kjo 11 Дек 2016 в 19:14

3 ответа

Лучший ответ

Вы не можете вызвать исключение в той же точке, в которой Python его вызывает, нет. Просто создайте исключение, когда есть больше ключевых аргументов, чем вы можете обработать.

Это должно быть в порядке; вся информация для отладки неправильного вызова функции все еще присутствует в трассировке.

1
Martijn Pieters 11 Дек 2016 в 16:48

Если вы уже точно знаете, какие ключевые аргументы вы хотите принять, тогда вы вообще не хотите использовать **kwargs. Вы просто хотите использовать старые аргументы с ключевыми словами.

Похоже, вы ищете функцию, которая была добавлена в Python 3 (см. PEP 3102) для поддержки аргументов "только для ключевых слов": аргументы, которые никогда не будут автоматически заполнены позиционным аргументом.

>>> def reply(*args, preamble='Not '):
...     print(args)
...     print(preamble)
...     
>>> reply('arg1', 'arg2')
('arg1', 'arg2')
Not 
>>> reply('arg1', 'arg2', preamble='yeah')
('arg1', 'arg2')
yeah
>>> reply('arg1', 'arg2', preamble='yeah', wot='wat')
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-6-b16c25a18626> in <module>()
----> 1 reply('arg1', 'arg2', preamble='yeah', wot='wat')

TypeError: reply() got an unexpected keyword argument 'wot

То, что вы хотите, невозможно в Python 2. Обычный шаблон в коде Python 2 просто использует **kwargs и четко документирует принятые параметры в строке документации . Обычно мы просто игнорируем любые ложные аргументы, они не вызывают ошибку.

2
wim 11 Дек 2016 в 17:11

Я не одобряю всю эту проверку прототипов и магию аргументов, потому что она нарушает принцип KISS, однако я думаю, что есть способ реализовать (почти) точное поведение, которое вы хотите:

def reply(*args, **kwargs):
    try:
        my_arg_check(args, kwargs, *other_parameters)
    except MyArgCheckFailure as exc:
        raise TypeError(exc.my_custom_message)
0
Gribouillis 11 Дек 2016 в 16:50