1. Material: The Mysteries of Cosmic Variables¶
What's Going on?¶
As the name Elementary Programming suggests this course teaches you how to program without any kind of pre-requirements. It is going to be a rocky road but we hope that the great accomplishments at the end of it will make up for all the trouble. The pre-exercises and instructions exist to give you everything you need before diving into this learning material. From there you will learn to install and run the correct applications and to write some basic math into the
Python console
. You may even learn what this mysterious console is. Heck, you'll even catch a glimpse of what code files
are.There's a lot of variables when it comes to learning programming but none of them are insurmountable. Learning is generally enhanced with some sense of purpose. This is a good moment to think about what You would use programming for. There are use cases for programming skills in almost every field and even in many hobbies. Programming skills can open up surprising opportunities in many aspects of life. However, nothing is quite as effective as motivation from personal aspirations. What could You program?
Within the framework of this course we can only offer you one goal: everything you learn in the course eventually leads up to the final project. If you cannot come up with a more personal purpose for programming, at least keep the final project topic of your choice in mind when going through these materials. One morning you might wake up with a mystic enlightenment about how a minesweeper game works under the hood; or how RLC circuits can be solved programmatically; or how measurements can fit into a nice plot. In that moment it's good to reflect upon where you started and how far you've come. We recommend that you take a look at the topics right now, here at the start.
Alright Sensei. Will You Now Tell Me What Programming Is?¶
Programming is not all about writing
code
. Code is simply the most apparent manifestation of programming. A lot of programming activities can be done in secret within your mind. Only when you start to write code the uninitiated around will realize what you're up to - but it will be too late! Your plan to take over the world is already underway. What else is there besides code then? In more general terms we can say that a programmer solves
problems
. In our context a problem is any need that can be fulfilled by programming - be it the need for your own minesweeper game, or the need to post memes online for others to like and share. While neither of these would qualify as actual Problems, once some need is selected to be fulfilled with programming it effectively becomes a programming problem. The solution to a programming problem is generally a plan of what the code itself should do. At this stage the
solution model
may only exist only within the programmer's mind - or perhaps on paper. Creating solution models to problems is the first invisible programming skill.Knowing the possibilities of code is essential in producing a solution. However, at this stage it's too early to fuss over small details like how the code will actually be written. It is mandatory to understand programming concepts though: how program execution is controlled and how programs process information. When learning these concepts it's good to remember that they are largely not unique to Python. Common programming concepts exist in all programming languages in one way or another. Conceptualizing a
solution model
is the second invisible programming skill.Only after going through this process of solving a problem and then conceptualizing the solution we can finally fuss about the minor detail that is writing the code itself. We say "minor" because the code itself is just
syntax
that the programmer uses to describe their solution to the computer in an unambiguous manner. Compared to everything else it's more like a formality really. If you only know how to write code you won't get very far in programming. In the Beginning There Was a Variable¶
Learning goals: After this section you'll know what variables are. You'll know how to use variables to make the computer remember things. You will also have have a basic understanding of how variables are connected to the computer's memory. Assignment operator will be familiar. Last but not least you are also familiar with rules and conventions regarding variable names.
Introduction to the World of Variables¶
Our very first programming concept will be memory. As soon as we're dealing with anything beyond individual statements remembering things becomes a necessity. In programming this concept of memory is implemented through
variables
that reside in the computer's memory. In Python variables are created when they are assigned a value. This is done with the assignment operator =
with the variable on the left and the value on the right. x = 1337
A major difference exists between computer and human memory: the computer does not know the meaning of things it remembers while humans (usually) do. The value 1337 is just a number that resides somewhere in memory. The computer cannot respond directly to a request such as "give me the previously calculated value of x". However it does know what each memory address contains and it can answer if asked for a value from a specified location. Luckily programmers don't need to know these memory addresses - that is what variables are for. In this example, the variable is x.
Digesting Variables¶
The concept of
variable
can be explained in many ways. At its core it's an agreement between the programmer and the Python interpreter
that a certain word (e.g. x) is used to refer
to a specific location in the computer's memory. As a result of this agreement both parties can talk about data in their own terms: the programmer can refer to things with descriptive names while the computer can handle memory addresses. This can be simplified to saying that after executing x = 1337
, x means 1337 - even though this is not completely accurate.While we often use phrasing such as "variable contains" the variables themselves don't really contain anything. It's just a shorthand for indicating that the
variable
refers
to a certain value
in the computer's memory. Variables are like headings in a book index or labels on a map. Neither contains what they refer to; they simply show where it is. The map is not aware what "Oulu" means to whoever is looking for it - it simply knows that such a name has been given to a certain geographical region. So if
x = 1337
means that we define a name x that refers to memory slot that now contains the value 1337, what do the following two lines mean?y = x
x = 4451
The first line defines another name for the value x
refers
to. As a result both x and y now refer to the same value
, i.e. 1337. This is not renaming since both x and y will exist; neither is it copying because only one value 1337 exists in memory. The second line creates a new value 4451 that will be assigned to another memory address. Immediately after we redefine x to refer to this new memory location and the value it contains. As a result we now have both x and y but their values are now respectively 4451 and 1337. The animation below illustrates the relationship between names and values.One important thing to note about
assignment
is that despite having the same symbol (=) as mathematical equivalence it's not the same operation. In particular the order of operands is meaningful. The variable must always be on the left and the value on the right. If you try to swap them...1337 = x
you will get:
SyntaxError: can't assign to literal
This means there is
syntax error
because it's impossible to assign anything to a literal value
. You also can't have an operator on the left side:x + 3 = 5
because this will also cause an explosion:
SyntaxError: can't assign to operator
This time the messages is a bit more confusing. It's just a way of saying that we can't assign to the
x + 3
operation
. We'll dive a bit more into interpreting error messages at later sections in this material.To summarize: it's only possible to have a
variable
on the left side of the = operator. Everything else should happen on the right side. Later we'll see a couple of other things that can be on the left side though. Since being on the left side of the operator means the variable is being assigned to we can infer that when it's present elsewhere we want to retrieve its value instead. Therefore when the following statement is processed we can imagine the [!term=Value!] of each variable in place of its name. When the statement is carried out, the value of each variable is retrieved from memory before proceeding with other instructions (i.e. the + operator). Likewise the right side of the assignment operator is executed in its entirety before anything is assigned to the variable on the left. x = y + 4451
The last we saw of y it was referring to the value 1337. As we retrieve it and add 4451 the new value of x ends up as 5788 because the addition is carried out before the new value is assigned to x. To be more precise the new value 5788 is stored in a new memory location, and x is changed to refer to that location. The old value of x will be left by its lonesome in its memory slot until it gets replaced by something.
As we're going through
assignment
and other operations
we can also instate our first style guideline: there should always be a single space between each operator
and its operand(s)
- otherwise the code would look kinda crammed. This is also in accordance with Python's official style guide. What a coincidence. So, just like in the examples, one space before and after each operator. And yes, we will check - or rather the automated evaluation of tasks does.It's also possible to "update" the value of a
variable
. For instance if we have a variable that contains the score of a sportsball team and they score another point, we could have a line like this to handle the situation:score = score + 1
This line retrieves the value of score then adds one to it and finally saves the result and updates score to refer to the new value. There's also a shorthand
syntax
for writing the same statement
and it looks like this:score += 1
The same can be done with other
operators
as well.Variable Naming¶
There are rules to naming
variables
that are absolute in Python. Only letters, numbers and underscore are allowed in variable names. Names also cannot start with numbers. Furthermore, names that start with an underscore have a specific implied meaning that is beyond the scope of this course - therefore starting names with underscore should be avoided. Within these rules you are allowed to name your variables as you wish. Please avoid funny meme names though. The following example shows some valid and well chosen names:matches = 5
player1 = "Goofy"
player_score = 41.8
Variables are agreements between the programmer and the computer about the meaning of each name in the code. Since the computer doesn't particularly care about the names, you as the programmer have the freedom to choose names in a way that makes your job easier. The number 1337 doesn't hold any specific meaning to the computer but it does to you. Best practice is to name variables accordingly so that future you also knows what they mean. For instance x is not a descriptive name for your commute distance. Besides it's not just for future you - there will be other people reading your code as well. Within the context of the course it's much faster for TAs to help you if they don't need to spend extra time figuring out what each variable name means. Naming
variables
is also a good measure for whether you've understood the code you've written. If you can't name the result of a statement
what are the chances you understand what it does in the first place?On this course we also follow the the official naming guidelines for Python. In short: names are written with lowercase letters only and multiple words in a name are separated with underscores. Like so:
donkey_height = 100
. We make an exception for SI units: if you use them it's better to use the proper symbol even if it would be an uppercase letter. However, this only applies to symbols that exist in the English alphabet - Greek letters and such should be avoided.Ever-Expanding Math¶
With what we got so far we can write some simple code that solves simple equations and does other kinds of math. The functionality of a basic calculator is at least something but by itself not particularly impressive. It's time to expand our horizons.
Learning goals: After this section you'll know mathematical basic operators. Typing lines of code expands to lines that do more than one thing. You will also become more familiar with the math-like execution order of code. The concepts of integers and floating point numbers - and some complications related to them - are also on the menu.
Operation Square Root¶
Operator
is a symbol that indicates an operation
(e.g. mathematical operations like addition with the +-symbol). Operations use operands
that are placed on both sides of the operator. Most common operators in programming are mathematical operators that are introduced in this material and logical operators that are in the next material. Python knows the basic operators with their familiar symbols: +, -, * and / (addition, subtraction, multiplication and division respectively). We're now mostly missing power and root. Both of these can be actually done with the same operator: **. This makes sense because root is the same as power with fractions.
cube_volume = side_length ** 3
side_length = cube_volume ** (1 / 3)
There are also a couple more
operators
related to division that are occasionally needed. First we have // which indicates integer division. Second, % operator gives the remainder. Let's explore these with a short task and get back to them when they become more relevant (in the third material).Just like math, Python also has execution order or
precedence
for operators
. For mathematical operators, precedence is familiar: multiplication and division take place before addition and subtraction; equal precedence from left to right. Execution order can be altered with parentheses so that anything inside parentheses takes precedence. Precedence and operators can be further investigated from Python's documentation. Reading documentation in general is an essential programming skill.Two Kinds of Numbers¶
Numbers in math are divided into various categories like integers and decimal numbers. These categories are also important in programming. In Python decimal numbers are identified by the existence of the decimal point. Therefore 5 is always and integer but 5.0 is a decimal number. Decimal commas are not to be used in Python because commas have a very different meaning.
However since computers deal in ones and zeros they are in fact not very good with real decimal numbers. In computing terminology a separate term is used instead of "decimal number" to indicate the difference:
floating point number
or float. 87.41871478 # This is a float
A floating point number is an approximation of a decimal number, i.e. it is not exactly accurate. In most scenarios the inaccuracy manifests after so many decimals that it generally does not matter in practice because results are usually rounded eventually anyway. For instance let's look at cubic root of 64, i.e.
64 ** (1 / 3)
. The cubic root should be exactly 4. However if we actually execute the statement in Python, we get 3.9999999999999996. You can find an in-depth description of floating point issues from Python's tutorial. The inaccuracy is not very relevant here because the result is still 4 after rounding. Likewise we would need to make quite a large number of calculations before this inaccuracy produces an error.There is, however, a very fast way to produce an error from this inaccuracy. Occasionally it's necessary to
convert
numbers from one type to another. Usually from float to integer because there are certain use cases that only accept integers. To illustrate the problem, let's consider screen coordinates. A computer screen consists of picture elements or pixels each of which are produced by physical components. This makes it a discrete coordinate system. Your mouse cursor will always point at a pixel - never between them. What follows is that the position of graphical objects on the screen cannot be expressed as decimal numbers. When a program needs to draw on the screen, it has to convert the object's position to an integer.
This conversion is done with two
functions
in Python: int
and float
. Named as abbreviations of integer and floating point number respectively. Without going into details about what functions are just yet, let's just see what happens when we use them:In [1]: float(4)
Out[1]: 4.0
In [2]: int(4.0)
Out[2]: 4
This looks simple enough. But?
One vital fact about the int function: it only returns the integer part of a number - there is no rounding. You can achieve the same result by applying integer division with one:
3.9999999999999996 // 1
. If we recklessly convert a float result from a calculation to an integer, this can result in a significant error. The result should be rounded first, and for this we need some new tools.Functions - Threat or Opportunity?¶
Our next contestants are... functions. The term may be familiar from math, most commonly as f(x). This notation defines a statement and its result is said to be the function of x - in other words the result value depends on the value of x. While not exactly identical, it's a good starting point for delving into functions in programming context.
Learning goals: After this section you are familiar with the concept of functions: what they are, why they exist, where they are and how to use them. You will also learn how functions are separate parts of a program that can communicate with the rest of it. Finally you'll come to know a few basic functions.
The Essence of Functions¶
Functions
are recycling at its best. If a snippet code that does one particular thing exists there's little point in re-inventing the wheel. Functions can be found built into Python
, from various modules
and libraries
. You can also write your own functions which is a very good habit to pick up. So good in fact that it's best to start working on it from the get-go. You don't need to understand the code below just yet - it's there just to show you what can be expected in the near future when we start to work on our very own functions.def calculate_mean(values):
return sum(values) / len(values)
mean = calculate_mean((6, 8, 10, 7, 9, 8, 5, 7))
An ideal
function
is an isolated, fully independent unit. In other words the function's internal workings are not dependent on any code outside of it. Functions written in such a way can be used in any context and always work as expected. Likewise a programmer doesn't need any understanding of a function's internal logic in order to use it - they only need to know how to use it. A function's documentation is responsible for describing anything you need to know about using it. Naturally Python's built-in functions
fulfil all these conditions. They include the previously seen int and float functions. Although we say that an ideal function is isolated it doesn't mean that it shouldn't have any means to communicate with the rest of the program. Understanding the nature of this communication is essential in learning to utilize functions. For the rest of the world, a function is a black box. Something just goes in and something comes out. These two things, input and output, form the
interface
that other parts of the program use to communicate with the function.A central term related to functions is
calling
. Just like a phone call, there are two endpoints in this process: the caller and the callée. The callée is the function; the caller is the part of the program where the function is called.A
function
is called through its interface. This interface dictates the rules about what kinds of things the function can handle, the order in which they must be presented and what can be expected as the result. From the function's perspective it's vital that the caller adheres to the restrictions placed by the interface. Let's look at the int function in order to make some actual sense of this. What we know of it so far is that it receives a number and returns the integer part of said number. Let's assume we are calculating the transition of a moving object along the x axis of a coordinate system. This is done by multiplying the object's directional unit vector's x component (e.g. 0.32) by the object's speed (e.g. 10).
speed = 10
direction_x = 0.32
transition_x = int(direction_x * speed)
Understanding the subject matter of this example is not essential. It's just a scenario where converting a number to an integer is actually needed. It's also a rather common scenario in game programming. The crux of this example is the third line where we can see a
function
called
. The part of code that's inside the parentheses defines what is sent to the function for processing. It's probably also noteworthy that due to precedence the operation inside the parentheses is executed first. Therefore the function is ultimately sent the result of the multiplication (i.e. 3.2). Once the function has done its thing and the dust settles, all that's left is the integer value 3 that will then be assigned
to the transition_x variable. The animation below illustrates what's going on.A World of Possibilities¶
We ended up looking at functions in the first place because we wanted convert the result of
64 ** (1 / 3)
to an integer correctly. Using just int(64 ** (1 / 3))
gave us 3 and left us very bamboozled. We found out the reason but are still lacking a solution.As we can witness from its documentation, Python comes with numerous
functions
. At the earlier stages of the course we're mostly playing around with functions found there, and quite frankly we won't touch majority of them either. Function names are usually quite well chose. For our problem - which was the lack of proper rounding - we will probably find a function called "round" useful. Just to be sure that it actually does what we want of it, let's learn how to read documentation. The very first line which contains the
function
name is crucial. This one line specifies how the function is to be called
. The words inside the parentheses, number and ndigits, are names for values
the function accepts. The names typically have been chosen to somehow reflect the nature of the value that the function can use. In this case the first value called number is the number that is to be rounded; the second called ndigits (number of digits) defines the desired precision of the result.These names, number and ndigits, belong the to a special category of
variables
that is called parameter
. On the other side of the equation where we call the function and give it a number to round and another number as the desired precision (e.g. 3.56 and 1) these values will be called arguments
.In [1]: round(3.56, 1)
So far so confusing, but what about the square braces around the ndigits parameter? First of all this has nothing to do with the
syntax
of defining functions. If you would look at how the round function is defined you would see something like this: def round(number, ndigits=0):
. The square braces are simple documentation markup that indicates ndigits being optional. In other words the function can be called without giving a value for this parameter. The text part of the document even says what happens if it's not given: ndigits will have the value 0. Therefore these two behave identically:In [1]: round(4.451, 0)
Out[1]: 4.0
In [2]: round(4.451)
Out[2]: 4.0
Because the
, 0
part of the first line is superfluous, the best practice is to leave it out entirely. This is almost always done in situations where we want to use the default value
of a parameter. Optional arguments
are only given when we want them to have a value that differs from the corresponding default:In [1]: round(4.451, 2)
Out[2]: 4.45
When
functions
are given multiple arguments
they are separated from each with commas. There is also another style guideline related to this: similarly to writing text, each comma should be followed by exactly one space. Arguments are assigned to parameters
in order. In the example above the number parameter gets its value from the literal value
argument 4.451 while ndigits gets the value of 2. Parameter and argument are not the same, they just share a value. An argument can be a literal value or a variables
but a parameter is always a variable.Functions in Use¶
Now we can finally write a code snippet that converts the result of
64 ** (1 / 3)
to an integer correctly:result = round(64 ** (1 / 3))
result = int(result)
This can also be achieved with a single line of code
result = int(round(64 ** (1 / 3)))
Precedence
follows familiar rules: the inner function call
is executed first. As there is kind of a lot going on on this line, the animation below illustrates what happens step by step.It's ultimately up to you to decide when to include multiple operations on one line and when to split them by saving intermediate results to
variables
. Readability is king in majority of programming. This means too long or complex lines should be avoided. However, on the flip side, code that constantly defines lots of variables for intermediate results also makes it harder to figure out the essential parts. We now know what
functions
are, where to find them and how to use them in code. Our next stretch is to look more into how functions are used and look at some functions that will be commonly used throughout the course.Our first point of interest is showing results while running
code files
. While working in the Python console
the results of various statements
were shown immediately unless they were assigned
to variables
. The console executes the entire statement and finally shows the result. However, when code files are ran the results of statements are not shown automatically. Instead the results are practically thrown into the bin if they are not assigned to variables. Therefore a code line such as4451 + 1337
does practically nothing. It does produce a result (5788) but the result is not used for anything and is therefore instantly discarded. Since running programs without seeing their results does not sound particularly fruitful, we should probably have a way to show relevant results. This can be done with the
print
function. Lo and behold, a simple example:result = int(round(64 ** (1 / 3)))
print(result)
the same without intermediate variable assignment:
print(int(round(64 ** (1 / 3))))
In a nut shell, print is a
function
that prints its argument
, followed by a newline character. If there are multiple print function calls in a program, each of those will be shown on their own line in the program output.As we are mostly dealing with numbers in the beginning, most of our useful functions besides print will be mathematical. These include functions for taking minimum, maximum and absolute values:
min
, max
and abs
. There are even more mathematical functions included in Python but they have been wrapped into a separate module
. Fear not, we will use them soon enough.Besides mathematical functions there's one function that is useful when working in the
Python console
: help
. Without any arguments it opens an interactive help interface. If instead given a function's name as an argument
it shows information about the specific function. The following call would show information about the round function:help(round)
Typically these help printouts are shorter and less detailed than the ones in the online documentation. It's still useful for quickly checking what kinds of arguments a function accepts etc. Once you're done looking you can exit back to the console by pressing Q.
The Secret Lives of Functions¶
Now we've learned enough basics about using
functions
. Our next trip takes us to the dark side of the moon where we will be creating our own functions. We will also take a look at the structure of code files while we're at it.Learning goals: After this section you know the syntax for defining functions. You will become aware about how the code inside a function is separate from the rest of the code. You will also have some understanding of how variables and their values behave within functions and function calls. As a bonus you also learn how to organize your code file and why it's important.
Function Legos¶
The internals of a
function
contain just ordinary code. There are however two important keywords
that are unique to functions. The first one of these is def
which indicates a statement
that defines a function. The second one is return
which indicates a statement that exits the function and returns code execution to the line where the function was called
from, and what value(s) will be returned
to it. Code inside a function is separated from rest of the code by indenting after the def statement. Here's an example that we can poke:def calculate_distance(speed, time):
return speed * time
This function is rather simple - so simple in fact that you probably would not want to make it a function in a real program. The first line uses the def
keyword
to tell Python that this line starts a function definition. The next line has been indented
, indicating that this line is inside the function, isolated from other code. The def line by itself also has three distinctive parts to it: the def keyword itself, the name of the function, and the function's parameters
. The rules and guidelines for naming
variables
also apply to naming functions
. However, function names typically have a verb to indicate an action - something the function performs. When function names are used in code they are also often followed with parentheses whether they're being defined or called. distance = calculate_distance(100, 2) # distance is a variable, calculate_distance is a function
On the line that start the function definition, the parentheses contain the function's parameters - in our example, speed and time - separated by a comma. Finally the definition line must always with a colon. Colon has a specific meaning in Python: it starts a new
code block
which means the next line(s) must be indented
. Function definition ends when the indented block ends. Often a line containing the
return
keyword is at the end of the function. This line will always exit the function. It also defines what the function returns
as its result. It defines the second half of the interface that the rest of the program uses to communicate with the function whereas the parameters
on the defining line form the first half.Parametric Argumentation¶
The concepts,
parameter
and argument
, are integral when playing with functions. Parameter is a local variable
- a name that is used for an argument's value inside the function. Argument is a literal value
or a variable
, and its value is given to a parameter when the function is called
. In other words an argument is assigned
to a parameter. Of course
arguments
are not assigned to parameters
randomly. Assignment is based on position: each argument will be assigned to the parameter in the corresponding position. It's also worth noting that despite the terminology being used, the value
is not actually moved or copied anywhere. The process simply creates a new reference
from the parameter to the existing value. The entire process is illustrated below:The concept of scope is critical in understanding parameters.
Local variables
are not visible outside of their scope. In this case the scope is the function. They simply do not exist beyond this scope. All parameters
and other variables
that are created inside the function are local variables. In addition to not being visible outside, they also cease to exist when the function's execution ends.This means that you can have variables with the same names inside two different functions and these will not interfere with each other in any way. There is also another scope - the
global scope
, or the main program's scope. Names within the global scope are readable from anywhere in the program. However, they cannot be assigned
new values outside of the main program's scope. For now everything that is not indented is part of the main program scope. If you try to assign to a main program variable from within a function, Python will instead create a new local variable
with the same name. The Return Trip¶
In the same vein as
values
are given to a function by assigning arguments
to parameters
, values are returned from a function via its return value
. The return value is defined by the return
keyword
like we did in the exercise above. Understanding the difference between variable
and value
is once again essential. When returning, variables are not suddenly released from within the function. What return statement does is that it offers a reference to the value that was returned. It is up to the caller to decide what to do wit it. Here's the previous example, slightly modified:def calculate_distance(speed, time):
distance = speed * time
return distance
v = 100
t = 0.6
s = calculate_distance(v, t)
In this example the
function
returns a reference to the value
of the distance variable
. In the last line of the example the reference is assigned
to a new variable s. The name distance will not be exposed outside the function and the variable itself will cease to exist after the function call has been completed. We do however have a new variable, s, which refers to the same value that distance used to refer to. The value crosses the boundary between function and main program, but the variable does not.As learned earlier, you can basically imagine a function's return value where the function call used to be once the call is completed. What follows is that instead of assigning the result to a variable it can also be passed on as an argument to another function. Which we actually already did:
result = int(round(64 ** (1 / 3)))
A function can also return multiple values. One such function is divmod which performs division and returns both the integer quotient and the remainder. When a function returns multiple values each of those values can be assigned to its own variable. This is simply a matter of putting a matching number of variables on the left side of the
assignment operator
:In [1]: dividend = 10
In [2]: divisor = 3
In [3]: quotient, remainder = divmod(dividend, divisor)
In [4]: print(quotient)
3
In [5]: print(remainder)
1
We actually saw this a moment ago in the exercise where the components of a 2-dimensional vector were returned from a function. If you've followed along so far, you should know that by the time the assignment is done, there are only two numbers on the right side of the assignment operator. Therefore it should also be possible to simply assign two
literal values
to two variables on one line. Turns out it is:In [1]: quotient, remainder = 10, 3
Writing Functions¶
So far it seems the rabbit hole that is
functions
goes way deep. Yet here they are in the beginning of the course. What makes them worth all this trouble?- Functions help in keeping your code in easily digestible pieces.
- Functions can be used to give names to procedures. With descriptive names this makes it easier to follow the program flow.
- You don't need to copy or retype the same code multiple times.
We hope that this list makes you experience enlightenment like the cat in this picture:
An ideal function does one clearly distinctive thing. Its name should also convey what the thing it does is. If a function does not fulfill these requirements it either does too many things or is poorly named.
Another prospect of ideal functions is that they are as reusable as possible - within reason. If a program contains several procedures that are almost similar, in an ideal scenario all of those can be done with one function. The reusability of functions is heavily influenced by the choice of
parameters
. In math this is relatively simple: make all unknowns in a formula function parameters so that they will be given as arguments
when the formula is needed in the program.In general
parametrization
is a skill that develops through programming experience. It can be practised by looking at mostly identical structures and spotting their differences. These differences can often be turned into function parameters. The exercises of this course generally ask you to implement reusable functions. To get the most out of them, you should stop for a while to think why each function has been defined as it has been. Function Placement¶
The placement of
functions
in code does matter. A function is created the moment its definition line - indicated by the def
keyword - is executed. Therefore the definition line must precede any lines that would use the function. At this point it's important to remember that code inside a function is not executed until the function is called
. This means that the order of function definitions does not matter. Code inside a function (A) can call another function (B) that is defined later in the code as long as function A is not called before function B is defined.Since the order doesn't usually matter, you can choose your own system for organizing functions in your code. In general functions should be defined in one clearly separate part of the code. Roughly a code file should be divided into three parts:
- initialization part, for various preparations;
- function definition part, where all functions are defined;
- main program part.
The initialization part does things that are required throughout the program. Generally this means defining
constants
and importing modules
. We'll get to both of these later. The function definition part should define all functions
in the program. It will therefore contain def statements
followed by indented function contents.Finally the main program part is where the program actually starts. Before that all functions should have been defined. Usually all a
main program
does is call
functions. If the functions are well named this makes the flow of the program very clear. In some programming languages the "main program" is placed inside a special main
function and the program starts from that function. A Prompt Lesson¶
Learning goals: After this section you'll know how to print messages to the user and how to prompt input from them. In particular you will know how to obtain a numerical value from the user to be used in the program.
More Vector Geometry¶
In this section we'll work on an earlier example. In an earlier exercise we introduced bunch of code that calculated unit vectors and motion using said unit vectors and a velocity. There is one particular glaring flaw in the program: in order for it to work as a proper vector calculator it would need a way to actually input points without editing the
code file
itself. As a more minor flaw, the program currently just prints out numbers without explaining what they are:4.0 3.0
In other words the user has no way of knowing where these numbers come from and what they mean. There are at least two immediate improvements that can be made: first of all the program should prompt for the vector's start and end points from the user; second it should label the numbers it prints out.
For now let's only make use of the function that calculates the unit vector. The other function can be left in the code, we're just not going to use it. We've included the code file below in case you've lost it. We've also removed any main program lines that are not related to calculating the unit vector, and added lines to print the unit vector.
It Talks!¶
The program should start by telling the user something along these lines: "This program calculates the unit vector between two points on a 2-dimensional plane". In order to do this, we need a new skill: text processing. In programming, text is usually called
(•_•)
( •_•)>⌐■-■
(⌐■_■)
...string. So don't allow your cat anywhere near your code.
string
because it has letters lined up after each other. Almost as if there were letters on a...(•_•)
( •_•)>⌐■-■
(⌐■_■)
...string. So don't allow your cat anywhere near your code.
So far we've used letters to name things like variables and functions in the program. How does Python know when something is a name and when it's a string then? Strings are marked by quotation characters. When a quotation character is encountered Python knows for certain that whatever follows after should be interpreted as a string. Likewise the string will continue until another quotation character is encountered. Single (') and double (") quotes can both be used as long as each string begins and ends with the same type of quote. The same explanation in code:
"string"
'another string'
variable
When it comes to choosing which quote to use, the general rule is just "choose one". I.e. whichever you choose, stick with it. The material uses double quote (") because it's generally more common in other languages. Some languages, e.g. C, only allow single characters to be quoted with single quotes and string must be marked with double quotes.
String
is its own separate type
. It joins the ranks of types that so far consist of integer and float
. Therefore string are values
that are stored in memory just like numbers and everything we've said about values so far also applies to strings. Strings can be assigned
to variables
, and they even support some operations
. They can also be printed with the print function
just like numbers. Therefore the program introduction line ("This program calculates the unit vector between two points on a 2-dimensional plane") can be shown to the user by passing it as an argument
to the print function:print("This program calculates the unit vector between two points on a 2-dimensional plane")
Our vector program should label the vector components it prints. We should also inform the user about the start and end points of the vector since at the moment it cannot be seen from anywhere. Adding the prints:
print("This program calculates the unit vector between two points on a 2-dimensional plane")
initial_x = 2
initial_y = 6
target_x = 10
target_y = 12
vector_x, vector_y = calculate_unit_vector(initial_x, initial_y, target_x, target_y)
print("Starting point (x y):", initial_x, initial_y)
print("Target point (x y):", target_x, target_y)
print("Directional vector (x y):", vector_x, vector_y)
We've also snuck in a new way to use the print function into this example: it can actually be given more than one argument to print. To be more precise, print is a function that prints all of its arguments (number unspecified) separated by spaces and terminated by a newline (in fact you can change the separator and terminator characters as well, but that's a matter for another time). If you run this program, you should see much more informative output:
This program calculates the unit vector between two points on a 2-dimensional plane Starting point (x y): 2 6 Target point (x y): 10 12 Directional vector (x y): 0.8 0.6
Input 101¶
Next we want the program to ask the user to provide the points instead of just letting deciding on its own. For majority of the course we will communicate with the user through text
prompts
because we only need one function
to use them: input
. The first thing to do when learning a new function is to see what Python itself says about it:In [1]: help(input)
Help on built-in function input in module builtins:
input(...)
input([prompt]) -> string
Read a string from standard input. The trailing newline is stripped.
If the user hits EOF (Unix: Ctl-D, Windows: Ctl-Z+Return), raise EOFError.
On Unix, GNU readline is used if enabled. The prompt string, if given,
is printed without a trailing newline before reading.
The basics are easy enough to spot: the function has one
optional
argument
and it returns a string
. The help text itself tells us that when the user terminates the input with the Enter key the newline produced by it is not included in the string returned
by the function. And the prompt parameter
? It contains a message that is displayed to the user. Its purpose is to instruct the user about what they should input.Since input works just like any old
function
the value
it returns
can be assigned
to a variable
. The biggest difference is that the return value is not directly dependent on the given argument
. The argument is just an instruction to the user, and whatever the user decides to input based on that, the function returns. The animation below illustrates how the input function works.Our present vector code's
main program
starts like this (excluding the instruction line):initial_x = 2
initial_y = 6
target_x = 10
target_y = 12
vector_x, vector_y = calculate_unit_vector(initial_x, initial_y, target_x, target_y)
By modifying the first four lines to use input we can obtain values for the
variables
from the user. We can also remove the lines that print the two points. The user should know what the points are since they were just asked to provide them! With these things in mind, the main program changes toprint("This program calculates the unit vector between two points on a 2-dimensional plane")
initial_x = input("Input starting point x coordinate: ")
initial_y = input("Input starting point y coordinate: ")
target_x = input("Input target point x coordinate: ")
target_y = input("Input target point y coordinate: ")
vector_x, vector_y = calculate_unit_vector(initial_x, initial_y, target_x, target_y)
print("Directional vector (x y):", vector_x, vector_y)
We're adding one space to the end of the input function
argument
. This will reduce claustrophobic feelings by separating the prompt message and the user's input from each other:Input starting point x coordinate: 2
Answers Can Be Wrong but Questions Never Are¶
The wall of text achieved in the previous exercise should look something like this:
This program calculates the unit vector between two points on a 2-dimensional plane Input starting point x coordinate: 2 Input starting point y coordinate: 6 Input target point x coordinate: 10 Input target point y coordinate: 12 --------------------------------------------------------------------------- TypeError Traceback (most recent call last) /some/folder/unitvector.py in() 15 target_x = input("Input target point x coordinate: ") 16 target_y = input("Input target point y coordinate: ") ---> 17 vector_x, vector_y = calculate_unit_vector(initial_x, initial_y, target_x, target_y) 18 print("Directional vector (x y):", vector_x, vector_y) /some/folder/unitvector.py in calculate_unit_vector(x0, y0, x1, y1) 1 def calculate_unit_vector(x0, y0, x1, y1): ----> 2 length = ((x1 - x0) ** 2 + (y1 - y0) ** 2) ** 0.5 3 ux = (x1 - x0) / length 4 uy = (y1 - y0) / length 5 return ux, uy TypeError: unsupported operand type(s) for -: 'str' and 'str'
An
error message
consists of two parts: the longer part with indentations shows where in the program the error occurred. The last line describes what kind of error was encountered. The first part shows a traceback
, essentially a chain of function calls
that led to the error. It starts from the main program
and ends with the part that ultimately caused the error. In this case the chain starts from the main program (written as <module>) line 17 where the calculate_unit_vector
function
was called. The error itself is encountered inside the calculate_unit_vector function on line 2. The last line tells us that we encountered a TypeError
exception
. This exception indicates that at least one of the values
in the program has the wrong type
. The description on the same line gives us more details: we're trying to subtract one string
from another. Debugging
is an essential programming skills and learning to read these error messages is an important part of it. Let's go through this thing in detail one more time:Traceback (most recent call last)
- the error message starts; this line indicates that what follows is a traceback of function calls that led to the error and that the most recently called function is last./some/folder/unitvector.py in <module>()
- the first line involved is in the main program level in the file "unitvector.py".---> 17 vector_x, vector_y = calculate_unit_vector(initial_x, initial_y, target_x, target_y)
- the first line that led to the error; the function call to calculate_unit_vector caused it. The surrounding lines are also visible - the arrow points to the line that caused the problem./some/folder/unitvector.py in calculate_unit_vector(x0, y0, x1, y1)
- the second frame in the call chain (and also the last) is found inside the calculate_unit_vector function in the "unitvector.py" file - in other words the function that was called in the previous frame.----> 2 length = ((x1 - x0) ** 2 + (y1 - y0) ** 2) ** 0.5
- the line in the second frame that ultimately caused the error to happen.TypeError: unsupported operand type(s) for -: 'str' and 'str'
- what actually happened: an incompatible type was used (TypeError); the text description gives a more accurate reason: substraction is not possible between two strings.
However although we've been shown the line where the error ultimately happens, this doesn't mean the cause of the error is on this line. And indeed, it isn't in this case.
So the actual problem is that the input
function
produces a string
while we need an integer or a floating point number
. By amazing coincidence what we learned earlier about conversion between these two number types with int
and float
functions comes in handy here as well. As it turns out they can also convert strings to numbers. The requirement is that the string needs to actually look like a number: either an integer or a decimal number. In this case converting to float is better because we never said the coordinates must be integers. This can be done by modifying the input lines:initial_x = type(input("Input starting point x coordinate: "))
After doing this for all input lines the program works as long as the user
inputs
only numbers. If the user doesn't input a number we're in all kinds of fresh and exiting problems. However for this material we're just gonna assume that no one's gonna do such a basic mistake... The updated code file is below:Modules¶
Learning goals: After this section you'll know what modules are and how to use modules that come with Python. More detailed explanations will be given later in the course.
Modules in the Spotlight¶
In this section we're going to only talk about
modules
that are included in Python itself. There's a vast amount of other modules - including every code file
you write - but at this stage we shan't fuss about them. Actually, you've already used one module: the drawing tasks in the pre-exercises use a module called Turtle. Back then in order to utilize it we just used a magic line of code that was not explained in any way: from turtle import *
. Since mysticism is bad for learning it's about time we explained what's going on with this line. From our perspective Turtle is a collection of functions
that for all intents and purposes behave just like any functions we've seen in the material so far. These functions come with Python but they've been packed to a separate module. This is done primarily because most programs don't need these features, and loading them would just produce extra baggage.Functions contained within modules are not automatically loaded up for all Python programs. Instead we need to indicate when we want to use them. This is the purpose of the
import statement
. The magical import statement we used in the pre-exercises is actually a less common method of importing modules. We're now going to introduce the more common and usually more correct way to do it. It may feel sillier at this point but it will keep your code much cleaner when projects start to get bigger. But first we need to talk about namespaces. Names in Space¶
One of the key points in this material has been that names defined inside a function only exist within that function. A similar principle applies to modules. The names defined within a module are said to be part of the module's
namespace
. However as opposed to functions, module names can be exposed to other modules by using the import statement. In its simplest form an import statement is just import module_name
, e.g. for Turtle import turtle
. The difference compared to the magical line from earlier is that this doesn't directly bring us all names from inside Turtle. Instead it only brings one: turtle
. How do we actually use the functions if all we have is some crappy
turtle
? The other names are attributes of the module object. In Python attributes of any object are accessed by prefixing the desired name with the name of the module it resides in and a single dot. E.g. drawing a line forward: turtle.forward(50)
. This method does require more typing but it has a distinct advantage: readers can easily tell with a glance that forward is a function that belongs to the turtle module. If import is done like we did in the pre-exercises, all of the names from that module are brought into our program's namespace. This makes it very hard for anyone reading the code to figure out where each name is coming from. Drawing a circle using
import turtle
would go like this:In [1]: import turtle
In [2]: turtle.circle(30)
Using the from-import syntax can also cause unexpected
name conflicts
. If the names from all imported modules are put into the same namespace the probability of name conflict increases with each added module. If a name conflict happens either the program will simply not run because some variable or function has been overwritten with another. Or perhaps even worse, if the program does run it may have unexpected behavior that is hard to debug.In some cases it gets tiresome and even messy to write the entire module name before each function when the same function is used repeatedly. However, this is not a reason to use heavy weaponry like the
from turtle import *
statement. In that line, the asterisk * is a shorthand for importing everything (i.e. it's a wildcard character). It can be replaced with a list that's been narrowed down to only contain names we actually need. For instance if we want to draw squares like we did in the pre-exercise, the following import would allow us to do it just like we did previously:from turtle import begin_fill, color, done, end_fill, forward, left
The advantage of doing this is that now we're clearly indicating which functions have been imported from Turtle. Another way to alleviate the module name writing problem is to use aliases: modules can be renamed upon import. This is particularly useful when functions from a module with a long name are used repeatedly. In order to do this, we would write an import like this:
import turtle as t
where the rightmost t is the new name. Drawing a circle would now look like:In [1]: import turtle as t
In [2]: t.circle(30)
Mathy Modules¶
As stated previously, for now we will only be using modules that are included in Python. We say "only" but there's quite a few of them already. Although there's a lot of them it's relatively easy to tell what each module could be used for based on their names and short descriptions. Depending on what you're doing, chances are you'll never need majority of them. At the moment we only need Turtle, and now we're adding just one other module to the pile: math. The module contains most common mathematical operations that are not common enough to be included in the list of
built-in functions
. In addition to functions, some modules also contain constants. They will be introduced in more detail in the next section. Constants from modules are used just like functions: prefix with module name and a dot. For instance, the math module contains the commonly used mathematical constants pi and e. These can be accessed in Python from the math module like this:
In [1]: import math
In [2]: math.pi
Out[2]: 3.141592653589793
In [3]: math.e
Out[3]: 2.718281828459045
The values for these constants are as accurate as possible for computers - the
Python console
just doesn't by default show more decimals. We'll play with the math module more in the exercises.Constants¶
Learning goals: After this section you know what constants are, what they are used for and how they affect the rest of the code.
The Cake is a Lie¶
Title is appropriate: Python doesn't actually have
constants
. Constants are nothing more than variables
that are just used in specific ways in code. In general terms constants are values that are usually set at the start of code execution and they cannot be changed. In this sense they are very related to mathematical and physical constants. They can be used to make formulas more readable: when some arbitrary number is given a name instead of using it as a literal value
. Names have meaning, numbers usually don't. This makes the code easier to follow for a reader because they don't need to figure out the meaning of a mystical arbitrary number in the code. There can also be situations where the same value is used in multiple places in the program - if the value needs changing later, it's much easier to change it one place. Constants should be defined in the initialization part of the code, after imports. Since constants are just variables they are created the same way: by
assigning
a value. While constants don't exist as a separate concept in Python, when a variable is roleplaying a constant, its name is written in uppercase.SPEED_OF_LIGHT = 299_792_458
This also shows off a feature introduced in Python 3.6: long numbers can be written by using underscore as a separator to make them more readable.
Since
constants
are defined at the beginning of the code file they belong to the global scope
which makes them conveniently available for reading inside functions
. As we've discussed earlier, functions can read variables from the main program's scope. However, please bear in mind that functions should never use values that it has not received either a) as a parameter
or b) from a constant. Summary¶
This chapter introduced the essential buildings blocks for learning programming.
Variables
are the means that computers use to remember values
during program execution. In Python variable and its value are separate entities: a variable refers
to a value that exists in the computer's memory. Even so, it is often said that variables contain values even though this is not exactly accurate.Our second essential building block are
functions
. Functions act as means to divide a program into smaller components that can be reused. Structuring of code is the main advantage of functions. Small components with a specific purpose are much easier to handle than big wholes that try to do everything. By splitting a program into functions it's possible to solve small problems one at a time which is far more effective than trying to solve the entire problem at once. Functions enable division of tasks within the program.An important aspect related to functions was
scopes
within the program. Particularly important was the principle of separating the internal scope of each function from the global scope
of the main program
. It is one of the more difficult concepts in the beginning. The rule of thumb is that functions should only read values from variables that are their parameter
. In addition they can read values of constants
.We also took a quick glance at how to use
modules
. Modules are a way to wrap together useful functions (and other things) that are related to a certain purpose. We looked at two examples: Turtle, which we use for simple drawing programs; and math which is more useful for serious programs. Scope was also important for modules, and in their case it was referred to as namespaces
. We looked at how the import statement
can be used to decide how to access functions from other namespaces. There will be more about modules in the fourth material where we also learn how to create them.Finally we briefly introduced
constants
that are used instead of repeatedly appearing literal values
. This is done for two reasons: first, a named constant is far more descriptive than an arbitrary value in the code; second, if the value would be present in multiple places, it is much easier to maintain the code if it is only defined in one place. Image Sources¶
- Screen capture of History Channel's Ancient Aliens, caption added.
- original license: CC-BY 2.0 (caption added)
- Screen capture of Inception (movie), caption added.
- original license: CC-BY 2.0 (caption added)
- original license: CC-BY 2.0 (caption added)
- original license: CC-BY 2.0 (caption added)
- oroginal license: CC-BY 2.0 (caption added)
Give feedback on this content
Comments about this material