2

functools.partial は非常に便利だと思いますが、引数を順不同でフリーズできるようにしたいと思います (フリーズしたい引数が常に最初のものであるとは限りません)。クラスのメソッドを一度に作成して、一部のメソッド パラメータが固定されていることを除いて、基になるオブジェクトと同じメソッドを持つプロキシ オブジェクトを作成します (クラスに適用するために部分的に一般化すると考えてください)。そして、パーシャルが元の機能を変更しないのと同じように、元のオブジェクトを編集せずにこれを行うことをお勧めします。

'bind' と呼ばれる functools.partial のバージョンをまとめてスクラップすることができました。これにより、パラメーターをキーワード引数で渡すことで順不同で指定できます。その部分は機能します:

>>> def foo(x, y):
...     print x, y
...
>>> bar = bind(foo, y=3)
>>> bar(2)
2 3

しかし、私のプロキシ クラスは機能しません。その理由はわかりません。

>>> class Foo(object):
...     def bar(self, x, y):
...             print x, y
...
>>> a = Foo()
>>> b = PureProxy(a, bar=bind(Foo.bar, y=3))
>>> b.bar(2)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: bar() takes exactly 3 arguments (2 given)

ランダムなドキュメント、ブログ、およびすべての部分で dir() を実行してからまとめたものに従っているだけなので、私はおそらくこれをあらゆる種類の間違った方法で行っています。これを機能させる方法とそれを実装するためのより良い方法の両方に関する提案をいただければ幸いです;) 私が確信していない詳細の1つは、これがすべて記述子とどのように相互作用するかです。コードは次のとおりです。

from types import MethodType

class PureProxy(object):
    def __init__(self, underlying, **substitutions):
        self.underlying = underlying

        for name in substitutions:
            subst_attr = substitutions[name]
            if hasattr(subst_attr, "underlying"):
                setattr(self, name, MethodType(subst_attr, self, PureProxy))

    def __getattribute__(self, name):
        return getattr(object.__getattribute__(self, "underlying"), name)

def bind(f, *args, **kwargs):
    """ Lets you freeze arguments of a function be certain values. Unlike
    functools.partial, you can freeze arguments by name, which has the bonus
    of letting you freeze them out of order. args will be treated just like
    partial, but kwargs will properly take into account if you are specifying
    a regular argument by name. """
    argspec = inspect.getargspec(f)
    argdict = copy(kwargs)

    if hasattr(f, "im_func"):
        f = f.im_func

    args_idx = 0
    for arg in argspec.args:
        if args_idx >= len(args):
            break

        argdict[arg] = args[args_idx]
        args_idx += 1

    num_plugged = args_idx

    def new_func(*inner_args, **inner_kwargs):
        args_idx = 0
        for arg in argspec.args[num_plugged:]:
            if arg in argdict:
                continue
            if args_idx >= len(inner_args):
                # We can't raise an error here because some remaining arguments
                # may have been passed in by keyword.
                break
            argdict[arg] = inner_args[args_idx]
            args_idx += 1

        f(**dict(argdict, **inner_kwargs))

    new_func.underlying = f

    return new_func

更新: 誰でも恩恵を受けることができる場合に備えて、私が行った最終的な実装は次のとおりです。

from types import MethodType

class PureProxy(object):
    """ Intended usage:
    >>> class Foo(object):
    ...     def bar(self, x, y):
    ...             print x, y
    ...
    >>> a = Foo()
    >>> b = PureProxy(a, bar=FreezeArgs(y=3))
    >>> b.bar(1)
    1 3
    """

    def __init__(self, underlying, **substitutions):
        self.underlying = underlying

        for name in substitutions:
            subst_attr = substitutions[name]
            if isinstance(subst_attr, FreezeArgs):
                underlying_func = getattr(underlying, name)
                new_method_func = bind(underlying_func, *subst_attr.args, **subst_attr.kwargs)
                setattr(self, name, MethodType(new_method_func, self, PureProxy))

    def __getattr__(self, name):
        return getattr(self.underlying, name)

class FreezeArgs(object):
    def __init__(self, *args, **kwargs):
        self.args = args
        self.kwargs = kwargs

def bind(f, *args, **kwargs):
    """ Lets you freeze arguments of a function be certain values. Unlike
    functools.partial, you can freeze arguments by name, which has the bonus
    of letting you freeze them out of order. args will be treated just like
    partial, but kwargs will properly take into account if you are specifying
    a regular argument by name. """
    argspec = inspect.getargspec(f)
    argdict = copy(kwargs)

    if hasattr(f, "im_func"):
        f = f.im_func

    args_idx = 0
    for arg in argspec.args:
        if args_idx >= len(args):
            break

        argdict[arg] = args[args_idx]
        args_idx += 1

    num_plugged = args_idx

    def new_func(*inner_args, **inner_kwargs):
        args_idx = 0
        for arg in argspec.args[num_plugged:]:
            if arg in argdict:
                continue
            if args_idx >= len(inner_args):
                # We can't raise an error here because some remaining arguments
                # may have been passed in by keyword.
                break
            argdict[arg] = inner_args[args_idx]
            args_idx += 1

        f(**dict(argdict, **inner_kwargs))

    return new_func
4

1 に答える 1

3

「バインディングが深すぎる」​​: in class に変更def __getattribute__(self, name):します。 すべての属性アクセスを傍受するため、設定したものすべてをバイパスして、それらの setattr の効果を無効にします。これは明らかにあなたが望むものではありません。他に定義されていない属性へのアクセスのためにのみ呼び出されるため、これらの呼び出しは「有効」で便利になります。def __getattr__(self, name):PureProxy__getattribute__setattr(self, name, ...__getattr__setattr

そのオーバーライドの本文では、(もうオーバーライドしていないため) に変更object.__getattribute__(self, "underlying")することもできますし、変更する必要があります。私が提案する他の変更があります (カウンターなどに使用している低レベルのロジックの代わりに) が、セマンティクスは変更されません。self.underlying__getattribute__enumerate

私が提案する変更により、サンプル コードは機能します (もちろん、より微妙なケースでテストを続ける必要があります)。ところで、私がこれをデバッグした方法printは、適切な場所にステートメントを貼り付けるだけでした (jurassic=era アプローチですが、それでも私のお気に入りです;-)。

于 2010-03-15T16:01:53.780 に答える