patternpythonMinor
Multigeneration evolution simulator, graphing phenotypic change
Viewed 0 times
evolutiongraphingphenotypicmultigenerationsimulatorchange
Problem
I created an evolution simulator. It takes random chance and applies it to phenotypes of species. This was very much for fun, and I would love any input on:
Also, please feel completely free to just run the program for fun! It is (I hope) cool to see how natural disasters will affect certain phenotypes in a population and how changing the chances of things like mutations and natural disasters affects the population as a whole! I had a lot of fun playing around with the different outcomes.
A quick overview of the buttons in the GUI:
Resultant graph:
Program in action:
```
#--------------------------------------
- Readability of code
- Efficiency of generation generating
- Better ways to create dynamic GUI elements
- Future mod-ability
- My usage of classes, something I'm historically not great at
- Any tips on how to improve how the program looks as well as behaves
Also, please feel completely free to just run the program for fun! It is (I hope) cool to see how natural disasters will affect certain phenotypes in a population and how changing the chances of things like mutations and natural disasters affects the population as a whole! I had a lot of fun playing around with the different outcomes.
A quick overview of the buttons in the GUI:
- Quit: quits the program
- Export profile: Saves all the current settings to a file that you can access later, using...
- Load profile: loads a presaved .profile file
NUM_ORG: original number of organisms in the population
OPT_OFF_NUM: optimal number of offspring
NAT_DIS_FREQ: Frequency of natural disasters, use number between 0 and 100
GEN_FREQ: how fast the generations reproduce, in seconds
POP_LIM: upper limit of the population (between 1000 and 9999)
FREQ_MUT: likelihood of a mutation occurring in an organism
MAX_MUT: maximum number of mutations in the population
GEN_NUM: Number of generations (it works pretty quickly, but results may vary)
EXECUTE MAIN: Runs the main function, generation a population list
GRAPH: generates the graph based off of the settings above the button
- Checkboxes: allows you to control what is graphed. For example, unchecking the first box removes the "heat-resistant" organisms from the graph
- Show Natural Disaster Lines?: Draws a line straight down from a natural disaster to show what generation it occurred at
Resultant graph:
Program in action:
```
#--------------------------------------
Solution
For python an official styleguide exists, PEP 8. It recommends:
There are automatic tools to check where your code violates these recommendation
I would use
Now you can do
Your
And other instances, where you have special behaviour for your different variables
You should avoid unnecessary globals, whenever you can. They are hard to keep track off, even worse when they are actually modified by functions. In all of your cases you could just make them parameters of the function instead of relying on
In
In python it is normal to iterate over the elements of a list, not its indices:
Use lists instead of dicts here:
The only difference:
Or even better, make a disaster class (and an entity class):
- 4 spaces per indentation level (there are a few instances where you have 5)
- descriptive variable names (no single letter) in
lower_case
- in argument lists, a space after a comma
- exactly 2 blank lines before a function and class definition
- 80 characters max per line
- whitespace around operators
There are automatic tools to check where your code violates these recommendation
pep8, which is commonly installed when you install python.I would use
collections.namedtuple for your IntVars. This allows you to get rid of quite a lot of duplication:from collections import namedtuple
...
Enum = namedtuple("Enum", "no oon ndf gf pl fm mm pop natcheck")
variables = Enum(*(IntVar() for _ in range(9)))
...
def graph():
...
approved_list = ['null']
approved_list += [characteristics[i+1] for i, var in enumerate(variables) if var.get() == 1]Now you can do
variables.no to get the variable no. You will have to change every appearance of the variables, though.Your
Checkbutton code can be replaced now with something like:buttons = []
for row, var in enumerate(variables):
if row <= 6:
text = "<---Graph"
elif row == 7:
text = "Graph pop"
elif row == 8:
text ="Show natural disaster lines?"
buttons.append(Checkbutton(main, text=text, variable=var))
if row <= 7:
buttons[-1].grid(row=row, column=2, sticky=W)
else:
buttons[-1].grid(row=row, column=2, sticky=W, columnspan=2)
buttons[-1].toggle()And other instances, where you have special behaviour for your different variables
range starts by default at 0, so there is no need to manually set it (unless you want to use step, without using a keyword, like range(3, step=2)).You should avoid unnecessary globals, whenever you can. They are hard to keep track off, even worse when they are actually modified by functions. In all of your cases you could just make them parameters of the function instead of relying on
global.In
class element_input, the button b is not assigned to self.b and will therefore probably not persist (or will at least not be accessible).In python it is normal to iterate over the elements of a list, not its indices:
with open(profilename, 'w') as out_file:
for element_name in gui_element_names:
out_file.write("%s\n" % c[element_name])Use lists instead of dicts here:
char_effect = {1:'-', 2:'+',3:'+',4:'+',5:'-',6:'+',7:'-',8:'+'}
char_effect = ['-', '+', '+', '+', '-', '+', '-', '+']The only difference:
index - 1Or even better, make a disaster class (and an entity class):
import random
from itertools import count
ADVANTAGES = "heat-resistant cold-resistant energy-efficient fast slow big small attractive".split()
class Disaster():
def __init__(self, name, immunities):
self.name = name
self.immunities = immunities
def survivors(self, population):
return [entity for entity in population if entity.characteristic in self.immunities]
class Entity():
ID = count()
def __init__(self, characteristic):
self.characteristic = characteristic
self.id = next(Entity.ID)
population = (Entity(random.choice(ADVANTAGES)) for _ in range(100))
landslide = Disaster("landslide", ["fast", "big", "small"])
population = landslide.survivors(population)Code Snippets
from collections import namedtuple
...
Enum = namedtuple("Enum", "no oon ndf gf pl fm mm pop natcheck")
variables = Enum(*(IntVar() for _ in range(9)))
...
def graph():
...
approved_list = ['null']
approved_list += [characteristics[i+1] for i, var in enumerate(variables) if var.get() == 1]buttons = []
for row, var in enumerate(variables):
if row <= 6:
text = "<---Graph"
elif row == 7:
text = "Graph pop"
elif row == 8:
text ="Show natural disaster lines?"
buttons.append(Checkbutton(main, text=text, variable=var))
if row <= 7:
buttons[-1].grid(row=row, column=2, sticky=W)
else:
buttons[-1].grid(row=row, column=2, sticky=W, columnspan=2)
buttons[-1].toggle()with open(profilename, 'w') as out_file:
for element_name in gui_element_names:
out_file.write("%s\n" % c[element_name])char_effect = {1:'-', 2:'+',3:'+',4:'+',5:'-',6:'+',7:'-',8:'+'}
char_effect = ['-', '+', '+', '+', '-', '+', '-', '+']import random
from itertools import count
ADVANTAGES = "heat-resistant cold-resistant energy-efficient fast slow big small attractive".split()
class Disaster():
def __init__(self, name, immunities):
self.name = name
self.immunities = immunities
def survivors(self, population):
return [entity for entity in population if entity.characteristic in self.immunities]
class Entity():
ID = count()
def __init__(self, characteristic):
self.characteristic = characteristic
self.id = next(Entity.ID)
population = (Entity(random.choice(ADVANTAGES)) for _ in range(100))
landslide = Disaster("landslide", ["fast", "big", "small"])
population = landslide.survivors(population)Context
StackExchange Code Review Q#139053, answer score: 4
Revisions (0)
No revisions yet.