1

I have a cursor object from a MongoDB find() query. This is essentially an iterable object which is made up of multiple dictionary entries.

I want to iterate over the cursor, take each dictionary in turn and convert it into a specific class instance, the type of which is defined inside the dictionary as a key (i.e. of the form dict['class_type']='class_type'). I want to create variables to reference these new class instances, with names defined by another dictionary key, i.e. dict['variable_name'] = 'name'. These names will be unique.

So far, I have tried the following:

def mongo_query_to_variable(mongo_query):

    for obj in mongo_query:
        class_type = obj['class_type']
        var_name = obj[key_for_var_name]

        exec(var_name + '=' + class_type + '(**obj)', globals())

This code seems to work when I implement it line by line in the shell but not when I try to use it as a function. I get the error:

NameError: name 'class_type' is not defined

I know it is better to try and avoid eval/exec, but am not sure how else I could dynamically choose the class type with the class_type string. The last line is essentially trying to replicate the accepted answer (2nd part) from this similar question, but with dynamic variable and class names: Convert Python dict to object?. The part of the code which goes in the class init is contained in a base class and inherited.

I have the global in there because the query will be of variable length, and I am not really sure how to return a dynamic number of variables and assign them names defined within the function. I have heard that it is better to avoid global so is there a better way of assigning the class instances to variables?

Any advice is appreciated; I am pretty new to programming and am aware I might be approaching this problem in the wrong way.

Cheers

Community
  • 1
  • 1
EngStan
  • 383
  • 2
  • 13
  • The indentation in your example is a bit off, could you confirm that its only copy-paste error when posting the question ? Indentation matters in Python. Cheers. – Wan B. Jan 20 '16 at 00:15
  • Well spotted Wan, yeah this a copy and paste mistake and was not present in the original code. Edited. – EngStan Jan 20 '16 at 12:42

1 Answers1

2

You've got some pretty bad design in there.

VAriable names should be created at code-writing time. You could even set variables dynamically in some corner cases. But usually, you want to use a dictionary if your describing name (the one you are using as variable-name) is changed. For one, if you create an unexpected variable dynamically, how would other program parts know it is there to start with? If you use a dictionary, it is introspectable through its keys. So my_objects['foo'] is a lot better than a foo variable which appears from nowhere. But, yes, it can be done, without resorting to exec.

First to instantiate your object. You should have all the desired classes in a single name space. You could have them in a module (they don't have to be defined in that module - you just have to import their names there: from vehicles import Car, Bus; from plants import Flower, Tree - woukld make these four classes available in the namespace of module which made the import.

Then you can retrieve the class object given the string name you retrieve from your mongodb. If you name the namespace module "myclasses":

import myclasses
   ...
   for obj in mongoquery: 
       cls = getattr(myclasses, obj['class_type']
       new_instance = cls(**obj)

Now, to store that in some place your program can find out later, as I said above, the preferable way would be to keep it in a dictionary. So:

   my_objects[obj[key_for_var_name]] = new_instance

would suffice.

To set it as a global variale in the current module (the module where this very code is running), you can do instead:

   globals()[obj[key_for_var_name]] = new_instance

Do that, against all advice, and the variable indicated will be avalilable in the current module. Code in other modules can access it by using module_name.<variable_name>.

And again, you can resort to a namespace module, and make it all easier - a Python module mostly blank (or with the possible variables initially set to None), and do this in your code:

setattr(namespace_module, obj[key_for_var_name], new_instance)

As you can see, Python is truly introspective in nature,and you rarely will need to resort to exec or eval. But that can induce horrible code practices. Keep in mind you habe to separate what is code and what is data in your application - variable names are usually code.

jsbueno
  • 99,910
  • 10
  • 151
  • 209
  • Thanks for the in-depth advice jsbueno. I am designing a large object-orientated model, which will use and reference several different class types and instances for calculations. Would proper practice be to have a master dictionary object and assign every class instance to a unique dictionary key as they are initialized? – EngStan Jan 27 '16 at 17:17