代码之家  ›  专栏  ›  技术社区  ›  Joseph Garvin

如何在一个对象上对多个方法使用functools.partial,并按顺序冻结参数?

  •  2
  • Joseph Garvin  · 技术社区  · 16 年前

    我发现functools.partial非常有用,但我希望能够按顺序冻结参数(您希望冻结的参数并不总是第一个),并且我希望能够将其同时应用于类上的多个方法,以生成与除了某些方法参数被冻结之外的基础对象(将其视为泛化部分以应用于类)。我更愿意在不编辑原始对象的情况下完成这项工作,就像partial不会更改其原始功能一样。

    我已经成功地将functools.partial的一个称为“bind”的版本拼凑在一起,该版本允许我通过关键字参数传递参数来无序地指定参数。那部分起作用:

    >>> 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
    
    1 回复  |  直到 16 年前
        1
  •  3
  •   Alex Martelli    16 年前

    你“绑得太深”:改变 def __getattribute__(self, name): def __getattr__(self, name): 课堂上 PureProxy . __getattribute__ 截获物 每一个 属性访问,因此绕过您设置的所有内容 setattr(self, name, ... 使那些塞塔特失去任何效果,这显然不是你想要的; __getattr__ 仅为访问属性而调用 未另行定义 所以那些 setattr 通话变得“有效”&有用。

    在覆盖的主体中,您可以也应该更改 object.__getattribute__(self, "underlying") self.underlying (因为你没有凌驾于 _获取属性__ 再多)。还有其他的变化我建议( enumerate 代替了你用于计数器等的低级逻辑,但是它们不会改变语义。

    根据我的建议,您的示例代码可以工作(当然,您必须继续使用更微妙的情况进行测试)。顺便说一句,我调试它的方式只是为了坚持 print 在适当的地方陈述(侏罗纪=时代的方法,但仍然是我最喜欢的;-)。