230 votes

Convertir une chaîne de caractères en objet de classe Python ?

Étant donné qu'une chaîne de caractères est entrée par l'utilisateur dans une fonction Python, j'aimerais en extraire un objet de classe s'il existe une classe portant 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 que c'est possible ?

20voto

Evan Fosmark Points 17732
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 permet de gérer avec précision les classes de l'ancien et du nouveau style.

7voto

Gustavo6046 Points 312

Si vous voulez vraiment récupérer les classes que vous faites avec une chaîne, vous devriez stocker (ou formuler correctement, référence ) dans un dictionnaire. Après tout, cela permettra aussi de nommer vos classes à un niveau plus élevé et d'éviter d'exposer des classes non désirées.

Exemple, d'un jeu où les classes d'acteurs sont définies en Python et où l'on veut é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 toute nouvelle classe, qui contiendrait les éléments suivants dict ci-dessus. Ce serait :

  • Permettre la création de plusieurs classes pour faciliter l'organisation (par exemple, une pour les classes d'acteurs et une autre pour les types de sons) ;
  • Faciliter les modifications tant du titulaire que des cours dispensés ;
  • Et vous pouvez utiliser les méthodes de classe pour ajouter des classes au dict. (Bien que l'abstraction ci-dessous ne soit pas vraiment nécessaire, elle est simplement pour... "illustration" ).

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-là est d'ajouter une méthode qui décape les ClassHolder pour que vous ne perdiez jamais tous les cours que vous avez faits :^)

UPDATE : 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

4voto

ollyc Points 691

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>

3voto

JoryRFerrell Points 32

En termes d'exécution de code arbitraire, ou de noms indésirables passés par l'utilisateur, vous pourriez disposer d'une liste de noms de fonctions/classes acceptables, et si l'entrée correspond à l'un des éléments de la liste, elle est évaluée.

PS : Je sais....kinda late....mais c'est pour tous ceux qui tomberont sur ce sujet à l'avenir.

2voto

Aron Ysidoro Points 922

Utilisation de importlib a été le plus efficace pour moi.

import importlib

importlib.import_module('accounting.views') 

Cela utilise notation par points des chaînes de caractères pour le module python que vous voulez importer.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X