Можно ли проверить, оформлена ли функция внутри другой функции? - PullRequest
0 голосов
/ 13 декабря 2018

Есть ли способ проверить внутреннюю функцию f1 в моем примере, если для вызова функции (здесь decorated или not_decorated) имеется определенный декоратор (в коде @out)?Передается ли такая информация в функцию?

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

Ответы [ 3 ]

0 голосов
/ 14 декабря 2018

Предположим, у вас есть оформление функции, подобное этому

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

, однако вы не можете получить к нему доступ (оно находится внутри сторонней библиотеки или чего-то еще).В этом случае вы можете обернуть его в другую функцию, которая добавляет имя украшения к результирующей функции

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
0 голосов
/ 14 декабря 2018

Для ясности, это вопиющая хакерская атака, поэтому я не рекомендую ее, но, поскольку вы исключили дополнительные параметры, и 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()

Попробуйте это онлайн!

Это предполагает определенную степень вложенности (ручное отслеживание с помощью f_back для учета is_wrapped_by, f1, decorated и, наконец, innerout). Если вы хотите определитьесли 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
0 голосов
/ 13 декабря 2018

Если вы открыты для создания дополнительного параметра в 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
...