【问题标题】:Is it possible to check if a function is decorated inside another function?是否可以检查一个函数是否在另一个函数中被装饰?
【发布时间】:2018-12-14 08:25:03
【问题描述】:

如果调用函数(此处为 decoratednot_decorated)具有特定的装饰器(代码为 @out),是否有任何方法可以检查函数 f1 的内部?是否将此类信息传递给函数?

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

预期输出:

I am
I am not

【问题讨论】:

  • 你可以在out()返回的函数中添加一个属性,它的存在可以在f1()中检查。
  • 让一个函数尝试检查调用它的代码的任何内容通常是最后的手段——您应该尝试寻找其他方法将必要的信息放入您的函数中,或者重新考虑您是否真的需要它。
  • 我将in 更改为innerin 是保留字,因此调用或返回 in 不起作用
  • 理论上,你的装饰器可以在它正在装饰的函数上设置一个属性。主要问题是函数不能引用自身(因此您无法检查f1 是否在f1 中具有该属性),为此您将需要另一个装饰器。见stackoverflow.com/questions/5063607/…
  • “装饰”函数本身不会留下任何痕迹。 @out def decoratd()... 只是 def decorated(): ...; decorated = out(decorated) 的语法糖。

标签: python python-decorators


【解决方案1】:

需要明确的是,这是令人震惊的骇客,所以我不推荐它,但是由于您已经排除了其他参数,并且f1 无论是否包装都将是相同的,因此您将骇客作为您的唯一的选择。解决方案是在包装函数中添加一个局部变量,其唯一目的是通过堆栈检查找到:

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

Try it online!

这假定了特定程度的嵌套(通过f_back 手动回溯以解释is_wrapped_by 本身、f1decorated,最后是inner(来自out)。如果你想要确定out 是否涉及调用堆栈中的任何地方,让is_wrapped_by 循环直到堆栈耗尽:

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

【讨论】:

    【解决方案2】:

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

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

    输出:

    I am 
    I am not
    

    【讨论】:

    • 很好的解决方案,但我无法在f() 中创建新参数。这是一个很大的障碍。
    【解决方案3】:

    假设你有一个像这样的函数装饰

    def double_arg(fun):
        def inner(x):
            return fun(x*2)
        return inner
    

    但是您无法访问它(它位于第 3 方库或其他东西中)。在这种情况下,您可以将其包装到另一个函数中,将装饰的名称添加到结果函数中

    def keep_decoration(decoration):
        def f(g):
            h = decoration(g)
            h.decorated_by = decoration.__name__
            return h
        return f
    

    并用包装器替换旧的装饰。

    double_arg = keep_decoration(double_arg)
    

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

    def is_decorated_by(f, decoration_name):
        try:
            return f.decorated_by == decoration_name
        except AttributeError:
            return False
    

    使用示例...

    @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'))
    

    输出

    11
    6
    True
    False
    

    【讨论】:

      猜你喜欢
      • 2021-10-05
      • 1970-01-01
      • 1970-01-01
      • 2019-02-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-01-26
      相关资源
      最近更新 更多