2009-07-24 29 views
125

Dada una cadena como entrada de usuario a una función de python, me gustaría obtener un objeto de clase si hay una clase con ese nombre en el espacio de nombres actualmente definido. Básicamente, quiero la implementación de una función que produzca este tipo de resultado:¿Convertir cadena a objeto de clase Python?

class Foo: 
    pass 

str_to_class("Foo") 
==> <class __main__.Foo at 0x69ba0> 

¿Esto es posible?

+2

Aquí hay algunas respuestas útiles, pero encontré la respuesta a esta pregunta particularmente útil: http://stackoverflow.com/q uestions/452969/does-python-have-an-equivalent-to-java-class-forname – Tom

Respuesta

79

Esto parece más simple.

>>> class Foo(object): 
...  pass 
... 
>>> eval("Foo") 
<class '__main__.Foo'> 
+24

Beaware la ramificación del uso de eval. Es mejor que se asegure de que la cadena que pasó no sea del usuario. – Overclocked

+16

El uso de eval() deja la puerta abierta para la ejecución de código arbitrario, por razones de seguridad, debe evitarse. –

+41

Eval no deja la puerta abierta a nada. Si tiene usuarios malvados y maliciosos que maliciosa y maliciosamente pueden pasar valores erróneos a eval, simplemente pueden editar la fuente de python. Como pueden editar la fuente python, la puerta está, estaba y siempre estará abierta. –

87

Se podría hacer algo como:

globals()[class_name] 
+5

Me gusta más que la solución 'eval' porque (1) es igual de fácil y (2) no se va abres la ejecución de código arbitrario. – mjumbewu

+0

pero está usando 'globals()' ... otra cosa que debe evitarse – Greg

+3

@Greg Quizás, pero 'globals()' es mucho menos malo que 'eval', y en realidad no es peor que' sys. módulos [__ nombre __] 'AFAIK. –

106

Esto podría funcionar:

import sys 

def str_to_class(str): 
    return getattr(sys.modules[__name__], str) 
+0

¿Qué pasará si la clase no existe? – riza

+1

Solo funcionará para la clase definida en el módulo actual – luc

+33

Lamento haber planteado esta respuesta de la muerte. @luc tiene razón, pero podemos reemplazar la última línea con 'return reduce (getattr, str.split (". "), sys.modules [__ name __])'. Esto dará el mismo efecto que 'eval()' pero no permitirá ejecutar código arbitrario. – jollyroger

2

sí, se puede hacer esto. Suponiendo que existen sus clases en el espacio de nombres global, algo como esto lo hará:

import types 

class Foo: 
    pass 

def str_to_class(s): 
    if s in globals() and isinstance(globals()[s], types.ClassType): 
      return globals()[s] 
    return None 

str_to_class('Foo') 

==> <class __main__.Foo at 0x340808cc> 
+1

En Python 3, ya no hay ClassType. – riza

+0

Use isinstance (x, type). –

18
import sys 
import types 

def str_to_class(field): 
    try: 
     identifier = getattr(sys.modules[__name__], field) 
    except AttributeError: 
     raise NameError("%s doesn't exist." % field) 
    if isinstance(identifier, (types.ClassType, types.TypeType)): 
     return identifier 
    raise TypeError("%s is not a class." % field) 

Este maneja con precisión tanto las clases de estilo antiguo y nuevo estilo.

+0

No necesita tipos.TypeType; es solo un alias para el "tipo" incorporado. –

+1

Sí, lo sé, pero creo que es más fácil de leer. Supongo que simplemente se reduce a la preferencia, sin embargo. –

71

Usted quiere la clase "Baz", que vive en el módulo "foo.bar". Con Python 2.7, desea utilizar importlib.import_module(), ya que esto hará la transición a pitón 3 más fácil:

import importlib 

def class_for_name(module_name, class_name): 
    # load the module, will raise ImportError if module cannot be loaded 
    m = importlib.import_module(module_name) 
    # get the class, will raise AttributeError if class cannot be found 
    c = getattr(m, class_name) 
    return c 

con el pitón < 2.7:

def class_for_name(module_name, class_name): 
    # load the module, will raise ImportError if module cannot be loaded 
    m = __import__(module_name, globals(), locals(), class_name) 
    # get the class, will raise AttributeError if class cannot be found 
    c = getattr(m, class_name) 
    return c 

Uso:

loaded_class = class_for_name('foo.bar', 'Baz') 
+1

bueno, funciona incluso cuando la clase no se importa originalmente en este módulo – parxier

