通过装饰器为class动态添加函数

Dynamically add function to class through decorator

我正在尝试找到一种通过装饰器向 class 动态添加方法的方法。 我的装饰器看起来像:

def deco(target):

    def decorator(function):
        @wraps(function)
        def wrapper(self, *args, **kwargs):
            return function(*args, id=self.id, **kwargs)

        setattr(target, function.__name__, wrapper)
        return function

    return decorator

class A:
    pass

# in another module
@deco(A)
def compute(id: str):
    return do_compute(id)

# in another module
@deco(A)
def compute2(id: str):
    return do_compute2(id)

# **in another module**
a = A()
a.compute() # this should work
a.compute2() # this should work

希望装饰者给classA加上compute()函数,A的任何对象都应该有compute()方法。 但是,在我的测试中,这仅在我将计算明确导入到创建 A 对象的位置时才有效。我想我遗漏了一些明显的东西,但不知道如何解决。感谢任何帮助!

我认为使用作为 class:

实现的装饰器会更简单
class deco:
    def __init__(self, cls):
        self.cls = cls

    def __call__(self, f):
        setattr(self.cls, f.__name__, f)
        return self.cls

class A:
    def __init__(self, val):
        self.val = val

@deco(A)
def compute(a_instance):
    print(a_instance.val)


A(1).compute()
A(2).compute()

产出

1
2

但是仅仅因为你能做到并不意味着你应该做到。这可能成为调试的噩梦,并且可能会给任何静态代码分析器或 linter 带来困难(PyCharm 例如“抱怨”Unresolved attribute reference 'compute' for class 'A'


为什么当我们将它拆分到不同的模块时它不能开箱即用(更具体地说,compute 在另一个模块 中定义时)?

假设如下:

a.py

print('importing deco and A')

class deco:
    def __init__(self, cls):
        self.cls = cls

    def __call__(self, f):
        setattr(self.cls, f.__name__, f)
        return self.cls

class A:
    def __init__(self, val):
        self.val = val

b.py

print('defining compute')

from a import A, deco

@deco(A)
def compute(a_instance):
    print(a_instance.val)

main.py

from a import A

print('running main')

A(1).compute()
A(2).compute()

如果我们执行 main.py 我们得到以下结果:

importing deco and A
running main
Traceback (most recent call last):
    A(1).compute()
AttributeError: 'A' object has no attribute 'compute'

有些东西不见了。 defining compute 不输出。更糟糕的是,compute 从未被定义,更不用说绑定到 A.

为什么?因为没有任何东西触发 b.py 的执行。仅仅因为它放在那里并不意味着它会被执行。

我们可以通过导入来强制执行。感觉有点侮辱我,但它之所以有效,是因为导入一个文件有一个 side-effect:它执行不受 if __name__ == '__main__ 保护的每一段代码,就像导入一个模块执行它的 __init__.py ] 文件。

main.py

from a import A
import b

print('running main')

A(1).compute()
A(2).compute()

产出

importing deco and A
defining compute
running main
1
2