python - 如何构建 python 装饰函数以使其更易于维护?
问题描述
我真的厌倦了通过在我的代码中装饰函数来管道值,虽然我认为使用 splat (**) 或编写装饰器可能有适合我的 python 解决方案,但我不确定最好的类型检查器友好的解决方案是。我对维护代码感到沮丧
@dataclass
class FooBar:
foo: FooOut
bar: BarOut
def foo(arg1: str, arg2: int) -> FooOut:
do_something()
return inner_func(arg1=arg1, arg2=arg2).foo
def bar(arg1: str, arg2: int) -> BarOut:
bar = inner_func(arg1=arg1, arg2=arg2).bar
bar2 = do_some_mutation(bar, arg1)
return bar2
def inner_func(arg1: str, arg2: int) -> FooBar:
# do something with the arguments
return FooBar(foo=some_value, bar=some_other_value)
现在问题来了,什么inner_func
时候需要一个烦人的新arg3: Optional[str]
参数。我想做的最后一件事是arg3: Optional[str] = None
到处添加,然后手动传递 arg3。
一种解决方案是将参数字典转换为对象。这没关系,只是它需要修改所有呼叫站点。一个有点混乱的变体是创建新函数foo_new(in: InputParams)
,这些函数接受输入对象并将旧函数委托给新函数。然后我们最终会弃用旧功能。
另一种解决方案是将签名转换为要使用的每个函数**kwargs
。除了让静态类型检查器满意之外,这将起作用。为了让某人知道有哪些有效参数,需要深入研究inner_func
以查看使用了什么。
我认为理想的解决方案是如果我可以做类似的事情
@dataclass
class InputParams:
arg1: str
arg2: int
arg3: Optional[str]
def bar(input: **InputParams) -> BarOut:
bar = inner_func(input)
bar2 = do_some_mutation(bar, input.arg1)
return bar
你会怎么做?非常感谢!
解决方案
似乎您几乎已经回答了您的问题,您的所有功能似乎都充当inner_func
. 您在 bar 中使用的事实arg1
有点令人难过,并且使事情变得比实际情况更复杂(例如,您不能忽略这些论点)。
处理这个问题的尝试通常有点失控,但至少应该是可重用的(希望对此的罐装解决方案可以替换我的代码块)。查看了包装因子、生成器和类,最终得到了这个。
示例用法:
def inner(a, b=2, c=3):
return a * b + c
@preprocessor(inner)
def double_a(arguments):
arguments.arguments['a'] *= 2
@postprocessor(inner)
def add_b_to_result(res, arguments):
return res + arguments.arguments['b']
def clean_double_a(arguments):
arguments.arguments['a'] *= 2
def clean_add_b_to_result(res, arguments):
return res + arguments.arguments['b']
both = processor(inner, pre=clean_double_a, post=clean_add_b_to_result)
@preprocessor(inner)
def both_alt(arguments):
return clean_double_a(arguments)
@both_alt.postprocessor
def both_alt_post(res, arguments):
return clean_add_b_to_result(res, arguments)
def self_tests():
assert inner(1) == 1 * 2 + 3
assert double_a(1) == 2 * 2 + 3
assert add_b_to_result(1) == 1 * 2 + 3 + 2
assert both(1) == 2 * 2 + 3 + 2
assert both_alt(1) == 2 * 2 + 3 + 2
print('Tests complete')
self_tests() # Tests complete
帮助库:
from functools import partial, update_wrapper
from inspect import signature
class Processor(object):
def __init__(self, func, pre=None, post=None):
self.__func = func
self.__sig = signature(func)
self.__preprocessor = pre
self.__postprocessor = post
if pre or post:
# Make this instance look like the first of pre/post
self.__wrapped_by_pre_post = True
update_wrapper(self, pre or post)
else:
# Make this instance look like the function
self.__wrapped_by_pre_post = False
update_wrapper(self, func)
def __call__(self, *args, **kwargs):
arguments = self.__sig.bind(*args, **kwargs)
arguments.apply_defaults()
if self.__preprocessor is not None:
new_args = self.__preprocessor(arguments)
if new_args is not None:
arguments = new_args
res = self.__func(*arguments.args, **arguments.kwargs)
if self.__postprocessor is not None:
res = self.__postprocessor(res, arguments)
return res
def preprocessor(self, func):
if self.__preprocessor is not None:
raise RuntimeError('defined multiple preprocessors')
self.__preprocessor = func
self.__wrap_if_first(func)
return self
def postprocessor(self, func):
if self.__postprocessor is not None:
raise RuntimeError('defined multiple postprocessors')
self.__postprocessor = func
self.__wrap_if_first(func)
return self
def __wrap_if_first(self, func):
# Make it look like the first to process, like getter/setters
if not self.__wrapped_by_pre_post:
update_wrapper(self, func)
self.__wrapped_by_pre_post = True
processor = Processor
def preprocessor(func):
return Processor(func).preprocessor
def postprocessor(func):
return Processor(func).postprocessor
推荐阅读
- apache-spark - 在 Windows 上使用 pyspark 不起作用-py4j
- php - 在 Woocommerce 3.2+ 中以编程方式向订单添加折扣
- html - 将图像放在另一个图像上并保持比例(HTML - CSS)
- php - 如何使用laravel制作带有特殊字符的密码
- arrays - 如何使用高阶函数获得对角线的总和?
- ssis - 从excel文件加载数据的SSIS问题
- excel - 从外部 vbs 运行时宏的行为不同
- java - 从 Java 中的 Tableau 仪表板中捕获表数据
- linux - tcpdump http 请求/响应 Wireshark
- .net - 获取struct的方法表