Inventing programming with Python

A draft of a Royal Institution masterclass to “invent” core programming concepts through looking at code.

Often, when people are taught programming for the first time, what they really get is basic syntax of a particular language. For example, there is a wealth of great hands-on intro tutorials on Python. The problem is that just showing people how to make a “for” loop to print numbers from 1 to 10 doesn’t explain why Python loops are written this way or how this is relevant or useful for real-world problems. I tried to address this issue by showing the reasoning behind some of the core concepts, rather than the rules of manipulating them. If you are thinking of ways to introduce a group of people to programming, maybe flicking through this could be useful.

Following are the notes outlining a very interactive masterclass/workshop session for a small audience with no programming experience.


We all know programming is very useful in making cool stuff. But what is programming really?

Q: What is programming?

A: Making computers compute things.

But is it really just that? You are going to be faced with two different types of programming problems in your life:

  1. School problems: small riddles you solve once “on the spot”
  2. Real problems: problems you solve for at least several days, maybe in a team

All the cool stuff, like programming robots or making websites belongs in the second category — you always want to improve whatever you are building, so you are coming back to it. All powerful programming languages, including Python, are built for solving real problems.

Q: Who worked on one programming task for more than a day?

Q: What can be a difference in code you write for type 1 and 2 problems?

A: When solving real problems, you need code to be understandable and extensible. These are the basic principles that drive all real-world code, once you understand them, programming languages are going to make much more sense! Also, you are going to better at solving real-world problems.

Note The class is meant to be very interactive. Students would submit code for review, and then we would discuss it all together. The goal is make sure students understand what every piece of demonstrated code does.


Problem 1. Make a program that computes squares of first three prime numbers

Solution 1.1:

print(2*2)
print(3*3)
print(5*5)
4
9
25

That works. Here we have a sequence of commands we call code, telling the computer to print things.

Q: Our job is done — this program makes the computer compute what we wanted. Right?

A: This code is not the best code we could write. There are multiple ways to make a computer do something, and some ways are better than others.

Simple code and data

Computers would rather read binary code they can execute in their processors.

Solution 1.2:

0000 0010 0000 0011 0000 0101 1111 1111 1010 1000 1110 1001 0101 0000 1010 1010 0010 1001 0010 0010 1010 0100

4
9
25

This program gives the right output too. The first three bytes are representations of 2, 3 and 5 — this is data. The rest could be binary code telling a computer to:

  1. Scan memory for numbers until it sees 1111 1111
  2. Square every number it sees and store them in place of the old number
  3. Scan memory for numbers and print them until it sees 1111 1111

Q: What if we want to modify this code to print squares of first four prime numbers?

A: 0000 0010 0000 0011 0000 0101 0000 0111 1111 1111 1010 1000 1110 1001 0101 0000 1010 1010 0010 1001 0010 0010 1010 0100

Q: What if we need first ten? Binary is hard to read and it is easy to make a mistake.

People invented programming languages to make computer programs easy to work with, so they could be changed and re-used. Computer programs are written for people. Hence, the goal of a programmer in any language (except for binary) is not only to make a computer do something, but to do it in a most understandable way. If you want to just make a computer do something, googling the solution works in most cases :)

Python has an amazing ecosystem and libraries that allow you to do so many things! Once you understand the logic of their code and how to work with it, you can start using it and easily start building things you consider complicated now. This is why real-life programming is important to understand.

Lists

There are a few ideas about how to make things easier to understand, in programming and in general, all of them very intuitive. Consider this list:

  1. Buy a can of tomatoes
  2. Buy a lemon
  3. Buy a loaf of bread

It is clear that it is a shopping list and it would be better to write it as:

Buy: <- action

  1. Can of tomatoes <- a list of objects
  2. Lemon
  3. Loaf of bread

In programming a similar idea is called Don’t repear yourself (DRY).

Solution 1.3:

for x in [2, 3, 5]: # <- a list of objects
print(x * x) # <- action
4
9
25

To avoid repetition we separated data from code. data: In Python [2, 3, 5] is called a list. code: this is for loop, it is doing exactly the same thing as

Solution 1.4:

x = 2
print(x * x)
x = 3
print(x * x)
x = 5
print(x * x)
4
9
25

Data

You can give lists names, just like you do with numerical variables. To know their length you can use a function len. By giving them a name you can show what the list means.

Solution 1.5:

primes = [2, 3, 5]
for x in primes:
print(x * x)
print()
print
(len(primes))
4
9
25
3

So in Solution 1.3 we just invented a data structure (list) and a code expression (for loop) to make it DRY.

Q: How would you now print out the first five prime numbers squared? Which code is easier to adapt?

Conclusion. Programming languages are similar to normal languages in a way they are there to express ideas for others to understand. We can invent constructions in a language that help us express ourselves better.

Problem 2. The school math Olympiad had 20 problems, out of which John solved 12, Jack solved 16 and Jane solved 17. Pupils get into the next round if they solved at least 13 problems. Pupils can enter their name into the computer and it is stored in variable name. Print 'YES' if they are getting into the next round, and 'NO' otherwise.

Solution 2.1:

name = 'Jack'
if name == 'John':
print('NO')
if name == 'Jack':
print('YES')
if name == 'Jane':
print('YES')
YES

To take actions based on a condition, we invented a code expression called if clause.

Q: What’s the problem with this code?

Q: What if there is a new student Joe who scored 20? How would you adapt this code?

A: This code is not easy to adapt, because it doesn’t show the logic behind who to print ‘YES’ to. We should include inferring ‘YES’ or ‘NO’ from a pupil’s score.

Solution 2.2:

