2

我正在尝试使用 https://github.com/bottlepy/bottle/commit/fa7733e075da0d790d809aa3d2f53071897e6f76 来decorator捕获cached_property 异常

https://pypi.python.org/pypi/cached-property

我想做如下简单的事情,但这不起作用

from pprint import pprint
import time
from cached_property import cached_property

class MyException(Exception):
    pass

def catch_my_exceptions(fn):
    def wrapped(*args, **kwargs):
        try:
            return fn(*args, **kwargs)
        except MyException as e:
            cls = args[0]
            err = 'Found error at {}: {}'.format(time.asctime(), e)
            cls.error_msgs.append(err)
            print(err)
            return
    return wrapped

class Foo(object):
    def __init__(self):
        self.vars = {}

    @cached_property
    @catch_my_exceptions
    def is_cache_working(self):
        self.vars[time.asctime()] = True
        time.sleep(3)
        print('running cache runner')
        return time.asctime()

fo = Foo()
for i in range(3):
    print(fo.is_cache_working)
    pprint(fo.vars)


# This doesn't trigger caching

running cache runner
Thu Feb 23 21:45:15 2017
{'Thu Feb 23 21:45:11 2017': True}
running cache runner
Thu Feb 23 21:45:18 2017
{'Thu Feb 23 21:45:11 2017': True, 'Thu Feb 23 21:45:15 2017': True}
running cache runner
Thu Feb 23 21:45:21 2017
{'Thu Feb 23 21:45:11 2017': True,
 'Thu Feb 23 21:45:15 2017': True,
 'Thu Feb 23 21:45:18 2017': True}



# Current solution that works:

我对此的破解是执行以下操作。有人可以建议我一个更好的方法。还有我如何将例外列表传递给这个my_cached_decorator

import time
from pprint import pprint
from cached_property import cached_property

class MyException(Exception):
    pass

class my_cached_property(cached_property):
    def __init__(self, func):
        super(self.__class__, self).__init__(func)

    def __get__(self, obj, cls):
        try:
            super(self.__class__, self).__get__(obj, cls)
        except MyException as e:
            err = 'Found error at {}: {}'.format(time.asctime(), e)
            print(err)
            value = obj.__dict__[self.func.__name__] = None
            return value

class Foo(object):
    def __init__(self):
        self.vars = {}

    @my_cached_property
    def is_cache_working(self):
        self.vars[time.asctime()] = True
        time.sleep(3)
        print('running cache runner')
        raise MyException('fooobar')
        return time.asctime()

fo = Foo()
for i in range(3):
    print(fo.is_cache_working)
    pprint(fo.vars)
4

2 回答 2

1

好吧,我发现了问题所在,这就是 cached_property 的工作方式。为了缓存它,它将值以与其包装的函数相同的名称写入实例。问题是它包装的函数的名称具有来自您的装饰器的名称“包装”。因此,如果您在初始 fo.is_cache_working 之后访问 fo.wrapped,您将获得缓存结果。

没有简单的方法将这两种想法混合在一起。最简单的解决方案是编写自己的 cached_property 来存储自己的值:

class cached_property(object):
    def __init__(self, func):
        self.func = func
        # you can store other function attributes here - such as __doc__ - if you want
        self.values = {}

    def __get__(self, instance, owner):
        if instance in self.values:
            return self.values[instance]
        else:
            value = self.values[instance] = self.func(instance)
            return value
于 2017-02-27T08:17:48.477 回答
1

这可能不是最好的解决方案,但您可以访问从装饰器返回给调用者的内部函数,也可以从装饰器的闭包中访问。

例子:

def decorator(f):
    def wrapper(*args, **kwargs):
        try:
            f(*args, **kwargs)
        except Exception as e:
            wrapper.__dict__.setdefault('errors', []).append(e)
    return wrapper

@decorator
def raiser():
    raise Exception('Oh no!')

> raiser()
> raiser.errors
[Exception('Oh no!')]
于 2017-02-23T22:18:27.687 回答