Learn Python

In this free tutorial you will learn the basics of Python step by step but fast in just two long pages.

Also make sure you checked [Understand programming] and [Learn programming] sections, which lays the foundation for what will follow.

This is a multi-track tutorial. What do I mean by that ? In many sections there are tabbed examples with accompanying explanations. On the first read, read only the {BASIC} tab. Once you absorb the material give it two, three days and then reread the tutorial, this time looking at all the tabs.

And then reread it again in 2-3 weeks.


Below we have a nice Editor that you can use to experiment as you learn. You can Copy&Paste the code, but never forget to tweak it and see what happens. This is one of the recipes to learn fast.
Also there is a link inside you can click to popup this Editor as a floating window.

Python editor : click to roll out

[ Click to popup the console ]

Editing info

You can use [Ctrl+Enter] shortcut to run the code you’ve written.

Functions format in Python is : function_name(parameter1, parameter2,….)

Read the comments in the source code boxes, because they explain what is happening.

The lessons in this tutorial follows the structure of [Learn programming].

That said we will start with very basic even trivial examples.


Hello World

First lets do the canonical example, the Hello-world program.
We use the print() function to display information.
A string is characters surrounded by quotes to distinguish them from the rest of the program. ( quoted text is know as Literal of String type.)

Type print(“hello world”) into the Editor and click RUN.

print("hello world")                                                                                                                                                

hello world

Yes Virginia it was that easy 😉

Variables

Next we will experiment with variables. We will simply assign Values to Variables. Read the comments inside the box, they explain what we are doing.

Speaking abut Comments, in Python we write them by starting a text with the #-sign. They have no impact on the program execution at all and are ignored.

Assignment and Reassignment are pretty easy to understand.
As I mentioned before Python is Dynamically typed and you can see this in action here i.e. the variable “x” can accept both Number and String as a value. In Statically typed languages this is not possible, you have to explicitly decide what will be the type in advance.

Exception: In some languages you can cast the value i.e. pretend it is of a different type, but this opens another can of worms …

You may not feel it now, but believe me it makes your life much easier from coding perspective to have dynamic types. Of course at the same time it makes other stuff worse. As with everything it is a trade-off.

#assignment, the value of variable x becomes 5
x = 5
#let see what's in the box
print(x)
#reassignment, x is no longer 5 but 7
x = 7
print(x)
#we can also assign a string to the same variable
x = "hello world"
#  ... of course x is no longer 7
print(x)

------
5
7
hello world

Another thing the above interaction shows is the mutability of the variables in Python. In contrast with many other languages and Math where the variables are immutable i.e. reassignment is not possible. This again has it’s trade-offs.

Now that we know how to assign variables, lets do quick rundown what we can do with Strings. Strings are list of characters.

Just follow the prints, they are self explanatory, except the last two for which we will learn more in Loops and Lists. BTW Strings support Lists API.

h = 'hello'
w = 'world!'
#concatenate strings
a = h + ' ' + w

#most Python types can be measured by len()
print('length: ',len(a))
print('first character: ',a[0])
print('Is "world" in the string : ','world' in a)
print('split: ',a.split())
print('strip: ',a.strip('!'))

#slicing ...
print('6th character onward: ',a[6:])
#loop over a string ..
for char in "abc" : print(char)
-----
Output:

length:  12
first character:  h
Is "world" in the string :  True
split:  ['hello', 'world!']
strip:  hello world
6th character onward:  world!
a
b
c

f-stringsf-strings

Let me introduce you also to f-strings, so that we can do fancy printing. Like normal Strings they are surrounded in quotes, but in addition have the character f in front of the string.
For example f”…..”.

Then inside the string whenever we want to print a variable we simply surround it with curly brackets, like I show in the examples below.

v = 55
s = 'world'

#use this to print the variables ..
print(f'Value: {v}, String:{s}')
#instead of
print('Value:',v,', String:',s)

w = 77
#you can also put expressions
print(f'Sum: {v + w}')
#compared to
print(f'Sum: ', (v + w))

z = f'Hello {s}'
print(z)
Value: 55, String:world
Value: 55 , String: world
Sum: 132
Sum: 132
Hello world

Types

In [Learn programming] we talked about Types but lets recap.

Types are used to describe the possible operations and interactions with other values and variables. For example using the symbol plus “+” with numbers or variables that are numbers will produce the sum of the operands. On the other hand with Strings “+” means concatenation.

