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

Functional programming 如何在对象上的多个方法上使用functools.partial,并无序冻结参数?,functional-programming,metaprogramming,python,standard-library,Functional Programming,Metaprogramming,Python,Standard Library,我发现functools.partial非常有用,但我希望能够按顺序冻结参数(您要冻结的参数并不总是第一个),并且我希望能够将它同时应用于类上的多个方法,使代理对象具有与基础对象相同的方法,但其某些方法参数被冻结(将其视为应用于类的泛化部分)。我更喜欢在不编辑原始对象的情况下执行此操作,就像partial不更改其原始函数一样 我成功地拼凑了一个名为“bind”的functools.partial版本,该版本允许我通过关键字参数传递参数来无序指定参数。这部分工作: >>> def

我发现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)
更新:如果任何人都能从中受益,以下是我最终采用的实施方案:

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
“绑定太深”:将类
PureProxy
中的
def\uuu getattribute\uuuuuuuu(self,name):
更改为
def\uuuu getattr\uuuuuuuuuuuuuuuuuuuuuuu(self,name):
\uuuu getattribute\uuuu
截取每个属性访问,从而绕过使用
setattr设置的所有内容(self,name,…
使那些setattr失去任何效果,这显然不是您想要的;
\uuu getattr\uuuu
仅用于访问未另行定义的属性,因此这些
setattr
调用变得“有效”和有用

在覆盖的主体中,您可以也应该将
对象更改为
self(“底层”)
,将
self.underfulding
(因为您不再覆盖
\uuu getattribute\uuuu
)。我还建议进行其他更改(
枚举
,以代替计数器使用的低级逻辑,等等)但它们不会改变语义


通过我建议的更改,您的示例代码可以正常工作(当然,您必须继续使用更微妙的情况进行测试)。顺便说一句,我调试这一点的方法只是将
语句粘贴在适当的位置(侏罗纪=纪元方法,但仍然是我最喜欢的;-).

为什么?这是一种让工作变得单调有趣的方法吗?:我有一个子类,它调用基类方法来为它做一些工作。它向基类方法传递一个对象,该对象调用它的一些方法。其中一些方法应该冻结某些参数——基本上,子类将自定义对象,然后询问基类to使用它做一些工作。还有其他方法可以做到这一点,但如果已经实现了,我认为它会简洁而强大。太棒了,你建议的更改对我来说很有效。我不认为getattr和getattribute之间的区别有什么关系,因为我没有意识到setattr会使用它:p
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