2

我发现 functools.partial 非常有用,但我希望能够无序冻结参数(您要冻结的参数并不总是第一个),并且我希望能够将它应用于几个一次对一个类使用方法,以创建一个代理对象,该对象具有与底层对象相同的方法,只是它的一些方法参数被冻结(将其视为泛化部分以应用于类)。而且我更愿意在不编辑原始对象的情况下执行此操作,就像 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() 拼凑而成的东西。关于如何使这项工作和更好的实现方法的建议将不胜感激;)我不确定的一个细节是这应该如何与描述符交互。代码如下。

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

您“绑定太深”:更改def __getattribute__(self, name):def __getattr__(self, name):in class PureProxy__getattribute__拦截每个属性访问,因此绕过您设置的所有内容,setattr(self, name, ...使这些 setattr 失去任何效果,这显然不是您想要的;__getattr__仅在访问未以其他方式定义的属性时才调用,因此这些setattr调用变得“可操作”且有用。

在该覆盖的主体中,您可以也应该更改object.__getattribute__(self, "underlying")self.underlying(因为您不再覆盖__getattribute__)。我建议进行其他更改(enumerate代替您用于计数器的低级逻辑等),但它们不会改变语义。

通过我建议的更改,您的示例代码可以工作(当然,您必须继续测试更微妙的案例)。顺便说一句,我调试这个的方式只是在适当的地方坚持print声明(侏罗纪=时代的方法,但仍然是我最喜欢的;-)。

于 2010-03-15T16:01:53.780 回答