首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >Functools扩展库

Functools扩展库
EN

Code Review用户
提问于 2022-01-11 14:05:35
回答 1查看 153关注 0票数 4

我编写了一个受functools启发的实用程序库,它为我经常在项目中使用的函数添加了一些常见的操作。和往常一样,我很感谢你的反馈。

functoolsplus.py

代码语言:javascript
复制
"""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

使用示例

下面的代码是示例用例,以了解如何使用库实用程序。

代码语言:javascript
复制
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)
EN

回答 1

Code Review用户

回答已采纳

发布于 2022-01-11 14:28:30

我不认为PerfCounter是一个很好的类模型。startend在构造上无效,这应该是一个危险的标志。当前的协议还允许调用用户初始化startend本身,这是没有意义的;因此,即使将其建模为类,也不应该是@dataclass。我认为,更自然的调用模式是一个简单的函数,它接受一个函数引用,调用它,并返回一个已经存在的float,名为timeit (不是您的timeit;内置的timeit)。在这一点上,可能不是一个好主意,与该名称重叠。为什么支持回调?如果您已经在修饰代码,那么您可以在执行之后立即访问代码的一部分,那么为什么不.只是在那里做点什么,而不是注册一个回调?

任何依赖于want_instance的东西都是不理想的应用程序设计的症状。您应该已经知道了一切是否都是实例方法。

票数 5
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/272874

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档