装饰者可以访问类的私有成员吗?

时间:2013-09-09 06:44:36

标签: python oop styles decorator information-hiding

我正在编写一个解析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中并不是一个大问题,但我仍然很好奇 - 我在这里做了什么坏事吗?让装饰者依赖于他们所关联的类的实现细节是不是很糟糕?

编辑:我不清楚Duncan的答案是如何解决的,所以也许这有点像kludge-y,但这会是一个更简单的解决方案吗?

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

2 个答案:

答案 0 :(得分:3)

关于这一点有一点代码味道,我想我会同意pylint在这个上虽然它是非常主观的。

你的装饰器看起来像是一个通用装饰器,但它与类的内部实现细节联系在一起。如果您尝试在其他课程中使用它,如果没有在_cache中初始化__init__,则无法使用它。我不喜欢的链接是在类和装饰器之间共享一个名为'_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]

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

Profile

答案 1 :(得分:2)

你做的对我来说很好看。该错误可能是因为pylint无法确定cached_resource仅通过其内部函数“访问”self._cache,最终类的方法(由装饰者)。

为此pylint tracker提出问题可能值得。使用静态分析可能很难处理,但目前的行为似乎并不正确。

相关问题