在 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.farrays
,interval
- list
of 2 floats
), 但我想让用户有更多的自由:例如函数应该接受 int
作为 data
或 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
其中 _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):
....
...
它确实有效,但有几件非常令人不安的事情:
- 它使代码混乱(虽然这是一个小问题,我发现这个解决方案比在每个方法开始时显式调用转换函数要紧凑得多)
- 如果我需要将此装饰器与另一个装饰器(
@staticmethod
或 post 处理方法输出的东西)组合,这些装饰器的顺序很重要
- 最令人不安的是这种形式的装饰器完全隐藏了方法的参数结构并且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):
....
- 我同意你的看法,这是一个更好的解决方案,所以这里没什么可做的。
- 我想你会想出一个好的顺序,并会继续保持下去,这不会成为未来的问题。
这是个问题,你是对的,但用 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 更新。
为了使代码更加简洁,不要向装饰器提供参数 - 从装饰函数中推导它们。
我正在写一个 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.farrays
,interval
- list
of 2 floats
), 但我想让用户有更多的自由:例如函数应该接受 int
作为 data
或 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
其中 _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):
....
...
它确实有效,但有几件非常令人不安的事情:
- 它使代码混乱(虽然这是一个小问题,我发现这个解决方案比在每个方法开始时显式调用转换函数要紧凑得多)
- 如果我需要将此装饰器与另一个装饰器(
@staticmethod
或 post 处理方法输出的东西)组合,这些装饰器的顺序很重要 - 最令人不安的是这种形式的装饰器完全隐藏了方法的参数结构并且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):
....
- 我同意你的看法,这是一个更好的解决方案,所以这里没什么可做的。
- 我想你会想出一个好的顺序,并会继续保持下去,这不会成为未来的问题。
这是个问题,你是对的,但用
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 更新。
为了使代码更加简洁,不要向装饰器提供参数 - 从装饰函数中推导它们。