Декоратор не работает должным образом с командой yield


Поскольку я очень часто использую один и тот же блок try-catch, я решил создать декоратор, который делает это автоматически.

def tryexc(func):
    @wraps(func)
    def wrapper(self, *args, **kwargs):
        try:
            func(self, *args, **kwargs)
        except Exception as e:
            mLib.log('EXCEPTION RAISED')
            mLib.log('ARGS:n'+'n'.join(str(x) for x in args))
            mLib.log(str(e))
            mLib.log(traceback.format_exc())
    return wrapper
Он действует правильно в большинстве ситуаций вместо тех, когда в методе используется выход.
class test_class():
    def __init__(self):
        self.text = 'TEST TEXT'

    @tryexc
    def x(self,a):
        print self.text 
        # yield self.text

    @tryexc
    def y(self,a):
        print list(self.x(5))

test_c = test_class()
test_c.y(5)

Когда yield self.text комментируется, все работает нормально. Текст напечатан. Но когда строка не комментируется, она ловит исключение.

    print list(self.x(5))
TypeError: 'NoneType' object is not iterable
Я не очень люблю декораторов, поэтому буду признателен за любой совет. список (self.x (5)) должно быть ['тест Текст'] на мой взгляд.
1 3

1 ответ:

Давайте возьмем простой пример, чтобы объяснить вашу проблему -

>>> def tryexc(func):
...     def wrapper(*args, **kwargs):
...         try:
...             func(*args, **kwargs)
...         except Exception as e:
...             print("Hmm", e)
...     return wrapper
...
>>> @tryexc
... def a():
...     return "Something"
...
>>> a()
>>>
Как вы можете видеть выше , функция a должна возвращать 'Something', но когда она была вызвана, она ничего не вернула. Почему?

Поскольку при вызове оформленной функции сначала вызывается оболочка, а затем эта оболочка вызывает фактическую функцию, когда фактическая функция возвращает что-то, оболочка должна вернуть это. Но в вашем случае этого не происходит. Так вот почему ты здесь получение None при вызове x() и это приводит к ошибке NoneType.

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

return func(*args, **kwargs)

Но в вашем случае, если вы просто возвращаете, то произойдет то , что он вернет объект генератора , сгенерированный при вызове func(), но если возникнет какое-либо исключение при вызове фактической функции (при итерации по объекту генератора), он не будет пойман вашим декоратором. Пример, чтобы показать, что -

>>> @tryexc
... def a():
...     for i in range(10):
...             yield i
...     raise Exception('Hmm123')
...
>>> list(a())
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 5, in a
Exception: Hmm123
Это происходит потому, что как только оболочка возвращает объект генератора, этот поток заканчивается, и мы больше не находимся внутри него. Для вашего случая генератора, что вам действительно нужно сделать, это создать другой декоратор, который бы дал результаты от объекта генератора, возвращенного func(). Пример / Демо -
def tryexcgenerator(func):
    def wrapper(*args, **kwargs):
        try:
            for i in func(*args, **kwargs):
                yield i
        except Exception as e:
            print("Hmm", e)
    return wrapper
>>> @tryexcgenerator
... def a():
...     for i in range(10):
...             yield i
...     raise Exception('Hmm123')
...
>>> list(a())
Hmm Hmm123
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

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