Overview of Python

Introduction


This tutorial is meant as a brief introduction to the building blocks of Python and, to some extent, computer science in general. Its target audience is someone who is just beginning to learn the fundamentals of computer science, and can be used as a teaching/studying tool. Those reading this guide may wish to read it several times: perhaps every few days while they are beginning their journey into learning Python. The goal should be to know all of these topics and definitions by heart.


Content is organized into different sections that each reinforce various aspects of Python. Each section assumes knowledge of the previous, and attempts to use no knowledge from future sections.


Without further ado, let's begin!


Section 1: Data Types


A data type is a specific representation of a piece of data. Consider some pieces of data about yourself: you have a name, an age, and a home address. Your name is a piece of data represented by a sequence of letters. Everyone's name is really just a sequence of letters, so we can think about the data type of someone's name as being a sequence of letters. Your age is a number, so its data type is a number. Your address is a more complex data type, but generally consists of a house number, street name, city, state, and zip-code. All of those things make up the data type known as an address.


Python comes with several built-in data types that we can use. These are:


  • None: a special type that represents the idea of "nothing." The only valid value of the None type is None.
  • Integers: whole numbers, such as 5 or -12 or 432132. Integers include negative numbers and 0.
  • Floats: decimal numbers, such as 1.5 or 12.5256.
  • Strings: a sequence of characters surrounded by quotes. The quotes themselves are not part of the string, but are just a way to tell Python "the characters between these quotes are a string, not code!" An example of a string is "hello world"
  • Booleans: a value that is either true or false. The only values that a boolean can be is either True or False. Booleans are useful for making logical decisions about which lines of code get executed in a program. We will explore this in a later section.
  • Lists: a collection of items in a specific order. The items inside a list can be different types. To create a list, we write a sequence of comma-separated items surrounded by square brackets, such as ["hello world", "this is a list", 42, -25, 1.5]. If we have a list, we can add items to it, which we will explore later.
  • Tuples: a collection of items in a specific order that cannot be changed. Tuples are identical to lists except that once they are created, they cannot be changed (we cannot add items to them). To create a tuple, we use parentheses instead of square brackets: ("hello world", "this is a tuple", 12, -1.25).
  • Sets: a collection of unique items that have no specific order. Unlike lists and tuples, items in a set do not have an order. You can think of a set as a bag of items. You can add items to the bag, peek inside the bag to find out what's in it, and you can even keep pulling random items out of the bag one at a time until you run out of things in the bag. But, you can't line all the items of the bag up in a specific order. To create a set, you provide a sequence of comma-separated values surrounded by curly braces: {"hello world", "this is a set", 42, True}. The items of a set must be immutable, which we will discuss later.
  • Dictionaries: a collection of (key, value) pairs. A dictionary maps keys to their corresponding values. You can imagine a dictionary like a table with two columns: on the left is a key, and on the right is the value for that key. To create a dictionary, you provide a sequence of comma-separated key value pairs (where the key and value are separated by a colon), surrounded by curly braces. For example, {"luke": 22, "phil": 36, "jacob": 12} is a dictionary. The keys of a dictionary must be immutable, which we will discuss right now!



Section 2: Mutable vs. Immutable


In Python, data types are either mutable or immutable. In English, the word "mutable" means "can change." Likewise, "immutable" means "cannot change." Python types that are immutable are:

  • Integers
  • Floats
  • Booleans
  • Strings
  • Tuples


Python types that are mutable are:

  • Lists
  • Sets
  • Dictionaries


This should make sense, since we can add elements to lists, sets, and dictionaries. Remember that only immutable things can be elements of sets or keys in dictionaries!



Section 3: Variables


It is often useful to create some piece of data and be able to access it again later. To do this, we can create a variable. If we want to define a variable my_string to be "hello world" we would write

my_string = "hello world"

Now, every time we reference my_string, we are really referencing the string "hello world". For example, if we then went on to create a list [my_string, "I used a variable!"], it would really be the list ["hello world", "I used a variable!"].


It's important to note that the value a variable refers to is looked up when we first use it. For example, consider the code

my_string = "hello world"
my_list = [my_string , "I used a variable!"]
my_string = "goodbye world"

The value of my_list is still ["hello world", "I used a variable!"], and NOT ["goodbye world", "I used a variable!"]. This is because my_string is looked up on the second line, when the list is created. 


However, remember that some objects are mutable. Consider the following code:

my_first_list = ["hello world"]
my_second_list = [my_first_list, "how are you?"]
my_first_list[0] = "goodbye world"

(the third line will be discussed in the next section; it is just changing the first element of my_first_list). What is the value of my_second_list now? It will actually be [["goodbye world"], "how are you?"]. This is because my_first_list is a list which is being changed. When my_first_list is evaluated on the second line, it finds a list, and so my_second_list just remembers "okay, my first element is a list. Whatever the current value of that list is will be my first element." Line 3 changes that list, so the change is reflected in my_second_list.


Let's quickly compare this code to

