Ввод в заблуждение (?) TypeError при передаче аргументов ключевого слова в функцию, определенную с помощью позиционных аргументов - PullRequest
8 голосов
/ 01 февраля 2010

В cPython 2.4:

def f(a,b,c,d):
    pass

>>> f(b=1,c=1,d=1)
TypeError: f() takes exactly 4 non-keyword arguments (0 given)

но:

>>> f(a=1,b=1,c=1)
TypeError: f() takes exactly 4 non-keyword arguments (3 given)

Очевидно, я на самом деле не понимаю механизм обработки аргументов Python. Кто-нибудь хочет поделиться этим светом? Я вижу, что происходит (что-то вроде заполнения слотов аргументов, затем сдачи), но я думаю, что это запутало бы новичка.

(также, если люди лучше задают ключевые слова - что-то вроде "кишки" - пожалуйста, пометите заново)

Ответы [ 2 ]

13 голосов
/ 01 февраля 2010

Когда вы говорите

def f(a,b,c,d):

вы говорите python, что f принимает 4 позиционных аргументов. Каждый раз, когда вы вызываете f, ему должно быть задано ровно 4 аргумента, и первое значение будет присвоено a, второе - b и т. Д.

Вам разрешено звонить f с чем-то вроде

f(1,2,3,4) или f(a=1,b=2,c=3,d=4), или даже f(c=3,b=2,a=1,d=4)

но во всех случаях должно быть указано ровно 4 аргумента.

f(b=1,c=1,d=1) возвращает ошибку, поскольку для a не было предоставлено значение. (0 дано) f(a=1,b=1,c=1) возвращает ошибку, поскольку для d не было предоставлено значение. (3 дано)

Количество приведенных аргументов указывает, как далеко Python прошел до того, как понял, что произошла ошибка.

Кстати, если вы скажете

def f(a=1,b=2,c=3,d=4):

тогда вы говорите python, что f принимает 4 необязательных аргументов. Если определенный аргумент не указан, то его значение по умолчанию предоставляется автоматически. Тогда вы могли бы сойти с звонка

f(a=1,b=1,c=1) или f(b=1,c=1,d=1)

0 голосов
/ 21 ноября 2012

Теоретически возможно обернуть получившуюся ошибку TypeError чем-то более понятным и информативным. Однако есть много мелких деталей, некоторые из которых я не знаю, как решить.

ПРИМЕЧАНИЕ: приведенный ниже код является едва работающим примером, а не полным решением.

try:
    fn(**data)
except TypeError as e:
    ## More-sane-than-default processing of a case `parameter ... was not specified`
    ## XXX: catch only top-level exceptions somehow?
    ##  * through traceback?
    if fn.func_code.co_flags & 0x04:  ## XXX: check
        # it accepts `*ar`, so not the case
        raise
    f_vars = fn.func_code.co_varnames
    f_defvars_count = len(fn.func_defaults)
    ## XXX: is there a better way?
    ##  * it catches `self` in a bound method as required. (also, classmethods?)
    ##  * `inspect.getargspec`? Imprecise, too (for positional args)
    ##  * also catches `**kwargs`.
    f_posvars = f_vars[:-f_defvars_count]
    extra_args = list(set(data.keys()) - set(f_vars))
    missing_args = list(set(f_posvars) - set(data.keys()))
    if missing_args:  # is the case, raise it verbosely.
        msg = "Required argument(s) not specified: %s" % (
          ', '.join(missing_args),)
        if extra_args:
            msg += "; additionally, there are extraneous arguments: %s" % (
              ', '.join(extra_args))
        raise TypeError(msg, e)
        #_log.error(msg)
        #raise
    raise
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...