views:

125

answers:

5

I've been struggling for a couple of days now with the following...

I'm trying to find a way to instantiate a number of objects which I can name via a raw_input call, and then, when I need to, look at its attributes via the 'print VARIABLE NAME' command in conjunction with the str() method.

So, to give an example. Let's say I want to create a zoo of 10 animals...

    class Zoo(object): 
        def __init__(self, species, legs, stomachs):
            self.species = species
            self.legs = legs
            self.stomachs = stomachs


for i in range(9): 
    species = raw_input("Enter species name: ")
    legs = input("How many legs does this species have? ")
    stomachs = input("...and how many stomachs? ")
    species = Zoo(species, legs, stomachs)

The idea is that the 'species' variable (first line of the for loop) e.g species = Bear becomes the object 'Bear' (last line of loop), which in conjunction with a str() method and the 'print Bear' command would give me the bears attributes.

Like I say, I've struggled for a while with this but despite looking at other posts on similar themes still can't figure out a way. Some say use dictionaries, others say use setattr() but I can't see how this would work in my example.

+6  A: 

If you just want to introduce new named variables in a module namespace, then setattr may well be the easiest way to go:

import sys

class Species:
    def __init__(self, name, legs, stomachs):
        self.name = name
        self.legs = legs
        self.stomachs = stomachs

def create_species():
    name = raw_input('Enter species name: ')
    legs = input('How many legs? ')
    stomachs = input('How many stomachs? ')
    species = Species(name, legs, stomachs)
    setattr(sys.modules[Species.__module__], name, species)

if __name__ == '__main__':
    for i in range(5):
        create_species()

If you save this code to a file named zoo.py, and then import it from another module, you could use it as follows:

import zoo
zoo.create_species() # => enter "Bear" as species name when prompted
animal = zoo.Bear # <= this object will be an instance of the Species class

Generally, though, using a dictionary is a more "Pythonic" way to maintain a collection of named values. Dynamically binding new variables has a number of issues, including the fact that most people will expect module variables to remain fairly steady between runs of a program. Also, the rules for naming of Python variables are much stricter than the possible set of animal names -- you can't include spaces in a variable name, for example, so while setattr will happily store the value, you'd have to use getattr to retrieve it.

rcoder
+1 for the last paragraph. A dictionary is the right way to go.
Laurence Gonsalves
A: 
class Zoo(object):
    def __init__(self, name):
        self.name = name
        self.animals = []

    def __str__(self):
        return ("The %s Zoo houses:\n" % self.name) + "\n".join(str(an) for an in self.animals)

class Animal( object ):
    species = None
    def __init__(self, legs, stomach):
        self.legs = legs
        self.stomach = stomach

    def __str__(self):
        return "A %s with %d legs and %d stomachs" % ( self.species, self.legs, self.stomach )


class Bear( Animal ):
    species = "Bear"

class Karp( Animal ):
    species = "Karp"


## this is the point ... you can look up Classes by their names here
## if you wonder show to automate the generation of this dict ... don't.
## ( or learn a lot Python, then add a metaclass to Animal ;-p )
species = dict( bear = Bear,
                karp = Karp )

zoo = Zoo( "Strange" )
while len(zoo.animals) < 9:
    name = raw_input("Enter species name: ").lower()
    if name in species:
        legs = input("How many legs does this species have? ")
        stomachs = input("...and how many stomachs? ")
        zoo.animals.append( species[name]( legs, stomachs ) )
    else:
        print "I have no idea what a", name, "is."
        print "Try again" 

print zoo
THC4k
+1  A: 

It's really, truly, SERIOUSLY a bad idea to create barenamed variables on the fly -- I earnestly implore you to give up the requirement to be able to just print FOOBAR for a FOOBAR barename that never existed in the code, much as I'd implore a fellow human being who's keen to commit suicide to give up their crazy desires and give life a chance. Use dictionaries, use a function that takes 'FOOBAR' as the argument and looks it up, etc.

But if my fellow human being is adamant about wanting to put an end to their days, I might switch to suggestions about how to do that with the least collateral damage to themselves and others. The equivalent here would be...:

class Zoo(object): 
    def __init__(self, species, legs, stomachs):
        self.species = species
        self.legs = legs
        self.stomachs = stomachs
        import __builtin__
        setattr(__builtin__, species, self)

By explicitly using the __builtin__ module, you ensure you can "print thespeciesname" from ANY module -- not just the one defining Zoo, nor just the one instantiating it.

It's STILL a terrible idea, but this is the least-horror way to implement it.

Alex Martelli
A: 
>>> class Bear():
...     pass
... 
>>> class Dog():
...     pass
... 
>>> 
>>> types = {'bear': Bear, 'dog': Dog}
>>> 
>>> types['dog']()
<__main__.Dog instance at 0x75c10>
Tzury Bar Yochay