52

I'm using a Builder pattern in Python to separate a bunch of different configuration possibilities. Basically, I have a bunch of classes that are named ID... (e.g. ID12345). These all inherit from the base Builder class. In my script, I need to instantiate an instance for each class (about 50) every time this app runs. So, I'm trying to see if instead of doing something like this:

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

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

ID12345.run()
ID01234.run()

Can I do something like this (I know this doesn't work):

IDS = ["ID12345", "ID01234"]

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

That way, when I need to add a new one in the future, all I have to do is add the id to the IDS list, rather than peppering the new ID throughout the code.

EDIT:

Looks like there are different opinions based on where the data is coming from. These IDs are entered in a file that no one else has access to. I'm not reading the strings from the command line, and I'd like to be able to do as little alteration when adding a new ID in the future.

smci
  • 32,567
  • 20
  • 113
  • 146
scottm
  • 27,829
  • 22
  • 107
  • 159
  • Are these ID classes in the same file as the loop, or do you import them from somewhere else? – Justin Voss Feb 16 '09 at 16:31
  • Duplicate: http://stackoverflow.com/questions/452969/does-python-have-an-equivalent-to-java-class-forname – S.Lott Feb 16 '09 at 18:04
  • 17
    this is not a duplicate of that, this is a python question, not a java one, the question you referenced is asking if a function exists in python that exists in java with little explanation behind it, remember that the question is how people find it, not the answers so just because an answer exists in the other question that may answer this one doesn't mean people are going to find it unless they are thinking of the question in terms of java like the OP of that question. – Rick Sep 07 '10 at 07:29
  • @Rick and scottm: often Java people assume design patterns are synonymous with Java; I edited "Builder pattern in Python" in the first line to prevent that. – smci Mar 17 '22 at 20:44

6 Answers6

74

If you wanted to avoid an eval(), you could just do:

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

Provided that the class is defined in (or imported into) your current scope.

dbr
  • 165,801
  • 69
  • 278
  • 343
GoldenBoy
  • 1,381
  • 1
  • 8
  • 11
  • +1 - this works great, just did this in conjunction with `abc` to dynamically build one instance of all subclasses of a base class. – Arj Sep 03 '15 at 15:34
  • 4
    is `globals()[classname_string]()` still a good way to instantiate an object given only the name of class as a string? Still because I am asking the question almost 8 years after the original response. – Vishal Apr 23 '18 at 15:07
  • What if you want the dynamic class to inherit from another class? – Brōtsyorfuzthrāx Oct 07 '22 at 03:48
23

Not sure this is what you want but it seems like a more Pythonic way to instantiate a bunch of classes listed in a string:

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>]
Veltzer Doron
  • 934
  • 2
  • 10
  • 31
dbr
  • 165,801
  • 69
  • 278
  • 343
  • 5
    But what if you don't know what the class names will be until runtime? What if they are read from a file or command line? – RyanN Feb 03 '11 at 21:06
  • 2
    @RyanN Not sure what you mean - the `IDS` list is just an example, it could come from anywhere (file etc) – dbr Apr 16 '11 at 07:31
  • 1
    I'm probably overlooking something, but in idClasses you have a class called ID12345. I see now that the OP already had a bunch of classes he wanted to instantiate, and I'm thinking of a single generic class instantiated multiple times e.g. a class `class car():\r\n def __init__(self): pass` instantiated as ['ford', 'toyota', 'vw'] In construct() you could do `setattr(self,builderName,idClasses())`. That way you would end up with director.ford, director.toyota, and director.vw objects. – RyanN Apr 27 '11 at 19:41
8

Never use eval() if you can help it. Python has so many better options (dispatch dictionary, getattr(), etc.) that you should never have to use the security hole known as eval().

Ignacio Vazquez-Abrams
  • 776,304
  • 153
  • 1,341
  • 1,358
  • 11
    -1: "never" is too strong. It's not a security hole unless you also have malicious users with access to configuration files. Preventing malicious users from touching config files is easy. – S.Lott Feb 16 '09 at 21:39
  • 19
    I read "never" to mean "avoid at all costs unless you're absolutely certain its what you need AND you know what you're doing". Using the word "never" is a good short alternative though, as it scares those who don't know what they're doing away. Those who do, will know when to ignore this anyway. –  Feb 17 '09 at 09:49
  • 7
    This answer isn't really helpful without actually showing any of the alternatives – Harald Scheirich Dec 05 '16 at 19:47
6

Simplest way is to just create a dict.

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

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

Then use it (your code example):

IDS = ["ID12345", "ID01234"]

ProcessDirector = ProcessDirector()
for id in IDS:
    builder = namedclass[id]() 
    ProcessDirector.construct(builder)
    builder.run()
nosklo
  • 217,122
  • 57
  • 293
  • 297
  • 1
    This is the most pythonic way to do it! It's in fact how we mimic the missing "switch statement" in python as well!! – Devy Aug 15 '17 at 19:51
  • This is certainly one way to do it, however it is not exactly what the question is about: it's not using a string to derive the class to be instantiated, but its actual class. One downside of this is that `namedclass` must come after all the classes it lists. Django also uses strings to refer to models not yet defined. – minusf Apr 12 '18 at 21:33
  • @minusf the question **is** actually about deriving which class to instantiate from a string. By using a dict you have the most efficient and safe way so I don't see why not use it. – nosklo Apr 18 '18 at 19:07
1

I decided to put together the accepted answer and a comment on the accepted answer. I also added the overloaded __getitem__ so this looks more like a factory pattern.

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

Edited: I added in some error handling.

Edited: Placed under permissive Creative Commons license. Enjoy.

Daisuke Aramaki
  • 316
  • 3
  • 7
0

There's some stuff missing from your question, so I'm forced to guess at the omitted stuff. Feel free to edit your question to correct the 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()

This works very nicely. It doesn't instantiate from a string -- in practice you don't often want this. Sometimes, but rarely. More commonly, you a list of class objects from which you want instance objects.

If you actually are getting your class names from the command line, then you'd make the following small change.

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 ]

Everything else remains the same.

[Also, if possible, try to start instance variable names with lowercase letters. Names which start with Uppercase Letters are usually class names.]

Edit

Removed eval. In spite of the fact that eval() is absolutely not a security hole. Eval (and exec and execfile) are only a problem if someone specifically grants access to malicious users.

S.Lott
  • 384,516
  • 81
  • 508
  • 779