2011-03-19 14 views
24

Tengo un código en forma de cadena y me gustaría crear un módulo sin escribir en el disco.Cargar módulo de cadena en python

Cuando intento usar IMP y un objeto StringIO hacer esto, me sale:

>>> imp.load_source('my_module', '', StringIO('print "hello world"')) 
Traceback (most recent call last): 
    File "<stdin>", line 1, in <module> 
TypeError: load_source() argument 3 must be file, not instance 
>>> imp.load_module('my_module', StringIO('print "hello world"'), '', ('', '', 0)) 
Traceback (most recent call last): 
    File "<stdin>", line 1, in <module> 
ValueError: load_module arg#2 should be a file or None 

¿Cómo puedo crear el módulo sin tener un archivo real? Alternativamente, ¿cómo puedo envolver un StringIO en un archivo sin escribir en el disco?

ACTUALIZACIÓN:

NOTA: Este problema es también un problema en python3.

El código que intento cargar solo es parcialmente confiable. Lo he probado con ast y he decidido que no importa nada ni hago nada que no me guste, pero no confío lo suficiente como para ejecutarlo cuando tengo variables locales corriendo que podrían modificarse, y No confío en mi propio código para no interferir con el código que intento importar.

creé un módulo vacío que sólo contiene lo siguiente:

def load(code): 
    # Delete all local variables 
    globals()['code'] = code 
    del locals()['code'] 

    # Run the code 
    exec(globals()['code']) 

    # Delete any global variables we've added 
    del globals()['load'] 
    del globals()['code'] 

    # Copy k so we can use it 
    if 'k' in locals(): 
     globals()['k'] = locals()['k'] 
     del locals()['k'] 

    # Copy the rest of the variables 
    for k in locals().keys(): 
     globals()[k] = locals()[k] 

A continuación, puede importar mymodule y llame mymodule.load(code). Esto funciona para mí porque me he asegurado de que el código que estoy cargando no use globals. Además, la palabra clave global es solo una directiva de analizador y no puede hacer referencia a nada que esté fuera del ejecutivo.

Esto realmente es demasiado trabajo para importar el módulo sin escribir en el disco, pero si alguna vez quiere hacerlo, creo que es la mejor manera.

+0

Si usted no confía en el código, no cargarlo. Es terriblemente difícil hacer que un intérprete de Python sea realmente seguro (por ejemplo, hay algunas maneras de parchar el bytecode de un marco de pila diferente una vez que tienes acceso a un objeto de código). Entonces, si esto es solo para evitar errores, podría funcionar, por seguridad, probablemente sea una mala idea. – schlenk

+0

He usado ast bastante extensamente para asegurarme de que no puede hacer nada peligroso. Puede haber agujeros, pero estoy bastante seguro de que los agujeros se pueden reparar si se descubren. –

+0

'del locals() ['code']' no hace nada, ya que el diccionario 'locals()' no es más que un proxy, un reflejo de los lugareños reales. Simplemente use 'del código' si debe eliminar una referencia. No es que esté seguro de por qué estás haciendo esto en absoluto. ¿Por qué todo el baile con 'globals()' y 'locals()' * en primer lugar *? –

Respuesta

48

Aquí es cómo importar una cadena como un módulo (Python 2.x):

import sys,imp 

my_code = 'a = 5' 
mymodule = imp.new_module('mymodule') 
exec my_code in mymodule.__dict__ 

En Python 3, Exec es una función, por lo que esto debería funcionar:

import sys,imp 

my_code = 'a = 5' 
mymodule = imp.new_module('mymodule') 
exec(my_code, mymodule.__dict__) 

acceder ahora a los atributos del módulo (y funciones, clases, etc.) como:

print(mymodule.a) 
>>> 5 

para ignorar cualquier nuevo intento de importar, agregar el módulo a sys:

sys.modules['mymodule'] = mymodule 
+0

