поймать исключение неправильных аргументов, в общем случае - PullRequest
5 голосов
/ 21 сентября 2011

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

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

Как я могу реализовать функцию wrong_arguments ниже?

Пример:

try:
    return myfunc(*args)
except TypeError, error:
    #possibly wrong number of arguments
    #we know how to proceed if the error occurred when calling myfunc(), 
    #but we shouldn't interfere with errors in the implementation of myfunc
    if wrong_arguments(error, myfunc):
        return fixit()
    else:
        raise

Добавление:

Есть несколько решений, которые хорошо работают в простом случае, но ни один из текущих ответов не будет работать в реальном случае декорированных функций.

Учтите, что это возможные значения myfunc выше:

def decorator(func):
    "The most trivial (and common) decorator"
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)

def myfunc1(a, b, c='ok'):
    return (a, b, c)

myfunc2 = decorator(myfunc1)
myfunc3 = decorator(myfunc2)

Здесь даже терпит неудачу даже консервативный метод «посмотреть перед вами» (проверка спецификации аргумента функции), поскольку большинство декораторов будут иметь аргумент *args, **kwargs независимо от декорированной функции. Проверка исключений также кажется ненадежной, поскольку myfunc.__name__ будет просто «оберткой» для большинства декораторов, независимо от имени основной функции.

Есть ли хорошее решение, если функция может иметь или не иметь декораторы?

Ответы [ 8 ]

7 голосов
/ 21 сентября 2011

Вы можете сделать:

    try:
        myfunc()
    except IndexError:
        trace = sys.exc_info()[2]
        if trace.tb_next.tb_next is None:
            pass
        else:
            raise

Хотя это немного уродливо и может показаться нарушением инкапсуляции.

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

РЕДАКТИРОВАТЬ

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

4 голосов
/ 15 октября 2011

Я не фанат магии таким образом.Я подозреваю, что у вас скорее есть основная проблема проектирования.

- оригинальный ответ и код, который был слишком неопределенным для удаленной проблемы -

Редактировать после понимания конкретной проблемы:

from inspect import getargspec

def can_call_effectively(f, args):
    (fargs, varargs, _kw, df) = getattr(myfunc, 'effective_argspec', \
        getargspec(myfunc))
    fargslen = len(fargs)
    argslen = len(args)
    minargslen = fargslen - len(df)
    return (varargs and argslen >= minargslen) or minargslen <= argslen <= fargslen

if can_call_effectively(myfunc, args)
    myfunc(*args)
else:
    fixit()

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

Редактировать: больше кода, декоратор для прозрачных декораторов.

def transparent_decorator(decorator):
    def wrapper(f):
        wrapped = decorator(f)
        wrapped.__doc__ = f.__doc__
        wrapped.effective_argspec = getattr(f, 'effective_argspec', getargspec(f))
        return wrapped
    return wrapper

Используйте это на своем декораторе:

@transparent_decorator
def decorator(func):
"The most trivial (and common) decorator"
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    return wrapper  # line missing in example above

Теперь, если вы создадите myfunc1 - myfunc3, как указано выше, они будут работать точно так же, как и ожидалось.

2 голосов
/ 21 сентября 2011

вы можете сделать это, сделав что-то вроде

>>> def f(x,y,z):
    print (f(0))


>>> try:
    f(0)
except TypeError as e:
    print (e.__traceback__.tb_next is None)


True
>>> try:
    f(0,1,2)
except TypeError as e:
    print (e.__traceback__.tb_next is None)


False

но лучшим способом является подсчет количества аргументов функции и сравнение с ожидаемым числом аргументов

len(inspect.getargspec(f).args) != len (args)
2 голосов
/ 21 сентября 2011

Тьфу, к сожалению, не совсем. Лучше всего проанализировать возвращаемый объект ошибки и посмотреть, упоминается ли myfunc и количество аргументов.

Итак, вы бы сделали что-то вроде:

except TypeError, err:
    if err.has_some_property or 'myfunc' in str(err):
        fixit()
    raise
1 голос
/ 28 февраля 2015

Я знаю, что это старый пост, но я наткнулся на этот вопрос и позже с лучшим ответом. Этот ответ зависит от новой функции в Python 3, объекты подписи

С помощью этой функции вы можете написать:

sig = inspect.signature(myfunc)
try:
    sig.bind(*args)
except TypeError:
    return fixit()
else:
    f(*args)
1 голос
/ 13 октября 2011

Хорошо написанные оболочки сохранят имя функции, подпись и т. Д. Функций, которые они переносят;однако, если вам нужно поддерживать обертки, которые этого не делают, или если у вас есть ситуации, когда вы хотите перехватить ошибку в обертке (а не только в окончательной функции-обертке), то не существует общего решения, которое будет работать.

1 голос
/ 21 сентября 2011

Вы можете получить трассировку и посмотреть ее длину.Попробуйте:

import traceback as tb
import sys

def a():
    1/0

def b():
    a()

def c():
    b()

try:
    a()
except:
    print len(tb.extract_tb(sys.exc_traceback))

try:
    b()
except:
    print len(tb.extract_tb(sys.exc_traceback))

try:
    c()
except:
    print len(tb.extract_tb(sys.exc_traceback))

Это печатает

2
3
4
0 голосов
/ 19 октября 2011

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

Вместо этого звучит так, будто вы пытаетесь выполнить обработку ошибок способом C (без обработки исключений), где возвращаемое значение функции указывает либо на отсутствие ошибки (обычно 0), либо на ошибку (не 0 значение).Итак, я бы попробовал написать вашу функцию, чтобы она возвращала значение, и чтобы вызывающая сторона проверяла возвращаемое значение.

...