用装饰器类装饰实例方法,用类实例替换方法

问题描述 投票:0回答:1

我希望能够用类

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
)并在测试执行器中访问该元数据。如果有人看到更好的方法,我非常乐意收到意见!

python python-decorators python-descriptors instance-methods
1个回答
0
投票

这段代码本身就很好。

但是,如果想要简化

__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)

返回
© www.soinside.com 2019 - 2024. All rights reserved.