如何用装饰器包裹 func.__code__.co_filename?

How to get wrapped func.__code__.co_filename with decorator?

下面是一个简单的文件夹结构,test.py中的所有函数都有关键字装饰器。

 lib
  |--- keyword.py
 main
  |--- test.py

Keyword.py

from functools import wraps
def keyword(name=None, tags=(), types=()):

    def _method_wrapper(func):

        @wraps(func)
        def _passargs(self, *args, **kwargs):  
            print(func.__code__.co_filename)  # --> '..\main\test.py'
            return func(self, *args, **kwargs)

        print(_passargs.__code__.co_filename)  # --> '..\lib\keyword.py'
                
        return _passargs

    return _method_wrapper 

注意:print(..) 只是一个例子,我需要 _passargsfunc 都有相同的代码对象,而不是打印另一个变量:)

如您所见,_passargs 得到了错误的 co_filename

本文来自robotframework keyword.py,我根据自己的需要对其进行了修改。但是我没有弄清楚如何使 _passargsfunc 都具有正确的源文件位置,以便 robot.libdoc 可以正确生成 doc.libspec。

有人能帮忙吗?

期望值

func.__code__.co_filename = '..\main\test.py'
_passargs.__code__.co_filename = '..\main\test.py'

Python版本=3.8.10

在这里回答我自己的问题,以帮助可能遇到同样问题的人。

原始装饰器函数:

from functools import wraps
def keyword(name=None, tags=(), types=()):

    def _method_wrapper(func):

        @wraps(func)
        def _passargs(self, *args, **kwargs):  
            print(func.__code__.co_filename)  # --> '..\main\test.py'
            return func(self, *args, **kwargs)

        print(_passargs.__code__.co_filename)  # --> '..\lib\keyword.py'
                
        return _passargs

    return _method_wrapper 

大多数时候,我们只需要最后一层装饰器(在本例中为return func(self, *args, **kwargs))来return包装函数的适当属性。

然而,在我的例子中,有一个函数将在初始化 class 期间使用此 keyword 装饰器扫描所有函数。它不会进入 def _passargs 级别,因为我们没有提供任何参数,只是试图获取包装的 func (return _passargs) 相关属性。

要解决此问题,我们只能在运行时覆盖 co_filename

这是在装饰器中覆盖 __code__ 对象的解决方案。

from types import CodeType
from functools import wraps


def keyword(name=None, tags=(), types=()):

    def _method_wrapper(func):

        @wraps(func)
        def _passargs(self, *args, **kwargs):  
            print(func.__code__.co_filename)  # --> '..\main\test.py'
            return func(self, *args, **kwargs)

        fix_co_filename(_passargs, func.__code__.co_filename)
        print(_passargs.__code__.co_filename)  # --> '..\main\test.py'
                
        return _passargs

    return _method_wrapper 


def fix_co_filename(func, co_filename):
    fn_code = func.__code__
    func.__code__ = CodeType(
        fn_code.co_argcount,
        fn_code.co_posonlyargcount,
        fn_code.co_kwonlyargcount,
        fn_code.co_nlocals,
        fn_code.co_stacksize,
        fn_code.co_flags,
        fn_code.co_code,
        fn_code.co_consts,
        fn_code.co_names,
        fn_code.co_varnames,
        co_filename,
        fn_code.co_name,
        fn_code.co_firstlineno,
        fn_code.co_lnotab,
        fn_code.co_freevars,
        fn_code.co_cellvars)