¡Un gran consejo! Código muy pequeño y agradable para [Reflection] (http://en.wikipedia.org/wiki/Reflection_%28computer_programming%29) –

+0

Esto hace que las sentencias 'import' en' my_code' se evalúen como 'None'. – Kimvais

+0

@Kimvais: No puedo reproducir eso. Importación de obras * bien *. –

0

El documentation for imp.load_source dice (el subrayado es mío):

El argumento de archivo es el archivo de código fuente abierto para la lectura de un texto, desde el principio. Actualmente debe ser un objeto de archivo real, no una clase definida por el usuario que emula un archivo.

... así que puede que no tenga suerte con este método, me temo.

¿Quizás eval sería suficiente para usted en este caso?

Esto suena como un requisito bastante sorprendente, sin embargo, podría ayudar si agrega algo más a su pregunta sobre el problema que realmente está tratando de resolver.

0

puede usar exec o eval para ejecutar el código python como una cadena. vea here, here y here

0

Usted podría simplemente crear un objeto del Módulo y rellenarlo en sys.modules y poner su código adentro.

Algo así como:

import sys 
from types import ModuleType 
mod = ModuleType('mymodule') 
sys.modules['mymodule'] = mod 
exec(mycode, mod.__dict__) 
+0

'mod' no parece ser un objeto de asignación y' eval' solo puede manejar expresiones (es decir, no puede manejar 'x = 1') por lo que realmente lo limita. –

+0

Funciona con 'exec (mycode, mod .__ dict __)'. – HerpDerpington

+0

Gracias, actualizando la respuesta con el 'exec'. – schlenk

3

Si el código para el módulo se encuentra en una cadena, se puede renunciar a utilizar StringIO y usarlo directamente con exec, como se ilustra a continuación con un archivo llamado dynmodule.py.
Obras en Python 2 & 3.

from __future__ import print_function 

class _DynamicModule(object): 
    def load(self, code): 
     execdict = {'__builtins__': None} # optional, to increase safety 
     exec(code, execdict) 
     keys = execdict.get(
      '__all__', # use __all__ attribute if defined 
      # else all non-private attributes 
      (key for key in execdict if not key.startswith('_'))) 
     for key in keys: 
      setattr(self, key, execdict[key]) 

# replace this module object in sys.modules with empty _DynamicModule instance 
# see Stack Overflow question: http://bit.ly/ff94g6) 
import sys as _sys 
_ref, _sys.modules[__name__] = _sys.modules[__name__], _DynamicModule() 

if __name__ == '__main__': 
    import dynmodule # name of this module 
    import textwrap # for more readable code formatting in sample string 

    # string to be loaded can come from anywhere or be generated on-the-fly 
    module_code = textwrap.dedent("""\ 
     foo, bar, baz = 5, 8, 2 

     def func(): 
      return foo*bar + baz 

     __all__ = 'foo', 'bar', 'func' # 'baz' not included 
     """) 

    dynmodule.load(module_code) # defines module's contents 

    print('dynmodule.foo:', dynmodule.foo) 
    try: 
     print('dynmodule.baz:', dynmodule.baz) 
    except AttributeError: 
     print('no dynmodule.baz attribute was defined') 
    else: 
     print('Error: there should be no dynmodule.baz module attribute') 
    print('dynmodule.func() returned:', dynmodule.func()) 

Salida:

dynmodule.foo: 5 
no dynmodule.baz attribute was defined 
dynmodule.func() returned: 42 

Configuración de la entrada '__builtins__'-None en el diccionario execdict evita que el código se ejecute directamente cualquier funciones incorporadas, como __import__, y por eso hace que correrlo sea más seguro. Puedes aliviar esa restricción al agregarle cosas que sientas que son correctas y/o requeridas.

También es posible agregar sus propias utilidades y atributos predefinidos que desea poner a disposición del código, creando así un contexto de ejecución personalizado para que se ejecute. Ese tipo de cosas puede ser útil para implementar un "complemento". en "u otra arquitectura extensible por el usuario.