Можно ли вызвать exec, чтобы он был совместим как с Python 3, так и с Python 2?

Я использую оператор exec в некотором коде Python 2 и пытаюсь сделать этот код совместимым как с Python 2, так и с Python 3, но в Python 3 оператор exec превратился из оператора в функцию. Можно ли написать код, совместимый как с Python 2, так и с Python 3? Я читал о двойной разработке Python 2 и Python 3, но меня интересуют конкретные решения оператора exec/ меняется функция.

Я понимаю, что exec обычно не рекомендуется, но я создаю плагин Eclipse, который реализует живое кодирование поверх PyDev. Дополнительную информацию см. на странице проекта.


person Don Kirkby    schedule 09.10.2012    source источник
comment
Прочтите книгу: python3porting.com/differences.html#exec   -  person Lennart Regebro    schedule 10.10.2012
comment
@LennartRegebro, этот источник ошибается насчет exec   -  person Antti Haapala    schedule 12.03.2016
comment
Ха, я почти уверен, что пробовал это. Попробую еще раз.   -  person Lennart Regebro    schedule 14.03.2016


Ответы (3)


Некоторые руководства по переносу на Python exec неправильно:

Если вам нужно передать глобальные или локальные словари, вам нужно будет определить пользовательскую функцию с двумя разными реализациями, одну для Python 2 и одну для Python 3. Как обычно, six включает в себя отличную реализацию под названием exec_().

Для переноса кода Python 2 в Python 3 такая пользовательская функция не требуется (*). Вы можете делать exec(code), exec(code, globs) и exec(code, globs, locs) в Python 2, и это работает.

Python всегда принимал совместимый с Python 3 «синтаксис» для exec, пока существовал exec. Причина этого в том, что Python 2 и Python 1 (?!) имеют хак, чтобы оставаться обратно совместимыми с Python 0.9.8, в котором exec была функцией. Теперь, если exec передается кортеж из 2, он интерпретируется как (code, globals), а в случае кортежа из 3 он интерпретируется как (code, globals, locals). Да, exec_ в six излишне усложняется.

Таким образом,

exec(source, global_vars, local_vars)

гарантированно будет работать так же в CPython 0.9.9, 1.x, 2.x, 3.x; и я также проверил, что он работает в Jython 2.5.2, PyPy 2.3.1 (Python 2.7.6) и IronPython 2.6.1:

Jython 2.5.2 (Release_2_5_2:7206, Mar 2 2011, 23:12:06) 
[Java HotSpot(TM) 64-Bit Server VM (Oracle Corporation)] on java1.8.0_25
Type "help", "copyright", "credits" or "license" for more information.
>>> exec('print a', globals(), {'a':42})
42

*) Существуют небольшие различия, поэтому не весь код Python 3 работает в Python 2, а именно

  • foo = exec допустимо в Python 3, но не в Python 2, как и map(exec, ['print(a + a)', 'print(b + b)']), но я действительно не знаю причин, по которым кто-то захочет использовать эти конструкции в реальном коде.
  • Как выяснил Пол Хауншелл, в Python 2 следующий код вызовет SyntaxError: неквалифицированный exec не разрешен в функции 'print_arg', поскольку он содержит вложенную функцию со свободными переменными:

    def print_arg(arg):
        def do_print():
            print(arg)
        exec('do_print()')
    

    Следующая конструкция работает без исключения.

    def print_arg(arg):
        def do_print():
            print(arg)
        exec 'do_print()' in {}
    

    До Python 2.7.9, если бы кто-то использовал exec('do_print()', {}) для последнего вместо этого, был бы выдан тот же самый SyntaxError; но начиная с Python 2.7.9 синтаксический анализатор/компилятор также допускает этот альтернативный синтаксис кортежа.

Опять же, решение в крайних случаях может состоять в том, чтобы отказаться от использования exec и вместо этого использовать eval (eval можно использовать для выполнения скомпилированного байт-кода с compile в режиме exec):

def print_arg(arg):
    def do_print():
        print(arg)

    eval(compile('do_print(); print("it really works")', '<string>', 'exec'))

