关于python:是否可以检查一个函数是否被修饰在另一个函数中?

Is it possible to check if a function is decorated inside another function?

在我的例子中,如果调用一个函数(这里的decoratednot_decorated有一个特定的修饰符(代码为@out),有没有办法检查函数f1的内部?这些信息是否传递给函数?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def out(fun):
    def inner(*args, **kwargs):
        fun(*args, **kwargs)
    return inner

@out
def decorated():
    f1()

def not_decorated():
    f1()

def f1():
    if is_decorated_by_out: # here I want to check it
        print('I am')
    else:
        print('I am not')

decorated()
not_decorated()

预期输出:

1
2
I am
I am not


很明显,这是一个骇客,所以我不建议这样做,但是由于您已经排除了附加参数,而且无论包装与否,f1都将是相同的,所以您将黑客作为唯一的选择。解决方案是在包装函数中添加一个局部变量,以便通过堆栈检查找到:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import inspect

def out(fun):
    def inner(*args, **kwargs):
        __wrapped_by__ = out
        fun(*args, **kwargs)
    return inner

def is_wrapped_by(func):
    try:
        return inspect.currentframe().f_back.f_back.f_back.f_locals.get('__wrapped_by__') is func
    except AttributeError:
        return False

@out
def decorated():
    f1()

def not_decorated():
    f1()

def f1():
    if is_wrapped_by(out):
        print('I am')
    else:
        print('I am not')

decorated()
not_decorated()

尝试在线!

这假设了特定程度的嵌套(通过f_back进行手动回溯,以说明is_wrapped_by本身、f1decorated以及最后到inner的嵌套(从out开始)。如果要确定调用堆栈中是否有out参与,请执行is_wrapped_by循环,直到堆栈耗尽:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def is_wrapped_by(func):
    frame = None
    try:
        # Skip is_wrapped_by and caller
        frame = inspect.currentframe().f_back.f_back
        while True:
            if frame.f_locals.get('__wrapped_by__') is func:
                return True
            frame = frame.f_back
    except AttributeError:
        pass
    finally:
        # Leaving frame on the call stack can cause cycle involving locals
        # which delays cleanup until cycle collector runs;
        # explicitly break cycle to save yourself the headache
        del frame
    return False

假设你有一个像这样的功能装饰

1
2
3
4
def double_arg(fun):
    def inner(x):
        return fun(x*2)
    return inner

但是,您不能访问它(它在第三方库或其他地方)。在这种情况下,您可以将它包装成另一个函数,该函数将装饰的名称添加到生成的函数中。

1
2
3
4
5
6
def keep_decoration(decoration):
    def f(g):
        h = decoration(g)
        h.decorated_by = decoration.__name__
        return h
    return f

用包装纸代替旧的装饰。

1
double_arg = keep_decoration(double_arg)

甚至可以编写一个助手函数来检查函数是否被修饰。

1
2
3
4
5
def is_decorated_by(f, decoration_name):
    try:
        return f.decorated_by == decoration_name
    except AttributeError:
        return False

使用示例…

1
2
3
4
5
6
7
8
9
10
11
12
@double_arg
def inc_v1(x):
    return x + 1

def inc_v2(x):
    return x + 1

print(inc_v1(5))
print(inc_v2(5))

print(is_decorated_by(inc_v1, 'double_arg'))
print(is_decorated_by(inc_v2, 'double_arg'))

产量

1
2
3
4
11
6
True
False


如果您愿意在f1中创建附加参数(也可以使用默认参数),则可以使用functools.wraps并检查__wrapped__属性的存在性。为此,请将包装函数传递给f

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import functools

def out(fun):
  @functools.wraps(fun)
  def inner(*args, **kwargs):
     fun(*args, **kwargs)
  return inner

@out
def decorated():
  f1(decorated)

def not_decorated():
  f1(not_decorated)

def f1(_func):
  if getattr(_func, '__wrapped__', False):
    print('I am')
  else:
    print('I am not')

decorated()
not_decorated()

输出:

1
2
I am
I am not