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

Vla*_*mir 6 python oop arguments numpy python-decorators

我正在编写一个类,其中有许多方法在类似的参数类型上运行:

class TheClass():
    def func1(self, data, params, interval):
        ....   
    def func2(self, data, params):
        ....
    def func3(self, data, interval):
        ....
    def func4(self, params):
        ....
    ...
Run Code Online (Sandbox Code Playgroud)

关于这些参数有一定的约定(例如data/ params应该是numpy.farrays,interval- a list的2 floats),但是我想允许用户有更多的自由:例如,函数应该接受intdata或者params,或者只接受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
Run Code Online (Sandbox Code Playgroud)

其中_convert_data,_convert_params_convert_interval做肮脏的工作.然后我按如下方式定义类:

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):
        ....
    ...
Run Code Online (Sandbox Code Playgroud)

它可以解决问题,但有几个非常令人不安的事情:

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

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

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

为了避免混淆,有一个convertparameters包装器的修改解决了第三个问题(掩盖签名和方法的文档字符串) - 由n9code for Python> 2.5 建议.

使用decorator模块(单独安装:) pip install decorator我们可以同时传输所有函数的"元数据"(文档字符串,名称和签名),去掉包装器内部的嵌套结构

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
Run Code Online (Sandbox Code Playgroud)

更新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)
Run Code Online (Sandbox Code Playgroud)

而且使用起来要简单得多.唯一重要的是继续为不同函数之间的参数使用相同的名称.

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

    @convertparameters
    def func2(self, data, params):
        ....
Run Code Online (Sandbox Code Playgroud)

bag*_*rat 3

  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
    
    Run Code Online (Sandbox Code Playgroud)

    很困难,这仅适用于 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
    
    Run Code Online (Sandbox Code Playgroud)

根据user3160867 的更新进行编辑