The compiler/interpreter needs this information to allocate memory, check for inconsistencies and catch errors. Types helps by preventing you comparing Apples and Oranges 🙂

So here are Python built-in data types :

Text :str
Number types :int, float, complex
Sequence Types:list, tuple, range
Mapping Type:dict
Set Type:set
Boolean Type:bool
Binary Types:bytes, bytearray, memoryview

We use the type() function to check the type of a variable.

type(5)
type("hello")
type(True)

---
int
str
bool

Expressions

Expressions are similar to what you may expect from Math, here a some examples :

#first let's use it like calculator and sum two values
print(5 + 7)
#now let's do the same, but with variable and value
x = 5
print(x + 7)
#what about other math operation
print(x * 7)
#what if both operands are variables ?
y = 7
print(x * y)

-----
12
12
35
35

Now try to guess before opening the Answer. How would you …

Conditionals

Let’s put fork in the road …

At some point we have to make a decisions between options, for this we use if-statements.
The format of if-else is the following :

if cond1 : action1
elsif cond2 : action2
elsif cond3 : action3
else : action0 #default

#oneline shortcut if
res = val1 if cond1 else val2

The == double-equal sign below is used as comparison operator, so that it can be distinguished from assignment = .

#we setup the previous scenario
x = 5
y = 7
#we can compare values. check that 5 is bigger than 3 and if yes display the text bigger.
if 5 > 3 : print('bigger')

#check the opposite
if 5 < 3 : print('bigger') #false does not print anything

#.. and variables. we set earlier y to 7, so this should succeed   
if y == 7 : print('y == 7')

# ... and we can be sure is not 8, cause nothing prints
if y == 8 : print('y == 7') #false does not print

#now lets try one condition with two possible outcomes
if y == 8 :
   print('y == 8') #true case, will be selected if y was 8 but is not.
else :
   print('y =/= 8') #false case, but is selected cause y is not 8

#here we introduce bigger-or-equal comparison and complex conditions
if x >= 5 and y > 5 : print('yes')
else : print('no') 

#test one-line if and modulo operation
res = "even" if 12 % 2 == 0 else "odd"
print(res)

-----
bigger
...
y == 7
...
y =/= 8
yes
even

The (5 < 3) case does not print anything. The same for all condition that are false, except the before-last one which on purpose I made abit confusing.

In the if-conditions part you could also use the following connectors : not, and, or, >,<, >=, <=

Before we move on, lets do some quick tests. Try to guess the answer.

Loops

Here I have some explaining to do. The print() function support an optional argument called “end“, which specifies what it will print after the string is printed. By default it is a new line, but in our case we want to use comma for ergonomic reasons, so that the output doesn’t take 15 lines and stretch half HTML page.

With that taken care of lets dig in. We use the range(start, end, step) function to generate a range of numbers, from start to end-1 with a jumps of size specified by step argument.

# print the numbers from 1 to 4
for i in range(1,5): print(i,end=',')

#the same result but different loop type
i = 1 #initialize the index var, has to be outside the loop
while i < 5 :
  print(i,end=',')
  i = i + 1 #update the index var

#DO cond : ... WHILE  
i = 1
while True :#infinite
  print(i,end=',')
  i = i + 1
  if i > 4 : break #exit if true

-----
1,2,3,4,1,2,3,4,1,2,3,4,

The first loop uses the numbers generated by the range() function and at every cycle assign it to the ‘i‘ variable. The body of the loop print()’s the value of ‘i‘.

The second loop does the same thing, but depends on condition to be true to continue. In addition the programmer is responsible for initializing and updating the index variable. The benefit of this construct is that we can have any logical condition to make a decision for how long to repeat the code, not just counting numbers.

FOR : How many times to repeat …

WHILE : How long to repeat …

The third loop looks weird. That is because Python does not support the so called DO … WHILE loop. This type of loop is similar to the second case but the condition is at the end. The consequence is that the loop is guaranteed to run at least ONCE. There are often cases where you would need that.
So what we did to imitate this kind of a loop is to set the head-condition to always be true (which means infinite loop). Then in the body of the loop we put the tail-condition with the break statement to exit the loop, if this condition happens to become true.

The break statement has a contra-part called continue, which is used to skip the rest of the loop body instructions and start from the beginning again.

And now some brain teasers …

Sub-Summary

We covered most of the basic blocks for writing a program and are at the point where we can write fully functional but short scripts. So far we have no way to handle repeatable code which is scattered across the program, instead we have to write it over and over, which is a real downer. Loops can only handle repeatable code which is in a single place.