Я написал более подробный ответ о внутреннем устройстве exec, eval и compile на В чем разница между eval, exec и compile в Python?

person Antti Haapala    schedule 26.04.2015
comment
Это неверно, если exec находится в функции. Вы получаете SyntaxError: unqualified exec is not allowed in function 'whatevs' it contains a nested function with free variables - person Hounshell; 28.12.2016
comment
@Hounshell Я добавил отказ от ответственности. Однако в исходной версии моего ответа говорится, что для переноса кода Python 2 в Python 3 такая пользовательская функция не требуется. Этот код не работал в Python 2 с самого начала; и в любом случае нуждается в оболочке в Python 2. - person Antti Haapala; 28.12.2016
comment
Но разве exec 'print "a"' in {}, {} не получится в этой ситуации в Python 2? exec('print "a"', {}, {}) выдает такое же исключение. - person Hounshell; 28.12.2016
comment
Я получаю разные результаты на разных машинах. Не уверен, что это связано с версией ОС или Python. pastebin.com/xgCjf8MT TL;DR: Ubuntu 14.04 с Python 2.7.6 выдает ошибку, OSX 10.11.6 с Python 2.7.10 нет. - person Hounshell; 28.12.2016
comment
Но это не совместимо с Python3. SyntaxError: invalid syntax Я думаю, что единственный способ сделать что-то, что совместимо со всеми тремя (‹2.7.9, ›= 2.7.9, › 3.0), — это использовать eval. Я попробовал свои силы в этом в отдельном ответе. - person Hounshell; 28.12.2016

Я нашел несколько вариантов для этого, прежде чем Антти опубликовал свой ответ о том, что Python 2 поддерживает Python 3 синтаксис функции exec.

Первое выражение также может быть кортежем длины 2 или 3. В этом случае необязательные части должны быть опущены. Форма exec(expr, globals) эквивалентна exec expr in globals, а форма exec(expr, globals, locals) эквивалентна exec expr in globals, locals. Кортежная форма exec обеспечивает совместимость с Python 3, где exec — это функция, а не инструкция.

Если вы по какой-то причине не хотите использовать это, вот все другие варианты, которые я нашел.

Импортировать заглушки

Вы можете объявить две разные заглушки импорта и импортировать ту, которая работает с текущим интерпретатором. Это основано на том, что я видел в исходном коде PyDev.

Вот что вы помещаете в основной модуль:

try:
    from exec_python2 import exec_code #@UnusedImport
except:
    from exec_python3 import exec_code #@Reimport

Вот что вы вставили в exec_python2.py:

def exec_code(source, global_vars, local_vars):
    exec source in global_vars, local_vars

Вот что вы вставили в exec_python3.py:

def exec_code(source, global_vars, local_vars):
    exec(source, global_vars, local_vars)

Exec в Eval

Нед Бэтчелдер опубликовал метод, заключающий оператор exec в вызов eval, поэтому он выиграл не вызывает синтаксическую ошибку в Python 3. Это умно, но непонятно.

# Exec is a statement in Py2, a function in Py3

if sys.hexversion > 0x03000000:
    def exec_function(source, filename, global_map):
        """A wrapper around exec()."""
        exec(compile(source, filename, "exec"), global_map)
else:
    # OK, this is pretty gross.  In Py2, exec was a statement, but that will
    # be a syntax error if we try to put it in a Py3 file, even if it isn't
    # executed.  So hide it inside an evaluated string literal instead.
    eval(compile("""\
def exec_function(source, filename, global_map):
    exec compile(source, filename, "exec") in global_map
""",
    "<exec_function>", "exec"
    ))

Шесть пакетов

Пакет six представляет собой библиотеку совместимости для написания кода, который будет работать как на Python 2, так и на Python 3. В нем есть функция exec_(), которая транслируется в обе версии. Я не пробовал.

