如何为函数本身设置 repr? [复制]
Posted
技术标签:
【中文标题】如何为函数本身设置 repr? [复制]【英文标题】:How to set a repr for a function itself? [duplicate] 【发布时间】:2021-02-12 06:27:55 【问题描述】:__repr__
用于返回对象的字符串表示,但在 Python 中,函数本身也是对象,can have attributes。
如何设置函数的__repr__
?
我看到here 可以为函数外的函数设置属性,但通常在对象定义本身内设置__repr__
,所以我想在函数定义本身内设置repr。
我的用例是我使用tenacity 重试具有指数退避的网络函数,并且我想记录我上次调用的函数的(信息性)名称。
retry_mysql_exception_types = (InterfaceError, OperationalError, TimeoutError, ConnectionResetError)
def return_last_retry_outcome(retry_state):
"""return the result of the last call attempt"""
return retry_state.outcome.result()
def my_before_sleep(retry_state):
print("Retrying : attempt ended with: \n".format(retry_state.fn, retry_state.attempt_number, retry_state.outcome))
@tenacity.retry(wait=tenacity.wait_random_exponential(multiplier=1, max=1200),
stop=tenacity.stop_after_attempt(30),
retry=tenacity.retry_if_exception_type(retry_mysql_exception_types),
retry_error_callback=return_last_retry_outcome,
before_sleep=my_before_sleep)
def connect_with_retries(my_database_config):
connection = mysql.connector.connect(**my_database_config)
return connection
目前retry_state.fn
显示类似于@chepner 所说的<function <lambda> at 0x1100f6ee0>
,但我想向它添加更多信息。
【问题讨论】:
您可以使用 func.__name__ 打印函数的名称 我认为带有__call__
方法的类更适合这个用例。
@Georgy 似乎我在最初的搜索中错过了这一点,我的问题确实是一个重复的问题,但是这里的答案要好得多。
【参考方案1】:
我认为自定义装饰器会有所帮助:
import functools
class reprable:
"""Decorates a function with a repr method.
Example:
>>> @reprable
... def foo():
... '''Does something cool.'''
... return 4
...
>>> foo()
4
>>> foo.__name__
'foo'
>>> foo.__doc__
'Does something cool.'
>>> repr(foo)
'foo: Does something cool.'
>>> type(foo)
<class '__main__.reprable'>
"""
def __init__(self, wrapped):
self._wrapped = wrapped
functools.update_wrapper(self, wrapped)
def __call__(self, *args, **kwargs):
return self._wrapped(*args, **kwargs)
def __repr__(self):
return f'self._wrapped.__name__: self._wrapped.__doc__'
演示:http://tpcg.io/uTbSDepz.
【讨论】:
这与Hielke's older answer 基本相同(只是不能根据每个功能自定义repr
)。
@ShadowRanger 是的,我的答案写得太慢了...... :(
我喜欢添加update_wrapper
。【参考方案2】:
您可以将retry_state.fn
更改为retry_state.__name__
。我使用了很多这样的装饰器。如果添加装饰器,则每次调用感兴趣的函数时都会调用它。
def display_function(func):
""" This decorator prints before and after running """
@functools.wraps(func)
def function_wrapper(*args, **kwargs):
print(f'\nNow: Calling func.__name__.')
entity = func(*args, **kwargs)
print(f'Done: Calling func.__name__.\n')
return entity
return function_wrapper
此外,python 中的重试模块允许您执行一些默认情况下正在执行的操作。我经常使用装饰器:
import retrying
@retrying.retry(wait_exponential_multiplier=1000, wait_exponential_max=10000)
【讨论】:
【参考方案3】:您不能对实际功能执行此操作; function
类型是不可变的,并且已经定义了 __repr__
,并且 __repr__
是在类型而不是实例上查找的,因此在给定函数上更改 __repr__
不会改变行为。
虽然在这种情况下可能没有用,但您可以创建自己的可调用类(类似于 C++ 函子),并且那些可以定义自己的__repr__
。例如:
class myfunction:
@staticmethod # Avoids need to receive unused self
def __call__(your, args, here):
... do stuff and return as if it were a function ...
@classmethod # Know about class, but again, instance is useless
def __repr__(cls):
return f'cls.__name__(a, b, c)'
您可以在最后将其转换为类的单例实例(使其在使用方式上等同于普通函数):
myfunction = myfunction()
用该类的单个实例替换该类。
注意:在实际代码中,我几乎肯定会更改打印位置,以便以更有用的方式打印,而无需修改函数。与普通函数或包装的普通函数相比,这没有太多开销(因为我们将函数本身放在__call__
而不是包装中,使其更快,但需要为每个“友好的repr
函数”),但决定如何以人类友好的方式表示自己并不是函数的工作;这是你的工作,视情况而定。
【讨论】:
我想我更喜欢 Hielke 的回答,因为作为包装器,模式可以更容易地重复。顺便说一句,我从来没有试过这个。添加__call__
作为类方法是否通过屏蔽__init__
有效地使类不可实例化?
@flakes: 不。__call__
在您“调用”实例时被调用(这就是为什么您会使用 myfunction = myfunction()
将其替换为类的实例)。因此,在类名之后加上括号会调用__init__
(如果已定义,则在__new__
之后)。将它们放在类的实例之后调用__call__
。有趣的琐事:如果你有一个定义__call__
的元类,元类的__call__
将在__new__
/__init__
之前被调用,因为你正在调用元类的一个实例(类本身)。
@flakes:我同意 Hielke 的回答更灵活。但我也认为这是把责任放在了错误的地方。如果您想要一个函数的友好字符串版本,请编写一小段代码在您需要的地方进行转换,不要让人们将他们的函数包装在整个代码库中以使它们与您的单个消费者一起工作。
我大体上同意你的观点,但考虑到该方法已经包含了韧性,这是一个相对较低的成本。
也同意这更像是一个 x/y 问题。我觉得这更像是“我能把这种语言弯曲到什么程度?”输入问题。【参考方案4】:
您可以使用一个装饰器,它返回一个带有__call__
和__repr__
集合的类:
class CustomReprFunc:
def __init__(self, f, custom_repr):
self.f = f
self.custom_repr = custom_repr
def __call__(self, *args, **kwargs):
return self.f(*args, **kwargs)
def __repr__(self):
return self.custom_repr(self.f)
def set_repr(custom_repr):
def set_repr_decorator(f):
return CustomReprFunc(f, custom_repr)
return set_repr_decorator
@set_repr(lambda f: f.__name__)
def func(a):
return a
print(repr(func))
【讨论】:
啊,你赢了我几秒钟!值得强调的是,您实际上并没有将__repr__
添加到函数中,而是添加到包装函数的类中。
注意:这确实会使函数调用变慢。实际上,我可能只是调整打印它的点以生成更友好的字符串,而不更改或包装函数。
@flakes 是的,装饰器现在返回一个类。所以它现在是CustomReprFunc
的一个实例。
@ShadowRanger 是的,您可以使用 CustomReprFunc 以及例如制作一个默认发送__name__
或只是一个字符串的装饰器。可以节省相当多的函数调用,你要记住Python中的函数调用是相当慢的。
我建议也使用functools.update_wrapper
使类的行为更像实际函数(请参阅我的答案)。【参考方案5】:
已经设置好了。
>>> repr(lambda x:x)
'<function <lambda> at 0x1100f6ee0>'
问题是function
类型是不可变的,所以你不能只为function.__repr__
分配一个新函数,你也不能创建function
的子类型来覆盖__repr__
. (并不是说创建子类的实例很容易,即使可以定义它。)
【讨论】:
奇怪的是,尝试覆盖.__repr__
似乎不会产生错误,尽管它不会影响repr()
的结果:python repl 中的def f(x): return 2*x f.__repr__=lambda:'x -> 2x' repr(f) f.__repr__()
显示'<function f at 0x102a8d048>'
对于repr(f)
和'x -> 2x'
对于f.__repr__()
。
@Stef: __repr__
与大多数特殊方法一样,部分出于性能原因,查找的是类型,而不是实例。所以在特定函数上重新分配它不会做任何事情(你不能在 function
类型上重新分配它)。以上是关于如何为函数本身设置 repr? [复制]的主要内容,如果未能解决你的问题,请参考以下文章
zipfile:如何为 Zipfile 设置密码? [复制]
SwiftUI 和 MVVM:当视图模型更改“数据源”(`@Publish items`)本身时,如何为“列表元素更改”设置动画