1
\$\begingroup\$

I'm trying to see if there is a better way to design this. I have a class Animal that is inherited by Male and Female classes (Female class has an additional attribute). I also have a class called Habitat, an instance of each would contain any number of Animals including 0.

class Animal:
 def __init__(self, life_span=20, age=0):
 self.__life_span = life_span
 self.__age = age
 def get_life_span(self):
 return self.__life_span
 def get_age(self):
 return self.__age
 def age(self):
 self.__age += 1
class Female(Animal):
 __gender = 'female'
 def __init__(self, pregnant=False):
 self.__pregnant = pregnant
 def impregnate(self):
 self.__pregnant = True
class Male(Animal):
 __gender = 'male'
class Habitat:
 def __init__(self, list_of_animals=[Male(), Female()]): 
 self.__list_of_animals = list_of_animals
 def __add_male(self):
 self.__list_of_animals.append(Male())
 def __add_female(self):
 self.__list_of_animals.append(Female())
 def add_animal(self):
 if random.choice('mf') == 'm':
 self.__add_male()
 else:
 self.__add_female() 
 def get_population(self):
 return self.__list_of_animals.__len__()

Before I add any more functionality, I would like to find out if that's a proper way to design these classes. Maybe there is a design pattern I can use? I'm new to OOP, any other comments/suggestions are appreciated.

asked Feb 10, 2013 at 0:37
\$\endgroup\$
7
  • 2
    \$\begingroup\$ FWIW, don't call obj.__len__(), use len(obj) instead. Also, no need to use name mangling (double leading underscores): Just use self.age or self._age, especially use the former in favor of trivial getters (you can later replace them with a property, if you need that extra power, without changing the way client code works with your objects). \$\endgroup\$ Commented Feb 10, 2013 at 0:40
  • \$\begingroup\$ It would appear you're new to OOP in general - what's your experience? \$\endgroup\$ Commented Feb 10, 2013 at 0:41
  • \$\begingroup\$ @tallseth: already flagged as such. OP: if the moderators agree, your question will be automatically migrated for you. Please do not double-post. \$\endgroup\$ Commented Feb 10, 2013 at 0:41
  • \$\begingroup\$ @JonClements somehow I managed to avoid the whole thing and it's been bugging me for years, so now I'm trying to get a grip of it \$\endgroup\$ Commented Feb 10, 2013 at 0:46
  • \$\begingroup\$ @MartijnPieters thanks, can I do it myself without waiting for moderators? \$\endgroup\$ Commented Feb 10, 2013 at 0:48

1 Answer 1

4
\$\begingroup\$

In Habitat.__init__, you've committed a classic beginner's fallacy: using a list as a default argument.

In Python, every call to a function with a default argument will use the same default object. If that object is mutable (e.g. a list) any mutations to that object (e.g. .append) will affect that one object. In effect, all your Habitats will end up using the exact same list unless you specify a non-default argument.

Instead, use None as the default argument, and test for it:

def __init__(self, animals=None):
 if animals is None:
 animals = [Male(), Female()]
answered Feb 10, 2013 at 0:40
\$\endgroup\$
0

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

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.