我希望能够用类
Step
来装饰实例方法,以便这些方法被 Step
对象替换。同时,我希望能够选择能够使用 Step(func, options)
实例化步骤。
下面的示例似乎就是这样做的,但我不确定这是否是实现此功能的更简洁的方法?
from typing import Callable
import functools
def step(options: "StepOptions" = None):
"""Step decorator"""
def decorator(func: Callable) -> Step:
return Step(func, options)
return decorator
class Step:
def __init__(self, func: Callable, options: "StepOptions" = None):
self.func = func
self.options = options if options else StepOptions()
def __get__(self, instance, owner=None):
# Creating a bound method by passing the instance to functools.partial
bound_method = functools.partial(self.func, instance)
functools.update_wrapper(bound_method, self.func)
# Return a new Step instance with the bound method
return Step(bound_method, self.options)
def __call__(self, *args, **kwargs):
return self.func(*args, **kwargs)
def __repr__(self):
return f"Step(func: {self.func.__name__}, options: {self.options})"
class StepOptions:
def __init__(self, retries: int = 0, timeout_s : float = None):
self.retries = retries
self.timeout_s = timeout_s
def __repr__(self):
return f"StepOptions(retries: {self.retries}, timeout_s: {self.timeout_s})"
class Test:
def __init__(self):
self.steps= [
Step(self.test_1, options=StepOptions(retries=3)),
self.test_2,
self.test_3,
]
self.value = 2
def test_1(self):
print("This is test_1")
return self.value
@step()
def test_2(self) -> str:
print("This is test_2")
return 20
@step(StepOptions(timeout_s=10))
def test_3(self) -> int:
print("This is test_3")
return 30
test = Test()
for test_step in test.steps:
print(f"type: {type(test_step)}")
print(test_step)
result = test_step()
print(result)
输出
type: <class '__main__.Step'>
Step(func: test_1, options: StepOptions(retries: 3, timeout_s: None))
This is test_1
2
type: <class '__main__.Step'>
Step(func: test_2, options: StepOptions(retries: 0, timeout_s: None))
This is test_2
20
type: <class '__main__.Step'>
Step(func: test_3, options: StepOptions(retries: 0, timeout_s: 10))
This is test_3
30
特别是我不确定
__get__
的Step
方法。到目前为止,我只看到了类似的例子
def __get__(self, instance, owner=None):
return functools.partial(self.__call__, instance)
但是,这样做时,
print(test_2)
会导致
functools.partial(<bound method Step.__call__ of Step(func: test_2, options: StepOptions(retries: 0, timeout_s: None))>, <__main__.Test object at 0x78f0e1a19fc0>)
而不是
Step(func: test_2, options: StepOptions(retries: 0, timeout_s: None))
一些背景知识:我计划在测试执行器中使用这个构造。这个想法是,通过从每个测试步骤函数创建一个
Step
实例,我有一个地方可以存储每个步骤的元数据(例如 StepOptions
)并在测试执行器中访问该元数据。如果有人看到更好的方法,我非常乐意收到意见!
这段代码本身就很好。
但是,如果想要简化
__get__
并且不重新创建“绑定步骤实例”,您可以只在 partial(self.__call__, instance)
中返回 __get__
并假设在 __call__
中,args
的第一个元素始终是实例。 (您甚至可以明确地命名它)。
...
def __get__(self, instance, owner):
return functools.partial(self.__call__, instance)
def __call__(self, instance, *args, **kwargs):
...
我认为您不必处理每个
Step
实例的多个克隆以及“绑定”和“未绑定”步骤的代码库可以更容易管理 - 尽管您这样做的方式为您提供了一些额外的灵活性.
(*) 我没有检查我的建议是否适用于您想要使用的所有方式
Step
- 它可能需要对“内部实例方法”方式进行一些调整。
除此之外,您可能不需要
def step
包装器 - __init__
可以保留自己的 - 只需检查它是否获得 func
参数 - 否则,立即使用 return partial type(self, options=options)
返回