如何在对象上的多个方法上使用functools.partial,并将参数按顺序冻结? [英] How can I use functools.partial on multiple methods on an object, and freeze parameters out of order?

查看:129
本文介绍了如何在对象上的多个方法上使用functools.partial,并将参数按顺序冻结?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我发现functools.partial非常有用,但是我希望能够按顺序冻结参数(您要冻结的参数并不总是第一个),我希望能够应用它同时在一个类的几个方法上创建一个代理对象,该对象具有与基础对象相同的方法,除了其某些方法参数被冻结(将其视为泛化部分适用于类)。我更喜欢在不编辑原始对象的情况下执行此操作,就像partial不会更改其原始函数一样。



我已经设法将一个版本functools.partial称为绑定,它允许我通过关键字参数传递参数来指定参数。这部分工作:

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

但是我的代理类不起作用, m不知道为什么:

 >>> 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(最近一次调用最后一次):
在< module>中,第1行的文件< stdin>
TypeError:bar()只需要3个参数(给出2个)

这样做是错误的,因为我只是从随机文档,博客和运行dir()的所有部分拼凑而成。对于如何完成这项工作以及更好的实现方法的建议将不胜感激;)我不确定的一个细节是,它应该如何与描述符进行交互。代码如下。

  from类型import MethodType 

class PureProxy(object):
def __init __(self,underlying,** substitutions):
self.underlying =底层

替换中的名称:
subst_attr =替换[名称]
如果hasattr( subst_attr,underlying):
setattr(self,name,MethodType(subst_attr,self,PureProxy))
$ b $ def __getattribute __(self,name):
return getattr .__ getattribute __(self,underlying),name)

def bind(f,* args,** kwargs):
允许您将函数的参数冻结为特定值。与
functools.partial不同,你可以通过名字来冻结参数,这个参数有
的奖励,让你可以冻结它们。args将被视为
partial,但是kwargs会正确的考虑你是否按名称指定了
a定期参数。
argspec = inspect .getargspec(f)
argdict = copy(kwargs)
$ b如果hasattr(f,im_func):
f = f.im_func

args_idx = argspec.args中arg = 0

如果args_idx> = len(args):
break

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

num_plugged = args_idx
$ b $ def new_func(* inner_args,** inner_kwargs):
args_idx = 0
for arg在argspec.args [num_plugged:]中:
如果arg在argdict中:
continue
如果args_idx> = len(inner_args):
#我们不能在这里引发错误因为其他一些参数
#可能已经通过关键字传入。
break
argdict [arg] = inner_args [args_idx]
args_idx + = 1

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

new_func.underlying = f

return new_func

更新:如果任何人都能受益,下面是我的最终实现:

 来自类型import MethodType 

class PureProxy(object):
用途:
>>> 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 ,**替换):
self.underlying =底层

替换中的名称:
subst_attr =替换[名称]
如果是isinstance(subst_attr,Freez eArgs):
base_func = getattr(底层,名称)
new_method_func = bind(underlying_func,* subst_attr.args,** subst_attr.kwargs)
setattr(self,name,MethodType(new_method_func,自我,PureProxy))

def __getattr __(self,name):
return getattr(self.underlying,name)
$ b $ class FreezeArgs(object):
def __init __(self,* args,** kwargs):
self.args = args
self.kwargs = kwargs

def bind(f,* args,* * kwargs):
可以将函数的参数冻结为特定值。与
functools.partial不同,您可以通过名称来冻结参数,该参数具有
的奖金,让您可以将其冻结。 args将被视为
partial,但如果您指定
作为名称的常规参数,则kwargs会适当考虑。
argspec = inspect.getargspec(f)
argdict = copy(kwargs)
$ b $如果hasattr(f,im_func):
f = f。 im_func

args_idx = 0
用于argspec.args中的arg:
如果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
argspec.args中的arg [num_plugged:]:
如果arg中包含argdict:
继续
如果args_idx> = len(inner_args):
#我们不能在这里提出错误,因为一些剩余的参数
#可能已经通过关键字传入。
break
argdict [arg] = inner_args [args_idx]
args_idx + = 1

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

return new_func


解决方案

您的绑定太深:更改 def __getattribute __(self,name): def __getattr __(self,name):在类 PureProxy 中。 __ getattribute __ 拦截每个属性访问,并绕过您用 setattr设置的所有内容(self,name,... 使这些setattr失去任何效果,这显然不是你想要的; __ getattr __ 仅用于访问属性 / em>所以这些 setattr 调用变得有效并且很有用。

在该重写的主体中,你可以也应该改变 object .__ getattribute __(self,underlying) to self.underlying 不要重写 __ getattribute __ 任何更多)。我建议其他更改(枚举您使用的计数器级逻辑等),但它们不会改变语义。



随着我建议的更改,您的示例代码有效(您将拥有继续测试更微妙的情况当然)。顺便说一句,我调试的方式只是坚持在打印语句在适当的地方(一个侏罗纪=时代的方法,但仍然是我的最爱; - )。

I find functools.partial to be extremely useful, but I would like to be able to freeze arguments out of order (the argument you want to freeze is not always the first one) and I'd like to be able to apply it to several methods on a class at once, to make a proxy object that has the same methods as the underlying object except with some of its methods parameters being frozen (think of it as generalizing partial to apply to classes). And I'd prefer to do this without editing the original object, just like partial doesn't change its original function.

I've managed to scrap together a version of functools.partial called 'bind' that lets me specify parameters out of order by passing them by keyword argument. That part works:

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

But my proxy class does not work, and I'm not sure why:

>>> 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)

I'm probably doing this all sorts of wrong because I'm just going by what I've pieced together from random documentation, blogs, and running dir() on all the pieces. Suggestions both on how to make this work and better ways to implement it would be appreciated ;) One detail I'm unsure about is how this should all interact with descriptors. Code follows.

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

Update: In case anyone can benefit, here's the final implementation I went with:

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

解决方案

You're "binding too deep": change def __getattribute__(self, name): to def __getattr__(self, name): in class PureProxy. __getattribute__ intercepts every attribute access and so bypasses everything that you've set with setattr(self, name, ... making those setattr bereft of any effect, which obviously's not what you want; __getattr__ is called only for access to attributes not otherwise defined so those setattr calls become "operative" & useful.

In the body of that override, you can and should also change object.__getattribute__(self, "underlying") to self.underlying (since you're not overriding __getattribute__ any more). There are other changes I'd suggest (enumerate in lieu of the low-level logic you're using for counters, etc) but they wouldn't change the semantics.

With the change I suggest, your sample code works (you'll have to keep testing with more subtle cases of course). BTW, the way I debugged this was simply to stick in print statements in the appropriate places (a jurassic=era approach but still my favorite;-).

这篇关于如何在对象上的多个方法上使用functools.partial,并将参数按顺序冻结?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