if name == 'John':
score = 12
if name == 'Jack':
score = 16
if name == 'Jane':
score = 17

if score >= 13:
print('YES')
else:
print('NO')
YES

Dictionary

Q: How do you now adapt the code to account for Joe?

A: You have to repeat two lines again, changing name to Joe and score to 20. The top block mostly contains data, let’s invent a data structure to store mappings between things — a dictionary.

key → value

scores = {
'John': 12,
'Jack': 16,
'Jane': 17
}

print(scores)
print()
print(scores['John'])
{'Jane': 17, 'John': 12, 'Jack': 16}

12

Q: How do you now adjust the code for Joe?

A:

scores['Joe'] = 20

print(scores)

{'Jane': 17, 'John': 12, 'Jack': 16, 'Joe': 20}

You can get keys and values separately as lists:

scores.keys()

['Jane', 'John', 'Jack']

scores.values()

[17, 12, 16]

Q: How do you now solve the problem with the dictionary?

Solution 2.3:

scores = {
'John': 12,
'Jack': 16,
'Jane': 17
}

if scores[name] >= 13:
print('YES')
else:
print('NO')
YES

To be very short you can also use a so-called ternary operator (composed of three parts) to compress the if-else statement:

Solution 2.4:

print('YES' if scores[name] >= 13 else 'NO')
YES
Recap of everything we invented this far

Code

We invented lists and dictionaries to organize data. Let’s now invent functions — to organize code expressions:

arguments (variable 1, …, variable N) → [function] → value

Most functionality in programming languages comes in functions. They some arguments as input and produce output. You can call them using by passing an argument list to them in parenthesis.

For example, function sum takes a list of numbers as in input and returns their sum.

A:

sum([1, 2, 3, 4, 5])
15

Q: What is an argument here? What is a return value?

Problem 3. Same as Problem 2, but new rules: instead of solving 13 problems to get into the next rounds, each pupil needs to solve at least one problem more than an average result.

How would you change the code?

Solution 3.1:

scores = {
'John': 12,
'Jack': 16,
'Jane': 17
}
name = 'Jack'
if scores[name] - sum(scores.values()) / len(scores.values()) >= 1:
print('YES')
else:
print('NO')
YES

Q: What is the problem with this code?

A: Our previous code read pretty easily: scores[name] >= 13 - if the score of pupil with this name is bigger or equal to thirteen. The new one is confusing, we want it to read naturally. Just like we have a name to a piece of data, we can give names to pieces of code by defining functions.

Solution 3.2:

scores = {
'John': 12,
'Jack': 16,
'Jane': 17
}
name = 'Jack'
def average(numbers):
sum_of_numbers = sum(numbers)
count = len(numbers)
return sum_of_numbers / count

if scores[name] - average(scores.values()) >= 1:
print('YES')
else:
print('NO')
YES

Notice how we can use whatever the names we want when defining functions. When writing a function you can think only about how to use input arguments to get output. This way code expresses what you are doing better.

With such a flexible tool as functions we can get code to human language as we want, for example:

Solution 3.3:

scores = {
'John': 12,
'Jack': 16,
'Jane': 17
}
name = 'Jack'
def average(numbers):
sum_of_numbers = sum(numbers)
count = len(numbers)
return sum_of_numbers / count
def problems_solved_more_than_average(name, all_scores):
score_for_name = all_scores[name]
score_average = average(all_scores.values())
return score_for_name - score_average

if problems_solved_more_than_average(name, scores) >= 1:
print ('YES')
else:
print ('NO')
YES

Q: How do we decide which parts of code to define as functions?

Q: Which part of code / logic changed compared to Problem 2?

A: How we decide on who gets into the next round based on their score.

Solution 3.4:

scores = {
'John': 12,
'Jack': 16,
'Jane': 17
}

name = 'Jack'

def average(numbers):
sum_of_numbers = sum(numbers)
count = len(numbers)
return sum_of_numbers / count

def passed_to_next_round(name, all_scores):
score_for_name = all_scores[name]
score_average = average(all_scores.values())
return score_for_name - score_average >= 1

if passed_to_next_round(name, scores):
print('YES')
else:
print('NO')
YES

Notice how our code is now longer than the initial solution, but more understandable.

Bonus points. Remember ternary operator? :)

scores = {
'John': 12,
'Jack': 16,
'Jane': 17
}

name = 'Jack'

def average(numbers):
sum_of_numbers = sum(numbers)
count = len(numbers)
return sum_of_numbers / count

def passed_to_next_round(name, all_scores):
score_for_name = all_scores[name]
score_average = average(all_scores.values())
return score_for_name - score_average >= 1

print('YES' if passed_to_next_round(name, scores) else 'NO')
YES

Before, we used structures like lists and dictionaries to easily store and edit data.

Functions allow us to isolate pieces of code, so complicated computations read easier. Now you can look over this code quickly without knowing exactly what the criterion for passing is. This is called abstraction, when you abstract from details to be able to only look at what you need to know.

Summary

Code is written for people to read and change. To make it easy to read and change, we use data structures (like lists or dictionaries) and code expressions (like for loops, if-else statements or functions like “len”). We also define our own functions to try and make code so easy to understand it almost reads like human language.


A natural next step after this workshop would be to introduce classes:

built-in function (len, sum)          ⟷ functions (def …)
built-in data structures (list, dict) ⟷ classes (class …)

Nevertheless, I imagine classes may not be necessary for a lot of applications people have in mind when starting to code.

Please feel free to contact me if you have any corrections or suggestions!

P. S. Medium requires a title picture for every article, so here is a blurry pavement from Sintra, Portugal