Wednesday, 25 October 2017

Prime Numbers and Goldbach's Conjecture with Python

Prime numbers (those integers that cannot be divided neatly without fractions or remainders by any other numbers except 1 and itself) are big business. As in they are used in business mainly for secure transactions. Other folks just find them interesting in their own right, speculating whether they follow a predictable pattern (which we have not discovered yet) or not.

The method I have written for finding them is not nearly as sophisticated as those of more skilled mathematicians. It uses a method called the Sieve of Eratosthenes. Basically the program takes a number and tries to divide it by all the numbers below it (except for 1). If any do divide neatly, then it is not a prime number.

Another mathematician called Goldbach came up with a conjecture that any non-prime number could be created by adding two prime numbers together. So for example, 2 is a prime, 4 is not a prime and can be expressed as 2 * 2, or in Goldbach's conjecture 2+2.
Goldbach's conjecture seems to hold true as far as practical calculations show, but nobody is sure if that is because of some important underlying principle or if it is just very easy to do so.

So this program asks for a maximum number. It then goes from 2 to the maximum number, using the sieve of Eratosthenes to find if each number is a prime, and if not then whether it fits Goldbach's conjecture.


#!/usr/bin/python3
def goldbach(evennumber):
    for x in primelist:
        for y in primelist:
            if x + y == evennumber:
                print(", Goldbach conjecture: "+str(x)+" + "+str(y)+" = "+str(evennumber))
                return()
    print("Problem with Goldbach Conjecture!"); exit()
count = 0
maxcount = input("Please enter maximum count: ")
try: int(maxcount)
except: print("Bad input"); exit()
else: maxcount = int(maxcount)
testnumber = 2
primelist = [2]
while count < maxcount-2 :
    testnumber +=1
    prime = True
    count += 1
    for testfactor in primelist:
        if testnumber % testfactor == 0:
            print(str(testnumber)+" is divisible by "+str(testfactor), end='')
            prime = False
            if testfactor == 2: goldbach(testnumber)
            else: print('')
            break
    if prime == True:
        primelist.append(testnumber)
        print(str(testnumber) + " is a prime!")
#print(primelist)
print("Length of prime list is "+str(len(primelist)))
And a typical output is:

>>> ================================ RESTART ================================
>>>
Please enter maximum count: 2000
3 is a prime!
4 is divisible by 2, Goldbach conjecture: 2 + 2 = 4
5 is a prime!
6 is divisible by 2, Goldbach conjecture: 3 + 3 = 6
7 is a prime!
.....
1996 is divisible by 2, Goldbach conjecture: 3 + 1993 = 1996
1997 is a prime!
1998 is divisible by 2, Goldbach conjecture: 5 + 1993 = 1998
1999 is a prime!
2000 is divisible by 2, Goldbach conjecture: 3 + 1997 = 2000
Length of prime list is 303

>>>


 

Monday, 23 October 2017

Gravity and Radius for a Stanford Torus

In science fiction one way to get around the lack of gravity is to produce artificial gravity by centrifugal force - spinning something around in a circle will create an acceleration similar to gravity as it tries to travel in a straight line and therefore away from the centre of rotation.

The rotating space station has become a staple of science fiction, most notably in Stanley Kubrik's 2001: A Space Odyssey, and in Elysium. It was first seriously proposed at Stanford University and has since become known as the Stanford Torus.
One thing I wondered is how big does a space station need to be to produce Earth-like gravity (9.8 m/s2)? It actually depends on how fast it is rotating.
I had a look online and found the equation I was looking for:
Acceleration = velocity2 / radius

So I came up with a Python program to help work it out. Given any two factors in that equation the program will calculate the third plus the period of rotation (how long it takes to make a complete rotation).
#!/usr/bin/python3
import math
print ("Program for calculating stats of Stanford Torus")
rad = input("Please enter radius (m): ")
if rad == "": radGiven = False
elif int(rad) > 0:
    radGiven = True
    rad = float(rad)
    circumf = 2 * math.pi * rad
else: print ("Invalid answer"); radGiven = False
accel = input("Please enter required centripetal acceleration (m/s2): ")
if accel == "": accelGiven = False
elif float(accel) > 0: accelGiven = True; accel = float(accel)
else: print ("Invalid answer"); accelGiven = False
if accelGiven == True and radGiven == True:
    veloc = math.sqrt(accel * rad)
    period = circumf / veloc
    print ("Velocity at edge is " + str(veloc) + "m/s")
    print ("Period at edge is " + str(period) +"sec")
else:
    period = input("Please enter period in sec: ")
    if period == "": print("Not enough information for calculation")
    elif float(period) > 0 and radGiven == True:
        period = float(period)
        veloc = circumf / period
        print ("Velocity at edge is " +str(veloc) + "m/s")
        accel = veloc * veloc / rad
        print("Acceleration at edge is " + str(accel) + "m/s2")
    elif float(period) > 0 and accelGiven == True:
        period = float(period)
        veloc = period * accel
        rad = veloc * veloc / accel
        print ("Velocity at edge is " + str(veloc) + "m/s")
        print ("Radius at edge is " + str(rad) + "m")

And here are some typical results.
======== RESTART: C:\Users\pc\Documents\Programming\StanfordTorus.py ========
Program for calculating stats of Stanford Torus
Please enter radius (m):
200
Please enter required centripetal acceleration (m/s2):
Please enter period in sec:
200
Velocity at edge is 6.283185307179586m/s
Acceleration at edge is 0.19739208802178715m/s2

>>>
======== RESTART: C:\Users\pc\Documents\Programming\StanfordTorus.py ========
Program for calculating stats of Stanford Torus
Please enter radius (m):
Please enter required centripetal acceleration (m/s2):
4.9
Please enter period in sec: 20
Velocity at edge is 98.0m/s
Radius at edge is 1959.9999999999998m

>>>
As you can see, entering a blank into the input for one of the factors will mean the program will try to calculate that missing factor.
Importing the math module gives us a quick and accurate value of Pi, necessary for the velocity at the edge of the torus.

Thursday, 19 October 2017

A script to convert CSV to HTML tables

HTML can be a pain to write manually, especially when it comes to tables.
This script does the basics. It asks for and tries to open up the source file, then you give it the name of the new file the HTML table will be saved to.
It works on the very simple idea of splitting each row by its separator (which the user needs to specify, such as ; or ,) and then inserting the HTML 'separators' of <td> and </td>. It could be improved on - at the moment HTML/CSS formatting and style needs to be entered manually after conversion and there is no allowance for the first row being different. Nonetheless, I believe it could save me a lot of time and bother.
#!/usr/bin/python3
import sys

print ("HTML Table Maker!")
filechoice = input("Please enter name of file with data, including file extension: ")
try:
    FX= open(filechoice, 'r')
