Как реализовать функциональность «декоратора» в объекте «класс»? - PullRequest
2 голосов
/ 08 мая 2020

Я пытаюсь создать класс, который подавляет stdout и stderr. У меня он не работает при использовании with statement, но я хочу расширить функциональность, чтобы также использовать его в качестве декоратора, который я можно использовать для подавления вывода функций. Возможно ли, чтобы все полностью содержалось в классе, или оболочка должна быть функцией вне класса?

Я пытаюсь следовать этим ресурсам, но мне сложно адаптироваться к моей ситуации:

https://stackabuse.com/pythons-classmethod-and-staticmethod-explained/

Как реализовать Python декоратор с аргументами как класс?

import os,sys, functools


class Suppress(object):
    def __init__(self, show_stdout=False, show_stderr=False):
        self.show_stdout = show_stdout
        self.show_stderr = show_stderr
        self.original_stdout = None
        self.original_stderr = None

    def __enter__(self):
        devnull = open(os.devnull, "w")

        # Suppress streams
        if not self.show_stdout:
            self.original_stdout = sys.stdout
            sys.stdout = devnull

        if not self.show_stderr:
            self.original_stderr = sys.stderr
            sys.stderr = devnull

    def __exit__(self, *args, **kwargs):
        # Restore streams
        if not self.show_stdout:
            sys.stdout = self.original_stdout

        if not self.show_stderr:
            sys.stderr = self.original_stderr


    def __call__(self, *args, **kwargs):
        def decorator(func):
            @functools.wraps(func)
            def wrapper(*args, **kwargs):
                with self(*args, **kwargs):
                    return func(*args, **kwargs)
            return wrapper
         return decorator



with Suppress(show_stdout=False, show_stderr=False):
    print("stdout", file=sys.stdout)
    print("stderr", file=sys.stderr)

Я также пытаюсь использовать эту функцию в качестве декоратора:

@Suppress(show_stdout=True)
def f(x, y):
    print(x, file=sys.stdout)
    print(y, file=sys.stderr)
    return x*y
a = f(1,2)
# 1
# a = 2

У меня было аналогичный декоратор работает (хотя и очень некрасиво) для оболочки в стиле matplotlib. Однако это должно было использовать внешнюю функцию и не входило в класс.

# # Decorators
# def stylize(style="seaborn-white"):
#     def decorator(func):
#         @functools.wraps(func)
#         def wrapper(*args, **kwargs):
#             with plt.style.context(style):
#                 return func(*args, **kwargs)
#         return wrapper
#     return decorator
# # Wrappers
# def subplots_wrapper(style="seaborn-white", *args, **kwargs):
#     @stylize(style)
#     def inner_wrapper(*args, **kwargs):
#         return plt.subplots(*args, **kwargs)
#     return inner_wrapper(*args, **kwargs)

Ответы [ 2 ]

1 голос
/ 08 мая 2020

Вы можете определить это, комбинируя различные части из contextlib. Короче говоря, здесь используется ExitStack для объединения запрошенных диспетчеров контекста перенаправления в один диспетчер контекста и ContextDecorator для использования полученного диспетчера контекста в качестве декоратора.

from contextlib import ExitStack, ContextDecorator, redirect_stdout, redirect_stderr
import sys
from os import devnull


class Suppress(ContextDecorator, ExitStack):
    def __init__(self, show_stdout=False, show_stderr=False, **kwargs):
        super().__init__(**kwargs)
        self.redirections = []
        if show_stdout:
            self.redirections.append(redirect_stdout)
        if show_stderr:
            self.redirections.append(redirect_stderr)

    def __enter__(self):
        rv = super().__enter__()

        if self.redirections:
            f = rv.enter_context(open(devnull, "w"))
            for r in self.redirections:
                rv.enter_context(r(f))
        return rv

__init__ хранит классы redirect_stdout и redirect_stderr, которые потребуются в списке.

__enter__, если были запрошены какие-либо перенаправления, открывает соответствующий приемник и выполняет соответствующие перенаправления. В качестве стека выхода Suppress позаботится о том, чтобы перенаправления завершились и приемник был закрыт.

1 голос
/ 08 мая 2020

Работает, если вы используете просто with self без аргументов:

   def __call__(self, function):

        @functools.wraps(function)
        def decorated(*args, **kwargs):
            with self:
                return function(*args, **kwargs)
        return decorated
...