4

Pythonで関連するメソッドデコレータのファミリを作成する方法を理解するように教えられたので、良いpythonであり、oopの原則と一致する方法を考えるのに苦労しています。

相互に矛盾する目標は、装飾されたメソッドがバインドされているインスタンスのデコレータ属性と属性の両方にアクセスできるようにすることです。これが私が意味することです:

from functools import wraps

class AbstractDecorator(object):
    """
    This seems like the more natural way, but won't work
    because the instance to which the wrapped function
    is attached will never be in scope.
    """
    def __new__(cls,f,*args,**kwargs):
        return wraps(f)(object.__new__(cls,*args,**kwargs))

    def __init__(decorator_self, f):
        decorator_self.f = f
        decorator_self.punctuation = "..."

    def __call__(decorator_self, *args, **kwargs):
        decorator_self.very_important_prep()
        return decorator_self.f(decorator_self, *args, **kwargs)

class SillyDecorator(AbstractDecorator):
    def very_important_prep(decorator_self):
        print "My apartment was infested with koalas%s"%(decorator_self.punctuation)

class UsefulObject(object):
    def __init__(useful_object_self, noun):
        useful_object_self.noun = noun

    @SillyDecorator
    def red(useful_object_self):
        print "red %s"%(useful_object_self.noun)

if __name__ == "__main__":
    u = UsefulObject("balloons")
    u.red()

もちろん、これは生成します

My apartment was infested with koalas...
AttributeError: 'SillyDecorator' object has no attribute 'noun'

もちろん、これを機能させる方法は常にあることに注意してください。たとえば、十分な引数を持つファクトリを使用すると、SillyDecorator の作成されたインスタンスにメソッドをアタッチできますが、継承を使用してこれを行う合理的な方法があるかどうか疑問に思っていました。

4

2 に答える 2

2

@miku は、記述子プロトコルを使用するという重要なアイデアを得ました。これは、デコレータ オブジェクトを「有用なオブジェクト」から分離しておく改良点です。これは、基礎となるオブジェクトにデコレータ情報を保存しません。

class AbstractDecorator(object):
    """
    This seems like the more natural way, but won't work
    because the instance to which the wrapped function
    is attached will never be in scope.
    """
    def __new__(cls,f,*args,**kwargs):
        return wraps(f)(object.__new__(cls,*args,**kwargs))

    def __init__(decorator_self, f):
        decorator_self.f = f
        decorator_self.punctuation = "..."

    def __call__(decorator_self, obj_self, *args, **kwargs):
        decorator_self.very_important_prep()
        return decorator_self.f(obj_self, *args, **kwargs)

    def __get__(decorator_self, obj_self, objtype):
        return functools.partial(decorator_self.__call__, obj_self)      

class SillyDecorator(AbstractDecorator):
    def very_important_prep(decorator_self):
        print "My apartment was infested with koalas%s"%(decorator_self.punctuation)

class UsefulObject(object):
    def __init__(useful_object_self, noun):
        useful_object_self.noun = noun

    @SillyDecorator
    def red(useful_object_self):
        print "red %s"%(useful_object_self.noun)

>>> u = UsefulObject("balloons")
... u.red()
My apartment was infested with koalas...
red balloons

修飾されたメソッドとそれがバインドされているオブジェクトの両方へのアクセスを提供するものであるため、記述子プロトコルがここで重要です。内部__get__では、有用なオブジェクト ID ( ) を抽出してメソッドobj_selfに渡すことができます。__call__

の属性としてfunctools.partial単純に格納するのではなく、(またはそのようなメカニズム)を使用することが重要であることに注意してください。装飾されたメソッドはクラス上にあるため、SillyDecorator のインスタンスは 1 つだけ存在します。この SillyDecorator インスタンスを使用して、有用なオブジェクト インスタンス固有の情報を格納することはできません。複数の UsefulObjects を作成し、それらの装飾されたメソッドにすぐに呼び出さずにアクセスすると、奇妙なエラーが発生します。obj_selfdecorator_self

ただし、もっと簡単な方法があるかもしれないことは指摘しておく価値があります。あなたの例では、デコレータに少量の情報を保存しているだけで、後で変更する必要はありません。その場合は、デコレータ メーカー関数を使用する方が簡単かもしれません: 引数 (または複数の引数) を受け取り、デコレータを返す関数で、その動作はそれらの引数に依存します。次に例を示します。

def decoMaker(msg):
    def deco(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            print msg
            return func(*args, **kwargs)
        return wrapper
    return deco

class UsefulObject(object):
    def __init__(useful_object_self, noun):
        useful_object_self.noun = noun

    @decoMaker('koalas...')
    def red(useful_object_self):
        print "red %s"%(useful_object_self.noun)

>>> u = UsefulObject("balloons")
... u.red()
koalas...
red balloons

デコレータを作成するたびにメッセージを再入力したくない場合は、事前に decoMaker を使用してデコレータを作成し、後で再利用できます。

sillyDecorator = decoMaker("Some really long message about koalas that you don't want to type over and over")

class UsefulObject(object):
    def __init__(useful_object_self, noun):
        useful_object_self.noun = noun

    @sillyDecorator
    def red(useful_object_self):
        print "red %s"%(useful_object_self.noun)

>>> u = UsefulObject("balloons")
... u.red()
Some really long message about koalas that you don't want to type over and over
red balloons

これは、さまざまな種類のデコレータのクラス継承ツリー全体を記述するよりもはるかに冗長ではないことがわかります。あらゆる種類の内部状態を格納する非常に複雑なデコレータを作成している場合を除き (とにかく混乱する可能性があります)、このデコレータ メーカーのアプローチはより簡単な方法かもしれません。

于 2013-01-02T07:45:43.413 に答える
2

http://metapython.blogspot.de/2010/11/python-instance-methods-how-are-they.htmlから適応。このバリアントはターゲットインスタンスに属性を設定するため、チェックを行わなくてもターゲット インスタンスの属性を上書きできることに注意してください。以下のコードには、このケースのチェックは含まれていません。

punctuationまた、この例では属性を明示的に設定していることにも注意してください。より一般的なクラスは、その属性を自動検出できます。

from types import MethodType

class AbstractDecorator(object):
    """Designed to work as function or method decorator """
    def __init__(self, function):
        self.func = function
        self.punctuation = '...'
    def __call__(self, *args, **kw):
        self.setup()
        return self.func(*args, **kw)
    def __get__(self, instance, owner):
        # TODO: protect against 'overwrites'
        setattr(instance, 'punctuation', self.punctuation) 
        return MethodType(self, instance, owner)

class SillyDecorator(AbstractDecorator):
    def setup(self):
        print('[setup] silly init %s' % self.punctuation)

class UsefulObject(object):
    def __init__(self, noun='cat'):
        self.noun = noun

    @SillyDecorator
    def d(self): 
        print('Hello %s %s' % (self.noun, self.punctuation))

obj = UsefulObject()
obj.d()

# [setup] silly init ...
# Hello cat ...
于 2013-01-02T07:30:11.560 に答える