web-dev-qa-db-fra.com

Pouvez-vous utiliser une chaîne pour instancier une classe?

J'utilise un modèle de générateur pour séparer un tas de possibilités de configuration différentes. Fondamentalement, j'ai un tas de classes qui sont nommées ID (quelque chose comme ID12345). Tout cela hérite de la classe de générateur de base. Dans mon script, j'ai besoin d'instancier une instance pour chaque classe (environ 50) à chaque exécution de cette application. Donc, j'essaie de voir si au lieu de faire quelque chose comme ça:

ProcessDirector = ProcessDirector()
ID12345 = ID12345()
ID01234 = ID01234()

ProcessDirector.construct(ID12345)
ProcessDirector.construct(ID01234)

ID12345.run()
ID01234.run()

Puis-je faire quelque chose comme ça (je sais que cela ne fonctionne pas):

IDS = ["ID12345", "ID01234"]

ProcessDirector = ProcessDirector()
for id in IDS:
  builder = id() #some how instantiate class from string
  ProcessDirector.construct(builder)
  builder.run()

De cette façon, lorsque je dois en ajouter un nouveau à l'avenir, tout ce que j'ai à faire est d'ajouter l'ID à la liste IDS, plutôt que de poivrer le nouvel ID dans tout le code.

ÉDITER

Il semble qu'il y ait différentes opinions en fonction de la provenance des données. Ces identifiants sont entrés dans un fichier auquel personne d'autre n'a accès. Je ne lis pas les chaînes de la ligne de commande, et j'aimerais pouvoir faire aussi peu de modifications lors de l'ajout d'un nouvel ID à l'avenir.

36
scottm

Je ne suis pas sûr que ce soit ce que vous voulez, mais cela semble être un moyen plus pythonique d'instancier un tas de classes répertoriées dans une chaîne:

class idClasses:
    class ID12345:pass
    class ID01234:pass
# could also be: import idClasses

class ProcessDirector:
    def __init__(self):
        self.allClasses = []

    def construct(self, builderName):
        targetClass = getattr(idClasses, builderName)
        instance = targetClass()
        self.allClasses.append(instance)

IDS = ["ID12345", "ID01234"]

director = ProcessDirector()
for id in IDS:
    director.construct(id)

print director.allClasses
# [<__main__.ID12345 instance at 0x7d850>, <__main__.ID01234 instance at 0x7d918>]
17
dbr

Si vous vouliez éviter un eval (), vous pourriez simplement faire:

id = "1234asdf"
constructor = globals()[id]
instance = constructor()

À condition que la classe soit définie (ou importée) dans votre portée actuelle.

58
GoldenBoy

Jamais utilisez eval() si vous pouvez l'aider. Python a donc beaucoup de meilleures options (dictionnaire de répartition, getattr(), etc.) que vous ne devriez jamais avoir à utiliser le trou de sécurité appelé eval().

9

Le moyen le plus simple est de créer simplement un dict.

class A(object): 
    pass
class B(object): 
    pass

namedclass = {'ID12345': A, 'ID2': A, 'B': B, 'AnotherB': B,  'ID01234': B}

Ensuite, utilisez-le (votre exemple de code):

IDS = ["ID12345", "ID01234"]

ProcessDirector = ProcessDirector()
for id in IDS:
    builder = namedclass[id]() 
    ProcessDirector.construct(builder)
    builder.run()
5
nosklo

J'ai décidé de rassembler le réponse acceptée et un commentaire sur la réponse acceptée. J'ai également ajouté le __getitem__ Surchargé de sorte que cela ressemble plus à un modèle d'usine.

import sys
import traceback
import ipdb


class CarTypes:
    class Toyota:
        def __repr__(self):
            return "Toyota()"
        def __str__(self):
            return "Instance of Toyota() class"
    class Nissan:
        def __repr__(self):
            return "Nissan()"
        def __str__(self):
            return "Instance of Nissan() class"


class Car:
    def __init__(self):
        self._all_classes = {}

    def construct(self, builder_name):
        setattr(self, builder_name, CarTypes())
        try:
            target_class = getattr(CarTypes, builder_name)
            instance = target_class()
            self._all_classes[builder_name] = instance
        except AttributeError:
            print("Builder {} not defined.".format(builder_name))
            traceback.print_stack()

    def __getitem__(self, type_name):
        return self._all_classes[type_name]

    def car_type(self, type_name):
        return self._all_classes[type_name]


IDS = ["Toyota", "Nissan", "Unknown"]

director = Car()
for id in IDS:
    director.construct(id)

print(director["Toyota"])
print(director["Nissan"])
print(director.car_type("Toyota"))
print(director.car_type("Nissan"))

Modifié : j'ai ajouté une gestion des erreurs.

Modifié : placé sous licence Creative Commons permissive . Prendre plaisir.

1
Daisuke Aramaki

Il y a des choses qui manquent à votre question, donc je suis obligé de deviner les choses omises. N'hésitez pas à modifier votre question pour corriger les omissions.

class ProcessDirector( object ):
    # does something

class ID12345( SomeKindOfProcess ):
    pass

class ID001234( SomeKindOfProcess ):
    pass

idList= [ID12345, ID01234]

theProcessDirector = ProcessDirector()
for id in idList:
  builder = id() #Instantiate an object from the class object
  theProcessDirector.construct(builder)
  builder.run()

Cela fonctionne très bien. Il ne s'instancie pas à partir d'une chaîne - en pratique, vous ne le souhaitez pas souvent. Parfois, mais rarement. Plus communément, vous une liste d'objets de classe à partir de laquelle vous souhaitez des objets d'instance.

Si vous obtenez réellement vos noms de classe à partir de la ligne de commande, vous feriez la petite modification suivante.

validClasses = [ ID12345, ID01234 ]
validMap = dict( ( (c.__name__, c) for c in validClasses ) )
nameStrings = [ "ID12345", "ID01234" ] # from your command-line 
idList= [ validMap[s] for s in nameStrings ]

Tout le reste reste le même.

[Aussi, si possible, essayez de démarrer les noms de variable d'instance avec des lettres minuscules. Les noms qui commencent par des lettres majuscules sont généralement des noms de classe.]

Modifier

eval supprimé. Malgré le fait que eval() n'est absolument pas un trou de sécurité. Eval (et exec et execfile) ne sont un problème que si quelqu'un accorde spécifiquement l'accès à des utilisateurs malveillants.

0
S.Lott