2012-10-09 41 views
6

Estoy usando la instrucción exec en algún código de Python 2, y estoy tratando de hacer que el código sea compatible tanto con Python 2 como con Python 3, pero en Python 3, exec ha cambiado de una declaración a una función. ¿Es posible escribir código que sea compatible con Python 2 y 3? He leído sobre Python 2 and Python 3 dual development, pero estoy interesado en soluciones específicas para los cambios de declaración/función exec.¿Es posible llamar a Exec para que sea compatible tanto con Python 3 como con Python 2?

Me doy cuenta de que exec generalmente se desaconseja, pero estoy creando un plugin de Eclipse que implementa codificación en vivo sobre PyDev. Vea el project page para más detalles.

+0

leído el libro: http://python3porting.com/differences.html#exec –

+0

@LennartRegebro esa fuente es equivocado acerca de ' exec' –

+0

Huh, estoy bastante seguro de que lo intenté. Lo intentaré de nuevo. –

Respuesta

9

Algunos Python porting guides get the execmal:

If you need to pass in the global or local dictionaries you will need to define a custom function with two different implementations, one for Python 2 and one for Python 3. As usual six includes an excellent implementation of this called exec_() .

No se necesita tal función personalizada al puerto de Python 2 código en Python 3 (*). Puede hacer exec(code), exec(code, globs) y exec(code, globs, locs) en Python 2, y funciona.

Python tiene siempre aceptado Python 3 compatible "sintaxis" para exec durante el tiempo que existió exec. La razón de esto es que Python 2 y Python 1 (?!) Tienen un truco para quedarse retrocompatible con Python 0.9.8 en el que exec era una función. Ahora, si exec se pasa una 2-tupla, se interpreta como (code, globals) y en el caso de una 3-tupla, se interpreta como (code, globals, locals). Sí, el exec_ in six es innecesariamente complicado.

Por lo tanto,

exec(source, global_vars, local_vars) 

se garantiza que funcione de la misma manera en CPython 0.9.9, 1.x, 2.x, 3.x; y también he comprobado que funciona en Jython 2.5.2, 2.3.1 PyPy (Python 2.7.6) y 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 

*) Hay diferencias sutiles de manera que no todos Python 3 código funciona en Python 2, a saber

  • foo = exec es válida en Python 3, pero no en Python 2, y así es map(exec, ['print(a + a)', 'print(b + b)']), pero realmente no sé ninguna razón por qué alguien querría usar estos construye en código real.
  • como se encuentra por Paul Hounshell, en Python 2, el siguiente código elevará SyntaxError: unqualified exec is not allowed in function 'print_arg' because it contains a nested function with free variables:

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

    La siguiente construcción de obras sin excepción.

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

    Antes de Python 2.7.9, si se utiliza exec('do_print()', {}) para este último lugar, el mismo SyntaxError habría sido arrojado; pero desde Python 2.7.9 el analizador/analizador también permitiría esta sintaxis de tupla alternativa.

Una vez más, la solución en casos extremos podría ser la de renunciar al uso de exec y utilizar eval lugar (eval can be used to execute bytecode that is compiled with compile in exec mode):

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

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

I han escrito una respuesta más detallada sobre internals de exec, eval y compile en What's the difference between eval, exec, and compile in Python?

+1

Esto no es cierto si exec está en una función. Obtienes 'SyntaxError: exec no calificado no está permitido en la función 'whatevs' contiene una función anidada con variables libres' – Hounshell

+0

@Hounshell He añadido un descargo de responsabilidad. Sin embargo, la versión original de mi respuesta dice que "No se necesita esa función personalizada para portar ** código de Python 2 ** a Python 3". Ese código no funcionaba en Python 2 para empezar; y necesita un contenedor en Python 2 de todos modos. –

+0

¿Pero no imprimiría "exec" "a" 'in {}, {} 'en esa situación en Python 2? 'exec ('print' a '', {}, {})' arroja la misma excepción. – Hounshell

6

