Étant donné une chaîne en tant qu'entrée utilisateur dans une fonction Python, j'aimerais en extraire un objet de classe s'il y a une classe avec ce nom dans l'espace de noms actuellement défini. Essentiellement, je veux l'implémentation d'une fonction qui produira ce genre de résultat:
class Foo:
pass
str_to_class("Foo")
==> <class __main__.Foo at 0x69ba0>
Est-ce possible?
Réponses:
Cela semble le plus simple.
>>> class Foo(object): ... pass ... >>> eval("Foo") <class '__main__.Foo'>
la source
Cela pourrait fonctionner:
import sys def str_to_class(classname): return getattr(sys.modules[__name__], classname)
la source
def str_to_class(str): return vars()[str]
?Vous pouvez faire quelque chose comme:
la source
globals()
... une autre chose à éviterglobals()
est sans doute beaucoup moins mauvais queeval
, et pas vraiment pire que l'sys.modules[__name__]
AFAIK.Vous voulez la classe
Baz
, qui vit dans le modulefoo.bar
. Avec Python 2.7, vous souhaitez utiliserimportlib.import_module()
, car cela facilitera la transition vers 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
Avec 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
Utilisation:
loaded_class = class_for_name('foo.bar', 'Baz')
la source
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)
Cela gère avec précision les classes de style ancien et nouveau.
la source
J'ai regardé comment django gère cela
django.utils.module_loading a ceci
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])
Vous pouvez l'utiliser comme
import_string("module_path.to.all.the.way.to.your_class")
la source
Si vous voulez vraiment récupérer les classes que vous créez avec une chaîne, vous devez les stocker (ou les référencer correctement ) dans un dictionnaire. Après tout, cela permettra également de nommer vos classes à un niveau supérieur et d'éviter d'exposer des classes indésirables.
Exemple, à partir d'un jeu où les classes d'acteurs sont définies en Python et que vous voulez éviter que d'autres classes générales soient atteintes par l'entrée de l'utilisateur.
Une autre approche (comme dans l'exemple ci-dessous) consisterait à créer une nouvelle classe entière, qui contient ce qui
dict
précède. Ce serait:Exemple:
class ClassHolder: 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: 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: 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!"
Cela me renvoie:
<__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!
Une autre expérience amusante à faire avec ceux-ci consiste à ajouter une méthode qui décape le
ClassHolder
pour ne jamais perdre toutes les classes que vous avez faites: ^)MISE À JOUR: Il est également possible d'utiliser un décorateur comme raccourci.
class ClassHolder: def __init__(self): self.classes = {} def add_class(self, c): self.classes[c.__name__] = c # -- the decorator def held(self, c): self.add_class(c) # Decorators have to return the function/class passed (or a modified variant thereof), however I'd rather do this separately than retroactively change add_class, so. # "held" is more succint, anyway. return c def __getitem__(self, n): return self.classes[n] food_types = ClassHolder() @food_types.held class bacon: taste = "salty" @food_types.held class chocolate: taste = "sweet" @food_types.held class tee: taste = "bitter" # coffee, ftw ;) @food_types.held class lemon: taste = "sour" print(food_types['bacon'].taste) # No manual add_class needed! :D
la source
Oui, vous pouvez le faire. En supposant que vos classes existent dans l'espace de noms global, quelque chose comme ceci le fera:
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>
la source
En termes d'exécution de code arbitraire ou de noms non désirés par l'utilisateur, vous pouvez avoir une liste de noms de fonctions / classes acceptables, et si l'entrée correspond à une dans la liste, elle est évaluée.
PS: Je sais ... un peu tard ... mais c'est pour tous ceux qui trébucheront là-dessus à l'avenir.
la source
Utiliser importlib a fonctionné le mieux pour moi.
import importlib importlib.import_module('accounting.views')
Cela utilise la notation de point de chaîne pour le module python que vous souhaitez importer.
la source