inspect.getcode на лямбде в декораторе класса возвращает весь класс в PY3 - PullRequest
0 голосов
/ 21 июня 2020

Мы, наконец, переходим с Python 2.7.13 на Python 3.6.6, и я обнаружил странное поведение inspect.getcode, которое ведет себя совсем по-другому в Python 3.6.6 по сравнению с Python 2.7. 13.

У нас есть несколько классов, у которых есть декоратор, который определяет, разрешено ли экземпляру этого класса выполнять определенную операцию. Эта проверка выполняется отдельным модулем, который управляет этими декораторами, а не самим классом. Чтобы избежать непреднамеренных изменений, мы используем модульное тестирование, чтобы записать код внутри декоратора и сравнить его с эталонным тестом. Код записывается с помощью inspect.getcode (). Если декоратор использует внешний метод, то результат, полученный PY2 и PY3, идентичен - код метода. Однако, если декоратор использует лямбда-выражение, результат будет совершенно другим:

  • PY2 возвращает весь декоратор, а не только лямбда-выражение, но это не проблема для модульного теста.
  • PY3, однако, возвращает декоратор и весь класс - и это проблема, потому что теперь любое изменение в классе нарушит этот модульный тест.

Я не смог найти никаких задокументированных различий в inspect.getsource (), который бы это объяснил. Мне что-то здесь не хватает или это ошибка? Я знаю, что есть простые обходные пути, например, я мог бы просто проверить возвращаемую строку и вырезать все, что ниже лямбда, но я бы предпочел понять, почему это происходит.

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

checks = dict()  # a mapping of class to check function

# the class decorator
def check(check_function):
    def wrap(wrapped):
        checks[wrapped] = check_function
        return wrapped
    return wrap

# example 1: check with lambda. This demonstrates the problem
@check(lambda cls: True)
class checked_with_lambda(object):
    def run(self):
        pass

# example 2: check with external helper. This works as expected
def my_checker(obj):
    return True

@check(my_checker)
class checked_with_function(object):
    def run(self):
        pass


# this is how the check would be used. This works fine
c = checked_with_lambda()
if checks[type(c)](c):
    c.run()

# this is what a unit test would do 
# (I have added the looks-like line to show that the check function is actually what I am expecting)
import six, inspect
for cls, check in six.iteritems(checks):
    print('Check for "{}":\n'
          'looks like: "{}"\n'
          'code: "{}"\n'.format(cls.__name__,
                                repr(check),
                                inspect.getsource(check)))

Если я запустил этот код с Python 2.7.13 , это ожидаемый результат:

Check for "checked_with_lambda":
looks like: "<function <lambda> at 0x0000025967FB2A58>"
code: "@check(lambda cls: True)
"

Check for "checked_with_function":
looks like: "<function my_checker at 0x0000025967FB2AC8>"
code: "def my_checker(obj):
    return True
"

В PY3 это выглядит совсем иначе - обратите внимание, как код функции проверки для первого класса включает в себя весь код класса:

Check for "checked_with_lambda":
looks like: "<function <lambda> at 0x000002A2B295BC80>"
code: "@check(lambda cls: True)
class checked_with_lambda(object):
    def run(self):
        pass
"

Check for "checked_with_function":
looks like: "<function my_checker at 0x000002A2B295BD08>"
code: "def my_checker(obj):
    return True
"

1 Ответ

0 голосов
/ 22 июня 2020

AFAICT, это преднамеренное изменение в Python 3. inspect.getsource основано на строке, а в python 3 вспомогательный класс inspect.BlockFinder определяет, что строка, в которой находится определение лямбда, является декоратором и возвращает все оформленное объект. Python 2 этого не делал. Вам просто нужно будет обойти это.

...