To mitigate this problem we will use :

Functions

Functions allow us to isolate and encapsulate repeatable code. This increases the complexity of the program but also decreases the redundancy. Abstractly you can think of a function as a black-box with inputs and outputs.

One benefit of creating Functions when done right is re-usability, another is decreased chance of errors. Shorter code is easier to analyze.
The shortcoming is that we now added the cost of communication between the function and the main program and the decision what information to pass back and forth.

In conclusion the decision to move a piece of code in a function is a trade-off as always.

Let’s now practice ….

Synopsis

We can refer a function in two ways, one by defining a it and another by calling it.

Functions in Python are defined by using the def keyword in the following way :

def function_name(parg1,parg2,narg1=dval1,narg2=dval2):
   ... do something ....
   return rval
  • pargX : required, positional argument
  • nargX : optional, named argument
  • dvalX : default value for named argument
  • rval : return value of the function

The return line is not required, in case it is omitted the function return a special value called None.

and here is how we call a function :

#the return value is assigned to the variable
var = function_name(pval1,pval2) #omitted optional args

or

var = function_name(parg1,parg2,oval1,oval2)

At minimum all the positional arguments has to be provided when calling a function otherwise you will get an error.

The body of the function can include anything of what we learned so far and more.

Usage

We start by doing the simplest things : arithmetic operations and counting.

The function sum accepts two arguments a and b, adds the two together and returns the result. The logic for the square is the same.
Then we test the Functions by calling them and printing the result.

def sum(a,b): return a + b
def square(a): return a * a

a_sum = sum(5,7)
print("sum: ", a_sum)

squared = square(5)
print("square: ", squared)

def count(a,b):
  for i in range(a,b+1):
    print(i, end=',')

count(1,10)
print()

def count2(a,b,c):
  for i in range(a,b+1,c):
    print('(',i,')', end=',')
    
count2(1,10,2)
-----
Output:

sum:  12
square:  25
1,2,3,4,5,6,7,8,9,10,
( 1 ),( 3 ),( 5 ),( 7 ),( 9 ),

Next we count the same way like we did in the loops section, but this time because we do that inside a function we can parameterize it. Like I promised instead of writing counting loop every time we need it, we will just call a function.

Think about it … let say we wrote 10 counting loops in our program … but now for some reason we decide we want to skip every second number and print it in brackets. What we have to do would be to change every loop manually … this sucks. But now that the code is in a function we will do the change in a single place , ergo count2().

You can check for more fancy examples by clicking on the {FANCY} tab.

We start again with simple examples … i.e. trying to calculate the euclidean distance between two points.

The first line contains the word import , which will do exactly that import a functionality stored in library called math.
Then we can access any function in this library using : math.function_name() syntax
Libraries contain Functions and classes from other programmers packaged for reuse, so we don’t have to write it our-self.

The second import sets up an alias, so that you can use np.function_name(), instead of the more verbose numpy.function_name()

Next we define two Functions as we described earlier. The new print() calls use two arguments, a string and a call to the already defined function. ….. cntd. >>

#more fancy Functions

import math
import numpy as np

#those are helper Functions, divide and conquer, KISS
def sum(a,b): return a + b
def square(a): return a * a

print("sum: ", sum(5,7))
print("square: ", square(5))

#calculate euclidean distance, verbose
def edist(a,b,x=10, y=10): 
  #first square the diffs
  ax = square(a - x)
  by = square(b - y)
  #sum them
  s = sum(ax,by)
  #square-root them and return the result
  return round(math.sqrt(s),3)

def edist2(a,b,x=10, y=10):# equivalent to edist() 
  return math.sqrt(sum(square(a - x), square(b - y)))

  
#now try calling it in different ways
print(edist(5,5))
#double check using numpy array
print(np.linalg.norm( np.array([5,5]) - np.array([10,10]) ))

print("(5,5),(10,10) : ", edist(5,5,10,10))
print("(5,5),(11,11) : ", edist(5,5,11,y=11))
print("(3,5),(10,13) : ", edist(3,5,x=10,y=13))
-----
Output:

sum:  12
square:  25
7.071
7.0710678118654755
(5,5),(10,10) :  7.071
(5,5),(11,11) :  8.485
(3,5),(10,13) :  10.63

