HiveBrain v1.2.0
Get Started
← Back to all entries
patternpythonMinor

Simulating a river ecosystem

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
riversimulatingecosystem

Problem

I'm currently working my way through a textbook to enhance my beginner Python skills (Goodrich, Data Structures and Algorithms in Python). The first, somewhat in-depth problem involving OOP was the following:


Write a Python program to simulate an ecosystem containing bears and
fish. The ecosystem consists of a river, which is modeled as a list.
Each element should be a Bear object, a Fish object, or None. Animals
have attributes gender and strength. In each time step, each animal
randomly either moves to an adjacent tile or stays where it is. If a
fish encounters a bear, the fish gets eaten. If two animals of the
same type encounter each other, a new instance of that animal type is
generated if they are the same gender. Otherwise, they fight and the
one with larger strength survives.

I would really appreciate some feedback on my code. I am not doing this as part of a class, so it's hard to tell whether I'm doing well and where I need improvement.

```
#!/usr/bin/env python3

"""
Simulation of river environment containing bears and fish.
"""

import random

class Animal:
"""Animals are the inhabitants of the river"""

def __init__(self, gender = None, strength = None):
"""Initialize animal

gender gender of the animal (M, F) determines mating or fighting (default random)
strength stregth of animal, determines winner of fight (default random)
"""

if not gender:
self._gender = random.choice(['M','F'])
else:
self._gender = gender
if not strength:
self._strength = random.randint(0,9)
else:
self._strength = strength

def get_gender(self):
"""Return the animals gender"""
return self._gender

def get_stren(self):
"""Return the animals strength"""
return self._strength

class Bear(Animal):
def __init__(self, gender = None, strength = None):
super().__init__(gender, strength)

cla

Solution

Don't compare against None

Don't compare against None with == or !=.
Use is None and is not None.

Use @property instead of getters

Instead of getters like this:

def get_stren(self):
    """Return the animals strength"""
    return self._strength


It's more natural to use properties:

@property
def stren(self):
    return self._strength


When you use this, instead of animal.get_stren(), you will have to write animal.stren (without parentheses).

I also dropped the redundant comment.

Generalize when possible

The current code has many hard-wired elements for Bear and Fish,
for example:

for i in range(self._length):
        rval = random.randint(1,3)
        if rval == 1:
            self._contents.append(Bear())
        elif rval == 2:
            self._contents.append(Fish())
        else:
            self._contents.append(None)


If later you want to add one more animal type,
you will have to edit the code in many places,
and it can be quite troublesome.
You can make the above example easier to extend like this:

animal_types = (Bear, Fish)
    len_animal_types = len(animal_types)

    for _ in range(self._length):
        rval = random.randint(0, len_animal_types)
        if rval == len_animal_types:
            self._contents.append(None)
        else:
            self._contents.append(animal_types[rval]())


Another opportunity for generalization is when you print letter symbols depending on the animal type:

if type(x) == Bear:
    s += 'B'
elif type(x) == Fish:
    s += 'F'


It would be better to add a @property in Animal:

@property
def symbol(self):
    return self.__class__.__name__[0]


And then you could use simply x.symbol to get the initial letter of the class name of the Animal instance.

Other simplifications

Since strings are iterables,
instead of the tedious:

self._gender = random.choice(['M', 'F'])


You can write simpler:

self._gender = random.choice('MF')


Follow PEP8

PEP8 is the official Python coding style guide. Please follow it.

For example, instead of:

def __init__(self, gender = None, strength = None):


Write like this:

def __init__(self, gender=None, strength=None):

Code Snippets

def get_stren(self):
    """Return the animals strength"""
    return self._strength
@property
def stren(self):
    return self._strength
for i in range(self._length):
        rval = random.randint(1,3)
        if rval == 1:
            self._contents.append(Bear())
        elif rval == 2:
            self._contents.append(Fish())
        else:
            self._contents.append(None)
animal_types = (Bear, Fish)
    len_animal_types = len(animal_types)

    for _ in range(self._length):
        rval = random.randint(0, len_animal_types)
        if rval == len_animal_types:
            self._contents.append(None)
        else:
            self._contents.append(animal_types[rval]())
if type(x) == Bear:
    s += 'B'
elif type(x) == Fish:
    s += 'F'

Context

StackExchange Code Review Q#88520, answer score: 6

Revisions (0)

No revisions yet.