我编写了一个受functools启发的实用程序库,它为我经常在项目中使用的函数添加了一些常见的操作。和往常一样,我很感谢你的反馈。
functoolsplus.py
"""More higher-order functions and operations on callable objects."""
from dataclasses import dataclass
from time import perf_counter
from functools import wraps
from sys import exit, stderr # pylint: disable=W0622
from typing import Any, Callable, IO, Optional, Union
__all__ = [
'coerce',
'exiting',
'exitmethod',
'instance_of',
'timeit',
'wants_instance'
]
Decorator = Callable[[Callable[..., Any]], Callable[..., Any]]
@dataclass
class PerfCounter:
"""Performance counter."""
start: Optional[float] = None
end: Optional[float] = None
on_exit: Optional[Callable[..., Any]] = None
def __enter__(self):
self.start = perf_counter()
return self
def __exit__(self, typ, value, traceback):
self.end = perf_counter()
if self.on_exit is None:
return None
if len(self.on_exit.__code__.co_varnames) == 1:
return self.on_exit(self)
return self.on_exit()
@property
def duration(self) -> float:
"""Return the duration."""
return self.end - self.start
class exitmethod: # pylint: disable=C0103
"""Decorator class to create a context manager,
having the passed function as exit method.
"""
def __init__(self, function: Callable[..., Any]):
self.function = function
def __enter__(self):
return self
def __exit__(self, typ, value, traceback):
if wants_instance(self.function):
return self.function(self, typ, value, traceback)
return self.function(typ, value, traceback)
def coerce(typ: type) -> Callable[..., Any]:
"""Converts the return value into the given type."""
def decorator(function: Callable[..., Any]) -> Callable[..., typ]:
"""Decorates the given function."""
@wraps(function)
def wrapper(*args, **kwargs) -> typ:
"""Wraps the respective function."""
return typ(function(*args, **kwargs))
wrapper.__annotations__['return'] = typ
return wrapper
return decorator
def exiting(function: Callable[..., Any]) -> Callable[..., Any]:
"""Makes a function exit the program with its return code."""
@wraps(function)
def wrapper(*args, **kwargs) -> Any:
"""Wraps the respective function."""
result = function(*args, **kwargs)
exit(result or 0)
return wrapper
def instance_of(cls: Union[type, tuple[type]]) -> Callable[[Any], bool]:
"""Returns a callback function to check the instance of an object."""
return lambda obj: isinstance(obj, cls)
def timeit(file: IO = stderr, flush: bool = False) -> Decorator:
"""Times the execution of the given function."""
def print_duration(
function: Callable[..., Any]
) -> Callable[[PerfCounter], None]:
"""Prints a perf counter."""
def inner(ctr: PerfCounter) -> None:
print('Function', function.__name__, 'took', ctr.duration,
file=file, flush=flush)
return inner
def decorator(function: Callable[..., Any]) -> Callable[..., Any]:
"""The actual decorator."""
@wraps(function)
def wrapper(*args, **kwargs):
"""Wraps the original function."""
with PerfCounter(on_exit=print_duration(function)):
return function(*args, **kwargs)
return wrapper
return decorator
def wants_instance(function: Callable[..., Any]) -> bool:
"""Determines whether the respective function is considered a method."""
try:
return function.__code__.co_varnames[0] == 'self'
except IndexError:
return False下面的代码是示例用例,以了解如何使用库实用程序。
from time import sleep
from typing import Iterator
import functoolsplus # Change accordingly
with functoolsplus.PerfCounter() as ctr:
sleep(3)
print('Performace countner:', ctr, ctr.duration)
@functoolsplus.exitmethod
def value_error_guard(typ, value, traceback):
if isinstance(value, ValueError):
print('Doing stuff with ValueError:', value)
return True
with value_error_guard:
raise ValueError('Darn it.')
@functoolsplus.coerce(frozenset)
def get_squares(n: int) -> Iterator[int]:
"""Yields square numbers from 0 to n-1."""
for number in range(n):
yield number ** 2
input('Press enter to view help(get_squares):')
help(get_squares)
print('Squares:', get_squares(5))
NUMBERS = [1, 2, 3.0, 4, 5, 6.2, '42']
INTEGERS = filter(functoolsplus.instance_of(int), NUMBERS)
print('Numbers:', *NUMBERS)
print('Integers:', *INTEGERS)
@functoolsplus.timeit(flush=True)
def timed_sleep(seconds: float) -> None:
return sleep(seconds)
timed_sleep(3)
class Foo:
def foo(self) -> int:
return 42
@classmethod
def bar(cls):
return cls()
@staticmethod
def spamm():
pass
print('Foo.foo() wants instance:', functoolsplus.wants_instance(Foo.foo))
print('Foo.bar() wants instance:', functoolsplus.wants_instance(Foo.bar))
print('Foo.spamm() wants instance:', functoolsplus.wants_instance(Foo.spamm))
@functoolsplus.exiting
def main(exit_code: int) -> int:
print('Exiting with exit code:', exit_code)
return exit_code
main(42)发布于 2022-01-11 14:28:30
我不认为PerfCounter是一个很好的类模型。start和end在构造上无效,这应该是一个危险的标志。当前的协议还允许调用用户初始化start和end本身,这是没有意义的;因此,即使将其建模为类,也不应该是@dataclass。我认为,更自然的调用模式是一个简单的函数,它接受一个函数引用,调用它,并返回一个已经存在的float,名为timeit (不是您的timeit;内置的timeit)。在这一点上,可能不是一个好主意,与该名称重叠。为什么支持回调?如果您已经在修饰代码,那么您可以在执行之后立即访问代码的一部分,那么为什么不.只是在那里做点什么,而不是注册一个回调?
任何依赖于want_instance的东西都是不理想的应用程序设计的症状。您应该已经知道了一切是否都是实例方法。
https://codereview.stackexchange.com/questions/272874
复制相似问题