4

Web ページのプロファイルへのインターフェイスを提供するために、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
4

2 に答える 2

3

それについてはコードの匂いが少しあります。かなり主観的ですが、これについては pylint に同意すると思います。

デコレーターは汎用デコレーターのように見えますが、クラスの内部実装の詳細に結び付けられています。別のクラスから使用しようとした場合、_cacheinの初期化なしでは機能しません__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(たとえば、キャッシュ リセット メソッドを使用する場合) である可能性があります。その場合、デコレーター内でキャッシュを完全に管理できますが、そうする場合は、キャッシュをデコレーターに格納して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]
于 2013-09-09T08:34:51.650 に答える
2

あなたがしたことは私にはうまく見えます。エラーはおそらく、pylint が内部関数を介してcached_resourceのみ「アクセス」していることを認識できないためです。これは、最終的にクラスのメソッド (デコレータによって割り当てられます) です。self._cache

これについては、 pylint トラッカーで問題を提起する価値があるかもしれません。静的解析で処理するのは難しいかもしれませんが、現在の動作は正しくないようです。

于 2013-09-09T06:59:55.770 に答える