except:
    print("Invalid File Name!")
    exit()

print(FX.readline())
FX.seek(0) # Returns reading point back to top of file
sep = input("Please enter column separator: ")

newfile = input("New File name (inc. extension)? ")
FN = open(newfile, 'w')
FN.write('<table>\n')
for line in FX:
    linelist = line.split(sep)
    FN.write('\t<tr>')
    for col in linelist:
        FN.write('<td>'+col+'</td>')
    FN.write('</tr>\n')
FN.write('</table>\n')    
FX.close()
FN.close()

The line FX.seek(0) is a new thing - I may have mentioned before that when a Python program is writing to or reading from a file, it maintains a progress point, like a theoretical cursor. 
fileobject.seek(linenumber) resets this progress point to a particular line (in this case back to the start). 
And the output is as follows:
>>> ================================ RESTART ================================
>>> 
HTML Table Maker!
Please enter name of file with data, including file extension: groceries.csv
Butter (250g), 1.50, 2, 

Please enter column separator: ,
New File name (inc. extension)? groceries.html
>>> 
And now I can embed the contents of groceries.html into this blog page which is based on HTML:

Item NamePrice per ItemNumber of Items
Butter (250g)1.502
Chocolate Biscuits (300g)1.502
Flour (1kg)0.503
Milk (1 pint)0.705
Pork Sausages (500g)4.501
Rice (1kg)1.701
Strawberry Jam (400g)2.001

or even add a bit of formatting:

Item NamePrice per ItemNumber of Items
Butter (250g)1.502
Chocolate Biscuits (300g)1.502
Flour (1kg)0.503
Milk (1 pint)0.705
Pork Sausages (500g)4.501
Rice (1kg)1.701
Strawberry Jam (400g)2.001

Wednesday, 18 October 2017

Motivation for and Usefulness of Computer Programs

Why write programs? Is it purely a hobby? Is it a series of boxes for us to tick for work or study? Is it to show how clever we are?
For myself one major attraction to programming is the ability to create electronic tools that help me do things with data. I like to think that the code is not an end in itself but the way we get the results we want - whether that is entertaining, interesting or useful for some further purpose.
There is an essay/paper on software development called the Cathedral and the Bazaar by Eric S Raymond (link here). Throughout it he highlights a series of principles and lessons which I have found useful guidance. The very first one is:
  1. Every good work of software starts by scratching a developer's personal itch.