Encontré varias opciones para hacer esto, antes de que Antti publicara his answer que Python 2 admite Python 3 exec function syntax.

The first expression may also be a tuple of length 2 or 3. In this case, the optional parts must be omitted. The form exec(expr, globals) is equivalent to exec expr in globals , while the form exec(expr, globals, locals) is equivalent to exec expr in globals, locals . The tuple form of exec provides compatibility with Python 3, where exec is a function rather than a statement.

Si no desea utilizar eso por alguna razón, aquí están todas las otras opciones que encontré.

importación Talones

Usted puede declarar dos talones de importación diferentes e importación lo que se trabaja con el intérprete actual. Esto se basa en lo que vi en el código fuente de PyDev.

Esto es lo que se pone en el módulo principal:

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

Esto es lo que pone en exec_python2.py:

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

Esto es lo que pone en exec_python3.py:

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

Exec en Eval

Ned Batchelder publicó una técnica que envuelve la declaración exec en una llamada a eval por lo que no causará un error de sintaxis en Python 3. Es inteligente, pero no está claro.

# 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" 
    )) 

Seis paquete

El six package es una biblioteca de compatibilidad para escribir código que se ejecutará bajo tanto Python 2 y Python 3. Tiene una exec_() function que se traduce en ambas versiones. No lo he intentado.

+1

Seis es increíble, debes darle una oportunidad. Hice la transición de Python 3, lo hice mucho más fácil, y mucho menos hacky (te estoy mirando, "Exec in Eval"). – delnan

+0

Estaba un poco indeciso acerca de agregar una dependencia de proyecto, @delnan. ¿Acabas de incluir algunos archivos adicionales en tu proyecto o alguien más que usa tu proyecto necesita instalar también seis? –

+0

En mi caso, porté una herramienta de compilación que se inicia automáticamente para la instalación, por lo que las dependencias eran difíciles (aunque hay algunas, simplemente no son necesarias cuando se inicia), especialmente con seis. Así que terminé uniéndolo como un submódulo, lo cual está permitido y era trivial a excepción de 'six.moves' (necesita un cambio de una línea, pero sigue siendo un cambio, y no lo necesitaba). Puede ver los cambios en su totalidad en https://github.com/paver/paver/pull/82 (no se asuste por la gran cantidad de cambios, el 90% de eso es de un script virtualenv bootstrap y eliminar y agregando bibliotecas empaquetadas). – delnan

1

Necesitaba hacer esto, yo co No usé seis, y mi versión de Python no es compatible con el método de @ Antti porque lo usé en una función anidada con variables libres. No quería importaciones innecesarias tampoco. Esto es lo que se me ocurrió. Esto probablemente tiene que estar en el módulo, no en un método:

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

Posteriormente, _exec obras como la versión python3. Puede entregarle una cadena o ejecutarla a través del compile().No obtendrá los globales o lugareños que probablemente quiera, así que páselos en:

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

print_arg(7) # Prints '7' 

O no. Soy una publicación de StackOverflow, no un policía.

actualizaciones:

¿Por qué no sólo tiene que utilizar eval()? eval() espera una expresión , mientras que exec() espera declaraciones. Si acabas de obtener una expresión, realmente no importa lo que uses porque todas las expresiones válidas son declaraciones válidas, pero lo contrario no es verdadero. Simplemente ejecutar un método es una expresión, incluso si no devuelve nada; hay un None implícito devuelto.

Esto se demuestra por tratar de eval pass, que es una declaración:

>>> 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 
+0

ah eso es inteligente –

+0

excepto, por supuesto, no necesita ** 'exec' ** para ejecutar el código ... puede usar' eval' en su lugar, y no estaría sujeto a estos errores de sintaxis –

+0

Pero 'eval' no permite declaraciones múltiples. Esto solo usa 'eval' para concluir el' exec'. Todavía es 'exec' que ejecuta el código. Respuesta actualizada para demostrar un caso en el que 'eval' fallaría. – Hounshell

Cuestiones relacionadas