¿Convertir cadena a objeto de clase Python?

Dada una cadena como entrada del 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 definido actualmente. Esencialmente, quiero la implementación de una función que produzca este tipo de resultado:

class Foo: pass str_to_class("Foo") ==>  

¿Es esto, en absoluto, posible?

Advertencia : eval() se puede usar para ejecutar código Python arbitrario. Nunca debes usar eval() con cadenas que no sean de confianza. (Consulte Seguridad de la evaluación de Python () en cadenas no confiables? )

Esto parece más simple.

 >>> class Foo(object): ... pass ... >>> eval("Foo")  

Esto podría funcionar:

 import sys def str_to_class(classname): return getattr(sys.modules[__name__], classname) 

Podrías hacer algo como:

 globals()[class_name] 

Quieres la clase Baz , que vive en el módulo foo.bar . Con Python 2.7, desea usar importlib.import_module() , ya que esto facilitará la transición a Python 3:

 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 Python <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 

Utilizar:

 loaded_class = class_for_name('foo.bar', 'Baz') 
 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) 

Esto maneja con precisión tanto las clases de estilo antiguo como las de estilo nuevo.

He visto cómo django maneja esto

django.utils.module_loading tiene esto

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

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

En términos de ejecución de código arbitrario, o nombres de usuarios no deseados, puede tener una lista de nombres de funciones / clases aceptables, y si la entrada coincide con una de la lista, se evalúa.

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

Sí, usted puede hacer esto. Asumiendo que sus clases existen 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') ==>  

Usando importlib funcionó mejor para mí.

 import importlib importlib.import_module('accounting.views') 

Esto utiliza la notación de puntos de cadena para el módulo de python que desea importar.

Si realmente desea recuperar las clases que realiza con una cadena, debe almacenarlas (o redactarlas correctamente, hacer referencia a ellas) en un diccionario. Después de todo, eso también permitirá nombrar sus clases en un nivel superior y evitar la exposición de clases no deseadas.

Por ejemplo, en un juego en el que las clases de actores se definen en Python y desea evitar que otras clases generales sean alcanzadas por la entrada del usuario.

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

  • Permitir que se realicen múltiples titulares de clase para una organización más fácil (por ejemplo, uno para clases de actores y otro para tipos de sonido);
  • Hacer modificaciones tanto al titular como a las clases que se llevan a cabo más fácilmente
  • Y puedes usar métodos de clase para agregar clases al dict. (Aunque la siguiente abstracción no es realmente necesaria, es meramente 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 aa print a.bar() print "--" print "Done experiment!" 

Esto me devuelve:

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

Otro experimento divertido que se puede hacer con ellos es agregar un método que ClassHolder el ClassHolder para que nunca pierdas todas las clases que hiciste: ^)