如何在对象的多个方法上使用 functools.partial 并无序冻结参数?

发布于 2024-08-25 23:42:50 字数 4833 浏览 3 评论 0原文

我发现 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() 。关于如何使这项工作以及更好的实现方法的建议将不胜感激;)我不确定的一个细节是这应该如何与描述符交互。代码如下。

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

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

oО清风挽发oО 2024-09-01 23:42:50

您“绑定太深”:将 PureProxydef __getattribute__(self, name): 更改为 def __getattr__(self, name): >。 __getattribute__ 拦截每个属性访问,因此绕过您使用 setattr(self, name, ...) 设置的所有内容,使这些 setattr 失去任何效果,这显然不是您想要的; __getattr__ 仅用于访问未另行定义的属性,因此这些 setattr 调用将成为“有效” 是

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

通过我建议的更改,您的示例代码可以工作(当然,您必须继续测试更微妙的情况),我调试它的方式只是坚持 print< 。 /code> 语句放在适当的位置(侏罗纪=时代的方法,但仍然是我最喜欢的;-)。

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

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文