Python装饰器可转换函数或方法的输入和/或输出

时间:2019-04-07 22:44:50

标签: python python-decorators

下面的功能可以完成(似乎可以完成)工作,但是似乎需要的锅炉板要多得多。

我敢肯定还有更优雅的方法。可以将其中的某些代码排除在外的代码,使它看起来不像现在的复制/粘贴/编辑补丁工作。

不过请注意,优雅并不是全部:我不希望性能受到影响。例如,我可以通过做两个装饰器将代码切成两半:一个用于输入转换,另一个用于输出转换。但这会比当前版本的效率低。

pip --version

一些使用示例:

def input_output_decorator(preprocess=None, postprocess=None):
    def decorator(func):
        if inspect.ismethod(func):
            if preprocess is not None:
                if postprocess is not None:  # both pre and post processes
                    @wraps(func)
                    def func_wrapper(self, *args, **kwargs):
                        return postprocess(func(self, preprocess(*args, **kwargs)))
                else:  # a preprocess but no postprocess
                    @wraps(func)
                    def func_wrapper(self, *args, **kwargs):
                        return func(self, preprocess(*args, **kwargs))
            else:  # no preprocess
                if postprocess is not None:  # no preprocess, but a postprocess
                    @wraps(func)
                    def func_wrapper(self, *args, **kwargs):
                        return postprocess(func(*args, **kwargs))
                else:  # no pre or post process at all
                    func_wrapper = func
            return func_wrapper
        else:
            if preprocess is not None:
                if postprocess is not None:  # both pre and post processes
                    @wraps(func)
                    def func_wrapper(*args, **kwargs):
                        return postprocess(func(preprocess(*args, **kwargs)))
                else:  # a preprocess but no postprocess
                    @wraps(func)
                    def func_wrapper(*args, **kwargs):
                        return func(preprocess(*args, **kwargs))
            else:  # no preprocess
                if postprocess is not None:  # no preprocess, but a postprocess
                    @wraps(func)
                    def func_wrapper(*args, **kwargs):
                        return postprocess(func(*args, **kwargs))
                else:  # no pre or post process at all
                    func_wrapper = func
            return func_wrapper

    return decorator

基于@ micky-loo(接受)的答案,并受到@a_guest答案的启发,我的最终实现是:

    >>> # Examples with "normal functions"
    >>> def f(x=3):
    ...     '''Some doc...'''
    ...     return x + 10
    >>> ff = input_output_decorator()(f)
    >>> print((ff(5.0)))
    15.0
    >>> ff = input_output_decorator(preprocess=int)(f)
    >>> print((ff(5.0)))
    15
    >>> ff = input_output_decorator(preprocess=int, postprocess=lambda x: "Hello {}!".format(x))(f)
    >>> print((ff('5')))
    Hello 15!
    >>> ff = input_output_decorator(postprocess=lambda x: "Hello {}!".format(x))(f)
    >>> print((ff(5.0)))
    Hello 15.0!
    >>> print((ff.__doc__))
    Some doc...
    >>>
    >>> # examples with methods (bounded, class methods, static methods
    >>> class F:
    ...     '''This is not what you'd expect: The doc of the class, not the function'''
    ...     def __init__(self, y=10):
    ...         '''Initialize'''
    ...         self.y = y
    ...     def __call__(self, x=3):
    ...         '''Some doc...'''
    ...         return self.y + x
    ...     @staticmethod
    ...     def static_method(x, y):
    ...         return "What {} {} you have".format(x, y)
    ...     @classmethod
    ...     def class_method(cls, x):
    ...         return "{} likes {}".format(cls.__name__, x)
    >>>
    >>> f = F()
    >>> ff = input_output_decorator()(f)
    >>> print((ff(5.0)))
    15.0
    >>> ff = input_output_decorator(preprocess=int)(f)
    >>> print((ff(5.0)))
    15
    >>> ff = input_output_decorator(preprocess=int, postprocess=lambda x: "Hello {}!".format(x))(f)
    >>> print((ff('5')))
    Hello 15!
    >>> ff = input_output_decorator(postprocess=lambda x: "Hello {}!".format(x))(f)
    >>> print((ff(5.0)))
    Hello 15.0!
    >>> print((ff.__doc__))
    This is not what you'd expect: The doc of the class, not the function

2 个答案:

答案 0 :(得分:1)

使用两个不同的装饰器可以实现更紧凑的实现。的确,在进行预处理后,您还将在链中再进行一次函数调用,但这几乎不会影响性能。

import functools
import inspect

def preprocess(pre):
    def decorator(func):
        if inspect.ismethod(func):
            def wrapper(self, *args, **kwargs):
                return func(self, pre(*args, **kwargs))
        else:
            def wrapper(*args, **kwargs):
                return func(pre(*args, **kwargs))
        return functools.wraps(func)(wrapper)
    return decorator

def postprocess(post):
    def decorator(func):
        def wrapper(*args, **kwargs):
            return post(func(*args, **kwargs))
        return functools.wraps(func)(wrapper)
    return decorator

答案 1 :(得分:0)

您不需要进行检查检查。可以拼合if / else嵌套,以使代码更具可读性。

from functools import wraps

def input_output_decorator(preprocess=None, postprocess=None):
    def decorator(func):
        if preprocess and postprocess:
            @wraps(func)
            def func_wrapper(*args, **kwargs):
                return postprocess(func(preprocess(*args, **kwargs)))
        elif preprocess:
            @wraps(func)
            def func_wrapper(*args, **kwargs):
                return func(preprocess(*args, **kwargs))
        elif postprocess:
            @wraps(func)
            def func_wrapper(*args, **kwargs):
                return postprocess(func(*args, **kwargs))
        else:
            func_wrapper = func

        return func_wrapper

    return decorator