7

我试图确保help()在 Python 2.7 REPL 上运行会显示__doc__functools.partial. 当前help()functools.partial“函数”上运行显示__doc__类的functools.partial,而不是我的包装函数的__doc__。有没有办法做到这一点?

考虑以下可调用对象:

def foo(a):
    """My function"""
    pass

partial_foo = functools.partial(foo, 2)

运行help(foo)将导致显示foo.__doc__。但是,运行会help(partial_foo)导致Partial object__doc__

我的第一种方法是使用functools.update_wrapper正确替换部分对象__doc__foo.__doc__. 但是,由于pydoc的方式,这并不能解决“问题” 。

我已经调查了 pydoc 代码,问题似乎是它partial_foo实际上是一个Partial 对象而不是典型的函数/可调用对象,有关该详细信息的更多信息,请参阅此问题

默认情况下,如果通过inspect.isclass__doc__确定传递的对象是类,pydoc 将显示对象类型,而不是实例。有关代码本身的更多信息,请参见render_doc 函数

所以,在我上面的场景中,pydoc 显示的是类型的帮助,而functools.partial不是__doc__我的functools.partial实例的帮助。

无论如何要更改我的调用help()functools.partial传递给的实例,help()以便它显示__doc__实例的,而不是类型?

4

3 回答 3

2

我找到了一种非常hacky的方法来做到这一点。我编写了以下函数来覆盖该__builtins__.help函数:

def partialhelper(object=None):
    if isinstance(object, functools.partial):
        return pydoc.help(object.func)
    else:
        # Preserve the ability to go into interactive help if user calls
        # help() with no arguments.
        if object is None:
            return pydoc.help()
        else:
            return pydoc.help(object)

然后只需在 REPL 中将其替换为:

__builtins__.help = partialhelper

这行得通,似乎还没有任何重大缺点。但是,上面的幼稚实现没有办法支持仍然显示某些对象__doc__的。要么全有,要么全无,但可能会为包装的(原始)函数附加一个属性,以指示是否应显示原始函数。但是,在我的情况下,我永远不想这样做。 functools.partial__doc__

请注意,当使用 IPython 和嵌入功能时,上述内容不起作用。这是因为 IPython 直接使用对 'real' 的引用来设置 shell 的命名空间__builtin__,请参阅代码和旧邮件列表了解为什么会这样。

因此,经过一番调查,还有另一种方法可以将其破解到 IPython 中。我们必须重写site._HelperIPython 用来显式设置帮助系统的类。以下代码将在调用 BEFORE 时执行此操作IPython.embed

import site
site._Helper.__call__ = lambda self, *args, **kwargs: partialhelper(*args, **kwargs)

我在这里还有其他缺点吗?

于 2013-05-21T15:36:46.327 回答
0

如何实施你自己的?

def partial_foo(*args):
    """ some doc string """
    return foo(*((2)+args))

不是一个完美的答案,但如果你真的想要这个,我怀疑这是唯一的方法

于 2013-05-21T15:01:09.267 回答
0

您发现了问题 - 部分函数不是典型的函数,并且 dunder 变量不会结转。这不仅适用于__doc__, 也适用于__name__,__module__等等。不确定在提出问题时是否存在此解决方案,但是您可以通过重写partial()为装饰器工厂来更优雅地(“优雅地”解释)实现这一点。由于装饰器(和工厂)不会自动复制 dunder 变量,因此您还需要使用@wraps(func)

def wrapped_partial(*args, **kwargs):
    def foo(func):
        @wraps(func)
        def bar(*fargs,**fkwargs):
            return func(*args, *fargs, **kwargs, **fkwargs)
        return bar
    return foo

使用示例:

@wrapped_partial(3)
def multiply_triple(x, y=1, z=0):
    """Multiplies three numbers"""
    return x * y * z

# Without decorator syntax: multiply_triple = wrapped_partial(3)(multiply_triple)

带输出:

>>>print(multiply_triple())
0
>>>print(multiply_triple(3,z=3))
9
>>>help(multiply_triple)

help(multiply_triple)
Help on function multiply_triple in module __main__:

multiply_triple(x: int, y: int = 1, z: int = 0)
    Multiplies three numbers

不起作用的东西,但在使用多个装饰器时会提供信息

你可能会想,就像我第一次做的那样,基于PEP-318中装饰器的堆叠语法,你可以将包装和部分函数定义放在单独的装饰器中,例如

def partial_func(*args, **kwargs):
    def foo(func):
        def bar(*fargs,**fkwargs):
            return func(*args, *fargs, **kwargs, **fkwargs)
        return bar
    return foo

def wrapped(f):
     @wraps(f)
     def wrapper(*args, **kwargs):
         return f(*args, **kwargs)
     return wrapper

@wrapped
@partial_func(z=3)
def multiply_triple(x, y=1, z=0):
    """Multiplies three numbers"""
    return x * y * z

在这些情况下(并且以相反的顺序),一次应用一个装饰器,并且@partial_func中断包装。这意味着如果您尝试使用要包装的任何装饰器,则需要在装饰器的返回函数本身由@wraps(func). 如果你使用多个装饰器,它们都必须变成被包装的工厂。


让装饰器“包装”的替代方法
由于装饰器只是函数,因此您可以编写一个返回的copy_dunder_vars(obj1, obj2)函数,obj2但所有 dunder 变量都来自obj1. 调用为:

def foo()
    pass

foo = copy_dunder_vars(decorator(foo), foo)

这违背了首选语法,但实用性胜过纯度。我认为“不强迫你重写你从其他地方借来的并且基本保持不变的装饰器”适合这一类。包完之后,别忘了丝带和蝴蝶结;)

于 2020-09-27T20:32:27.570 回答