3

別のクラスのメソッド セットを模倣し、プロキシ経由で後者のように動作するクラスを生成する必要があります。たとえば、IfBaseは模倣するDeleguateクラスであり、次のように動作する必要があるクラスBaseです。

b = Base(args)
b.any_function()

と厳密に同等です

d = Deleguate(b)
d.any_function()

Deleguateに既に存在する関数を使用する場合Base、上書きされません。これは、継承とメソッドのオーバーライドで期待される種類の動作です。継承は、私が取り組んでいるプロジェクトのコンテキストではオプションではありません (他の制約の中でも特に、ファクトリ コードにアクセスできません)。そして、それが物事を複雑にしています。

したがって、「プロキシ」デコレータをコーディングすることにしました。

import inspect

def proxy(bridge, target):
    def proxyfy(cls):
        for _, func in inspect.getmembers(target, predicate=inspect.ismethod):
            fname = func.__name__
            if fname in cls.__dict__:
                print 'ignoring %s.%s' % (cls, fname)
                continue
            print 'adding %s.%s' % (cls, fname)
            def proxy_func(self, *args, **kwargs):
                print 'calling %s.%s.%s' % (cls, bridge, fname)
                bridge_member = getattr(self, bridge)
                return getattr(bridge_member, fname)(*args, **kwargs)
            setattr(cls, fname, proxy_func)
        return cls
    return proxyfy

class Base(object):
    def __init__(self, i):
        self._i = i

    def __bar(self):
        print 0

    def foo(self):
        print self._i

    def foo2(self):
        print 2 * self._i


@proxy('_proxy', Base)
class Deleguate(object):
    def __init__(self, base):
        self._proxy = base

    def foo2(self):
        print 4 * self._proxy._i

d = Deleguate(Base(1))
d.__bar() # d._proxy.__bar()
d.foo()   # d._proxy.foo()
d.foo2()  # d.foo2()

次の出力が得られます。

adding <class '__main__.Deleguate'>.__bar
ignoring <class '__main__.Deleguate'>.__init__
adding <class '__main__.Deleguate'>.foo
ignoring <class '__main__.Deleguate'>.foo2
calling <class '__main__.Deleguate'>._proxy.foo2
2
calling <class '__main__.Deleguate'>._proxy.foo2
2
4

新しいクロージャーを割り当てると思ったのですsetattr(cls, fname, proxy_func)が、引数は各ループステップで上書きされ、最後の関数の引数のみfoo2が保持されます。Deleguateしたがって、引数を使用する「生成された」関数を呼び出すfoo2...

クロージャー引数が上書きされるのはなぜですか? そのようなプロキシ コードを生成する方法はありますか? 予想される出力は次のとおりです。

adding <class '__main__.Deleguate'>.__bar
ignoring <class '__main__.Deleguate'>.__init__
adding <class '__main__.Deleguate'>.foo
ignoring <class '__main__.Deleguate'>.foo2
calling <class '__main__.Deleguate'>._proxy.__bar
0
calling <class '__main__.Deleguate'>._proxy.foo
1
4
4

1 に答える 1

0

関数はクロージャーを作成しますが、ループは作成しません。変数名fname は のローカル変数ですproxyfy。ネストされた関数proxy_funcは、このローカル変数を参照します。しかし、ネストされた関数が呼び出された時点で、for-loop

    for _, func in inspect.getmembers(target, predicate=inspect.ismethod):

が完了し、ローカル変数fnameはループの最後で最後の値を参照します'foo2'。したがって、どのメソッドを呼び出しても、それぞれproxy_funcが を呼び出すことになりfoo2ます。

の異なる値fnameをそれぞれproxy_funcにバインドするには、新しいキーワード パラメータbnameをデフォルト値とともに使用できます。デフォルト値は、関数の実行時ではなく、定義時に関数にバインドされます。だから使うなら

    for bname, func in inspect.getmembers(target, predicate=inspect.ismethod):

これbnameをデフォルト値として使用します。

        def proxy_func(self, bname=bname, *args, **kwargs):

次に、それぞれproxy_funcが適切な を呼び出しますbname

したがって、コードを最小限に変更するだけで、デフォルト値を持つキーワード パラメータを追加proxy_funcして、現在のメソッド名を記憶することができます。

def proxy(bridge, target):
    def proxyfy(cls):
        for bname, func in inspect.getmembers(target, predicate=inspect.ismethod):
            fname = func.__name__
            if fname in cls.__dict__:
                print 'ignoring %s.%s' % (cls, fname)
                continue
            print 'adding %s.%s' % (cls, fname)
            def proxy_func(self, bname=bname, *args, **kwargs):
                print 'calling %s.%s.%s' % (cls, bridge, bname)
                bridge_member = getattr(self, bridge)
                return getattr(bridge_member, bname)(*args, **kwargs)
            setattr(cls, fname, proxy_func)
        return cls
    return proxyfy

ただし、使用する方が簡単かもしれないと思います__getattr__

def proxy(bridge):
    def proxyfy(cls):
        def __getattr__(self, attr):
            target = getattr(self, bridge)
            if attr.startswith('__') and not attr.endswith('__'):
                # unmangle
                attr = '_{}{}'.format(type(target).__name__, attr)
            return getattr(target, attr)
        setattr(cls, '__getattr__', __getattr__)
        return cls
    return proxyfy

実行可能な例を次に示します。

import inspect

def proxy(bridge, target):
    def proxyfy(cls):
        for bname, func in inspect.getmembers(target, predicate=inspect.ismethod):
            fname = func.__name__
            if fname in cls.__dict__:
                print 'ignoring %s.%s' % (cls, fname)
                continue
            print 'adding %s.%s' % (cls, fname)
            def proxy_func(self, bname=bname, *args, **kwargs):
                print 'calling %s.%s.%s' % (cls, bridge, bname)
                bridge_member = getattr(self, bridge)
                return getattr(bridge_member, bname)(*args, **kwargs)
            setattr(cls, fname, proxy_func)
        return cls
    return proxyfy

def proxy(bridge):
    def proxyfy(cls):
        def __getattr__(self, attr):
            target = getattr(self, bridge)
            if attr.startswith('__') and not attr.endswith('__'):
                # unmangle
                attr = '_{}{}'.format(type(target).__name__, attr)
            return getattr(target, attr)
        setattr(cls, '__getattr__', __getattr__)
        return cls
    return proxyfy

class Base(object):
    def __init__(self, i):
        self._i = i

    def __bar(self):
        print 0

    def foo(self):
        print self._i

    def foo2(self):
        print 2 * self._i


# @proxy('_proxy', Base)
@proxy('_proxy')
class Delegate(object):
    def __init__(self, base):
        self._proxy = base

    def foo2(self):
        print 4 * self._proxy._i

d = Delegate(Base(1))
d.__bar() # d._proxy.__bar()
d.foo()   # d._proxy.foo()
d.foo2()  # d.foo2()
于 2013-10-26T14:21:33.033 に答える