2

En términos de ejecución de código arbitrario, o nombres pasados ​​de usuario no deseados, podría tener una lista de nombres de funciones/clases aceptables, y si la entrada coincide con uno en la lista, se evalúa.

PD: Lo sé .... un poco tarde ... pero es para cualquier otra persona que tropiece con esto en el futuro.

+6

Si va a mantener la lista, también podría mantener un dict de nombre a clase. Entonces no hay necesidad de eval(). – Fasaxc

1

Usando importlib funcionó lo mejor para mí.

import importlib 

importlib.import_module('accounting.views') 

Este utiliza cadena de notación con puntos para el módulo de Python que desea importar.

6

He mirado en la forma en Django se encarga de esta

django.utils.module_loading tiene este

def import_string(dotted_path): 
    """ 
    Import a dotted module path and return the attribute/class designated by the 
    last name in the path. Raise ImportError if the import failed. 
    """ 
    try: 
     module_path, class_name = dotted_path.rsplit('.', 1) 
    except ValueError: 
     msg = "%s doesn't look like a module path" % dotted_path 
     six.reraise(ImportError, ImportError(msg), sys.exc_info()[2]) 

    module = import_module(module_path) 

    try: 
     return getattr(module, class_name) 
    except AttributeError: 
     msg = 'Module "%s" does not define a "%s" attribute/class' % (
      module_path, class_name) 
     six.reraise(ImportError, ImportError(msg), sys.exc_info()[2]) 

Usted puede usarlo como import_string("module_path.to.all.the.way.to.your_class")

1

Si realmente desea recuperar las clases que realice con una cadena, debe almacenar (o redactar correctamente, referencia) en un diccionario. Después de todo, eso también permitirá nombrar tus clases en un nivel superior y evitar exponer las clases no deseadas.

Ejemplo, de un juego donde las clases de actor están definidas en Python y desea evitar que otras clases generales sean contactadas por la entrada del usuario.

Otro enfoque (como en el ejemplo a continuación) sería hacer una nueva clase completa, que contiene el dict anterior. Esto sería:

  • Permitir que se creen múltiples titulares de clases para facilitar la organización (como, uno para las clases de actor y otro para los tipos de sonido);
  • Modifique tanto el titular como las clases;
  • Y puede usar los métodos de clase para agregar clases al dict. (Aunque la abstracción a continuación no es realmente necesaria, es simplemente para ... "ilustración").

Ejemplo:

class ClassHolder(object): 
    def __init__(self): 
     self.classes = {} 

    def add_class(self, c): 
     self.classes[c.__name__] = c 

    def __getitem__(self, n): 
     return self.classes[n] 

class Foo(object): 
    def __init__(self): 
     self.a = 0 

    def bar(self): 
     return self.a + 1 

class Spam(Foo): 
    def __init__(self): 
     self.a = 2 

    def bar(self): 
     return self.a + 4 

class SomethingDifferent(object): 
    def __init__(self): 
     self.a = "Hello" 

    def add_world(self): 
     self.a += " World" 

    def add_word(self, w): 
     self.a += " " + w 

    def finish(self): 
     self.a += "!" 
     return self.a 

aclasses = ClassHolder() 
dclasses = ClassHolder() 
aclasses.add_class(Foo) 
aclasses.add_class(Spam) 
dclasses.add_class(SomethingDifferent) 

print aclasses 
print dclasses 

print "=======" 
print "o" 
print aclasses["Foo"] 
print aclasses["Spam"] 
print "o" 
print dclasses["SomethingDifferent"] 

print "=======" 
g = dclasses["SomethingDifferent"]() 
g.add_world() 
print g.finish() 

print "=======" 
s = [] 
s.append(aclasses["Foo"]()) 
s.append(aclasses["Spam"]()) 

for a in s: 
    print a.a 
    print a.bar() 
    print "--" 

print "Done experiment!" 

Esto me devuelve:

<__main__.ClassHolder object at 0x02D9EEF0> 
<__main__.ClassHolder object at 0x02D9EF30> 
======= 
o 
<class '__main__.Foo'> 
<class '__main__.Spam'> 
o 
<class '__main__.SomethingDifferent'> 
======= 
Hello World! 
======= 
0 
1 
-- 
2 
6 
-- 
Done experiment! 

Otro experimento divertido para hacer con ellos es añadir un método que pepinillos la ClassHolder lo que nunca se pierden todas las clases que did: ^)

Cuestiones relacionadas