如何为函数实现__str__?

时间:2017-11-23 09:56:25

标签: python function

给定函数foo

def foo(x):
     pass

通过调用strrepr来打印其表示形式会让您感到无聊:

str(foo)
'<function foo at 0x119e0c8c8>'

我想知道是否可以覆盖函数的__str__方法来打印其他内容。基本上,我想做:

str(foo)
"I'm foo!'

现在,我理解函数的描述应来自__doc__,这是函数的docstring。但是,这只是一个实验。

在尝试找出此问题的解决方案时,我遇到了为{em> __str__ 实施classesHow to define a __str__ method for a class?

这种方法涉及使用__str__方法定义元类,然后尝试在实际类中分配__metaclass__挂钩。

我想知道是否可以对班级function做同样的事情,所以这就是我的尝试 -

In [355]: foo.__class__
Out[355]: function

In [356]: class fancyfunction(type):
     ...:     def __str__(self):
     ...:         return self.__name__
     ...:     

In [357]: foo.__class__.__metaclass__ = fancyfunction
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)

我认为它不起作用,但值得一试!

那么,为函数实现__str__的最佳方法是什么?

2 个答案:

答案 0 :(得分:29)

Python中的函数只是一个可调用对象。使用def定义函数是一种方式来创建这样的对象。但实际上没有什么可以阻止你创建一个可调用类型并创建它的实例来获得一个函数。

所以以下两件事基本相同:

def foo ():
    print('hello world')


class FooFunction:
    def __call__ (self):
        print('hello world')

foo = FooFunction()

除了最后一个显然允许我们设置函数类型的特殊方法,如__str____repr__

class FooFunction:
    def __call__ (self):
        print('hello world')

    def __str__ (self):
        return 'Foo function'

foo = FooFunction()
print(foo) # Foo function

但是为此创建一个类型变得有点单调乏味,这也使得理解函数的功能变得更加困难:毕竟,def语法允许我们只是定义功能体。所以我们希望保持这种状态!

幸运的是,Python有一个很棒的功能叫做装饰器,我们可以在这里使用它。我们可以创建一个函数装饰器,它将自定义类型中的任何函数包装起来,调用__str__的自定义函数。这看起来像这样:

def with_str (str_func):
    def wrapper (f):
        class FuncType:
            def __call__ (self, *args, **kwargs):
                # call the original function
                return f(*args, **kwargs)
            def __str__ (self):
                # call the custom __str__ function
                return str_func()

        # decorate with functool.wraps to make the resulting function appear like f
        return functools.wraps(f)(FuncType())
    return wrapper

然后我们可以通过简单地装饰它来将__str__函数添加到任何函数中。这看起来像这样:

def foo_str ():
    return 'This is the __str__ for the foo function'

@with_str(foo_str)
def foo ():
    print('hello world')
>>> str(foo)
'This is the __str__ for the foo function'
>>> foo()
hello world

显然,这样做有一些限制和缺点,因为你不能完全重现def对装饰器内新功能的重复。

例如,使用inspect模块查看参数将无法正常工作:对于可调用类型,它将包含self参数,并且在使用通用装饰器时,它只会是能够报告wrapper的详细信息。但是,可能存在某些解决方案,例如this question中讨论的解决方案,可以让您恢复某些功能。

但这通常意味着您需要投入大量精力才能在函数对象上进行__str__工作,这可能很少使用。因此,您应该考虑是否确实需要__str__函数实现,以及您将对这些函数执行何种操作。

答案 1 :(得分:10)

如果您发现自己包装功能,那么查看functools.partial会很有用。它当然主要用于绑定参数,但这是可选的。它也是一个包含函数的类,从头开始删除这样做的样板。

from functools import partial

class foo(partial):
    def __str__(self):
        return "I'm foo!"

@foo
def foo():
    pass

assert foo() is None
assert str(foo) == "I'm foo!"