my_first_list = ["hello world"]
my_second_list = [my_first_list, "how are you?"]
my_first_list = ["goodbye world"]

What's the value of my_second_list in this case? It's actually [["hello world"], "how are you?"]! This is because the third line is changing the actual list which my_first_list refers to, not the original list. Before you move on, you should make sure this makes complete sense :)



Section 4: Indexing


A lot of the data types we've seen are collections of items. We should really have a way to access specific items within a collection. We call retrieving a specific item from a collection "indexing into" the collection. To "index into" a collection, we used square brackets surrounding the index we which to get. For example, given the code

my_list = ["hello world", "goodbye world"]
x = my_list[0]

x will have the value "hello world". In Python, indices (plural of index) start at 0, meaning the first item of a list has an index of 0. If the list has n items, the last item has index n - 1


We can also "index into" a string or tuple. For example, given the code

my_tuple = ("hello world", "goodbye world")
x = my_tuple[1]
y = x[0]

x will have a value of "goodbye world" since that is the second (index 1) item of my_tuple. y will have a value of "g" since that is the character at index 0 of the x (which, remember, is the string "goodbye world"). 


We can also "index into" a dictionary to get the value for a specific key. When we "indexed into" a list, tuple, or string, the index we used was an integer that represented the position of the item we wanted. For dictionaries, our index will be the key for the value we want. For example, given the code

my_dict = {"luke": 22, "john": 12}
x = my_dict["luke"]


x will be 22, since that is the value associated with the key "luke"


We can also use indexing to define a value inside a list of dictionary. For example, the code

my_list = ["hello world", "goodbye world"]
my_list[1] = "how are you?"

will result in my_list having a value of ["hello world", "how are you?"]


Likewise, we can change the value for a key in (or add an entirely new key/value pair to) a dictionary! The code

my_dict = {"luke": 22, "john": 12}
my_dict["luke"] = 50
my_dict["jacob"]: 123

will result in my_dict being {"luke": 50, "john": 12, "jacob": 123}


A final note about indexing: if you try to retrieve an element that does not exist (either by providing a list/string an index that's higher than its final item's index, or by providing a dictionary a key it does not have), Python will not know what to do and stop executing code. 



Section 5: Expressions


In Python, it is useful to be able to write out entire expressions that evaluate to some final value. Two common types of expressions are:


  • Arithmetic expressions: expressions that perform some kind of math and result in some type of number (either an integer or decimal). Python supports the standard mathematical operations: addition (+), subtraction (-), multiplication (*), division (/), and exponentiation (**). If we write x = 1 + 2, x will have a value of 3. If we use multiple operations at the same time, Python will follow PEMDAS. x = 1 + 2 * 3 will result in x having a value of 7, since 2 * 3 is performed first. Just like in regular math, we can use parentheses to group together operations that should be performed first. Another common operation is the modulus (%) operation, which returns the remainder of division. For example, if we write x = 7 % 5, x would have a value of 2
  • Logical expressions: expressions that result in a boolean: either True or False. Some common logical operations are less than (<), greater than (>), less than or equal to (<=), greater than or equal to (>=), equal to (==), and not equal to (!=). If we write x = 2 > 1, x would have the value True, since 2 is greater than 1


We can also create complex logical expressions using and and or. If we write x = 2 > 1 and 10 < 20, x will have a value of False. The logical expression a and b evaluates to True if and only if both a and b are themselves True


However, if we wrote x = 2 > 1 or 10 < 20, x would be True. This is because the logical expression a or b evaluates to True if one or both of a or b are True


We can combine and and or expressions to create arbitrarily complex logical expressions. For example, we could write (a and (b or c)) or (d or (e and f)). Try to figure out when this evaluates to True!


Lastly, technically, anything that results in some kind of evaluation to a value is considered an expression. For example, the line "hello world" + " how are you?" is an expression that results in a value of "hello world how are you?" Indeed, strings, like numbers, can be "added" to produce a new string!



Section 6: Functions and Methods


We often want to write re-usable code. That is, pieces of code that can be run multiple times without typing it out every time. To do this, we can create and use functions. 


A function is a block of code that does something and potentially returns a value. If we want to call a function, we write the function name followed by parentheses with arguments, if there are any. A useful function that Python provides is the print function, which takes one argument: the thing to print. What print does it print a text representation of whatever was given to the function to the Python window. For example, print("hello world") will result in the text hello world being printed to the screen.


We can also define our own functions. Suppose we want to write a function called print_hello that only does one thing: it prints "hello world" to the console. We can do that by writing

def print_hello():
	print("hello world")


Now, if we want to call our function, we write can write print_hello()


The first line of our little program is def print_hello():. Let's analyze the parts of this line. First, the keyword def is used to tell Python "I am defining a function." Next, we wrote print_hello, which tells Python "the name of the function I am defining is print_hello." Then, we wrote (). Every function definition has parentheses after the name, which is where we would put the inputs to the function, if it has any. Lastly, we put a : to tell Python "this is the start of the function code."


