在 class 的所有方法中将参数转换为相同标准的 Pythonic 方式

Pythonic way of converting parameters to the same standard within all methods of a class

我正在写一个 class,其中有许多方法对相似类型的参数进行操作:

class TheClass():
    def func1(self, data, params, interval):
        ....   
    def func2(self, data, params):
        ....
    def func3(self, data, interval):
        ....
    def func4(self, params):
        ....
    ...

关于这些参数有一定的约定(例如 data/params 应该是 numpy.farraysinterval - list of 2 floats), 但我想让用户有更多的自由:例如函数应该接受 int 作为 dataparams,或者只有一个 int 作为 interval 然后假设这是起点 0,等等

因此,为了避免方法内的所有这些转换,应该只执行逻辑,我使用这样的装饰器:

def convertparameters(*types):
    def wrapper(func):

        def new_func(self, *args, **kwargs):
            # Check if we got enough parameters
            if len(types) > len(args):
                raise Exception('Not enough parameters')
            # Convert parameters
            new_args = list(args)
            for ind, tip in enumerate(types):
                if tip == "data":
                    new_args[ind] = _convert_data(new_args[ind])
                elif tip == "params":
                    new_args[ind] = _convert_params(new_args[ind])
                elif tip == "interval":
                    new_args[ind] = _convert_interval(new_args[ind])
                else:
                    raise Exception('Unknown type for parameter')
            return func(self, *new_args, **kwargs)

        return new_func
    return wrapper

其中 _convert_data_convert_params_convert_interval 做肮脏的工作。然后我定义 class 如下:

class TheClass():
    @convertparameters("data", "params", "interval")
    def func1(self, data, params, interval):
        ....   

    @convertparameters("data", "params")
    def func2(self, data, params):
        ....

    @convertparameters("data", "interval")
    def func3(self, data, interval):
        ....

    @convertparameters("params")
    def func4(self, params):
        ....
    ...

它确实有效,但有几件非常令人不安的事情:

  1. 它使代码混乱(虽然这是一个小问题,我发现这个解决方案比在每个方法开始时显式调用转换函数要紧凑得多)
  2. 如果我需要将此装饰器与另一个装饰器(@staticmethod 或 post 处理方法输出的东西)组合,这些装饰器的顺序很重要
  3. 最令人不安的是这种形式的装饰器完全隐藏了方法的参数结构并且IPython显示为func1(*args, **kwargs)

有没有更好的(或更多"Pythonic")方法来进行如此大规模的参数转换?

更新 1:解决方案 基于 n9code 的建议

为了避免混淆,对 convertparameters 包装器进行了修改,解决了第三个问题(屏蔽签名和方法文档字符串)- n9code 建议 Python >2.5。

使用decorator模块(单独安装:pip install decorator)我们可以同时传递所有函数的"metadata"(文档字符串,名称和签名)摆脱嵌套结构在包装内

from decorator import decorator 

def convertparameters(*types):

    @decorator
    def wrapper(func, self, *args, **kwargs):
        # Check if we got enough parameters
        if len(types) > len(args):
            raise Exception('Not enough parameters')
        # Convert parameters
        new_args = list(args)
        for ind, tip in enumerate(types):
            if tip == "data":
                new_args[ind] = _convert_data(new_args[ind])
            elif tip == "params":
                new_args[ind] = _convert_params(new_args[ind])
            elif tip == "interval":
                new_args[ind] = _convert_interval(new_args[ind])
            else:
                raise Exception('Unknown type for parameter')
        return func(self, *new_args, **kwargs)

    return wrapper

更新 2:根据 zmbq 的建议修改解决方案

使用inspect模块我们也可以摆脱装饰器的参数,检查初始函数的参数名称。这将消除另一层装饰器

from decorator import decorator
import inspect

@decorator
def wrapper(func, self, *args, **kwargs):
    specs = inspect.getargspec(func)

    # Convert parameters
    new_args = list(args)
    for ind, name in enumerate(specs.args[1:]):
        if name == "data":
            new_args[ind] = _convert_data(new_args[ind])
        elif name == "params":
            new_args[ind] = _convert_params(new_args[ind])
        elif name == "interval":
            new_args[ind] = _convert_interval(new_args[ind])
    return func(self, *new_args, **kwargs)

然后用法就简单多了。唯一重要的是在不同函数之间使用相同的参数名称。

class TheClass():
    @convertparameters
    def func1(self, data, params, interval):
        ....   

    @convertparameters
    def func2(self, data, params):
        ....
  1. 我同意你的看法,这是一个更好的解决方案,所以这里没什么可做的。
  2. 我想你会想出一个好的顺序,并会继续保持下去,这不会成为未来的问题。
  3. 这是个问题,你是对的,但用 functools.wraps 很容易解决。只需用它装饰你的newfunc,你将保存原始函数的签名。

    from functools import wraps
    
    def convertparameters(*types):
        def wrapper(func):
            @wraps(func)
            def new_func(self, *args, **kwargs):
                pass # Your stuff
    

    很难,这仅适用于 Python 3。在 Python 2 中,不会保留签名,只有 __name____doc__ 会保留。所以在 Python 的情况下,你可以使用 decorator 模块:

    from decorator import decorator
    
    def convertparameters(*types):
        @decorator
        def wrapper(func, self, *args, **kwargs):
            pass  # return a result
        return wrapper
    

编辑 基于 user3160867's 更新。

为了使代码更加简洁,不要向装饰器提供参数 - 从装饰函数中推导它们。