Now using sum(), square() and math.sqrt() lets define the euclidean distance function edist(). We split the calculation to several steps for clarification. I also wrote duplicate compact one-line function edist2().

On purpose the second coordinates x and y are optional arguments.
In any function call required arguments must be specified in the order they were declared, ergo they are also called positional arguments. The optional on the other hand can be written in any order but you always have to put them after the positional.

The first call edist(5,5) passes only the required arguments, but is equivalent to edist(a=5, b=5, x=10, y=10), because unless provided the default values are used.

Then we use numpy library function just to cross check if the results match.

After all that we call the same function in several different ways.

Recursion

As we discussed in [Learn programming] in recursion a function call itself.

The question now is how a function call itself. It is simple the head of the function should use the def-syntax as expected and the call should use obviously the call-syntax.
But there is one bigger difference, notably there must be a limiting condition, otherwise the recursion will continue forever.

Once again we start with a simple canonical example.

The basic example is the Fibonacci sequence : 1,1,2,3,5,8,13, …..

We have our limiting condition and the rest is verbatim from the math formula. There is the slight difference that we do two recursive calls and then we sum the result.

The calculation work backwards, lets take for example fib(5).
This call will invokes fib(4) and fib(3), which will calls fib(3),fib(2); fib(2),fib(1) …. eventually all will converge on calling fib(1), which is the limiting condition and will return a value of 1. This will unwind the calls, because they now “have” values that can be used to do the calculation and return the value to the callee, until it reaches the top call which returns the result. Phew !

def fib(n):
  #limiting condition
  if n < 2: return 1
  #return the sum of the previous two fib-numbers
  return fib(n-2) + fib(n-1)
    
print(fib(5))
-----
Output:

8

In this example we will reuse the fib() and square() Functions and demonstrate re-usability again via the modification of the count() function. Re-usability and re-purposing are very important tool in your arsenal, but it requires deliberation when and how to use them. (This silly example is just for illustration)

Let say we have multiple places in our program where we use the count() function, but we’ve got a new requirement to print consecutive Fibonacci’s and squares.

Now we have two choices either create two new separate counting Functions or re-purpose the one we already have. We will choose the latter.

This is possible to do because Python allow you to pass Functions as parameters. Most tutorials don’t introduce this functionality in a beginner course, but I don’t see why not. As you will see it feels very natural.

def fib(n):
    if n < 2: return 1
    return fib(n-2) + fib(n-1)
    
def square(a): return a * a

#identity function
def id(a): return a

def count(a,b,fun=id):
  for i in range(a,b+1) :
    print(fun(i), end=",")

def count2(a,b,fun=None):
  for i in range(a,b+1) :
    res = i if fun is None else fun(i)
    print(res, end=",")

    
count(1,10)
print()
count(1,10,fib)
print()
count(1,10,square)
-----
Output:

1,2,3,4,5,6,7,8,9,10
1,2,3,5,8,13,21,34,55,89,
1,4,9,16,25,36,49,64,81,100,

Look at the definition of count(), I just added one new optional parameter called fun .. def count(a,b,fun=id)

Two lines below we call the function fun(i), which is now alias for the function passed as argument. By default that will be a call to id(i).

Why would I create such a function that does not do anything useful ! The reason is that in this way I can preserve the function signature. Remember we have to keep the existing calls to count(a,b) backward compatible, unless we are OK to spend the time to fix them too.

Another option is to use different version like count2(), which handles the default case with internal logic. if we don’t pass the optional argument fun equals None.

Lets raise the stakes a notch, by creating two mutually recursive Functions. Wouldn’t that be fun 😕

Both Functions call each other and both have a limiting condition. It again work backwards, decreasing the number until it reaches zero. And because zero is “even” that’s why we return True in is_even() and False in is_odd().

The analysis we did this time is Declarative, which means we did not trace all the steps of execution (Procedural) as we did with fib(). You have to learn to do both if you want to become a good programmer.

At some point if you decide !! A good approach is to pickup Prolog, it forces you to be declarative.

def is_even(n):
  if n == 0: return True
  else: return is_odd(n-1)
  
def is_odd(n):
  if n == 0: return False
  else: return is_even(n-1)
  
print("is_even(4) : ", is_even(4))  
print("is_even(5) : ", is_even(5))  
print("is_odd(5) : ", is_odd(5))
-----
Output:

is_even(4) :  True
is_even(5) :  False
is_odd(5) :  True

Don’t forget to Bookmark this page … Ctrl+D and then ENTER would do it 😉