person Don Kirkby    schedule 09.10.2012
comment
Шесть — это круто, вы должны попробовать. Сделал перенос Python 3, который я сделал намного проще и гораздо менее хакерским (я смотрю на вас, Exec в Eval). - person ; 10.10.2012
comment
Я немного колебался по поводу добавления зависимости проекта, @delnan. Вы просто включаете несколько дополнительных файлов в свой проект, или любой, кто использует ваш проект, должен также установить шесть? - person Don Kirkby; 10.10.2012
comment
В моем случае я портировал инструмент сборки, который загружается для установки, поэтому зависимости были сложными (хотя некоторые из них есть, они просто не нужны при загрузке), особенно с шестью. Так что я закончил тем, что связал его как подмодуль, что разрешено и было тривиально, за исключением six.moves (требуется однострочное изменение, но все же изменение, и мне это не нужно). Полностью изменения можно увидеть на странице github.com/paver/paver/pull/82 (не пугайтесь большого количества изменений, 90% из которых связаны с загрузочным скриптом virtualenv и удалением и добавлением связанных библиотек). - person ; 10.10.2012
comment
@DonKirkby: вы можете либо объявить шесть зависимостями в setup.py, что означает, что любой, кто устанавливает ваш модуль, также автоматически установит шесть (при условии, что они используют easy_install, pip или buildout, и они должны). Или вы можете просто поместить файл six.py в свой модуль. - person Lennart Regebro; 29.11.2012

Мне нужно было сделать это, я не мог использовать шесть, и моя версия Python не поддерживает метод @Antti, потому что я использовал его во вложенной функции со свободными переменными. Я тоже не хотел ненужного импорта. Вот что я придумал. Вероятно, это должно быть в модуле, а не в методе:

try:
  # Try Python2.
  _exec_impls = {
    0: compile('exec code', '<python2>', 'exec'),
    1: compile('exec code in _vars[0]', '<python2>', 'exec'),
    2: compile('exec code in _vars[0], _vars[1]', '<python2>', 'exec'),
  }

  def _exec(code, *vars):
    impl = _exec_impls.get(len(vars))
    if not impl:
      raise TypeError('_exec expected at most 3 arguments, got %s' % (len(vars) + 1))
    return eval(impl, { 'code': code, '_vars': vars })

except Exception as e:
  # Wrap Python 3.
  _exec = eval('exec')

После этого _exec работает как версия Python3. Вы можете либо передать ему строку, либо запустить ее через compile(). Он не получит глобальные или локальные переменные, которые вам, вероятно, нужны, поэтому передайте их:

def print_arg(arg):
  def do_print():
    print(arg)
  _exec('do_print(); do_print(); do_print()', globals(), locals())

print_arg(7)  # Prints '7'

Или не. Я пост StackOverflow, а не полицейский.

Обновления:

Почему бы вам просто не использовать eval()? eval() ожидает выражение, а exec() ожидает операторы. Если вы только что получили выражение, на самом деле не имеет значения, что вы используете, потому что все допустимые выражения являются допустимыми утверждениями, но обратное неверно. Просто выполнение метода является выражением, даже если оно ничего не возвращает; возвращается подразумеваемый None.

Это демонстрируется попыткой eval pass, которая является утверждением:

>>> exec('pass')
>>> eval('pass')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<string>", line 1
    pass
       ^
SyntaxError: unexpected EOF while parsing
person Hounshell    schedule 28.12.2016
comment
за исключением, конечно, того, что вам не нужно exec для выполнения кода... вместо этого вы можете использовать eval, и это не будет подвержено этим синтаксическим ошибкам - person Antti Haapala; 28.12.2016
comment
Но eval не позволяет использовать несколько операторов. Это использует только eval для обертывания exec. Это все еще exec, на котором работает код. Обновлен ответ, чтобы продемонстрировать случай, когда eval не удастся. - person Hounshell; 29.12.2016
comment
см. мой ответ о том, как использовать eval. - person Antti Haapala; 29.12.2016
comment
Я только что попробовал ваш пример do_print() с Python 2.7 и 3.5. Они оба прекрасно работали со встроенным exec() и печатали 7. Никаких специальных _exec() не требовалось. Что не сработало для вас? - person Don Kirkby; 31.12.2016
comment
Антти говорил об этом выше. В 2.7.9 было изменение, я использовал 2.7.6 - person Hounshell; 02.01.2017