Just recently I volunteered to do a pre-101 kinda workshop for people wanting to learn programming. I had done this a few times in the past, but in different settings and goals in mind. The whole structure predates the sessions but I can’t remember when I first created them.
TL;dr the video of the workshop is posted below.
So learning to program is not hard, and based on my experience the best language to start from is Golang due to its simplicity (while I don’t like the language for its limited ability to express ideas in a more functional form, but it is a great language for a beginner). One of the company I worked for even once made everyone (both dev and non-devs) to pick up the language within a week by just following the tour.
Previous incarnations
The first time I did this was during a friend’s convocation. We met up for the event, and my friends were in a discussion related to their research. So another friend and I who were not joining the discussion got bored, and since we were in a computer lab with Internet access anyway, so I offered to teach her very basic programming while waiting for the rest.
She agreed, and we opened up repl.it and I did my first iteration of this programme with Python. The whole walkthrough took around 30mins.
The second time I did this, was to give an overview of how Rust works to programmers who are new and interested in the language. So the structure is similar but the focus was more on introducing the language.
Back to the workshop I conducted, I chose Python as a delivery language because I recently submitted my statistical analysis work to the organization in the form of Jupyter Notebook. If they are interested in playing with the notebook, it would be nice if they learn a thing or two in Python. Another additional prerequisite, besides CPython, is having poetry installed, as well as a preferred text editor (default choice is vscode but any editor would do, not too obsessed with tools here).
The whole programme is split into 4 parts, namely
- Operations and assignments
- Branching
- Repetitions
- Reuse
You can read this post as a companion piece to the workshop video I linked to the beginning part of this post.
Operations and assignments
First thing first, we shall start by opening up a repl (read -> evaluate -> print -> loop), which is a useful tool to have immediate feedback when starting to learn a new language. (Practically JavaScript is the best as the repl is built into every browser, but would advise against learning that as the first language for sanity purpose).
With poetry installed, just create a new project
$ poetry new --src learn-python
$ cd learn-python
Next start a new repl
$ poetry run python
Poetry was chosen as a prerequisite due to its use of virtual environment. Besides the fact that my submitted work was managed by poetry, it is generally a good practice to contain one’s work in an environment that does not affect other projects, or even the system. While this tutorial is not likely to affect anything, it is still a good habit to get used to.
The --src
flag, on the other hand, is just personal preference, as I like all source code stored within a specific folder.
Now that the repl is started, we can now do crazy things in it. I usually start with arithmetic operations (Things next to >>>
is things we type, and press enter to submit). Notice the result of a computation is returned below the things we typed, after pressing enter.
>>> 1 + 1
2
>>> 2 * 30
60
When we divide numbers, as the delivery language is Python, we could just
>>> 3 / 5
0.6
You might have heard about casting, or having to do some tricks like making one of the operand in this division a decimal number, in order to get 0.6
. But since Python is dynamically typed, it is rather forgiving in this regard. Also we briefly discussed about integer division and the remainder (modulo) operator.
>>> 3 // 5
0
>>> 3 % 5
3
Raising a number to a power with **
>>> 4 ** 2
16
Previously we saw decimal numbers, we can also do some sort of operations to the number, like rounding up to certain decimal places. Notice how it is different from simple arithmetic operations above, we see a name round
followed by parentheses, and another number in it (instead of something like 1 / 3 round 4
). We will see why this is in this form later, but remember the form).
>>> 1 / 3
0.33333333333
>>> round(1 / 3, 4)
0.3333
Besides numerical operations, we also have alphabetical characters (or anything you can type on your keyboard except a few special ones), and when we have a bunch/collection of them together, we call them a string. In Python, they are all enclosed in either matching single quotes '
or a double quotes "
>>> 'c'
'c'
>>> 'hello world'
'hello world'
A quick tip here, if you know your string contains an '
then enclose them with double quotes, otherwise single quotes
>>> "O'Connor"
"O'Connor"
>>> '"best" friend forever'
'"best" friend forever'
Extended read: escape sequence.
Unlike numbers, the quotation marks here is crucial as it denotes the character or string (a collection of characters) is to be treated as such. Without the quotes, they will be treated differently, as Python would think it represents something, or you are calling some language constructs (both of which would be introduced later).
Like numbers, we can perform some operations on it, for instance, joining two strings together (we call this concatenating, or concat for short)
>>> "hello" + " world"
"hello world"
or, turn everything to uppercase. This is the third form of performing an operation, which is somewhat similar to the previous one where we round numbers.
>>> 'hello'.upper()
’HELLO‘
A sidenote, so far after we enter our computer request into the repl, it returns and prints the result. However, this is usually not how most program runs. In order to print things to the screen, we use a print
function, and put the computation within the parentheses.
>>> print(1 + 1)
2
Though it doesn’t behave differently in this environment, but if we don’t print in the actual program we build later, the result of computation will not be shown at all.
Now that we know how to perform computations, how each type of data looks like, now we are interested in storing them. Often times we would have a big computation problem to be performed in steps. Hence, we are interested in storing the result of intermediate steps into something. This is when assignment comes in to play.
So the symbol, or the operator (like +
we saw just now) for assignment is =
. We put a name (begins with an alphabet, and an be any number or underscore of any length) on the left hand side, and a value on the right. The name on the left hand side is often called a variable.
>>> a_number = 20
>>> a_decimal = 0.3
>>> a_character = 'c'
>>> a_string = "Hello world"
This is how we store intermediate result of computation
>>> a_hard_computation = 42 + 0
One might think the symbol is somewhat related to the same symbol in mathematics, however it is not true. For instance, if we want to represent this straight line formula in Python
We would have to break mx
into m * x
, because Python allow more than one character as the name of a variable. Hence, the implicit multiplication symbol between the two needs to be made explicit.
>>> y = m * x + c
A big difference compared to Mathematics is that, everything on the right-hand side needs to be defined in prior. If Python does not know what m
, x
, and/or c
is, it will throw an error complaining they are not defined. Unlike in mathematics, we can leave any of those variable unknown, and solve for them given enough information. In this case if we need to find the value of x
given others are known, we would have to do the algebra work ourselves, such that
>>> x = (y - c) / m
Yes, like Mathematics, we can use parentheses to prioritize evaluation.
The name of a variable can be made with multiple words, as long as they are joined with either an underscore, or difference in case, or just mix everything together
join_multiple_words
: we call this snake casejoinMultipleWords
: we call this camel casejoinmultiplewords
: don’t do this, otherwise I haunt you in your sleep even though Python will never complain
Now that we know how to store value to be used next, we are now interested in moving on to the next bullet point, branching.
Branching
If you have ever read an interactive story book, you will see towards the end of a page you get to choose which page to read next depending on a choice you made.
The most basic way to emulate this kind of behaviour in Python is through an if statement, which has a form that looks like below (things enclosed in angle brackets are placeholders which we want to fill in later)
if <condition>:
<do something>
else:
<do another thing>
Before we talk about what goes into the <condition>
placeholder. Let us revisit what kind of data can we represent in Python. We have
- Integer number
1
,3
,42
- Decimal number
0.3
,3.1412
- Characters
'a'
,'c'
- Strings
'hello'
Besides that we now have a new type, with only two different possible values, True
or False
. These are what we call Booleans (named after George Boole).
Notice so far we see if
, else
, True
, False
, where they actually carry some meaning (we call these reserved keywords). Therefore, we shall not name our variable with these, i.e. we cannot have
>>> if = 42
Now that we know what a boolean is, we should now talk about operations we can do with them. If you heard about logic gates, you may know these operations
>>> True and True
True
>>> True or False
True
>>> False and False
False
All the common boolean operations are available and behaves as expected. We also have not
operator
>>> not False
True
Now on some operations that ends up with a boolean values, usually happens when we compare things. For instance, we want to check if a given number is odd
>>> a_number = 42
>>> a_number % 2 == 1
False
>>> not (a_number % 2 == 0)
False
>>> a_number % 2 != 0
False
The three different form of comparison all does the same thing – checking if a_number
is odd. Notice how we do a non-equal operation, which is !=
, as we cannot easily type the ≠ symbol easily on our keyboard.
Another reminder, an equality operator requires two =
symbols (a rather common mistakes when you program don’t behave as expected).
We also have other inequality operators too (namely less than, less than or equal, greater than, greater than or equal)
>>> a_number = 42
>>> a_number < 42
False
>>> a_number <= 42
True
>>> a_number > 42
False
>>> a_number >= 42
True
Now we know what returns a boolean, we can now insert these computations into the <condition>
placeholder, for instance we have a check for even number
if a_number % 2 == 0:
<do something>
else:
<do another thing>
The <do something>
and <do another thing>
are just a sequence of computation one would want to perform in those two cases. The first sequence would be executed if a_number
is True
according to the condition, otherwise the second sequence is executed.
We can insert a sequence of computation, one line after another to replace the <do something>
and <do another thing>
placeholders, as long as they are all indented. An indentation is the same number of spaces or tabs from the beginning of a line. Indentation has a meaning in Python, and in our case it shows that these two blocks of code belong to the if
statement or the else
clause.
So in our repl, we write them as follows
>>> a_number = 42
>>> if a_number % 2 == 0:
... print("I am happy")
... else:
... print("I am sad")
...
I am happy
Notice only one of the two print operations is done.
Sometimes, we are interested in extending the check further. So if we want to check if a number is 100, we should print other things. This is where we see a new keyword elif
which is a short form of else if
(because programmers are lazy). As a rule of thumb, we always handle the most specified requirement first.
>>> a_number = 42
>>> if a_number == 100:
... print("I score full marks")
... elif a_number % 2 == 0:
... print("I am happy")
... print(3 * 5)
... else:
... print("I am sad")
...
I am happy
So similarly to a simple if
statement, we can keep chaining new conditions to an existing one, if the first condition fails, we check the second, the third, and if all fails, the sequence of computation in else
clause is executed.
However, we now see elif
is optional, and else
is too. It is perfectly fine to omit the else
part entirely.
>>> a_number = 32
>>> if a_number == 1000:
... print("This will never happen")
... print("not kidding, this will never get printed")
...
Of course, we can also put True
or False
directly as the condition, but often times it is redundant.
>>> if True:
... print('do this regardless')
...
do this regardless
>>> if False:
... print("don't do this ever")
...
don't do this ever
Also we can insert logical operators we mentioned earlier to compose multiple boolean result as condition.
>>> if n % 2 == 0 and n == 100:
... print('do special thing')
Besides chaining multiple conditions, we can also perform another layer of branching within if
, elif
and/or else
.
>>> a_number = 42
>>> if a_number % 2 == 0:
... print('things happen before nested if')
...
... if a_number == 42:
... print('special things to do when number is 42')
... print('things to do after nested if')
things happen before nested if
special things to do when the number is 42
things to do after nested if
Notice I inserted a blank line before another the second if
statement. If is usually done for readability purpose. Otherwise, if everything is stuck together like the third print statement, it can be very hard to see to which part of the code it is associated with (in this case the first if).
Inserting another if-statement within another is what we call nesting. You can also see how indentation plays a role here. In the example, the first and third print statement corresponds to the a_number % 2 == 0
condition, and the second one corresponds to the a_number == 42
condition.
Nested-if statement can happen in the if
part, or elif
part, or even the else
part. Feel free to experiment.
This concludes branching, now we move on to repetitions.
Repetitions
As you get fluent with everything we cover so far, you might come across a need to repeat similar computations, or print similar things to the screen.
You may find yourself doing this (no variation)
>>> print(1)
1
>>> print(1)
1
>>> print(1)
1
>>> print(1)
1
>>> print(1)
1
or (repeating computations with small variation)
>>> print(1)
1
>>> print(2)
2
>>> print(3)
3
>>> print(4)
4
>>> print(5)
5
You bet lazy programmers like us will invent some sort of structure to make our lives easier.
Yes, there is a way to do this easily, with the use of while
loop. Generally, a while loop looks like this
while <condition>:
<do something>
This should somewhat remind you of if
statement we discussed earlier, as it looks very similar. This is how we perform the first task where we print the same thing exactly 5 times.
>>> counter = 0
>>> while counter < 5:
... print(1)
... counter = counter + 1
...
1
1
1
1
1
Before we get to the meaning of the code, there are two things we want to talk about. First on the re-assignment or an update to the counter
variable by referring to itself
counter = counter + 1
So assuming counter
is 0
, when we see this computation instruction for the first time, as I mentioned previously, everything on the right hand side must be known. So replacing the value of counter
on the right hand side
counter = 0 + 1
Now counter is 0 + 1
, which is 1
. This is again, why assignment operator =
is not the same equal sign used in Mathematics.
Now as for why we want a counter
? Remember we want to repeat the procedure exactly 5 times. Therefore, it is crucial to keep track of that somewhere, hence, we made a new variable counter
for this purpose.
Also another sidenote, we programmers are a weird bunch, we usually start counting from 0
. While there are exceptions (some niche languages might start from 1, like R/Mathlab programmers iirc), but for this we just remember to always start counting from 0
.
And the second task can be done as follows
>>> counter = 0
>>> while counter < 5:
... counter = counter + 1
... print(counter)
...
So how did we invent that condition for the loop, why do we keep updating the value of counter
? For this, you can rewrite the thing as follows to see what would happen if we don’t update counter
.
>>> counter = 0
>>> while counter < 5:
... print("hello world")
...
hello world
hello world
hello world
hello world
...
(You can press CTRL+C to halt that endless stream of hello world
printed to your screen, so we can continue)
Congratulations, you have just written your first infinite loop. Now you should somewhat understand why it is crucial to have counter
to update itself before we conclude a while
loop, and how the condition in while
statement ensures we don’t end up in infinite loop.
Just like branching, indentation here means the corresponding computations belongs to the while
condition. So the computation instruction will run one after another, and then go back to check the condition again with updated values.
In the example, after going through the loop for 1 round, the counter
got updated to 1
. When we perform the condition check with the new value of counter
, it would still pass, hence another round of execution. It then repeats until counter
got updated to 5
. Now the condition check returns a False
with this updated value of counter
, hence exits the loop.
So the rule of thumb here is, always remember to update the components involved in the condition check before ending a loop.
Just like if statements, we can nest while
loop(s) in an existing while
loop
while <condition>:
while <another condition>:
<computation>
How it works would be up to your imagination, and another thing you may find more interesting, you can nest an if-statement in it too (or vice-versa).
>>> counter = 0
>>> while counter < 5:
... if counter == 3:
... print('I am a proud 3')
...
... # remember to update counter
... counter = counter + 1
...
I am a proud 3
A new thing you find in the snippet above is the #
symbol. Anything follows the symbol is considered part of a comment, and would be ignored during execution. Us programmers usually drop a lot of those in our program to explain things or to curse co-workers jot notes so other readers can understand the code better.
Next we want to talk about lists. We actually see a variation of lists already, remember we had a bunch/collection of character, which we call string? Yes it is a type of list.
However, when we talk about list, we are usually referring to
>>> [1, 2, 3]
[1, 2, 3]
>>> ['a', 'b', 'c']
['a', 'b', 'c']
>>> [0.2, 0.3]
[0.2, 0.3]
>>> [True, True]
[True, True]
A list is constructed with a pair of square brackets, where each element of the list being separated with a ,
symbol. Also, as Python is rather lenient (dynamically typed language), we can also do
>>> [1, 'a', 3]
[1, 'a', 3]
These are some of the things we can do to lists
>>> [1, 2, 3] + [3, 4]
[1, 2, 3, 3, 4]
>>> len([1, 2, 3])
3
len
is just an operation to find out how many elements in a list.
Why is this useful, you may ask, you can use that in looping too. However, instead of while
, we do for
here.
>>> things = [1, 2, 3]
>>> for item in things:
... print(item)
...
1
2
3
You might see there’s no counter
, no condition
, and the things after for
keyword does not look like a condition at all. What happens here is that, just like a while
loop, after the last computation operation (in this case a print
), we go back to the first line of for
. What it does is that it extract the items in the things
list one after another whenever it is executed. So the first time it is executed, it returns a 1
, followed by 2
in the second run, and finally 3
. The loop will exit once it has no more item to be extracted from the list.
No more keeping track of counter, or remembering to update, yay!
However, you can re-implement the similar thing with while
loop too if you hate your life.
>>> counter = 0
>>> things = [1, 2, 3, 4, 5]
>>> while counter < len(things):
... print(things[counter])
... counter = counter + 1
...
1
2
3
4
5
Something to unpack here, you may now understand what counter does in the update and condition part. However, what is with the things[counter]
code?
That is how we access an individual item within a list for computation.
>>> things = [1, 2, 3, 4, 5]
>>> things[0]
1
>>> things[2]
3
>>> things[3] + 5
9
Remember we said start counting from 0
? This is another reason why. The first element of a list is 0
. So back to the while loop above, in the first iteration, counter is 0
so 1
was printed, then subsequent items are printed as the loop carries on.
If there is an intuition you can build throughout this session, you may find that things you find may fit will often fit. We have seen how things can nest within each other, yes, we can nest if
, while
or even for
within a for
loop as well.
Now how is it useful?
Imagine we have
>>> things = [ [1, 2, 3], [4, 5] ]
Oh, you just realized we can do a list of lists (:
And if you have not realized, yes, this is how we do nested for
>>> for sublist in things:
... for item in sublist:
... print(item)
...
1
2
3
4
5
So a quick walkthrough, when we first begin execution, we extract [1, 2, 3]
into sublist
. Then we go through the inner/nested for
loop, and we extract 1
out of sublist
and assign it to item
. Next, we print item
, which is 1
. Continue the inner loop execution until we exhaust all the members of the list, then we go back to the outer loop to grab [4, 5]
and assign into sublist
. Then we repeat the inner loop again with new sublist
.
Next we talk about reuse, which is another way to do similar things, but in a more controlled way.
Reuse
One of the thing you see me do repetitively throughout the whole session, is testing if a number is even. Also, if you have been paying attention, you know programmers are lazy. Assuming we still need that kind of computation, and we don’t want to keep typing the same thing over and over again.
some_number % 2 == 0
What we can do is to write a function, and the general form is
def <function name>(<arguments>):
<do things>
and for the check if a number is even
>>> def is_even(a_number):
... return a_number % 2 == 0
Like if
, while
or for
, we can have multiple computations done within a function as well, just that for this case we only need one line. If you want the result of the computation being used, remember to prepend the result computation with return
keyword. (It is OK to not have a line with return
too).
Also watch your indentation.
And now that we have that defined, how do we actually use it? We actually called quite a number of functions already, for instance a print
, round
, len
etc.
>>> print(1 + 300)
301
>>> round(0.33333333333, 4)
0.3333
>>> len([1, 2, 3])
3
You may have already guessed it, we can call our new function as follows
>>> a_number = 42
>>> is_even(a_number)
True
>>> is_even(3)
False
As we have our function returning a value, and it is in Boolean, so it can be used as condition in if
/elif
/while
.
>>> if is_even(4):
... print("hello")
...
hello
>>> while is_even(3):
... print('do this')
...
But what about "hello".upper()
? It is also some form of function call, but as an object method. While we are not covering it here, it is nice to know that it is just a variation of function call.
Related reading: object orientation in Python
A function can choose to not return a value, for instance you can have a function to print
an essay
>>> def essay():
... print("hello world")
... print("lorem ipsum dolor sit amet")
... print('my day is good')
...
No return statement, and also no function arguments (nothing in the parentheses). Therefore calling it would require nothing to be passed in too.
>>> essay()
hello world
lorem ipsum dolor sit amet
my day is good
You can also require multiple arguments (separate them with commas) while calling your function too
>>> def get_y(m, x, c):
... return m * x + c
...
>>> get_y(10, 10, 1)
101
Nesting if
, while
, for
is fine
>>> def foo():
... a_number = 32
... if 42 == a_number:
... print('bar')
...
Nesting a function into if
, while
, for
is also fine, but usually we don’t do that (imagine the confusion caused when it is placed in a while
loop and it gets defined multiple times).
>>> if True:
... def bar():
... print('baz')
...
Now that we are done with all the basics, we are ready for a real world program. Exit the repl by pressing CTRL+D. Open up a code editor and create file into the folder we created in the beginning of the session (this example uses vscode, but if you use other editor like nano
replace code
to nano
).
$ code fizzbuzz.py
(In)Famous programming puzzle
So this is a real world program, which is a rather famous programming quiz in job interview. The problem statement is given by
- For a list of 1-50
- Print “Fizz” if the number is divisible by 3
- Print “Fuzz” if the number is divisible by 5
- Print “FizzBuzz” if the number is divisible by both 3 and 5
- Otherwise just print the number
Let’s build the program in parts. One thing to remember about writing programs is that you don’t usually build everything in one go. So we start by creating a list of 1-50.
Typing out a list of 1 to 50 seems like a dumb thing, so Python provides a range
function that does exactly that, almost. Since we are going to loop it, let’s create a for
loop for that
for item in range(50):
print(50)
Save the file, and go back to your terminal, run the program
$ poetry run python fizzbuzz.py
0
2
3
...
47
48
49
Nice, we have the numbers printed out, but it starts from 0, which is annoying to non-coder. No big deal, just remember to add 1 next time.
Now we see what can we do next, if you read the problem statement, you will see divisible by <something> keep coming out. Let’s do a function and add it to the top of the file
def divisible(dividend, divisor):
return dividend % divisor == 0
Next, go through the 3 conditions, and remember, always perform the most specific condition first.
if divisible(item + 1, 3) and divisible(item + 1, 5):
print("fizz buzz")
elif divisible(item + 1, 3):
print("fizz")
elif divisible(item + 1, 5):
print("buzz")
else:
print(item + 1)
So this should do the trick, check your indentation, and re-run the program
$ poetry run python fizzbuzz.py
1
2
fizz
4
buzz
fizz
...
Check if the output is correct, and ensure your file looks like this
def divisible(dividend, divisor):
return dividend % divisor == 0
for item in range(50):
if divisible(item + 1, 3) and divisible(item + 1, 5):
print("fizz buzz")
elif divisible(item + 1, 3):
print("fizz")
elif divisible(item + 1, 5):
print("buzz")
else:
print(item + 1)
Some enhancements you may want to consider adding to the program are
- There are a lot of
item +
1, can we somehow stop repeating the same computation over and over? - Assuming you don’t know about
range
function, can this be rewritten with just acounter
andwhile
loop? (Hint, you can start your counter by1
too so noitem + 1
nonsense)
There, my friend, you have completed your first real program. You may now proceed to finally learn a language for real, it could be golang, or python, or any other language.
In any language you encounter, there would be some sort of variation to the 4 things above, they may be in different name (for instance Rust uses match
statement to do branching in addition to if
), or different forms (assignment practically doesn’t exist in functional language like Haskell but there are similar let
statement that sort of do “assignment” nonetheless). You might encounter more advanced concepts (remember my link to object oriented Python just now?), you can see them as a more efficient way to do the 4 different things we covered (assignment, branching, repetition, reuse).
Now you share my pain and joy of learning new languages (:
(if you find this post helpful, you can help by bugging me on social media to fix language problems, e.g. tenses, active/passive voice etc)