装饰器是否可以访问类的私有成员



我正在编写一个解析HTML的类,以便为网页上的配置文件提供接口。它看起来像这样:

class Profile(BeautifulSoup):
    def __init__(self, page_source):
        super().__init__(page_source)
    def username(self):
        return self.title.split(':')[0]

除了更复杂和耗时。由于我知道基础配置文件在Profile对象的生命周期内不会更改,因此我认为这将是缓存结果的好地方,以避免重新计算已知的值。我用装饰器实现了这一点,结果如下所示:

def cached_resource(method_to_cache):
    def decorator(self, *args, **kwargs):
        method_name = method_to_cache.__name__
        try:
            return self._cache[method_name]
        except KeyError:
            self._cache[method_name] = method_to_cache(self, *args, **kwargs)
            return self._cache[method_name]
    return decorator

class Profile(BeautifulSoup):
    def __init__(self, page_source):
        super().__init__(page_source)
        self._cache = {}
    @cached_resource
    def username(self):
        return self.title.split(':')[0]

当我把这段代码交给 pylint 时,它抱怨cached_resource可以访问客户端类的受保护变量。

我意识到公共和私有之间的区别在 Python 中并不是什么大问题,但我仍然很好奇——我在这里做了什么坏事吗?让装饰器依赖于与其关联的类的实现细节是糟糕的风格吗?

编辑:我不清楚邓肯答案中的闭包是如何工作的,所以也许这有点笨拙,但这是一个更简单的解决方案吗?

def cached_resource(method_to_cache):
    def decorator(self, *args, **kwargs):
    method_name = method_to_cache.__name__
    try:
        return self._cache[method_name]
    except KeyError:
        self._cache[method_name] = method_to_cache(self, *args, **kwargs)
    except AttributeError:
        self._cache = {}
        self._cache[method_name] = method_to_cache(self, *args, **kwargs)
    finally:
        return self._cache[method_name]
return decorator

这有点代码味,我想我会同意 pylint 的观点,尽管它非常主观。

您的装饰器

看起来像是一个通用装饰器,但它与类的内部实现细节相关联。如果您尝试从另一个类中使用它,如果没有在 __init__ 中初始化 _cache,它将无法工作。我不喜欢的链接是,名为"_cache"的属性的知识在类和装饰器之间共享。

您可以将_cache的初始化移出__init__并移入装饰器。我不知道这是否有助于安抚 pylint,它仍然需要类了解并避免使用该属性。这里一个更干净的解决方案(我认为)是将缓存属性的名称传递到装饰器中。这应该干净利落地断开链接:

def cached_resource(cache_attribute):
  def decorator_factory(method_to_cache):
    def decorator(self, *args, **kwargs):
        method_name = method_to_cache.__name__
        cache = getattr(self, cache_attribute)
        try:
            return cache[method_name]
        except KeyError:
            result = cache[method_name] = method_to_cache(self, *args, **kwargs)
            return result
    return decorator
  return decorator_factory

class Profile(BeautifulSoup):
    def __init__(self, page_source):
        super().__init__(page_source)
        self._cache = {}
    @cached_resource('_cache')
    def username(self):
        return self.title.split(':')[0]

如果你不喜欢很多装饰器调用重复属性的名称,那么:

class Profile(BeautifulSoup):
    def __init__(self, page_source):
        super().__init__(page_source)
        self._cache = {}
    with_cache = cached_resource('_cache')
    @with_cache
    def username(self):
        return self.title.split(':')[0]

编辑:马蒂诺认为这可能是矫枉过正。可能是如果您实际上不需要单独访问类中的 _cache 属性(例如,具有缓存重置方法)。在这种情况下,您可以完全在装饰器中管理缓存,但是如果您要这样做,则根本不需要在实例上使用缓存字典,因为您可以将缓存存储在装饰器中,并将密钥存储在Profile实例上:

from weakref import WeakKeyDictionary
def cached_resource(method_to_cache):
    cache = WeakKeyDictionary()
    def decorator(self, *args, **kwargs):
        try:
            return cache[self]
        except KeyError:
            result = cache[self] = method_to_cache(self, *args, **kwargs)
        return result
    return decorator
class Profile(BeautifulSoup):
    def __init__(self, page_source):
        super().__init__(page_source)
        self._cache = {}
    @cached_resource
    def username(self):
        return self.title.split(':')[0]

你所做的对我来说看起来不错。 该错误可能是因为 pylint 无法确定 cached_resource 只是通过其内部函数"访问"self._cache,该函数最终类的方法(由装饰器分配)。

为此,可能值得在 pylint 跟踪器上提出一个问题。 使用静态分析可能很难处理,但当前的行为似乎不正确。

相关内容

  • 没有找到相关文章

最新更新