0

This is going to look like class inheritance but I think it is not and there must be an easy way of doing the following. Take a look at this simple code:

class Land:
    def __init__(self):
        print "a new Land"
        self.farms = []

    def addfarm(self):
        self.farms.append(Farm())


class Farm:
    def __init__(self):
        print "a new farm"
        self.animals = []

    def addanimal(self,name):
        self.animals.append(Animal(name))

class Animal:
    def __init__(self, name):
        print "hi, I am %s" % name
        self.name = name


USA = Land()
USA.addfarm()
USA.farms[0].addanimal('George')
USA.farms[0].addanimal('Martin')
USA.addfarm()
USA.farms[1].addanimal('Polly')
USA.farms[1].addanimal('Ralph')

Is there an easy way of getting all animals without doing?:

for eachfarm in USA.farms:
    for each in eachfarm.animals:
        print each.name

I am asking this because if for instance the user wants to add a new George to farm 0 I would like to quickly be able to say that name is taken. I would also be able to quickly run a function that gives me all animals in the land or all farms. Should I be writing functions for all that or Python got its own?

I am also interested on knowing if my nested class structure is not correct and could end up causing issues.

For instance, lets say I have a function that given an animal tells me the perfect food mix for it. I would like to be able to run that function on all my animals and write back into their object. If they are nested I am afraid the function may get confused!

Thanks!

1
  • 1
    What would you want to write instead of that nested loop? Also, are you saying you want the list of "taken" names to be global across all farms, or just per-farm? What if the user tried to add a "George" to farm 1? Commented May 30, 2015 at 16:57

2 Answers 2

2

Using nested classes like this is perfectly fine and is not about inheritance at all. However you may want to choose slightly different data structures.

You say that in each farm you only want to be able to have one animal of each name. However, you use a list to store them. A list allows you to have as many animals of the same name inside as you want to, so you'd need to perform that check yourself when you add another one.

However, you could use a dict. A dict is an unordered data structure that links a key to a value. In your case you could use the name of the animal as the key and the Animal object for the value. Checking if a key exists can be done in constant time (as compared to linear time with a loop), since internally a dict is a hash table.

Example code might look like this:

class Land:
    def __init__(self):
        print "a new Land"
        self.farms = []

    def addfarm(self):
        self.farms.append(Farm())


class Farm:
    def __init__(self):
        print "a new farm"
        self.animals = {}

    def addanimal(self,name):
        if not name in self.animals:
            self.animals[name] = Animal(name)
            return True
        return False

class Animal:
    def __init__(self, name):
        print "hi, I am %s" % name
        self.name = name


USA = Land()
USA.addfarm()
USA.farms[0].addanimal('George')
USA.farms[0].addanimal('Martin')
USA.addfarm()
USA.farms[1].addanimal('Polly')
USA.farms[1].addanimal('Ralph')

This would prevent you from adding two animals of the same name to one farm, returning a boolean depending on whether the animal could be added to the farm or not.

To get all animals on all farms you will still need nested loops. But enabling iteration over the objects itself can be much nicer. If you do the following:

class Land(object):
    def __init__(self):
        print "a new Land"
        self.farms = []

    def addfarm(self):
        self.farms.append(Farm())

    def __iter__(self):
        for farm in self.farms:
            yield farm


class Farm(object):
    def __init__(self):
        print "a new farm"
        self.animals = {}

    def addanimal(self,name):
        if not name in self.animals:
            self.animals[name] = Animal(name)
            return True
        return False

    def __iter__(self):
        for name, animal in self.animals.iteritems():
            yield animal

class Animal(object):
    def __init__(self, name):
        print "hi, I am %s" % name
        self.name = name

You could then:

for farm in USA:
    for animal in farm:
        pass #do something here

According to your comment, you also want to be able to do land.getAllAnimals() and farm.getAllAnimals(). The latter is easily accomplished because farm works as an iterator over all animals. If you want a list you can simply call list(farm).
For land.getAllAnimals() there are two nice options. Both are to be added to the previous declaration.

Option 1
class Land(object):
    def getAllAnimals(self):
        for farm in self:
            for animal in farm:
                yield animal
Option 2
from itertools import chain

class Land(object):
     def getAllAnimals(self):
         return chain(*self)

Both will return iterators over all animals. To cast these into a list, simply call list on them. The former is easier to understand, but the latter is more concise and, in my opinion, nicer.

Sign up to request clarification or add additional context in comments.

4 Comments

Small nitpick: accessing a key in a dict is not linear time, it is constant time. Checking a list for existence is linear time.
You are absolutely right, that was more or less a typo (I actually gave the very brief reason why it is constant and still wrote linear). Thanks for pointing it out :)
Thanks @Cu3PO42 ... you say this is not about inheritance, but I was thinking about that concept because I suppose there could be a way of passing lists of items to the parent object. For instance, lets say Farm has a function to return all animals present. How would I pass that function up so it can be run by the Land object? (so the idea is to only write it once if I have Farm.getmetheanimals() I do not want to do for Farm in Land: farm.getmetheanimals() but hopefully can simply do Land.getmetheanimals() ... any ideas on that one? Thanks again!
I modified my answer to accommodate your comment, @Yona.
1

There is nothing wrong with nesting your loops, and it's just the way to do it. You might want to look into a more declarative approach, or you might want to store your data differently, but that's all just implementation detail and primarily a matter of taste.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.