你如何为函数实现__str__?

时间:2022-09-02 09:31:50

Given a function foo:

给定一个函数foo:

def foo(x):
     pass

Printing its representation by invoking str or repr gives you something boring like this:

通过调用str或repr来打印它的表示形式会给你带来如下无聊的东西:

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

I'd like to know if it is possible to override a function's __str__ method to print something else. Essentially, I'd like to do:

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

str(foo)
"I'm foo!'

Now, I understand that the description of a function should come from __doc__ which is the function's docstring. However, this is merely an experiment.

现在,我明白函数的描述应该来自__doc__,这是函数的docstring。然而,这仅仅是一个实验。

In attempting to figure out a solution to this problem, I came across implementing __str__ for classes: How to define a __str__ method for a class?

在试图找出这个问题的解决方案时,我遇到了为类实现__str__:如何为类定义__str__方法?

This approach involved defining a metaclass with an __str__ method, and then attempting to assign the __metaclass__ hook in the actual class.

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

I wondered whether the same could be done to the class function, so here's what I tried -

我想知道是否可以对类函数做同样的事情,所以这就是我试过的 -

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)

I figured it wouldn't work, but it was worth a shot!

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

So, what's the best way to implement __str__ for a function?

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

2 个解决方案

#1


27  

A function in Python is just a callable object. Using def to define function is one way to create such an object. But there is actually nothing stopping you from creating a callable type and creating an instance of it to get a function.

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

So the following two things are basically equal:

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

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


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

foo = FooFunction()

Except that the last one obviously allows us to set the function type’s special methods, like __str__ and __repr__.

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

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

    def __str__ (self):
        return 'Foo function'

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

But creating a type just for this becomes a bit tedious and it also makes it more difficult to understand what the function does: After all, the def syntax allows us to just define the function body. So we want to keep it that way!

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

Luckily, Python has this great feature called decorators which we can use here. We can create a function decorator that will wrap any function inside a custom type which calls a custom function for the __str__. That could look like this:

幸运的是,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

We can then use that to add a __str__ function to any function by simply decorating it. That would look like this:

然后我们可以通过简单地装饰它来将__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

Obviously, doing this has some limitations and drawbacks since you cannot exactly reproduce what def would do for a new function inside that decorator.

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

For example, using the inspect module to look at the arguments will not work properly: For the callable type, it will include the self argument and when using the generic decorator, it will only be able to report the details of wrapper. However, there might be some solutions, for example discussed in this question, that will allow you to restore some of the functionality.

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

But that usually means you are investing a lot of effort just to get a __str__ work on a function object which will probably very rarely be used. So you should think about whether you actually need a __str__ implementation for your functions, and what kind of operations you will do on those functions then.

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

#2


10  

If you find yourself wrapping functions, it's useful to look at functools.partial. It's primarily for binding arguments of course, but that's optional. It's also a class that wraps functions, removing the boilerplate of doing so from scratch.

如果你发现自己包装函数,那么查看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!"

#1


27  

A function in Python is just a callable object. Using def to define function is one way to create such an object. But there is actually nothing stopping you from creating a callable type and creating an instance of it to get a function.

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

So the following two things are basically equal:

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

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


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

foo = FooFunction()

Except that the last one obviously allows us to set the function type’s special methods, like __str__ and __repr__.

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

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

    def __str__ (self):
        return 'Foo function'

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

But creating a type just for this becomes a bit tedious and it also makes it more difficult to understand what the function does: After all, the def syntax allows us to just define the function body. So we want to keep it that way!

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

Luckily, Python has this great feature called decorators which we can use here. We can create a function decorator that will wrap any function inside a custom type which calls a custom function for the __str__. That could look like this:

幸运的是,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

We can then use that to add a __str__ function to any function by simply decorating it. That would look like this:

然后我们可以通过简单地装饰它来将__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

Obviously, doing this has some limitations and drawbacks since you cannot exactly reproduce what def would do for a new function inside that decorator.

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

For example, using the inspect module to look at the arguments will not work properly: For the callable type, it will include the self argument and when using the generic decorator, it will only be able to report the details of wrapper. However, there might be some solutions, for example discussed in this question, that will allow you to restore some of the functionality.

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

But that usually means you are investing a lot of effort just to get a __str__ work on a function object which will probably very rarely be used. So you should think about whether you actually need a __str__ implementation for your functions, and what kind of operations you will do on those functions then.

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

#2


10  

If you find yourself wrapping functions, it's useful to look at functools.partial. It's primarily for binding arguments of course, but that's optional. It's also a class that wraps functions, removing the boilerplate of doing so from scratch.

如果你发现自己包装函数,那么查看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!"