1

I want to create instances of a class from a string user has typed in so I used exec( ) function. The problem is I can't access instance by its name outside the function. My first thought was that it is a problem with scope of a function, and I still think it is but when I put instances in a list I can access them, just not using their name. I'm not really sure what is happening here.. Is there a way so that I could access instances by their name, like thing1.properties but outside the function because this is not my whole code so it would be messy to put all outside the function? Something like to create list of instances in a function and to "extract" all instances outside the function so I could access them outside the function. Here is the code:

class Things:
    def __init__(self, properties):
        self.properties = properties

listt = []
def create_instance():
    exec("thing1=Things('good')")
    listt.append(thing1)

create_instance()
print listt[0].properties
print thing1.properties
Nikola Lošić
  • 479
  • 1
  • 6
  • 18
  • 4
    This is almost certainly a very poor way of achieving your goal. What are you trying to do? – rlms Dec 05 '14 at 18:44
  • I wrote in the question. I want to create instances of a class from a string user has typed in. So when user types in "thing1" instance thing1 = Things() is created. If there is a better way to do it I would that you share it with me. – Nikola Lošić Dec 05 '14 at 18:47
  • 3
    Polluting the global scope is frowned upon in Python. You probably should store your instances at a dictionary instead. – Paulo Scardine Dec 05 '14 at 18:50
  • if you call `locals()` inside your `create_instance` function you will find `thing1` in there... so you want to add your new var to the current `globals()` (which really means into the current module's namespace) which is the same as this question http://stackoverflow.com/q/11813287/202168 – Anentropic Dec 05 '14 at 18:51
  • 1
    @Anentropic: the exec statement can receive a second argument that is a dictionary used as the scope for the first argument, it defaults to locals() but one can pass globals() instead (but really should not). – Paulo Scardine Dec 05 '14 at 18:53
  • Thank you all, I solved problem by using @PauloScardine suggestion. I stored instances in a dictionary and then I could use dict['thing1'].properties to access instance properties using instance name. – Nikola Lošić Dec 05 '14 at 19:06

1 Answers1

1

While I abhor polluting the global namespace, the exec statement can take a second argument to be used as the scope and defaults as locals():

>>> def foo(name):
...     exec "{} = 1".format(name)
... 
>>> def bar(name):
...     exec "{} = 1".format(name) in globals()
... 
>>> foo('a')
>>> a
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'a' is not defined
>>> bar('a')
>>> a
1

So if you pass globals as the scope, it will work as you want, but really? Polluting the global scope by itself is horrid, doing it while evaluating user supplied code is a damn liability.

[update]

Very helpful! Thank you! But what is now better way of doing it, dictionary or a global scope?

Perhaps you can store all instances into a class variable, for example:

class Thing(object):
    instances = {}
    def __init__(self, name, **properties):
        self.name = name
        self.properties = properties
        self.instances[name] = self
    def __repr__(self):
        t = '<"{self.name}" thing, {self.properties}>'
        return t.format(self=self)

Now you can do:

# declare your things
>>> Thing('foo', a=1, b=2)
>>> Thing('bar', a=3, b=4)

# retrieve them by name
>>> Thing.instances.get('foo')
<"foo" thing, {'a': 1, 'b': 2}>

>>> Thing.instances.get('foo').properties
{'a': 1, 'b': 2}

>>> Thing.instances.get('bar').properties
{'a': 3, 'b': 4}
Paulo Scardine
  • 73,447
  • 11
  • 124
  • 153