Although not always true for my programs (some of them are about investigating a new part of Python I've discovered or been told about), it certainly fits quite a few of my programs. It reminds me of a description of computer programming as "constructive laziness", creating something so you don't have to do so much work - it will do the work for you.   
In my mind there are a series of levels of usefulness of computer programs:
  • Stuff you could do in your own head. Imagine the words "Hello World". Okay, so why do you need to print it out? Or what's 2+3? If you need pencil and paper (or a Python program) to work that one out, maybe Python programming isn't for you. 
  • Stuff you can do with paper and pencil. What's 32 * 23? A few folks may well do that in their heads, but I need to work it out on paper. The answer is 736 by the way. What about using the Caesar Cipher? If all the letters are shifted one place forward along the alphabet, what is "J MPWF QZUIPO" when it is decoded? This is where computer programs may not be essential but can speed up processes.
  • Stuff you can do on office software. Spreadsheets, word processors, simple databases, email-managing programs and web browsers are so ubiquitous in the modern world that they are the first stop when solving problems or working on tasks that are too hard for pencil and paper. For loops in Python can be represented in a series of cells on a spreadsheet that reference each other. Similarly, the CSV files I've read and manipulated using Python can be opened on most spreadsheet programs. Heck, I'm impressed with whoever creates these, and very grateful as well. Whether I use these commercial programs or create a solution with my own Python program is often a matter of motivation and time. If I'm enthusiastic and have time to spare, I will have a go on Python. If I am in a hurry, or maybe the task is beyond my programming skill (which is often the case), I'll just open up the spreadsheet or whatever. 
  • Stuff you can do if you really know office software. This is a sort of follow-on from the previous type, in that if you can create functions in spreadsheets, can skilfully manipulate tables in word processors, do form letters etc. then you don't really need to write a Python program to do those things. If you know your spreadsheets, you could probably get it to create a D&D treasure generator. But if you don't, then programming the solution in Python can seem a reasonable alternative.   
  • Stuff that requires specialised software. These tasks are not easily solved on OpenOffice, MS Office or whatever you prefer. The solution may be out there in the wider world, but finding a trustworthy source that does not charge too much money is necessary. If I can do it in Python, then the time and effort spent learning Python really starts to pay off. Of course, that's a big if. 
  • Stuff where nobody else has created a software solution yet, and you can't do it in your head or on pencil and paper. This is where if you really want a software solution, you have to create it yourself (or maybe hire somebody else to write it for you, but then why are you reading this blog?). If you are not the only person to face this task, but you are the first one to solve it then you can make money from it. All commercial programs rely on supply and demand. If you are the only one supplying and there is enough demand, you could sell your work for a tidy sum - just look at the apps being created for smartphones. 
So is this the only reason why I do Python programming?
No. Actually, some of the reasons I offered at the top of this post are still valid. Programming is a creative process and can be quite satisfying. It stretches my mind and challenges me to do better. And it looks good on a CV as well.

Tuesday, 17 October 2017

Treasure Generator for Dungeons & Dragons

One of my hobbies has been Dungeons and Dragons. Although I don't play much these days, it has had a second lease of life as a subject to write computer programs for. Here is one such, when I was wondering how to generate a treasure hoard, given an approximate value and whether or not the treasure was mostly low-value copper coins, or high value gems and magic items.

#!/usr/bin/python3.5
import random
print ("Treasure hoard generating program")
print ("=================================")

print ("Please enter approximate value of treasure hoard: ")
total_value = input("? ")
total_value = int(total_value) # approximate total value. It doesn't work out precisely.

print ("On a scale of 1-10 how skewed is it towards high value items?")
skew = input("1= mostly low value coins, 10 = mostly magic items: ")
skew = int(skew) -1
# for ratio tuples [0] is copper, [1] is silver, [2] is electrum, [3] is gold
# [4] is platinum, [5] is gems, [6] is jewelry, [7] is magic items
skew_ratio1 = (0.75, 0.95, 0.98, 0, 0, 1, 0, 0)
skew_ratio2 = (0.50, 0.85, 0.90, 0.95, 0, 0.98, 1, 0)
skew_ratio3 = (0.30, 0.60, 0.70, 0.80, 0.85, 0.90, 0.95, 1)
skew_ratio4 = (0.25, 0.50, 0.65, 0.80, 0.85, 0.90, 0.95, 1)
skew_ratio5 = (0.15, 0.30, 0.45, 0.60, 0.75, 0.83, 0.91, 1)
skew_ratio6 = (0.10, 0.20, 0.40, 0.60, 0.75, 0.90, 0.95, 1)
skew_ratio7 = (0.07, 0.14, 0.20, 0.50, 0.65, 0.80, 0.90, 1)
skew_ratio8 = (0.05, 0.10, 0.15, 0.35, 0.50, 0.65, 0.80, 1)
skew_ratio9 = (0.03, 0.07, 0.12, 0.30, 0.45, 0.60, 0.85, 1)
skew_ratio10 = (0.01, 0.02, 0.10, 0.20, 0.30, 0.50, 0.75, 1)
skew_ratio_tup = (skew_ratio1, skew_ratio2, skew_ratio3, skew_ratio4, skew_ratio5, skew_ratio6, skew_ratio7, skew_ratio8, skew_ratio9, skew_ratio10)

selected_skew_ratio = skew_ratio_tup[skew] # should select the right set of ratios
spent_value = 0
copper_value = 0
silver_value = 0
electrum_value = 0
gold_value = 0
platinum_value = 0
gem_value = 0
jewelry_value = 0
magic_item_value = 0

chunk_number = 0
while spent_value < total_value:
    chunk_value = total_value * random.random() * 0.1
    chunk_choice = random.random()
    category_num = 0
    chunk_done = False
    for category in selected_skew_ratio:
        if category > chunk_choice and chunk_done == False:
            spent_value = spent_value + chunk_value
            if category_num == 0:
                copper_value = (copper_value + chunk_value)
            elif category_num == 1:
                silver_value = (silver_value + chunk_value)
            elif category_num == 2:
                electrum_value = (electrum_value + chunk_value)
            elif category_num == 3:
                gold_value = (gold_value + chunk_value)
            elif category_num == 4:
                platinum_value = (platinum_value + chunk_value)
            elif category_num == 5:
                gem_value = (gem_value + chunk_value)
            elif category_num == 6:
                jewelry_value = (jewelry_value + chunk_value)
            else:
                magic_item_value = (magic_item_value + chunk_value)
            #print (category_num, chunk_value)
            chunk_done = True
        else:
            category_num = category_num +1
    chunk_number = chunk_number +1
print ("Coins")
print ("=====")
copper_coins = format(int(copper_value * 100), ',d')
print ("Copper value = ", copper_value, "\n\tCopper Coins = ", copper_coins)
silver_coins = format(int(silver_value * 10), ',d')
print ("Silver value = ", silver_value, "\n\tSilver Coins = ", silver_coins)
electrum_coins = format(int(electrum_value * 5), ',d')
print ("Electrum value = ", electrum_value, "\n\tElectrum Coins = ", electrum_coins)
print ("Gold value = ", gold_value, "\n\tGold coins = ", format(int(gold_value), ',d'))
platinum_coins = format(int(platinum_value / 5), ',d')
print ("Platinum value = ", platinum_value, "\n\tPlatinum coins = ", platinum_coins)
print ("Gem value = ", gem_value)
print ("Jewelry value = ", jewelry_value)
print ("Magic Item value =", magic_item_value)
print ("Total assigned value = ", spent_value)
print ('\n')

gemstash = [] # what the treasure hoard contains - starts empty
total_gemvalue = 0
if gem_value < 10 and gem_value > 0:
    gem_string = ["1 small pretty stone worth ", int(gem_value)]
    total_gemvalue = total_gemvalue + int(gem_value)
else:
    gem_string = ""
    handle = open('gemfile.txt', 'r')
    gemlist = [] # table for storing data about gems
    for line in handle:
        linelist = line.split(', ')
        gemlist.append(linelist)
    remaining_gemvalue = gem_value
    size_tuple = ((0.2, "very small "), (0.5, "small "), (0.5, "small "), (1, "medium "), (1, "medium "), (1, "medium "), (2, "large "), (5, "huge "))
    quality_tuple = ((0.2, "poor "), (0.5, "flawed "), (0.5, "flawed "), (1, "normal "), (1, "normal "), (1, "normal "), (2, "flawless "), (5, "perfect "))
    while remaining_gemvalue >= 10:
        potential_gem = [random.choice(quality_tuple), random.choice(size_tuple), random.choice(gemlist)]
        potential_value = float(potential_gem[0][0]) * float(potential_gem[1][0]) * float(potential_gem[2][1])
        if potential_value <= remaining_gemvalue:
            gem_descrip = potential_gem[0][1] + potential_gem[1][1] + potential_gem[2][0]
            gem_final = [int(potential_value), gem_descrip]
            gemstash.append(gem_final)
            remaining_gemvalue = remaining_gemvalue - potential_value
            total_gemvalue = total_gemvalue + potential_value
    handle.close()
gemstash.append(gem_string)
print ("Gems")
print ("====")
for gem in gemstash:
    print (gem)
print ("Gem value is ", total_gemvalue)
print ('\n')

jewelrystash = []
jewelrylist = []
total_jewelryvalue = 0
remaining_jewelryvalue = jewelry_value
handle = open('jewelryfile.txt', 'r')
for line in handle:
    linelist = line.split(', ')
    linelist[1] = int(linelist[1])
    jewelrylist.append(linelist)
jewelry_tuple = (("plain ", 1), ("engraved ", 2), ("ornate ", 5), ("bejeweled ", 10))
metal_tuple = (("copper ", 1), ("silver ", 10), ("electrum ", 50), ("gold ", 100), ("platinum ", 500), ("mithril ", 2000))
while remaining_jewelryvalue >= 10:
    potential_jewel = [random.choice(jewelry_tuple), random.choice(metal_tuple), random.choice(jewelrylist)]
    potential_jewel_description = potential_jewel[0][0] + potential_jewel[1][0] + potential_jewel[2][0]
    potential_jewel_value = potential_jewel[0][1] * potential_jewel[1][1] * int(potential_jewel[2][1])
    if remaining_jewelryvalue > potential_jewel_value:
        jewelrystash.append((potential_jewel_description, potential_jewel_value))
        remaining_jewelryvalue = remaining_jewelryvalue - potential_jewel_value
        total_jewelryvalue = total_jewelryvalue + potential_jewel_value

print ("Jewellery")
print ("=========")
for jewel in jewelrystash:
    print (jewel)
print ("Jewelry value is", total_jewelryvalue)
print ("\n")
handle.close()

itemstash = []
itemlist = [] # magic items and values are from DMG 3.0
total_itemvalue = 0
remaining_itemvalue = magic_item_value
handle = open('magicitemfile.txt', 'r')
for line in handle:
    linelist = line.split(', ')
    linelist[1] = int(linelist[1])
    itemlist.append(linelist)
while remaining_itemvalue >= 200:
    potential_item = random.choice(itemlist)
    if remaining_itemvalue >= potential_item[1]:
        itemstash.append(potential_item)
        remaining_itemvalue = remaining_itemvalue - potential_item[1]
        total_itemvalue = total_itemvalue + potential_item[1]
print ("Magic Items")
print ("===========")
for magicitem in itemstash:
    print (magicitem)
print ("Magic Item value is ", total_itemvalue)

total_treasurevalue = (int(copper_value)) + (int(silver_value)) + (int(electrum_value)) + int(gold_value) + int(platinum_value) + total_gemvalue + total_jewelryvalue + total_itemvalue
print ("total treasure value is", total_treasurevalue)


First of all, this is a big program. 
Secondly it uses a number of data files, namely magicitemfile.txtjewelryfile.txt and gemfile.txtThese contain large amounts of data that did not seem suitable to have in the main code. 
Thirdly this is not entirely accurate in total value - it gives amounts about 10% either side of initial required value. 

It works by splitting the treasure up into randomly-sized chunks between 0% and 10% of the required value. 
That chunk is then randomly determined to be a particular treasure type (copper coins, silver coins, gems, jewellery etc). Rather than equal chances for each type, the chances are determined by looking up the skew_ratio which then gives the chances for each type. 
If the chunk is made of coins, then the number of coins is determined based on the value of the chunk. 
If the chunk is made of gems, jewellery or magic items, then random examples of those are generate. If their values are equal to or less than that of the chunk then the item is added and the value of the item deducted from the chunk. If there is enough value left over in the chunk, another example is generated until the remaining value of the chunk falls below a threshold.   
And the end result? Here is a typical run, generating a 100,000gp-value treasure trove

 RESTART: C:/Users/John/Dropbox/Misc Programming/Python/python3/test08a_treasure.py
Treasure hoard generating program
=================================
Please enter approximate value of treasure hoard:
?
100000
On a scale of 1-10 how skewed is it towards high value items?
1= mostly low value coins, 10 = mostly magic items:
5
Coins
=====
Copper value =  14977.991665483956
 Copper Coins =  1,497,799
Silver value =  15397.31065665539
 Silver Coins =  153,973
Electrum value =  18959.482408553344
 Electrum Coins =  94,797
Gold value =  2479.269646407558
 Gold coins =  2,479
Platinum value =  24353.349234787212
 Platinum coins =  4,870
Gem value =  1626.2385405454738
Jewelry value =  8493.713069086232
Magic Item value = 14708.117885848227
Total assigned value =  100995.4731073674


Gems
====
[30, 'poor small Aquamarine']
[100, 'poor huge Iolite']
[100, 'normal medium Amber']
[100, 'flawed very small Emerald']
[50, 'normal small Tourmaline']
[1000, 'flawless huge Carnelian']
[20, 'normal very small Peridot']
[30, 'normal medium Rose Quartz']
[7, 'flawed small Citrine']
[60, 'normal very small Garnet']
[120, 'flawless very small Pearl']

Gem value is  1617.5

Jewellery
=========
('ornate electrum vase', 7500)
('engraved silver fork', 100)
('plain copper spoon', 5)
('engraved copper torc', 100)
('ornate copper spurs', 50)
('plain copper crown', 100)
('plain silver spurs', 100)
('plain silver spurs', 100)
('plain silver necklace', 150)
('plain copper knife', 5)
('bejeweled copper pin', 50)
('plain copper dagger', 50)
('bejeweled copper fork', 50)
('plain copper chain', 20)
('ornate copper earring', 50)
('engraved copper nosering', 20)
('engraved copper headband', 40)
Jewelry value is 8490


Magic Items
===========
['Wand of Magic Missile', 750, 'DMG 3.0\n']
['Longsword +2', 8315, 'DMG 3.0\n']
['Scroll of 5th level cleric spell', 1125, 'DMG 3.0\n']
['Bolts +1 (50)', 2350, 'DMG 3.0\n']
['Scroll of 5th level wizard spell', 1125, 'DMG 3.0\n']
['Potion of Heroism', 900, 'DMG 3.0\n']
Magic Item value is  14565
total treasure value is 100837.5

>>>


The presentation could do with some cleaning up, but the idea is sound. And just in case you do play Dungeons & Dragons, the magicitemfile.txt derives its information from 3rd Edition Dungeon Master's Guide. If you prefer a different edition, feel free to generate your own magicitemfile.txt

Monday, 16 October 2017

Try, Except and Global

We've seen errors in the output of programs on this blog. But what if we wanted to do something that might result in an error but we don't want it to stop the program completely?
The Python keywords try and except are used to deal with exceptions (a sort of error) in a graceful way so the program doesn't simply crash.

The keyword global tells Python that a variable is used both within a function and beyond it in the wider program.
When variables are used inside a function, Python assumes that those variables are specific to that function, even if other variables in the program outside that function share the same name.
The global keyword overrides this assumption and makes it clear to Python (and anyone reading it) that this is the same variable used across the whole program.
Here is a program to demonstrate:

#!/usr/bin/python3
import os
import os.path
 
def sesame():
    contentlist = os.listdir()
    filelist = []
    for item in contentlist:
        if os.path.isfile(item):
            filelist.append(item)
    print (filelist)
    filename = input('Please enter filename: ')
    try:
        handle = open(filename, 'r')
        for line in handle:
            print(line, end='')
        handle.close()
    except:
        print ('File not found.')
        return
 
def finddir():
    global currentdir
    global dirname
    print ('start finddir(), current directory is:', currentdir)
    contentlist = os.listdir()
    dirlist = []
    for item in contentlist:
        if os.path.isdir(item):
            dirlist.append(item)
    print (dirlist)
    dirname = input('Please enter subdirectory or hit return to use this one: ')
    try:
        tempdir = currentdir+dirname+'\\'
        os.chdir(tempdir)
        currentdir = tempdir # This won't happen if previous line fails
    except:
        print('Sorry, not valid directory')
 
print ('Program to find and display contents of file')
global currentdir
global dirname
currentdir = os.getcwd()
dirname = 'x'
print ('Current directory is ', currentdir)
dirchoice = input('Keep current directory (Y/N)? ')
dirchoice = dirchoice.lower()
if dirchoice == 'n':
    currentdir = 'C:\\'
    os.chdir(currentdir)
    while dirname !='':
        finddir()
sesame()

So there are a number of things I should point out:
  • Here there are two different user-defined functions (finddir() and sesame()). Strictly speaking they did not need to be functions but I thought it would be good practice. 
  • Both functions use try...except... to attempt to open files or directories. 
  • The variables currentdir and dirname are used both in the main program and also in the finddir() function. The global statements for each makes this clear. 
  • As mentioned in a previous post, we are importing two modules: os and os.path, both of which contain useful functions that help in dealing with files, folders and paths:
    • os.listdir() returns a list of the contents of a directory/folder. If no path is given as an argument, it lists the contents of the current directory. 
    • os.path.isfile(filename) checks whether a given object or entity is a file
    • os.path.isdir(filename) checks whether a given object is a directory
    • os.chdir(path) changes the current working directory to the given path
    • os.getcwd() returns the path of the current working directory

Sunday, 15 October 2017

Combining Sets and Dictionaries

In a previous post I mentioned that you can convert lists and tuples into sets. I didn't mention dictionaries, because I wasn't sure if you could.
The answer seems to be "Not directly. But there are work-arounds".

Here is a script (with heavy commenting) that I wrote to see if I could use sets to do set-like operations on dictionaries.
#!/usr/bin/python3
dic_alpha = {1:'Alice', 2:'Bob', 3:'Charlie', 4:'Dave', 5:'Eric'}
dic_beta = {4:'Dave', 5:'Eric', 6:'Fiona', 7:'George', 8:'Hermione'}
set_alpha = set(dic_alpha.keys()) # creates a set from the keys of dic_alpha
print ('set_alpha is', set_alpha)
set_beta = set(dic_beta.keys())
print ('set_beta is', set_beta)
set_delta = set_alpha & set_beta # set_delta is the intersection
dic_gamma = dic_alpha
dic_gamma.update(dic_beta) # dic_gamma is a union of both dictionaries
print ('dic_gamma', dic_gamma)
dic_delta = {} # creates new empty dictionary
for key in set_delta:
    dic_delta[key] = dic_gamma[key] # creates new element in dic_delta
    #using dic_gamma to find the values for each key
print ('dic_delta is now', dic_delta) # dic_delta is now the intersection
#of the two dictionaries dic_alpha and dic_beta
 And here is the output:
 RESTART: C:/Users/John/Dropbox/Misc Programming/Python/python3/test08_dict_set.py
set_alpha is {1, 2, 3, 4, 5}
set_beta is {4, 5, 6, 7, 8}
dic_gamma {1: 'Alice', 2: 'Bob', 3: 'Charlie', 4: 'Dave', 5: 'Eric', 6: 'Fiona', 7: 'George', 8: 'Hermione'}
dic_delta <class 'dict'>
dic_delta is now {4: 'Dave', 5: 'Eric'}

>>>

 To be honest, I don't know if there is a cleaner, more efficient way of doing this.
Actually, if you just want the intersection and you are not interested in more in-depth set operations, this is a simpler way:

#!/usr/bin/python3
dic_alpha = {1:'Alice', 2:'Bob', 3:'Charlie', 4:'Dave', 5:'Eric'}
dic_beta = {4:'Dave', 5:'Eric', 6:'Fiona', 7:'George', 8:'Hermione'}

dic_delta = {}
for elem in dic_alpha:
    if elem in dic_beta:
        dic_delta[elem] = dic_alpha[elem]
print ('dic_delta is now', dic_delta)

and this does not actually use sets at all. It simply checks if any key in the first dictionary is also in the second dictionary. If so, the key and its corresponding value are added to the new dictionary, dic_delta.

One thing to be wary of here is that both scripts only check if keys are the same. It does not compare values. This means if the two dictionaries have the some of the same keys but those keys hold different values, you need to be careful which values you end up with.
In the first script the lines
dic_gamma = dic_alpha
dic_gamma.update(dic_beta) 
means that if any keys are shared between dic_alpha and dic_beta, the values from dic_beta will overwrite the ones in dic_gamma, inherited from dic_alpha
Whereas with the second one, the values are simply referenced from dic_alpha, as in the line
dic_delta[elem] = dic_alpha[elem]

Tuesday, 10 October 2017

Sets - unsorted collections

Sets are a form of data type. Like tuples, lists and dictionaries, sets are collections. However, unlike lists and tuples, sets are not ordered. The important thing is that each element in a set is unique. If you try to add multiple copies of an element to a set, only one is kept. This has its uses. For example, on the Python command line:

>>> simpleset  = {'a', 'b', 'c', 'd'}
>>> type(simpleset)
<class 'set'>
>>> print (simpleset)
{'c', 'd', 'a', 'b'}

This shows a number of things.
When creating a new set with elements in it, you use curly brackets {} (a.k.a. braces) like with dictionaries.
Elements are separated with commas.
>>> newset = simpleset | {'d', 'e', 'f', 'g'}
>>> print (newset)
{'g', 'a', 'b', 'f', 'c', 'd', 'e'}
>>>


Unlike strings, lists and tuples, you cannot use + to concatenate sets. Instead you use | for a union of two sets. This will return all the elements found either in one set or the other. As you can see in newset, the elements that are common to both sets are only found once in newset.
As sets are unordered, the elements will be printed out in any order, apparently randomly shuffled.

As well as union, there are other operations that can be performed on sets. For example:
>>> nextset = {'f', 'g', 'h', 'i', 'j'}
>>> print (newset)
{'g', 'a', 'b', 'f', 'c', 'd', 'e'}
>>> print (newset & nextset)
{'f', 'g'}
>>>
 & gives the intersection of two sets, i.e. the elements that occur in both of them.

To find the elements that are in one set or the other but not in both (i.e. the opposite of intersection), you find the symmetrical difference, using ^
>>> print (newset)
{'g', 'a', 'b', 'f', 'c', 'd', 'e'}
>>> print (nextset)
{'j', 'g', 'i', 'f', 'h'}
>>> print (newset ^ nextset)
{'c', 'j', 'a', 'i', 'b', 'd', 'h', 'e'}
>>>
or if you want the elements that are in just one set but not another, you can use - for difference. 
>>> print (newset - nextset)
{'c', 'a', 'b', 'd', 'e'}
>>>
 Although I am using strings in this simple example, like other collections (lists, tuples and dictionaries), sets can have other data types as their elements. 

If they are unordered , then why use sets rather than lists?
The simple answer is that they are useful when you just want one copy of each value in the collection - i.e. removing duplicates.
And you can use the built-in function set() to convert other collections into lists.
For example, you have a list of email addresses compiled from senders in your inbox. You want them in alphabetical order but also want to remove duplicates
>>> emaillist = ['alice@python.com', 'eric@python.com', 'charlie@python.com', 'bob@python.com', 'alice@python.com', 'dave@python.com', 'bob@python.com', 'bob@python.com', 'dave@python.com']
>>> print (type(emaillist))
<class 'list'>
>>> emailset = set(emaillist)
>>> print (type(emailset))
<class 'set'>
>>> print (emailset)
{'eric@python.com', 'dave@python.com', 'bob@python.com', 'charlie@python.com', 'alice@python.com'}
>>> emaillist = list(emailset)
>>> emaillist.sort()
>>> print (emaillist)
['alice@python.com', 'bob@python.com', 'charlie@python.com', 'dave@python.com', 'eric@python.com']
>>>
 Thus we can convert to a set using the set() function to remove duplicates, then convert back to list using the list() function to then sort into order. You can also convert between tuples and sets.

Sunday, 8 October 2017

Dictionaries

Dictionaries in Python are collections of elements, each with an identifying part called the key and something associated with that key, called the value.
The key is unique within each dictionary and identifies the element much as the index identifies each element of a list, string or tuple. The value need not be unique.

Dictionaries are a data type, like tuples and lists, and as such can be assigned to variables, and you can go through them with a for loop. A quick script as an example:

#!/usr/bin/python3
def main():
    meals = {'breakfast': 'cereal', 'elevenses': 'tea & digestives', 'lunch':'sausages & pasta', 'teatime': 'Earl Grey & cookies', 'Supper': 'Beef stew & mash'}
    for z in meals:
        print ('For', z, 'I had', meals[z])

main()


gives the output

 RESTART: C:/Users/John/Dropbox/Misc Programming/Python/python3/test08_dicttest.py
For breakfast I had cereal
For elevenses I had tea & digestives
For lunch I had sausages & pasta
For teatime I had Earl Grey & cookies
For Supper I had Beef stew & mash

>>>


So here meals is the variable that refers to the whole dictionary, which in code is enclosed in curly brackets or braces {}, as opposed to square brackets [] for lists and round brackets () for tuples.
There are five elements, not ten, in this particular dictionary - each element consists of a key (here the name of the meal) and the value (what the meal consisted of). I have paired them up using colons between the key and value for each element, and separating each element with a comma. Technically you can use other punctuation to do the same job, but I find this style easy to read.
The print() statement shows firstly that the for loop picks up the key (here z) and to get the corresponding value you use meal[z], which effectively asks Python 'What value does the key z hold in the dictionary meals?'

Here we have been using strings for both keys and values, but other data types can be used for either. This is a longer program that actually uses two user-defined functions:

#!/usr/bin/python3
staffdict = {
  'CEO':['Chief Executive Officer', 150000, 'Alice', 'Adams'],
  'CFO':['Chief Finance Officer', 120000, 'Bob', 'Banner'],
  'CS':['Company Secretary', 70000, 'Charlie', 'Chadwick'],
  'CIO':['Chief Information Officer', 90000, 'Donald', 'Davis'],
  'SMO':['Senior Marketing Officer', 90000, 'Ellen', 'Edwards'],
  'HOP':['Head of Personnel', 90000, 'Fred', 'Farmer']}


def display(post):
    print('Abbreviated Post:', post)
    print('Full title:', staffdict[post][0])
    print('Salary: £', staffdict[post][1])
    print('First name:', staffdict[post][2])
    print('Last name:', staffdict[post][3])
   
def main():
    print('Omnicorp senior personnel')
    stafflist = list(staffdict.keys())
    print (stafflist)
    print('Please enter post you wish to view (or just enter to quit)')
    post = 'x'
    while post != '':
        post = input('?:')
        post = post.upper()
        if post in stafflist:
            display(post)
        elif post == '':
            print('Quitting program')
            break
        else:
            print('Sorry, post not recognised')
       

main()

And the output is:
>>>
 RESTART: C:/Users/John/Dropbox/Misc Programming/Python/python3/test08_dicttest2.py
Omnicorp senior personnel
['CEO', 'CFO', 'CS', 'CIO', 'SMO', 'HOP']
Please enter post you wish to view (or just enter to quit)
?:
ceo
Abbreviated Post: CEO
Full title: Chief Executive Officer
Salary: £ 150000
First name: Alice
Last name: Adams
?:
smo
Abbreviated Post: SMO
Full title: Senior Marketing Officer
Salary: £ 90000
First name: Ellen
Last name: Edwards
?:
Quitting program

>>>


Okay, so the dictionary here is called staffdict, and the keys are strings, and the values are lists (with each index functioning as a column in a table).
Near the start of the main() there is the line
stafflist = list(staffdict.keys())
This creates a list (here called stafflist) of all the keys in the dictionary staffdict. This can be useful, such as for sorting (you cannot sort a dictionary directly, but you can sort a list of its keys).
The main() function is mostly a while loop.
As long as the user enters a valid key (checked by if post in stafflist:), it will call the function display(), with the key (here using the variable post) as the argument. This in turn will look up the value for that key and then present the contents of that value. 

Saturday, 7 October 2017

Homemade (User Defined) Functions

Functions can be described as a discrete set of instructions that perform a task and can be called using the function name. Think of them as miniature programs within a program. In QBasic they are known as subroutines.
I have already mentioned a number of functions:
  • There are built-in functions that are always available in Python, such as print(), sort() and int()
  • Then there are functions found in imported modules, such as random.choice() and os.getcwd()

It is relatively easy to create your own function - technically they shouldn't be described as homemade, but as user-defined functions. At its most basic, a function in a script might look like:

#!/usr/bin/python3
def hello():
    print ("Hello World!")

hello()

The second line def hello(): tells Python this is how we define the function hello(). Generally definitions of functions are at the start of the code as Python prefers things to be defined before it uses them (that also includes new variables).
The indented part after the def statement (here print ("Hello World") ) is the instructions the function carries out.
The bottom line hello() is not part of the function but is where the program calls (i.e. uses) the new function hello(), just as when we use print("Hello") we are calling the print() function.

Brackets and Arguments

The brackets are where you can insert any arguments, i.e. pieces of information the function may act on. So for the built-in function print("Hello World"), the string "Hello World" is the argument that the function uses.
For some functions, arguments are not always necessary - they can be called with empty brackets. What arguments should be supplied is dependent on what the function is. Some functions, such as range(), can accept 1, 2 or 3 arguments, each one having a different meaning.
However, having brackets after the function name tells Python this is a function, not a variable.
Here is a modified version of the script that accepts a single string argument (here in the variable name):
#!/usr/bin/python3
def hello(name):
    print ("Hello", name)

name = input("What is your name? ")
hello(name)

And the result is:
 RESTART: C:/Users/John/Dropbox/Misc Programming/Python/python3/test07_simplefunction2.py
What is your name? John
Hello John

>>>


Function names

Function names follow similar rules to variable names (no punctuation except underscore _ and cannot start with numbers, nor should it be the same as a keyword). And whatever the name is, you should follow it with brackets () to show it is a function, not a variable.
Technically you can overwrite an existing function by using the same name, but I am very cautious about this. I would much rather use a similar but different name so the original function is still useable.

Why create more functions?

So why bother with functions? Up to this point my programs have been short and simple enough that I have not needed to use user-defined functions. But there are good reasons for using them.
  • Firstly reuse within a single program. By calling a function, you don't have to re-enter all the instructions the function carries out. This is useful when you want to do the same sort of task several times but it doesn't fit the neat repetition of a for or while loop.
  • Secondly reuse between different programs. A function is defined in a single block of code that can be copied and pasted from one script to another or run/called from a different file.
  • Thirdly ease of maintenance. If there is something that needs to be changed in the function, you only need to change it in one place. If you have simply copied and pasted the lines of code that could have been a function, you need to find and change each occurrence of the code in question.
  • Fourthly the principle of modularity: Modularity is the idea of discrete pieces of code that have a particular input and a particular output. A programmer using the piece of code only needs to know the input and the output - they don't need to know the inner workings. For example, the function print() accepts the input of a string or something that can be converted to a string, and the output is that the string appears on the command line. You don't need to know exactly how print() does that, you just know what it does. Functions allow that and therefore keep programming relatively simple and abstract. Assuming they are well-written and bug-free, User-defined functions can be just as useful in this regard as built-in or imported functions. 

Friday, 6 October 2017

A Script for Sorting a File

This is based on a short script I have used a number of times, but I've modified it to allow for files outside the current directory. The simple version does not actually split lines up - it simply treats each line as a string and the contents of the whole file as a list of strings (thus if the file is full of numbers, they will be sorted ASCIIbetically, not numerically).

A word of warning - this program changes files in a way that is not easily reversible, so be careful which files you use it on. 

It includes a number of imported modules I have not discussed yet - sys and os.path (part of the os module). Here is the actual code:
#!/usr/bin/python3.5
import os.path
import os
import sys
print ("Program to sort the contents of a simple file")
print ("Current Working Directory is: ")
cwd = os.getcwd()
print (cwd)
pathchoice = input("Keep current directory (Y or N)?")
pathchoice = pathchoice.lower()
if pathchoice == "y":
    print ("Okay, looking for file in", cwd)
    print ("Please enter the name of the file to be sorted including extension: ")
    filename = input("? ")
    if os.path.isfile(filename): # if file exists
        fhandle = open(filename, 'r') # then open it up for reading
    else:
        print ("File not found. ")
        sys.exit() # Quits Python
else:
    print ("Please enter new absolute filepath. Remember to add extra backslashes as necessary")
    newpath = input("? ")
    if os.path.isdir(newpath): # checks if newpath is a directory/folder
        os.chdir(newpath) # changes to newpath
        print ("Please enter the name of the file to be sorted including extension: ")
        filename = input("? ")
        if os.path.isfile(filename): # if file exists
            fhandle = open(filename, 'r') # then open it up for reading
        else:
            print ("File not found. ")
            sys.exit() # Quits Python
    elif os.path.isfile(newpath):
        print ("File chosen")
        filename = newpath
        fhandle = open(filename, 'r')
    else:
        print ("Sorry, neither file nor folder found")
        sys.exit()
       
linelist = []
linecount = 0
for line in fhandle:
    linecount +=1
    linelist.append(line)
linelist.sort()
fhandle.close()
for line in linelist:
    print (line, end="") # prints line but doesn't add another newline
print ("number of lines: " + str(linecount))
fhandle = open(filename, 'w') # opens up same file as before but for writing
for line in linelist:
    fhandle.write(line)
fhandle.close()

 So there's a few things in here I feel I ought to explain
cwd = os.getcwd()
I encountered this in a previous post. It uses the getcwd() function in the os module to return the current working directory. Here it is then assigned to the variable cwd.

os.path.isfile(filename)
os.path.isdir(newpath)
These two check whether a given file is a file or if it is a directory and returns a Boolean (True/False) answer which can be used in the if...elif...else structure.

sys.exit() # Quits Python
Perhaps a bit drastic but it works. From what I can tell, it quits the program but not the Python shell, so it is easy enough to run the program again.
linelist = []
linecount = 0
for line in fhandle:
    linecount +=1
    linelist.append(line)

linelist.sort()
This is the heart of the script where it starts with an empty list (linelist) and reads each line in the file object fhandle, and appends the line onto the end of the list, while keeping track of the number of lines read/appended.
It then sorts the list.

print (line, end="") # prints line but doesn't add another newline
This gets around the quirk that the lines being read from the file already have a newline \n at the end, but print usually adds a newline after printing a string. This end="" tells print() to not add anything after the string, so when it prints, there is only one newline per line. 

fhandle = open(filename, 'w') # opens up same file as before but for writing
for line in linelist:
    fhandle.write(line)
fhandle.close()
It might seem unnecessary to open the file to read it, close it, then open it again for writing. There are two reasons why I have done this. 
Firstly if there is something wrong in the earlier part of the program and it quits early or throws an error, the file is not changed. 
Secondly as a Python program progresses through a file it has an imaginary cursor that generally keeps moving forward. Closing then reopening a file resets this progress point back to the start of the file. 

Thursday, 5 October 2017

CSV files and split()

A lot of data is in the form of tables, particularly regular tables that can be stored as CSV (Comma Separated Values) files.
As I explained in earlier posts, reading from files can be done one line at a time - this is effectively one table row at a time. And the columns are separated by commas or whatever punctuation. As long as there are a fixed number of commas, the columns can be distinguished. We can use the split() function to convert each line (which is read from the file as a string) into a list.

For example, here is the contents of a typical CSV file called groceries.csv .
Item Name, Price per Item, Number of Items
Butter (250g), 1.50, 2,
Flour (1kg), 0.50, 3
Milk (1 pint), 0.70, 5
Rice (1kg), 1.70, 1
Strawberry Jam (400g), 2.00, 1
Chocolate Biscuits (300g), 1.50, 2
Pork Sausages (500g), 4.50, 1

 And here is a script I have written to use the data in it:
#!/usr/bin/python3
fhandle = open('groceries.csv', 'r') # opens relevant file for reading
biglist = []  # Creates empty list
linecount = 0 # sets linecount variable
totalcost = 0.0
itemtotal = 0
for line in fhandle:
    if linecount > 0:
        linelist = line.split(',') # Splits each line
        linelist[1] = float(linelist[1])
        linelist[2] = int(linelist[2])
        subtotal = linelist[1] * linelist[2]
        totalcost += subtotal
        itemtotal += linelist[2]
        linelist.append(subtotal)
        print (linelist[0])
        print ('per item', linelist[1], 'no of items', linelist[2], 'subtotal', subtotal)
    linecount += 1
fhandle.close()
print ('Number of rows = ', linecount)
print ('Number of items = ', itemtotal)
print ('Total cost = £', totalcost)

I have used a few comments to explain to myself and anyone else reading the code what I am doing.
 And the results are:
>>>
 RESTART: C:/Users/John/Dropbox/Misc Programming/Python/python3/groceries01.py
Butter (250g)
per item 1.5 no of items 2 subtotal 3.0
Flour (1kg)
per item 0.5 no of items 3 subtotal 1.5
Milk (1 pint)
per item 0.7 no of items 5 subtotal 3.5
Rice (1kg)
per item 1.7 no of items 1 subtotal 1.7
Strawberry Jam (400g)
per item 2.0 no of items 1 subtotal 2.0
Chocolate Biscuits (300g)
per item 1.5 no of items 2 subtotal 3.0
Pork Sausages (500g)
per item 4.5 no of items 1 subtotal 4.5
Number of rows =  8
Number of items =  15
Total cost = £ 19.2

>>>
 
The format could do with some polishing up to make it look more like a table. But the point is we can read from a file and use split to turn it into a series of columns, and thus use each column for maths.
You might wonder "why not just use a spreadsheet?"
At this level of only simple arithmetic with the data, yes a spreadsheet could do the job. But Python is capable of doing so much more with the data than a spreadsheet.

 Actually, I have adjusted the output so that it looks more like a table. Here is the code, the first half being unaltered except for comments:
#!/usr/bin/python3
fhandle = open('groceries.csv', 'r') # opens relevant file for reading
biglist = []  # Creates empty list
linecount = 0 # sets linecount variable
totalcost = 0.0
itemtotal = 0
for line in fhandle:
    if linecount > 0: # Does not do this on linecount = 0 i.e. first line
        linelist = line.split(',') # Splits each line at each comma
        linelist[1] = float(linelist[1]) # converts second element into float
        linelist[2] = int(linelist[2]) # converts third element into integer
        subtotal = linelist[1] * linelist[2]
        totalcost += subtotal # adds subtotal variable to totalcost variable
        itemtotal += linelist[2]
        linelist.append(subtotal)
        space = ' ' * (25 - len(linelist[0])) # calculates space for formatting
        print (linelist[0], space, '£', linelist[1], 'X', linelist[2], '= £', subtotal)
 
    linecount += 1
   
fhandle.close()
print ('Number of rows = ', linecount)
print ('Number of items = ', itemtotal)
print ('Total cost = £', totalcost)

 And here is the output:
>>>
 RESTART: C:/Users/John/Dropbox/Misc Programming/Python/python3/groceries01.py

Butter (250g)              £ 1.5 X 2 = £ 3.0
Flour (1kg)                £ 0.5 X 3 = £ 1.5
Milk (1 pint)              £ 0.7 X 5 = £ 3.5
Rice (1kg)                 £ 1.7 X 1 = £ 1.7
Strawberry Jam (400g)      £ 2.0 X 1 = £ 2.0
Chocolate Biscuits (300g)  £ 1.5 X 2 = £ 3.0
Pork Sausages (500g)       £ 4.5 X 1 = £ 4.5
Number of rows =  8
Number of items =  15
Total cost = £ 19.2

>>>

Wednesday, 4 October 2017

Comments and Readability

Most folks who deal with computers would agree that the top priority for a computer program is that the computer can run it. I think of this as computer readability. If the Python interpreter can't read a program, it won't run and will throw an error, usually a syntax error.

In the past I have assumed this is the be-all and end-all of readability - as long as Python understands the program nothing else needs to understand the program. As I am not yet a professional programmer, so far (until I started this blog) I was the only one who had to read the programs I wrote.

Problems arose when I had written a program that was either quite complicated (at least by my standards) or involved something new that I had recently discovered in a textbook or web forum. I would be okay writing it (maybe after some trial, error and debugging) and while this process was going on I would be aware of the concepts I was trying to implement.
But if I came back to the program a week or a month later and looked at what I had done, the techniques, structures and functions I had been juggling in my head had by then faded away. I had to look at my code and work out what I had done.
Even if nobody else was looking at my code, I realised that human readability was useful for my own understanding of my own programs. I have borrowed a number of techniques that have helped me with my own programs and I hope will allow others to follow my coding more easily.
  • Don't be afraid to comment as often as you want to. Although different programmers will have different ideas of how much comment is appropriate there have been a few of my programs where there has been more comment than Python-readable code. Some may say that's too much, but if I'm the only one reading the code, then it's up to me. 
  • As I have become more familiar (fluent?) with Python, there are a number of basic expressions that I don't need to comment on - I am more likely to use comments on or around lines that I might struggle with. I may also comment on the bigger picture of how different parts of the program hang together, particularly when it is not obvious from the code itself. Sometimes the comments are not so much what I am doing in the code so much as why I am doing it. 
  • Appropriate and descriptive names. Currently the main names I have used on this blog are variables, though later on there will be functions and classes that the programmer gives names to. Although typing out long names is a bit more tiresome than short ones (and there is an increased chance of spelling errors), being given a clue what the name represents makes programming a lot clearer for me and makes logical errors less likely. In professional circles this is known as self-documenting code. Of course, it's sensible not to have names too long (whether variable, function or class) and if you want you can keep it short and sweet, with a comment at the start or when the name is defined/initialised to explain what it is. 
  • If a line of code starts looking so long, complicated and convoluted I am no longer sure what it means, I will try to split it up into two or more simpler lines. If that means I have to create a new variable to pass a value on from one line to another, so be it. I know some programmers try to get as much code onto a single line as possible, with expressions nested inside other expressions. That's okay up to a point. But it becomes like a sentence in English that is a paragraph long, full of phrases, conjunctions, transitions and structures. It's easier just to add a few full stops and split it into shorter sentences. 
  • Superfluous and unnecessary code should be eliminated. I have sometimes used the comment hash # to temporarily disable lines of code while I am debugging or experimenting. This is fine in the short term, but when I have decided that line really shouldn't be there I delete it rather than leave it there hashed out. Similarly if possible I will implement things directly rather than in a round-about way, keeping it to as few lines as feasible (while bearing in mind the point above this one). 
In companies and organisations where code is shared and maintained by lots of people, readability becomes very important. Although I have not yet needed to deal with the matter directly, I have heard that companies often have their own style guides saying how code should be made readable in a consistent way.

A lot of programmers using other languages will say that indentation is important for human readability. As we have seen, in Python it is necessary for computer readability - your program won't work (or at least not as expected) if you don't get indentation right according to the Python interpreter. This has the effect (I'm sure it is intentional) of making Python code easier to read.