Every indented line that follows the colon is part of the function. I will repeat this: every indented line that follows the colon is part of the function. If there is a line that is not indented, Python will think the function has no more lines. This is very important to remember when writing Python code!


Note that our function does not take in any arguments. Let's write a new function add_numbers which takes two numbers and prints their sum.

def add_numbers(a, b):
	print(a + b)


We would call this by writing, for instance, add_numbers(1, 2). In our function definition, we wrote (a, b), which tells Python "every instance of the variables a and b in this function refer to the values passed in by whoever called this function." We don't know what values are going to be passed in, so we just use the variables in our function's code.


So far, we've only see functions that "do something." We haven't seen any functions that return anything. Let's change our add_numbers code to not print the sum, but instead return it. 

def add_numbers(a, b):
	return a + b

Now, the code

x = add_numbers(2, 3)
print(x)

would print 5 to the console. Whenever we call a function, we can think of the function's call-site (add_numbers(2, 3) here) as being replaced with whatever the function returns. So, after the first line, x has the value of 5, since add_numbers(2, 3) returned 5. Before moving on, make sure this makes complete sense.


Now, let's discuss methods. Methods are a fancy name for functions that are operating on something. They behave exactly like a function: you can call them using parentheses and pass in arguments inside the parentheses. They "do something" and potentially return a value. The only difference between methods and functions is that methods are "operating" on something.


Let's look at an example of a method. Consider the code

my_list = ["hello world", "goodbye world"]
my_list.append("how are you?")

After this code, my_list has a value of ["hello world", "goodbye world", "how are you?"]. This is because append is a method that operates on a list. What append does is add the argument passed in to the end of the list that it is operating on. So, the list that the variable my_list is referring to does get get replaced, but the list itself changes. Again, this is what we mean when we say lists are mutable.


As you can see in this example, to call a method, you put a . after the thing you are calling the method on, and then call the method like a regular function.



Section 7: If/Else Statements


Often, we want to execute some code only under certain circumstances. To do this, we can use an if/else statement. Consider the following code:

if x > 100:
  print("x is big!")
else:
  print("x is small!")


What this code is doing is making a choice based on the logical expression x > 100. If it's True, it will take the path that prints "x is big!". Otherwise, it will follow the else branch and print "x is small!" Note that just like in our functions, the code "inside" of the if/else branches have to be indented. This is how we tell Python "these lines are part of the if/else branch!" An important thing to note is that one and only one branch is ever taken in an if/else statement. 


We can also perform an arbitrary number of checks. Consider the code

if x > 1000:
	print("x is HUGE!")
elif x > 500:
	print("x is very big!")
elif x > 100:
	print("x is kind of big!")
else:
	print("x is small!")

This code will first determine if x > 1000. If it is, it will take the "x is HUGE!" branch. The rest of the if/else statement is ignored, since one branch was taken.


However, suppose x > 1000 evaluates to False. The next thing this code does is check if x > 500. elif is short for "else if," which means "everything above me was False, so if my logical expression is True..." This process will repeat until all branches are checked and one evaluates to True. If nothing is True, the else branch will be taken. 



Section 8: While Loops


Another way to re-use code is to create a while loop. A while loop is a block of code that gets repeatedly executed until some logical expression is False. Consider the code

x = 0
while x < 10:
	print(x)
	x = x + 1


This will print every number from 0 to 9. While loops evaluate the logical expression at the start of each iteration. The code inside the while loop is executed if any only if the logical expression for that iteration is True


We can easily create an infinite while loop! Consider the code

while True:
	print("oops")

This will print "oops" over and over forever, since the condition the while loop checks never evaluates to False


Note that just like functions and if/else branches, every line included in the while loop's code must be indented.



Section 9: For Loops


Often we want to iterate over a collection of some sort. For example, maybe we want to do something to every item in a list. We can write code like

my_list = ["hello", "goodbye", "world"]
for word in my_list:
	print(word)


This will print every item in my_list. Note that we can replace word with anything we want. for xxxx in yyyy tells Python "iterate over yyyy. In every iteration, make a variable called xxxx that I can use to get the item for the current iteration." 


We can also iterate over tuples, sets, and strings. Tuples work exactly like the above example. Sets work similarly: when we iterate over a set, each item is included once, but the items are iterated in any order. Lists and tuples are iterated over in the order of the items. Since sets do not have an order, we don't know when we're going to get a specific element. 


When we iterate over a string, we actually iterate over its characters. For example,

for char in "hello world":
	print(char)

will print "h" then "e" then "l" and so on. 


Lastly, we can iterate over dictionaries too! Like sets, we don't know what order we will get key/value pairs in. But we can do

my_dict = {"luke": 22, "john": 12}
for name, age in my_dict:
	print(name + " is " + age + "years old")


This will print both "luke is 22 years old" and "john is 12 years old" in some undefined order.



Conclusion


Thus marks the end of the basics of Python. There is of course much more to explore, and I hope whoever is reading this is enjoying their journey through computer science so far! Stay tuned for more lessons, and keep reading, studying, and practicing!!


Copyright 2021 Luke Bordonaro