Мы, наконец, переходим с 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
"