phyton programming
phyton programming
“high-level programming language, and its core design philosophy is all about code readability and a
syntax which allows programmers to express concepts in a few lines of code.”
Python is one of the easiest languages that you can learn and work with. You can use it for software
development, server side of web development, machine learning, mathematics, and any type of
scripting that you can think of.
The good thing about Python is that it is widely used and accepted by many companies and academic
institutions. This makes it a really good choice, especially if you are just starting your coding journey.
Python also has a large community of developers that use it and are willing to help you if you get stuck
or have questions. This community has already published many open source libraries that you can start
using. They also actively keep improving them.
Python's syntax is quite similar to that of the English language, which makes it easier for you to
understand and use quite intuitively.
Python runs on an interpreter system, meaning that you don't have to wait for a compiler to compile the
code and then execute it. You can instead build prototypes quickly.
It also works on different platforms, such as Windows, Linux, Mac, Rapsberry Pi, and so on.
So you see – it's a great programming language, whether you're just starting out or you're looking to add
another useful tool to your kit.
Here's what we'll cover in this book:
Table of Contents
Python Basics
Operators in Python
Tuples in Python
Dictionaries in Python
Sets in Python
Functions in Python
Importing in Python
Python Basics
Indentation in Python
In other programming languages, white spaces and indentations are only used to make the code more
readable and prettier. But in Python, they represent a sub-block of code.
The following code is not going to work, since the second line is not properly intended:
if 100 > 10:
For it to work properly, the indentation should look like the following:
This may look hard to understand, but you can use a code editor that highlights such syntax errors quite
vividly. Moreover, the more Python code you write, the easier it gets for you to consider such
indentations as second nature.
Comments in Python
We use comments to specify parts of the program that should be simply ignored and not executed by
the Python interpreter. This means that everything written inside a comment is not taken into
consideration at all and you can write in any language that you want, including your own native
language.
In Python, you start a comment with the symbol # in front of the line:
You can also include more extended comments in multiple lines using triple quotes:
"""
Here we are typing in another line and we are still inside the same comment block.
In the previous line we have a space, because it's allowed to have spaces inside comments.
"""
We are going to use the method print() since it helps us see results in the console.
You do not need to know how it works behind the scenes or even know what a method is right now. Just
think of it as a way for us to display results from our code in the console.
Operators in Python
Even though you can just pull out your phone from your pocket and do some calculations, you should
also get used to implementing some arithmetic operators in Python.
When we want to add two numbers, we use the plus sign, just like in math:
print(50 + 4) # 54
print(50 - 4) # 46
print(50 * 4) # 200
print(8 / 4) # 2.0
This often results in float numbers (numbers with a decimal point, that is, not a round number). If we
want to only get integer numbers when doing division, that's called floor division. In that case, we should
use the double forward slashes like this:
print(50 // 4) # 12
print(8 / 4) # 2
We can also find the remainder of a number divided by another number using the percent sign %:
print(50 % 4) # 2
This operation can be helpful especially in cases when we want to check whether a number is odd or
even. A number is odd if, when divided by 2, the remainder is 1. Otherwise, it is even.
Here is an illustration:
print(50 % 4) # 2
print(51 % 4) # 3
When we want to raise a number to a specific power, we use the double asterisk:
print(2 ** 3) # 8
name = "Fatos"
age = 28
We can use this as a way to also swap values between variables. For example, let's say that we have two
variables a and b and we want to switch their values.
One logical way to do that would be to introduce a third variable that serves as a temporary variable:
a, b = 1, 2
print(a) # 1
print(b) # 2
c=a
a=b
b=c
print(a) # 2
print(b) # 1
a, b = 1, 2
print(a) # 1
print(b) # 2
b, a = a, b
print(a) # 2
print(b) # 1
total_sum = 20
current_sum = 10
Now we want to add the value of the current_sum to total_sum. To do that, we should write the
following:
This may not look accurate, since the right hand side is not equal to the left hand side. However, in this
case we are simply doing an assignment and not a comparison of both sides of the equation.
total_sum += current_sum
print(total_sum) # 30
Subtraction:
result = 3
number = 4
print(result) # -1
Multiplication:
product = 3
number = 4
product *= number # This is equal to product = product * number
print(product) # 12
Division:
result = 8
number = 4
print(result) # 2.0
Modulo operator:
result = 8
number = 4
print(result) # 0
Power operator:
result = 2
number = 4
result **= number # This is equal to result = result ** number
print(result) # 16
You likely learned how to do basic comparisons of numbers in school, such as checking whether a
specific number is larger than another number, or whether they are equal.
Equality operators
You can check whether two numbers are equal using the == operator:
print(2 == 3) # False
There is another operator that you can use to check whether 2 numbers are not equal. This is an
operator that you may have not have seen in your math classes written exactly in this way. This is the
operator !=.
print(2 != 3) # True
Inequality operators
Now here we are going to see how to check whether a number is larger than another number:
This is something that you should already know from your math classes.
When trying to check whether a number is greater than or equal to another number, we need to use this
operator >=:
Logical operators
In high school math, you may have learned about logical operators such as and and or.
Briefly speaking, for an expression to evaluate to True when using and, both statements should be true.
In Python, we implement it using and:
This example is going to evaluate to True since 5 is greater than 0, which evaluates to True, and 3 is less
than 5, which also evaluates to True. From this we get True and True, which evaluates to True.
2 is not greater than 5, so the statement in the left hand side is going to evaluate to False. No matter
what's on the right hand side, we are going to get the whole expression equal to False, since False and
any other value (like True) are going to result in False.
When you have statements where at least one of them should evaluate to True, then we should use the
or operator:
This is going to evaluate to True since the statement in the right hand side evaluates to True – so at least
one of them is true.
This is False since 0 is not greater than 2 and also 0 is not greater than 1. So the whole expression is
False.
Variables in Python
You can think about variables as the building blocks of any computer program that you might write.
You can use variables to store values and then reuse them as many times as you want. The moment you
want to change a value, you can just change it in one place and that new value that you just changed is
going to get reflected everywhere else where that variable is used.
A variable name must start with a letter or the underscore character. It cannot start with a number.
A variable name can only contain alpha-numeric characters and underscores (A-z, 0-9, and _ ).
Variable names are case-sensitive, meaning that height, Height, and HEIGHT are all different variables.
age = 28
In this example, we are initializing a variable with the name age and assigning the value 28 to it.
age = 28
salary = 10000
We can use pretty much any name that we want, but it is a better practice to use names that both you
and other any colleagues who work with you can understand.
We have other variable types in Python, such as float numbers, strings, and boolean values. We can even
create our own custom types.
height = 3.5
As you can see, this initialization is quite similar to the ones when we had integer numbers. Here we are
only changing the value on the right. The Python interpreter is smart enough to know that we are
dealing with another type of variable, namely a float type of variable.
Let's see an example of a string:
reader = "Fatos"
We put string values either in double quotes or single quotes to specify the value that we want to store
in string variables.
When we want to store boolean values, we need to use reserved words, namely, True and False.
text_visibile = False
We can also have a boolean value stored when we have expressions that result with a boolean value, for
example, when we compare a number with another number, such as:
is_greater = 5 > 6
This variable is going to get initialized with the value False since 5 is lower than 6.
Numbers in Python
We have three numeric types in Python: integers, floats, and complex numbers.
Integers
Integers represent whole numbers that can be both positive and negative and do not contain any
decimal part.
When adding, subtracting, or multiplying two integers, we get an integer as a result in the end.
print(3 + 5) # 8
print(3 - 5) # -2
print(3 * 5) # 15
print(result) # 81
When we want to divide two integers, we are going to get a float number.
Booleans
The boolean type represents truth values, True and False. You learned the explanation of this type in the
Numbers section, since booleans are indeed subtypes of the integer type.
More specifically, almost always a False value can be considered as a 0, whereas a True value can be
considered as a 1.
print(True * 5) # 5
The exception for such integer representations of Boolean values is when these values are strings such as
"False" and "True".
Floats
Float numbers are numbers that contain the decimal part, such as -3.14, 12.031, 9.3124, and so on.
We can also convert to float numbers using the float() function:
ten = float(10)
print(ten) # 10.0
When adding, subtracting, or dividing two float numbers, or a float number and an integer, we get a float
number as a result in the end:
print(3.4 * 2) # 6.8
print(3.4 + 2) # 5.4
print(3.4 - 2) # 1.4
Complex numbers
Complex numbers have both the real and the imaginary part that we write in the following way:
complex_number = 1 + 5j
print(complex_number) # (1+5j)
Strings in Python
Strings represent characters that are enclosed in either single quotes or double quotes. Both of them are
treated the same:
double_quote_greeting = "Hello. I'm fine." # When using double quotes, we do not need to escape the
apostrophe
When we want to include a new line in a string, we can include the special character \n:
print(my_string)
# I want to continue
Since strings are arrays of characters, we can index specific characters using indexes. Indexes start from 0
and go all the way until the length of the string minus 1.
We exclude the index that is equal to the length of the string since we start indexing from 0 and not from
1.
Here is an example:
string = "Word"
In this example, if we were to select individual characters of the string, they would unfold as follows:
string = "Word"
print(string[0]) # W
print(string[1]) # o
print(string[2]) # r
print(string[3]) # d
We can use negative indexes as well, which means that we start from the end of the string with -1. We
cannot use 0 to index from the end of a string, since -0 = 0:
print(string[-1]) # d
print(string[-2]) # r
print(string[-3]) # o
print(string[-4]) # W
We can also do slicing and include only a portion of the string and not the entire string. For example, if
we want to get characters that start from a specific index until a specific index, we should write it in the
following way: string[start_index:end_index], excluding the character at index end_index:
string = "Word"
print(string[0:3]) # Wor
If we want to start from a specific index and continue getting all the remaining characters of the string
until the end, we can omit specifying the end index, as follows:
string = "Word"
print(string[2:]) # rd
If we want to start from 0 and go until a specific index, we can simply specify the ending index:
string = "Word"
print(string[:2]) # Wo
This means that the value of string is equal to string[:2] (excluding the character at position 2) +
string[2:].
Note that strings in Python are immutable. This means that once a string object has been created, it
cannot be modified, such as trying to change a character in a string.
As an illustration, let's say that we want to change the first character in the string, namely switching W
with A in the following way:
string = "Word"
string[0] = "A"
Now, if we try to print string, we would get an error like the following:
String operators
first = "First"
second = "Second"
You can use this to repeat the string that many times. For example, if we want to repeat a string 5 times,
but we do not want to write it manually five times, we can simply multiply it with the number 5:
string = "Abc"
repeated_version = string * 5
print(repeated_version) # AbcAbcAbcAbcAbc
There are a few built-in methods of strings that we can use that can make it easier for us to manipulate
with them.
len() method
print(len(sentence)) # 10
replace() method
We can use replace() to replace a character or a substring of a string with another character or substring:
string = "Abc"
strip() method
strip() removes white spaces that can be in the beginning or at the end of a string:
print(string.strip()) # Hi there
split() method
We can use split() to convert a string into an array of substrings based on a specific pattern that is
mentioned as the separator.
For example, let's assume that we want to save all words of a sentence in an array of words. These words
are separated by white spaces, so we will need to do the splitting based on that:
print(string.split(" "))
join() method
join() is the opposite of split(): from an array of strings, it returns a string. The process of concatenation is
supposed to happen with a specified separator in between each element of the array that in the end
results in a single concatenated string:
We can use count() to count the number of times a character or a substring appears in a string:
print(string.count("e")) # 2
print(string.count("Hi")) # 1
print(string.count("Hi there")) # 1
find() method
find() lets us find a character or a substring in a string and returns the index of it. In case it does not find
it, it will simply return -1:
print(string.find("3")) # -1
print(string.find("e")) # 5
print(string.find("Hi")) # 0
print(string.find("Hi there")) # 0
lower()
lower() converts all characters of a string into lower case:
print(string.lower()) # hi there
upper()
print(string.upper()) # HI THERE
capitalize() method
We can use capitalize() to covert the first character of a string into uppercase:
print(string.capitalize()) # Hi there
title() method
title() converts starting characters of each word (sequences that are separated by white spaces) of a
string into uppercase:
print(string.title()) # Hi There
isupper() method
isupper() is a method that we can use to check whether all characters in a string are upper case:
string = "are you HERE"
another_string = "YES"
print(string.isupper()) # False
print(another_string.isupper()) # True
islower() method
another_string = "no"
print(string.islower()) # False
print(another_string.islower()) # True
isalpha() method
isalpha() returns True if all characters in a string are letters of the alphabet:
string = "A1"
another_string = "aA"
print(another_string.isalpha()) # True since both `a` and `A` are letters of the alphabet
isdecimal() method
another_string = "3.31"
yet_another_string = "3431"
String Formatting
Formatting a string can be quite useful since you may be using it quite frequently no matter the type of
project or script that you are working on.
Let's first illustrate why we need formatting and include string interpolation.
Imagine that I want to develop a software that greets people the moment they come in, such as:
This now looks great, but I am not the only person who uses it, right?
Now if someone comes and signs in, I will have to use their own names, such as:
This is just my first real user who is registering. I am not counting myself.
Now let's think that I get lucky and the second user also pops in on a Friday morning and our application
should display:
As you can see, we are getting somewhere from a business perspective since two new users just showed
up, but this is not a scalable implementation. We are writing a very static greeting expression.
You should already remember that we are about to mention something that I introduced back at the
beginning.
Yes, we will need to use variables and include a variable next to the string, as follows:
We can use curly braces to specify places where we want to put dynamic values, such as the following:
This is now going to put the first parameter of the method format() inside the first curly braces, which in
our case is Fatos. Then, in the second occurrence of the curly braces, it is going to put the second
parameter of the method format().
If we try to print the value of the string, we should get the following:
print(greeting)
We can specify parameters with indexes inside curly braces like the following that can then be used:
print(greeting)
We can also specify parameters inside the format() method and use those specific words inside curly
braces as a reference:
day_of_the_week="Friday")
We can combine both types of arguments in a single example, such as the following:
short_bio = 'My name is {name}. My last name is {0}. I love {passion}. I like playing {1}.'.format(
'Morina',
'Basketball',
name='Fatos',
passion='Programming'
print(short_bio)
# My name is Fatos. My last name is Morina. I love Programming. I like playing Basketball.
As you can see, using named arguments as opposed to positional ones can be less error-prone, since
their ordering in the format() method does not matter.
We can also use another way of formatting strings which consists of beginning a string with f or F before
the opening quotation marks or triple quotation marks and including names of the variables that we
want to be included in the end:
first_name = "Fatos"
day_of_the_week = "Friday"
continent = "Europe"
Here is another example where we are using a triple quotient after the F:
continent = "Europe"
print(i_am_here) # I am in Europe
Lists in Python
If you take a look at a bookshelf, you can see that the books are stacked and put closely together. You can
see that there are many examples of collecting, and structuring elements in some way.
This is also quite important in computer programming. We cannot just continue declaring countless
variables and manage them that easily.
Let's say that we have a class of students and want to save their names. We can start saving their names
according to the way they are positioned in the classroom:
first = "Albert"
second = "Besart"
third = "Fisnik"
fourth = "Festim"
fifth = "Gazmend"
The list can keep on going which will make it quite hard for us to keep track of all of them.
There is fortunately an easier way for us to put these in a collection in Python called a list.
Let's create a list called students and store in that list all the names declared in the previous code block:
Moreover, this way, it is easier for us to manage and also manipulate the elements in the list.
You may think that, "Well it was easier for me to just call first and get the value stored in there. Now it is
impossible to get a value from this new list, called students".
If we couldn't read and use those elements that we just stored in a list, that would make it less helpful.
Fortunately, lists have indexes, which start from 0. This means that if we want to get the first element in
a list, we need to use index 0 and not index 1 as you may think.
In the example above, the list items have these corresponding indexes:
# Indexes 0, 1, 2, 3, 4
students[0]
students[1]
As you can probably see, we simply need to write the name of the list and also the corresponding index
of the element that we want to get in the square brackets.
This list is, of course, not static. We can add elements to it, like when a new student joins the class.
Let's add a new element in the list students with the value Besfort:
students.append("Besfort")
We can also change the value of an existing element. To do that, we need to simply reinitialize that
specific element of the list with a new value.
students[0] = "Besim"
Lists can contain contain different types of variables, for example, we can have a string that contains
integers, float numbers, and strings:
combined_list = [3.14, "An element", 1, "Another element here"]
Slicing
Similar to strings, lists can also be sliced, which as a result returns a new list. This means that the original
list remains unchanged.
Let's see how we can get the first three elements of a list using slicing:
my_list = [1, 2, 3, 4, 5]
print(my_list[0:3]) # [1, 2, 3]
As you can see, we have specified 0 as the starting index and 3 as the index where the slicing should
stop, excluding the element at the ending index.
If we want to simply start from an index and get all the remaining elements in the list, meaning that the
end_index should be the last index, then we can omit and not have to write the last index at all:
my_list = [1, 2, 3, 4, 5]
print(my_list[3:]) # [4, 5]
Similarly, if we want to start from the beginning of the list and do the slicing until a specific index, then
we can omit writing the 0 index entirely, since Python is smart enough to infer that:
my_list = [1, 2, 3, 4, 5]
print(my_list[:3]) # [1, 2, 3]
Strings in Python are immutable, whereas lists are mutable, meaning that we can modify lists' content
after we declare them.
As an illustration, let's say that we want to change the first character in the string, namely switching S
with B in the following way:
string = "String"
string[0] = "B"
Now, if we try to print string, we would get an error like the following:
Now if we have a list and want to modify its first element, then we can successfully do so:
my_list[0] = 50
We can expand a list by concatenating it with another list using the + operator:
first_list = [1, 2, 3]
second_list = [4, 5]
print(first_list) # [1, 2, 3, 4, 5]
These lists do not need even have to have the same length.
To access elements of a list which is inside a list we need to use double indexes.
Let's see how we can access the element math_points inside the subjects list. Since math_points is an
element in the subjects list positioned at index 0, we simply need to do the following:
Now let's assume that we want to access Math inside the subjects list. Since Math is at index 1, we are
going to need to use the following double indexes:
print(subjects[0][1]) # 'Math'
List methods
len() is a method that you can use to find the length of a list:
We can also expand lists by adding new elements, or we can also delete elements.
We can add new elements at the end of a list using the append() method:
my_list.append("New element")
print(my_list)
If we want to add elements at specific indexes in a list, we can use the insert() method. We specify the
index in the first argument and the element that we want to add in the list as a second argument:
my_list.insert(1, "z")
We can delete elements from lists using the pop() method, which removes the last element in the list:
my_list = [1, 2, 3, 4, 5]
my_list.pop() # removes 5 from the list
print(my_list) # [1, 2, 3, 4]
print(my_list) # [1, 2, 3]
We can also specify the index of an element in the list that indicates which element in the list we should
delete:
my_list = [1, 2, 3, 4, 5]
print(my_list) # [2, 3, 4, 5]
We can also delete elements from lists using the del statement and then specifying the value of the
element that we want to delete:
my_list = [1, 2, 3, 4, 1]
print(my_list) # [2, 3, 4, 5]
print(my_list) # [4, 1]
my_list = [1, 2, 3, 4]
my_list.remove(3)
print(my_list) # [1, 2, 4]
reverse() lets us reverse the elements in a list. This is quite easy and straightforward:
my_list = [1, 2, 3, 4]
my_list.reverse()
print(my_list) # [4, 3, 2, 1]
Index search
Getting elements of a list using indexes is simple. Finding indexes of elements of a list is also easy. We
simply need to use the method index() and mention the element that we want to find inside a list:
print(my_list.index("Python")) # 2
Membership
This is quite intuitive and related to real life: We get to ask ourselves whether something is part of
something or not.
In Python, if we want to check whether a value is part of something, that we can use the operator in:
Since 1 is included in the array [1, 2, 3], the expression evaluates to True.
We can also use it not only with arrays of numbers, but with arrays of characters as well:
Since y is not a vowel and not included in the declared array, the expression in the second line of the
previous code snippet is going to result in False.
Similarly, we can also check whether something is not included using not in:
odd_numbers = [1, 3, 5, 7]
Since 2 is not included in the array, the expression is going to evaluate to True.
Sorting elements in a list is something that you may need to do from time to time. sort() is a built-in
method that makes it possible for you to sort elements in a list in an ascending order alphabetically or
numerically:
my_list = [3, 1, 2, 4, 5, 0]
my_list.sort()
print(my_list) # [0, 1, 2, 3, 4, 5]
alphabetical_list.sort()
There are other methods of lists that we have not included in here.
List comprehension
List comprehension represents a concise way in which we use a for loop to create a new list from an
existing one. The result is always a new list in the end.
Let's start with an example where we want to multiply each number of a list with 10 and save that result
in a new list. First, let's do this without using list comprehension:
Before we write the way we would implement this using list comprehension, let's write a way in which
we would create a list of only numbers that are greater than 0 in another list and increase those positive
numbers by 100:
positive_numbers = [] # Empty list
positive_numbers = [number + 100 for number in numbers if number > 0] # List comprehension
As you can see, this is much shorter and should take less time to write.
Let's take an example where we want to add each element of a list with each element in another list:
first_list = [1, 2, 3]
second_list = [50]
double_lists = [first_element +
In the end, we are going to get a resulting list that has the same number of elements as the list with the
longest length.
Tuples in Python
Tuples are collections that are ordered and immutable, meaning that their content cannot be changed.
They are ordered and we can access their elements using indexes.
print(vehicles)
All the indexing and slicing operations that we have seen in the list section apply for tuples as well:
print(len(vehicles)) # 4
print(vehicles[3]) # Tablet
print(vehicles.index('tablet')) # 3
print(sciences)
Membership check
We can check whether an element is part of a tuple using the operators in and not in just like with lists:
Instead of merging, we can also nest tuples into a single tuple by using tuples that we want to nest inside
the parenthesis:
print(sciences)
Immutability
Since tuples are immutable, we can't change them after we create them. This means that we cannot add
or delete elements in them, or append a tuple to another tuple.
We cannot even modify existing elements in a tuple. If we try to modify an element in a tuple, we are
going to face a problem like the following:
vehicles[0] = 'Truck'
print(vehicles)
There is another data structure in Python that allows us to specify our own custom indexes and not just
numbers. These are called dictionaries, and they are similar to dictionaries that we use to find the
meaning of words we do not understand.
Let's assume that you are trying to learn German and there is a new word that you have not had the
chance to learn before that you just saw at a market: Wasser.
Now, you can pick up your phone and check its corresponding meaning in English using Google Translate
or any other application of your choice. But if you were to use a physical dictionary, you would need to
find this word by going to that specific page and check its meaning sitting right beside it. The reference
or the key for the meaning of this word would be the term Wasser.
Now, if we want to implement this in Python, we should not use lists that have indexes only as numbers.
We should use dictionaries instead.
For dictionaries, we use curly braces and have each element that has two parts: the key and the value.
In our previous example, the key was the German word, whereas the value was its translation in English,
as you can see in the following example:
german_to_english_dictionary = {
"Wasser": "Water",
"Brot": "Bread",
"Milch": "Milk"
Now, when we want to access specific elements in the dictionary, we simply use keys. For example, let's
assume that we want to get the meaning of the word Brot in English. To do that, we can simply reference
that element using that key:
brot_translation = german_to_english_dictionary["Brot"]
print(translation) # Bread
When we print the value that we get, we are going to get the translation in English.
Similarly, we can get the English translation of the word Milch by getting the value of the element that
has Milch as the key:
milch_translation = german_to_english_dictionary["Milch"]
print(milch_translation) # Milk
We can also get the value of an element in a dictionary using get() and specifying the key of the item that
we want to get:
german_to_english_dictionary.get("Wasser")
Dictionaries can have duplicate values, but the all the keys should be unique. Take a look at this example
to see what I mean:
my_dictionary = dict([
('a', 1),
('b', 1),
('c', 2)
])
])
print(words)
# {'abandon': 'to give up to someone or something on the ground', 'abase': 'to lower in rank, office, or
esteem', 'abash': 'to destroy the self-possession or self-confidence of'}
We can add new values inside dictionaries by specifying a new key and a corresponding value. Then
Python is going to create a new element inside that dictionary:
words = {
'a': 'alfa',
'b': 'beta',
'd': 'delta',
words['g'] = 'gama'
print(words)
If we specify the key of an element that is already part of the dictionary, that element is going to be
modified:
words = {
'a': 'alfa',
'b': 'beta',
'd': 'delta',
words['b'] = 'bravo'
print(words)
If we want to remove elements from a dictionary, we can use the method pop() and also specify the key
of the element that we want to delete:
words = {
'a': 'alfa',
'b': 'beta',
'd': 'delta',
words.pop('a')
We can also delete values using popitem() which removes the last inserted key-value pair starting from
Python 3.7. In earlier versions, it deletes a random pair:
words = {
'a': 'alfa',
'b': 'beta',
'd': 'delta',
words['g'] = 'gamma'
words.popitem()
print(words)
There is another way that we can delete elements, namely by using del statement:
words = {
'a': 'alfa',
'b': 'beta',
'd': 'delta',
del words['b']
We can get the length of a dictionary using len() just like with lists and tuples:
words = {
'a': 'alfa',
'b': 'beta',
'd': 'delta',
print(len(words)) # 3
Membership
If we want to check whether a key is already part of a dictionary so that we avoid overriding it, we can
use the operator in and not in just like with lists and tuples:
words = {
'a': 'alfa',
'b': 'beta',
'd': 'delta',
Comprehension
We can use comprehension just like with lists to create dictionaries in a quick way.
To help us with that, we are going to need to use a method called items() that converts a dictionary into
a list of tuples. The element in index 0 is a key, whereas in position with index 1, we have a value.
points = {
'Festim': 50,
'Zgjim': 89,
'Durim': 73
elements = points.items()
Now let's create a new dictionary from this existing dictionary points using comprehension.
We can assume that a professor is in a good mood and generous enough to reward each student with a
bonus of 10 points. We want to add these new points to each student by saving these new points in a
new dictionary:
points = {
'Festim': 50,
'Zgjim': 89,
'Durim': 73
elements = points.items()
points_modified = {key: value + 10 for (key, value) in elements}
Sets in Python
Sets are unordered and unindexed collections of data. Since elements in sets are not ordered, we cannot
access elements using indexes or using the method get().
We cannot add duplicate elements in sets. This means that when we want to remove duplicate elements
from another type of collection, we can make use of this uniqueness in sets.
Let's start creating our first set using curly brackets as follows:
first_set = {1, 2, 3}
Like all data structures, we can find the length of a set using the method len():
print(len(first_set)) # 3
my_set.add(4)
print(my_set) # {1, 2, 3, 4}
If we want to add more than one element, then we need to use method update(). We use as an input for
this method a list, tuple, string or another set:
my_set = {1, 2, 3}
my_set.update([4, 5, 6])
print(my_set) # {1, 2, 3, 4, 5, 6}
my_set.update("ABC")
If we want to delete elements from sets, we can use methods discard() or remove():
my_set = {1, 2, 3}
my_set.remove(2)
print(my_set) # {1, 3}
If we try to delete an element that is not part of the set using remove(), then we are going to get an
error:
my_set = {1, 2, 3}
my_set.remove(4)
print(my_set) # KeyError: 4
To avoid such errors when removing elements from sets, we can use the method discard():
my_set = {1, 2, 3}
my_set.discard(4)
print(my_set) # {1, 2, 3}
If you remember high school math lessons, you should already know about union, intersection, and the
difference between two sets of elements. These operations are supported for sets in Python as well.
Union
Union represents the collection of all unique elements from both sets. We can find the union of two sets
using the pipe operator | or the union() method:
first_set = {1, 2}
second_set = {2, 3, 4}
union_set = first_set.union(second_set)
print(union_set) # {1, 2, 3, 4}
Intersection
Intersection represents the collection that contains elements that are in both sets. We can find it using
operator & or the intersection() method:
first_set = {1, 2}
second_set = {2, 3, 4}
intersection_set = first_set.intersection(second_set)
print(union_set) # {2}
Difference
The difference between two sets represents the collection that contains only the elements that are in
the first set, but not in the second. We can find the difference between two sets using the - operator or
the method difference()
first_set = {1, 2}
second_set = {2, 3, 4}
difference_set = first_set.difference(second_set)
print(difference_set) # {1}
As you can probably remember from high school, ordering of sets when we find the difference of two
sets matters, which is not the case with the union and intersection.
second_set = {2, 3, 4}
first_difference_set = first_set.difference(second_set)
print(first_difference_set) # {1}
second_difference_set = second_set.difference(first_set)
print(second_difference_set) # {3, 4}
Python is an object oriented programming language. That is why it uses constructor functions of classes
to do conversions from one type into another.
int() method
int() is a method that you use do a conversion of an integer literal, float literal (rounding it to its previous
integer number, that is 3.1 to 3), or a string literal (with the condition that the string represents an int or
float literal):
print(three) # 3
four = int(4.8) # converting a float number into its previous closest integer
print(four) # 4
five = int('5') # converting a string into an integer
print(five) # 5
float() method
float() is similarly used to create float numbers from an integer, float, or string literal (with the condition
that the string represents an int or float literal):
int_literal = float(5)
print(int_literal) # 5.0
float_literal = float(1.618)
print(float_literal) # 1.618
string_int = float("40")
print(string_int) # 40.0
string_float = float("37.2")
print(string_float) # 37.2
str() method
We can use str() to create strings from strings, integer literals, float literals, and many other data types:
int_to_string = str(3)
print(int_to_string) # '3'
float_to_string = str(3.14)
print(float_to_string) # '3.14'
string_to_string = str('hello')
print(string_to_string) # 'hello'
Other Conversions
To convert from one type of data structure into another type, we do the following:
destination_type(input_type)
Let us get started with specific types, so that it becomes much clearer.
Conversions to lists
We can convert a set, tuple, or dictionary into a list using the list() constructor.
When converting a dictionary into a list, only its keys are going to make it into a list:
books_dict = {'1': 'Book 1', '2': 'Book 2', '3': 'Book 3'}
If we want to keep both keys and values of a dictionary, we need to use the method items() to first
convert it into a list of tuples where each tuple is a key and a value:
books_dict = {'1': 'Book 1', '2': 'Book 2', '3': 'Book 3'}
print(dict_to_list)
Conversions to tuples
All data structures can be converted to a tuple using the tuple() constructor method, including a
dictionary In that case we get a tuple with the keys of the dictionary:
books_dict = {'1': 'Book 1', '2': 'Book 2', '3': 'Book 3'}
Conversions to sets
Similarly, all data structures can be converted to a set using the set() constructor method, including a
dictionary. In that case we get a set with the keys of the dictionary:
books_dict = {'1': 'Book 1', '2': 'Book 2', '3': 'Book 3'}
Conversions to dictionaries
Conversions into dictionaries cannot be done with any type of sets, lists, or tuples, since dictionaries
represent data structures where each element contains both a key and a value.
Converting a list, or a tuple into a dictionary can be done if each element in a list is also a list with two
elements, or a tuple with two elements.
books_tuple_list = [(1, 'Book 1'), (2, 'Book 2'), (3, 'Book 3')]
books_tuple_list = ([1, 'Book 1'], [2, 'Book 2'], [3, 'Book 3'])
books_list_list = ([1, 'Book 1'], [2, 'Book 2'], [3, 'Book 3'])
In case when we want to convert a set into a dictionary, we need to have each element as a tuple of
length 2.
books_tuple_set = {('1', 'Book 1'), ('2', 'Book 2'), ('3', 'Book 3')}
If we try to do a conversion of a set that has each element as a list of length 2 into a dictionary, we are
going to get an error:
books_list_set = {['1', 'Book 1'], ['2', 'Book 2'], ['3', 'Book 3']}
After we run the last code block, we are going to get an error:
TypeError: unhashable type: 'list'
In conclusion, Python has a variety of data types that you can use to store data. These data types are
important to know so that you can choose the right one for your needs.
Be sure to use the right data type for the task that is in front of you to avoid errors and optimize
performance.
Conditional statements
When you think about ways we think and also communicate with each other, you may get the
impression that we are indeed always using conditions.
If I am hungry, I eat.
This is also something that you can do in programming. We can use conditions to control the flow of the
execution.
To do that, we use the reserved term if and an expression that evaluates to a True or False value. We can
then also use an else statement where we want the flow to continue in cases when the if condition is not
met.
To make it easier to understand, let's assume that we have an example where we want to check whether
a number is positive:
if number > 0:
print("The given number is positive")
else:
If we were to have number = 2: we would enter into the if branch and execute the command that is used
to print the following text in the console:
If we would have another number, such as -1, we would see in the console the following message being
printed:
We can also add additional conditions – and not just 2 like above – by using elif which is evaluated when
the if expression is not evaluated.
if number > 0:
elif number == 0:
else:
Now if we were to have number = 0, the first condition is not going to be met, since the value is not
greater than 0. As you can guess, since the given number is equal to 0, we are going to see the following
message being printed in the console:
Looping / Iterator
Looping represents the ability of the program to execute a set of instructions over and over again until a
certain condition is met. We can do it with both while and for.
This looping is simple and quite straightforward. All you have to do is specify a starting state and mention
the range in which it should iterate, as you can see in the following example:
print(number)
In this example, we are iterating from 1 to 7 and printing each number (from 1 up to 7 excluding 7) in the
console.
We can change both the starting and the ending numbers in the range as we want. This way, we can be
quite flexible depending on our specific scenarios.
Let's now describe iterations with while. This is also another way of doing iterations that is also quite
straightforward and intuitive.
Here we need to specify a starting condition before the while block and also update the condition
accordingly.
The while loop needs a “loop condition.” If it stays True, it continues iterating. In this example, when
num is 11 the loop condition equals False.
number = 1
print(number)
number += 1 # This part is necessary for us to add so that the iteration does not last forever
This while block is going to print the same statements as the code we used with the for block.
Now that we have covered both iteration and lists, we can jump into ways of iterating through lists.
We do not just store things in data structures and leave them there for ages. We are supposed to be able
to use those elements in different scenarios.
print(student)
Yes, it's that simple. We are iterating through each element in the list and printing their values.
We can do this for dictionaries as well. But since elements in dictionaries have 2 parts (key and the
value), we need to specify both the key and the value as follows:
german_to_english_dictionary = {
"Wasser": "Water",
"Brot": "Bread",
"Milch": "Milk"
print("The German word " + key + " means " + value + " in English")
We can also get only keys from the elements of the dictionary:
print(key)
Note that key and value are simply variable names that we have chosen to illustrate the iteration. But we
can use any name that we want for our variables, such as the following example:
print("The German word " + german_word + " means " + english_translation + " in English")
This iteration is going to print the same thing in the console as the code block before the last one.
We can also have nested for loops. For example, let's say that we want to iterate through a list of
numbers and find a sum of each element with each other element of a list. We can do that using nested
for loops:
numbers = [1, 2, 3]
sum_of_numbers = [] # Empty list
for second_number in numbers: # Loop through the list and add the numbers
# add current first_number from the first_list to the second_number from the second_list
sum_of_numbers.append(current_sum)
print(sum_of_numbers)
# [2, 3, 4, 3, 4, 5, 4, 5, 6]
Sometimes we may need to exit a for loop before it reaches the end. This may be the case when a
condition has been met or we have found what we were looking for and there is no need to continue any
further.
In those situations, we can use break to stop any other iteration of the for loop.
Let's assume that we want to check whether there is a negative number in a list. In case we find that
number, we stop searching for it.
break
# Current number: 1
# Current number: 2
# Current number: -3
As we can see, the moment we reach -3, we break from the for loop and stop.
There can also be cases when we want to skip certain iterations since we are not interested in them and
they do not matter that much. We can do that using continue which prevents code execution below it in
that code block and moves the execution procedure towards the next iteration:
my_sum = 0
continue
my_sum += element
print(my_sum) # 7
pass is a statement we can use to help us when we are about to implement a method or something but
we haven't done it yet and do not want to get errors.
It helps us execute the program even if some parts of the code are missing:
my_list = [1, 2, 3]
pass # Do nothing
In conclusion, Python offers conditional statements to help you control the flow of your program.
The if statement lets you run a block of code only if a certain condition is met. The elif statement lets you
run a block of code only if another condition is met. And the else statement lets you run a block of code
only if no other condition is met.
These statements are very useful for controlling the flow of your program.
Functions in Python
There are plenty of cases when we need to use the same code block again and again. Our first guess
would be to write it as many times as we want.
Objectively, it does work, but the truth is, this is a really bad practice. We are doing repetitive work that
can be quite boring and it's also prone to more mistakes that we might overlook.
This is the reason why we need to start using code blocks that we can define once and then use that
same code anywhere else.
Just think about this in real life: You see a YouTube video that has been recorded and uploaded to
YouTube once. It is then going to be watched by many other people, but the video still remains the same
one that was uploaded initially.
In other words, we use methods as a representative of a set of coding instructions that are then
supposed to be called anywhere else in the code and that we do not have to write it repeatedly.
In cases when we want to modify this method, we simply change it at the place where it was first
declared and other places where it is called do not have to do anything.
To define a method in Python, we start by using the def keyword, then the name of the function and
then a list of arguments that we expect to be used. After that, we need to start writing the body of the
method in a new line after an indentation.
return our_sum
As you can see from the syntax highlighting, both def and return are keywords in Python that you cannot
use to name your variables.
Now, everywhere we want this add() to be called, we can just call it there and not have to worry about
implementing it entirely.
Since we have defined this method, we can call it in the following way:
result = add(1, 5)
print(result) # 6
You might think that this is such a simple method and start asking, why are we even bothering to write a
method for it?
You are right. This was a very simple method just to introduce you to the way we can implement
functions.
Let's write a function that finds the sum of numbers that are between two specified numbers:
result = 0
starting_number = starting_number + 1
return result
This is now a set of instructions that you can call in other places and do not have to write all of it again.
result = sum_in_range(1, 5)
print(result) # 10
Note that functions define a scope, which means that variables which are defined in that scope are not
accessible outside it.
For example, we cannot access the variable named product outside the scope of the function:
product = 1
starting_number = starting_number + 1
return product
When we call functions, we may make some of the arguments optional by writing an initial value for
them at the header of the function.
Let's take an example of getting a user's first name as a required argument and let the second argument
be an optional one.
We can now call that same function even though the the second argument is not specified:
user = get_user("Durim")
print(user) # Hi Durim
print(user) # Hi Gashi
As you can see, we can omit first_name since it is not required. We can also change the ordering of the
arguments when calling the function and it will still work the same:
Data Lifecycle
Variables that are declared inside a function cannot be accessed outside it. They are isolated.
def counting():
counting()
print(count) # This is going to throw an error when executing, since count is only declared inside the
function and is not acessible outside that
Similarly, we cannot change variables inside functions that have been declared outside functions and
that are not passed as arguments:
count = 3331
def counting():
counting()
print(count) # 3331
# This is declared outside the function and has not been changed
We can change mutable data that is passed through a function as arguments. Mutable data represents
data that we can modify even after it has been declared, Lists, for example, are mutable data.
def capitalize_names(current_list):
for i in range(len(current_list)):
current_list[i] = current_list[i].capitalize()
print("Inside the function:", current_list)
return current_list
print("Outside the function:", names) # Outside the function: ['Betim', 'Durim', 'Gezim']
In case of immutable data, we can only modify the variable inside a function, but the actual value
outside that function is going to remain unchanged. Immutable data are strings and numbers:
name = "Betim"
def say_hello(current_param):
return current_param
print("Value outside the function:", name) # Value outside the function: Betim
If we really want to update immutable variables through a function, we can assign a return value of a
function to the immutable variable:
name = "Betim"
def say_hello(current_param):
return current_param
# Here we are assigning the value of name to the current_param that is returned from the function
Lambda Functions
Lambda functions are anonymous functions that we can use to return an output. We can write lambda
functions using the following syntax pattern:
We'll start with a function that multiples each input with 10:
tenfold = lambda number : number * 10
print(tenfold(10)) # 100
Let's write another example in which we check whether the given argument is positive or not:
print(is_positive(3)) # 3 is positive
Note that we cannot use the if clause without the else clause inside a lambda function.
At this point, you may wonder, why do we need to use lambda functions, since they seem to be almost
the same as other functions?
So far, we have seen ways of calling functions using numbers and strings. We can actually call functions
with any type of Python object.
We can even provide an entire function as the argument of a function, which can provide a level of
abstraction that can be quite useful.
Let's see an example where we want to do a few conversions from one unit into another:
def convert_to_meters(feet):
def convert_to_feet(meters):
def convert_to_miles(kilometers):
def convert_to_kilometers(miles):
Now, we can make a general function and pass another function as an argument:
return operation(argument)
print(result) # 6.2137119223733395
As you can see, we have written convert_to_miles as a parameter of the function conversion(). We can
use other already defined functions like that:
result = conversion(convert_to_feet, 310)
print(result) # 1017.0603674540682
We can now make use of lambdas and make this type of abstraction much simpler.
Instead of writing all those four functions, we can simply write a concise lambda function and use it as a
parameter when calling the conversion() function:
return operation(argument)
print(result) # 6.2137119223733395
map() function
map() is a built-in function that creates a new object by getting results by calling a function on each
element of an existing list:
map(function_name, my_list)
my_list = [1, 2, 3, 4]
my_list = [1, 2, 3, 4]
filter() function
This is another built-in function that we can use to filter elements of a list that satisfy a condition.
Let's first filter out negative elements from a list using list comprehension:
Now, we'll filter elements using filter() and a lambda function. This function returns an object which we
can convert into a list using list():
non_negative_list = list(non_negative_filter_object)
You should now understand how you can call functions with other functions as arguments and why
lambdas are useful and important.
Decorators in Python
We can think of it as a dynamic way of changing the way a function, method, or class behaves without
having to use subclasses.
Once a function is being passed as an argument to a decorator, it will be modified and then returned as a
new function.
def reverse_list(input_list):
return input_list[::-1]
def reverse_list(input_list):
return input_list[::-1]
return another_function(input_list)
print(result) # [3, 2, 1]
def reverse_input_list(input_list):
# reverse_list() is now a local function that is not accessible from the outside
def reverse_list(another_list):
return another_list[::-1]
result = reverse_list(input_list)
return result # Return the result of the local function
print(result) # [3, 2, 1]
In this example, reverse_list() now is a local function and cannot be called outside the scope of the
reverse_input_list() function.
def reverse_list_decorator(input_function):
def function_wrapper():
returned_result = input_function()
reversed_list = returned_result[::-1]
return reversed_list
return function_wrapper
reverse_list_decorator() is a decorator function that takes as input another function. To call it, we need
to write another function:
def get_list():
return [1, 2, 3, 4, 5]
Now we can call the decorator with our new function as an argument:
print(result_from_decorator) # [5, 4, 3, 2, 1]
def reverse_list_decorator(input_function):
def function_wrapper():
returned_result = input_function()
reversed_list = returned_result[::-1]
return reversed_list
return function_wrapper
def get_list():
return [1, 2, 3, 4, 5]
decorator = reverse_list_decorator(get_list)
result_from_decorator = decorator()
# We can now print the result in the console
print(result_from_decorator) # [5, 4, 3, 2, 1]
We can also call a decorator using annotations. To do that, we use the @ sign before the name of the
decorator that we want to call and put it right above the name of the function:
def get_list():
return [1, 2, 3, 4, 5]
Now, we can simply call the functionget_list() and the decorator is going to be applied in it:
result_from_decorator = get_list()
print(result_from_decorator) # [5, 4, 3, 2, 1]
We can also use more than one decorator for a single function. Their order of execution starts from top
to bottom, meaning that the decorator that has been defined first is applied first, then the second one,
and so on.
Let's do a simple experiment and apply the same decorator that we defined in the previous section
twice.
Then we apply it again, but now with the returned result from the previous calling of the decorator:
In other words, reversing a list and then reversing that reversed list again is going to return the original
ordering of the list.
@reverse_list_decorator
@reverse_list_decorator
def get_list():
return [1, 2, 3, 4, 5]
result = get_list()
print(result) # [1, 2, 3, 4, 5]
Let's implement another decorator that only returns numbers that are larger than 1. We then want to
reverse that returned list with our existing decorator.
def positive_numbers_decorator(input_list):
def function_wrapper():
return numbers
return function_wrapper
Now we can call this decorator and the other decorator that we have implemented:
@positive_numbers_decorator
@reverse_list_decorator
def get_list():
result = get_list()
print(result) # [9, 7, 5, 3, 1]
def reverse_list_decorator(input_function):
def function_wrapper():
returned_result = input_function()
return reversed_list
return function_wrapper
# First decoorator
def positive_numbers_decorator(input_list):
def function_wrapper():
return numbers
return function_wrapper
@positive_numbers_decorator
@reverse_list_decorator
def get_list():
result = get_list()
print(result) # [9, 7, 5, 3, 1]
return result
return function_wrapper
@add_numbers_decorator
return a + b
Built-in decorators
Python comes with multiple built-in decorators, such as @classmethod, @staticmethod, @property, and
so on. We'll cover these in the next chapter.
Function Wrap Up
Python is an excellent language for writing functions because they are easy to write.
Lambda functions are a great way to make small, concise functions in Python. They're perfect for when
you don't need a full-blown function, or when you just want to test out a snippet of code.
Python decorators are a great way to improve code readability and maintainability. They allow you to
modularize your code and make it more organized. You can also use them to perform various tasks such
as logging, exception handling, and testing. So if you're looking for a way to clean up your Python code,
consider using decorators.
If you go to buy a cookie at a local store, you are going to get a version of the cookie that has been
produced in many other copies.
There's a cookie cutter at a factory that has been used to produce a large number of cookies that are
then distributed all throughout different stores where those cookies are then served to the end
customers.
We can think of that cookie cutter as a blueprint that has been designed once and is used many times
afterwards. We also use this sort of blueprint in computer programming.
A blueprint that is used to create countless other copies is called a class. We can think of a class like a
class called Cookie, Factory, Building, Book, Pencil, and so on. We can use the class of cookie as a
blueprint to create as many instances as we want of it that we call objects.
In other words, blueprints are classes that are used as cookie cutters, whereas the cookies that are
served at different stores are objects.
Object Oriented Programming represents a way of organizing a program using classes and objects. We
use classes to create objects. Objects interact with each other.
We do not use the exact same blueprint for every object that is out there. There is a blueprint for
producing books, another one for producing pencils, and so on. We need to categorize them based on
attributes and their functionalities.
An object that is created from the Pencil class can have a color type, a manufacturer, a specific thickness,
and so on. These are the attributes. A pencil object can also write which represents its functionality, or
its method.
We use classes and objects in different programming languages, including Python.
class Bicycle:
pass
We have used the keyword class to indicate that we are about to start writing a class and then we type
the name of the class.
We have added the pass because we don't want the Python interpreter to yell at us by throwing errors
for not continuing to write the remaining part of the code that belongs to this class.
Now, if we want to create new objects from this class Bicycle, we can simply write the name of the
object (which can be any variable name that you want) and initiailize it with the constructor method
Bicycle() that is used to create new objects:
favorite_bike = Bicycle()
In this case, favorite_bike is an object that is created from the class Bicycle. It gets all the functionalities
and attributes of the class Bicycle.
We can enrich our Bicycle class and include additional attributes so that we can have custom bikes,
tailored to our needs.
class Bicycle:
self.color = color
self.is_mountain_bike = is_mountain_bike
Note the usage of underscores before and after the name init of the method. They represent indicators
to the Python interpreter to treat that method as a special method.
This is a method that does not return anything. It is a good practice to define it as the first method of the
class, so that other developers can also see it being at a specific line.
Now, if we want to create new objects using this blueprint of bicycles, we can simply write:
We have provided our custom parameters for this bike and are passing them to the constructor method.
Then we get a new bike with those specific attributes in return. As you can probably tell, we are creating
a grey mountain bike of the brand Connondale.
We can also create objects from classes by using optional arguments as follows:
class Bicycle:
self.manufacturer = manufacturer
self.color = color
self.is_mountain_bike = is_mountain_bike
Now we have just created this object with these attributes, which are not currently accessible outside
the scope of the class.
This means that we have created this new object from the Bicycle class, but its corresponding attributes
are not accessible. To access them, we can implement methods that help us access them.
To do that, we are going to define getters and setters, which represent methods that we use to get and
set values of attributes of objects. We are going to use an annotation called @property to hep us with
that.
class Bicycle:
self._manufacturer = manufacturer
self._color = color
self._is_mountain_bike = is_mountain_bike
@property
def manufacturer(self):
return self._manufacturer
@manufacturer.setter
self._manufacturer = manufacturer
print(bike.manufacturer) # Connondale
We can write getters and setters for all the attributes of the class:
class Bicycle:
self._manufacturer = manufacturer
self._color = color
self._is_mountain_bike = is_mountain_bike
@property
def manufacturer(self):
return self._manufacturer
@manufacturer.setter
self._manufacturer = manufacturer
@property
def color(self):
return self._color
@color.setter
self._color = color
@property
def is_mountain_bike(self):
return self._is_mountain_bike
@is_mountain_bike.setter
self.is_mountain_bike = is_mountain_bike
Now that we have defined them, we can call these getter methods as attributes:
print(bike.manufacturer) # Connondale
print(bike.color) # Grey
print(bike.is_mountain_bike) # True
We can also modify the value that we initially used for any attribute by simply typing the name of object
and the attribute where we want to change the content:
bike.is_mountain_bike = False
bike.color = "Blue"
bike.manufacturer = "Trek"
Our classes can also have other methods as well and not just getters and setters.
Let's define a method inside the class Bicycle that we can then call from any object that we have created
from that class:
class Bicycle:
self._color = color
self._is_mountain_bike = is_mountain_bike
def get_description(self):
desc = "This is a " + self._color + " bike of the brand " + self._manufacturer
return desc
We have created a very simple method in which we are preparing a string as a result from the attributes
of the object that we are creating. We can then call this method like any other method.
Methods in Python
In a nutshell, we group a few statements in a code block called method. There we perform some
operations that we expect to be done more than once and do not want to write them again and again. In
the end, we may not return any result at all.
instance methods
class methods
static methods
Let's briefly talk about the overall structure of methods and then dive a little more into detail for each
method type.
Parameters
Parameters of a method make it possible for us to pass on dynamic values that can then be taken into
consideration when executing the statements that are inside the method.
The return statement represents the statement that is going to be the last one to be executed in that
method. It is an indicator for the Python interpreter to stop the execution of any other line and return a
value.
The first argument of a method in Python is self which is also one of the differences between a method
and a function. It represents a reference to the object to which it belongs to. If we don't specify as the
first argument of the method when being declared, the first argument is then treated as a reference to
the object.
We only write it when we declare the method, but we do not need to include it when we invoke that
particular method using an object as a caller.
It is not required that we name it self, but it is a convention that is widely practiced by developers writing
Python code all around the world.
Let's define an instance method inside the class Bicycle that we can then call from any object that we
have created from that class:
class Bicycle:
self._manufacturer = manufacturer
self._color = color
self._is_mountain_bike = is_mountain_bike
def get_description(self):
desc = "This is a " + self._color + " bike of the brand " + self._manufacturer
return desc
We have created a very simple method in which we are preparing a string as a result from the attributes
of the object that we are creating. We can then call this method like any other method:
# We are not passing any argument when calling the method get_description() since we do not need to
include self at all
Class methods
We have covered instance methods so far. These are methods that we can call with objects.
Class methods are methods that we can call using class names and that we can access without needing
to create any new object at all.
Since it is a specific type of method, we need to tell the Python interpreter that it is actually different.
We do that by making a change in the syntax.
We use the annotation @classmethod above a class method and cls similar to the usage of self for
instance methods. cls is just a conventional way of referring to the class that is calling the method – you
don't have to use this name.
blog = 'https://www.python.org/'
self.title = title
self.content = content
@classmethod
def get_blog(cls):
return cls.blog
Now let's call this class method that we have just declared:
print(Article.get_blog()) # https://www.python.org/
Note that we did not have to write any argument when calling the get_blog() method. On the other
hand, when we declare methods and instance methods, we should always include at least one
argument.
Static methods
These are methods that do not have direct relations to class variables or instance variables. You can think
of them as utility functions that are supposed to help us do something with arguments that are passed
when calling them.
We can call them by using both the class name and an object that is created by that class where this
method is declared. This means that they do not need to have their first argument related to the object
or class calling them (as was the case with using parameters self for instance methods and cls for class
methods).
There is no limit to the number of arguments that we can use to call them.
class Article:
blog = 'https://www.python.org/'
self.title = title
self.content = content
@classmethod
def get_blog(cls):
return cls.blog
@staticmethod
def print_creation_date(date):
Static methods cannot modify class or instance attributes. They are meant to be like utility functions.
class Article:
blog = 'https://www.python.org/'
self.title = title
self.content = content
@classmethod
def get_blog(cls):
return cls.blog
@staticmethod
Article.set_title('2022-07-21')
This is because static methods do not have any reference to self as they are not directly related to
objects or classes and so they cannot modify attributes.
Access modifier
When creating classes, we can restrict access to certain attributes and methods so that they are not
accessible that easily.
Public attributes
Public attributes are the ones that are accessible from both inside and outside the class.
By default, all attributes and methods are public in Python. If we want them to be private, we need to
specify that.
class Bicycle:
self.color = color
self.is_mountain_bike = is_mountain_bike
def get_manufacturer(self):
return self.manufacturer
In the previous code block, both color and get_manufacturer() are accessible outside the class since they
are public and can be accessed both inside and outside the class:
print(bike.color) # Grey
print(bike.get_manufacturer()) # Connondale
Private attributes
Private attributes can be accessed directly only from inside the class.
We can make properties attributes by using the double underscore, as you can see in the following
example:
class Bicycle:
self.manufacturer = manufacturer
self.color = color
self.is_mountain_bike = is_mountain_bike
Let's now see an example where we are declaring private methods using the double underscore in front
of the name of the method that we want to make private:
class Bicycle:
self.manufacturer = manufacturer
self.color = color
self.is_mountain_bike = is_mountain_bike
return self.__old
Now, if we want to call this private method from outside the class, an error is going to be thrown:
It is not a common practice to have private variables in Python. However, developers may find it
necessary to restrict access so that specific variables are not carelessly accessed and modified.
When you go out there and use a coffee machine, you aren't expected to know all the engineering
details that are behind that machine.
This is the same with your car. When you sit in your driver's seat, you do not analyze and understand all
the details of every part of the car. You have some basic idea about them, but other than that, you just
focus on driving.
This is a sort of restriction of access from people outside, so that they do not have to worry about exact
details that are going on inside.
We have seen so far the foundational blocks of object oriented programming, such as classes and
objects.
Classes are blueprints that are used to create instances called objects. We can use objects of different
classes to interact with each other and build a robust program.
When we work on our own programs, we may need to not let everyone know about all the details that
our classes have. So we can limit access to them, so that certain attributes are less likely to be accessed
unintentionally and be modified wrongfully.
To help us with that, we hide parts of a class and simply provide an interface that has fewer details about
the inner workings of our class.
Encapsulation
Abstraction
Encapsulation is not something special and unique just for Python. Other programming languages use it
as well.
In a nutshell, we can define it as binding data and methods in a class. We then use this class to create
objects.
We encapsulate classes by using private access modifiers that can then restrict direct access to such
attributes. This can restrict control.
We are then supposed to write public methods that can provide access to the outside world.
Let's define first define a getter and a setter method that we can use to get values:
class Smartphone:
self.__type = type # setting the type here in the beginning when the object is created
self.__type = value
def get_type(self):
return (self.__type)
Now, let's use this class to set the type and also get the type:
smartphone = Smartphone('iPhone') # we are setting the type using the constructor method
print(smartphone.get_type()) # iPhone
smartphone.set_type('Samsung')
print(smartphone.get_type()) # Samsung
What we have done so far is set and also read the value of a private attribute of an object created from
the Smartphone class.
We can also define getters and setters using the @property annotation.
class Bicycle:
self._manufacturer = manufacturer
self._color = color
@property
def manufacturer(self):
return self._manufacturer
@manufacturer.setter
self._manufacturer = manufacturer
@property
def color(self):
return self._color
@color.setter
self._color = color
Now that we have defined them, we can call these getter methods as attributes:
print(bike.manufacturer) # Connondale
print(bike.color) # Grey
We can also modify the value that we initially used for any attribute by simply typing the name of the
object and the attribute that we want to modify:
bike.is_mountain_bike = False
bike.color = "Blue"
Our classes can also have other methods as well, and not just getters and setters.
Let's define a method inside the class Bicycle that we can then call from any object that we have created
from that class:
class Bicycle:
self._manufacturer = manufacturer
self._color = color
self._is_mountain_bike = is_mountain_bike
def get_description(self):
desc = "This is a " + self._color + " bike of the brand " + self._manufacturer
return desc
We have created a very simple method in which we are preparing a string as a result from the attributes
of the object that we are creating. We can then call this method like any other method.
This looks quite promising and fancy, but you may not get it quite yet. You might need some additional
reasons why you need this type of hiding.
To drive this home, let's take another class, where we have a private attribute called salary. Let's say that
we don't care about encapsulation and we are only trying to build a class fast and use it in our project for
our accountant client.
class Employee:
self.name = name
self.email = email
self.salary = salary
Now, let's create a new employee object and initialize its attributes accordingly:
print(betim.salary) # 5000
Since salary is not being protected in any way, we can set a new salary for this new object without any
problem:
betim.salary = 25000
print(betim.salary) # 25000
As we can see, this person got five times the salary of what he was getting previously without going
through any type of evaluation or interviewing at all. In fact, it happened in a matter of seconds. That's
probably going to hit the budget of the company heavily.
We do not want to do that. We want to restrict access to the salary attribute so that it is not called from
other places. We can do that by using the double underscore before the attribute name as you can see
below:
class Employee:
self.__name = name
self.__email = email
self.__salary = salary
Now, if we try to access its attributes, we cannot do so, since they are private attributes:
print(betim.salary) # 1000
We can simply implement a method that returns the attributes but we are not providing any way for
someone to increase their salary sneakily:
class Employee:
self.__name = name
self.__email = email
self.__salary = salary
def get_info(self):
In summary, encapsulation helps us protect properties of objects and provides access them in a
controlled fashion.
Inheritance in Python
In real life, we can share many characteristics with other human beings.
We all need to eat food, drink water, work, sleep, move, and so on. These and many other behaviors and
characteristics are shared among billions of people all around the world.
They are not something unique that only our generation has. These traits have been around as long as
humans have.
This is also something that is going to last for future generations to come.
We can also have certain shared characteristics between objects and classes that we implement
ourselves in computer programming using inheritance. This includes both attributes and methods.
Let's imagine that we have a class called Book. It should contain a title, an author, a number of pages, a
category, an ISBN, and so on. We are going to keep our class simple and use only two attributes:
class Book:
self.title = title
self.author = author
def get_short_book_paragraph(self):
return short_paragraph
Now, we can create an object from this class and access it:
Let's now create a subclass of the class Book that inherits attributes and methods from the class Book,
but also has an additional method called get_book_description():
class Book:
self.title = title
self.author = author
def get_short_book_paragraph(self):
short_paragraph = "This is a short paragraph of the book."
return short_paragraph
class BookDetails(Book):
def get_book_details(self):
return description
Note the syntax in which we tell Python that BookDetails is a subclass of the class Book:
class BookDetails(Book):
If we try to access this new method from objects of the class Book, we are going to get an error:
print(first_book.get_book_details())
This happens because this method get_book_details() can be accessed only from objects of BookDetails:
We can, however, access any method that is defined in the parent class, which in our case is the Book
class:
print(first_book_details.get_short_book_paragraph())
In the previous classes, Book is considered a parent class or a superclass, whereas BookDetails is
considered a child class, or a subclass.
super() function
There is a special function called super() that we can use from a child class to refer to its parent class
without writing the exact name of the parent class.
We can use super() inside the constructor method of the subclass and even call the constructor of the
super class:
class Animal():
self.age = age
class Cat(Animal):
self.health = 100 # initializing a new attribute that is not in the parent class
We can also replace super() with the name of the parent class, which is going to work in the same way
again:
class Animal():
self.name = name
self.age = age
class Cat(Animal):
self.health = 100 # initializing a new attribute that is not in the parent class
Even changing the order of the lines inside the child's constructor will not cause any error at all.
We can use super() to access class properties of the parent class, which can be useful especially when
both the parent and the child class use the same name for an attribute.
To see that in action, let's assume that we have a class attribute called name which is present both in the
parent and the child class. We want to access this variable from both the parent class and the child class.
To do that, we simply need to write super() and then the name of the variable:
name = 'Samsung'
name = 'Amazon'
def get_product_details(self):
print("Producer:", super().name)
print("Seller:", self.name)
Now, if we call method get_product_details(), we are going to get the following printed in the console:
seller = Seller()
seller.get_product_details()
# Producer: Samsung
# Seller: Amazon
name = 'Samsung'
def get_details(self):
name = 'Amazon'
def get_details(self):
print(super().get_details())
seller = Seller()
seller.get_details()
# Producer name: Amazon
Types of inheritance
We can have different types of inheritance based on the relationship of parent classes and child classes:
Single
Multi-level
Hierarchical
Multiple
Hybrid
1. Single inheritance
class Animal:
def __init__(self):
self.health = 100
def get_health(self):
return self.health
class Cat(Animal):
self.health = 150
self.name = name
def move(self):
print("Cat is moving")
cat = Cat("Cat")
print(cat.get_health()) # 150
2. Multi-level inheritance
This is another type of inheritance where a class inherits from another class which inherits from another
class: Class A inherits from Class B which inherits from Class C.
class Creature:
self.alive = alive
def is_it_alive(self):
return self.alive
class Animal(Creature):
def __init__(self):
super().__init__(True)
self.health = 100
def get_health(self):
return self.health
class Cat(Animal):
super().__init__()
self.name = name
def move(self):
print("Cat is moving")
cat = Cat("Cat")
print(cat.is_it_alive())
# Calling the method from the parent class
print(cat.get_health()) # 150
3. Hierarchical inheritance
When we derive multiple child classes from the same parent class, then we have hierarchical
inheritance. These child classes inherit from the parent class:
class Location:
self.x = x
self.y = y
def get_location(self):
class Continent(Location):
pass
class Country(Location):
pass
continent = Continent(0, 0)
print(continent.get_location()) # (0, 0)
4. Multiple inheritance
We can have another type of inheritance, namely multiple inheritance which can help us inherit from
more than one class at the same time.
Let's assume that we have a class called Date and another one called Time.
We can then implement another class then inherits from both classes:
class Date:
def get_date(self):
return self.date
class Time:
def get_time(self):
return self.time
class DateTime(Date, Time): # Inheriting from both
def get_date_time(self):
return self.get_date() + ' ' + self.get_time() # getting methods from its parent classes
date_time = DateTime()
5. Hybrid inheritance
class Vehicle:
def print_vehicle(self):
print('Vehicle')
class Car(Vehicle):
def print_car(self):
print('Car')
class Ferrari(Car):
def print_ferrari(self):
print('Ferrari')
class Driver(Ferrari, Car):
def print_driver(self):
print('Driver')
Now, if we create an object from the class Driver, we can call all methods in all classes:
driver = Driver()
driver.print_vehicle() # Vehicle
driver.print_car() # Car
driver.print_ferrari() # Ferrari
driver.print_driver() # Driver
Polymorphism in Python
This is another important concept from Object Oriented Programming that refers to the possibility of an
object behaving like different forms and calling different behaviors.
An example of a built-in function that uses polymorphism is the method len() which can be used for both
strings and lists:
print(len('Python')) # 6
print(len([2, 3, -43])) # 3
We can take another example with a class called House. We can have different subclasses that inherit
methods and attributes from that superclass, namely classes such as Condo, Apartment,
SingleFamilyHouse, MultiFamilyHouse, and so on.
Let's assume that we want to implement a method in the House class that is supposed to get the area.
Each type of living residence has a different size, so each one of the subclasses should have different
implementations.
getAreaOfCondo()
getAreaOfApartment()
getAreaOfSingleFamilyHouse()
getAreaOfMultiFamilyHouse()
This would force us to remember the names of each subclass, which can be tedious and also prone to
errors when we call them.
Luckily, there is a simpler method that we can use that comes from polymorphism.
Let's say that we have two classes, namely Condo and Apartment. Both of them have the method
get_area() that returns a value.
Now the method that we are going to call depends on the class type of the object:
class Condo:
self.area = area
def get_area(self):
return self.area
class Apartment:
self.area = area
def get_area(self):
return self.area
condo = Condo(100)
apartment = Apartment(200)
Now, we can put both of them in a list and call the same method for both objects:
After we execute that, we are going to see the following in the console:
# 100
# 200
We can not only call a method from a superclass. We can also use the same name but have a different
implementation for it for each subclass.
class House:
self.area = area
def get_price(self):
pass
Then we'll implement subclasses Condo and Apartment of the superclass House:
class House:
self.area = area
def get_price(self):
pass
class Condo(House):
self.area = area
def get_price(self):
class Apartment(House):
self.area = area
def get_price(self):
As we can see, both subclasses have the method get_price() but different implementations.
We can now create new objects from subclasses and call this method which is going to polymorph based
on the object that calls it:
condo = Condo(100)
apartment = Apartment(200)
places_to_live = [condo, apartment]
print(place.get_price())
After we execute that, we are going to see the following in the console:
# 10000
# 60000
This is another example of polymorphism where we have specific implementation of a method that has
the same name.
Importing in Python
One of the main benefits of using a popular language such as Python is its large number of libraries that
you can use and benefit from.
Many developers around the world are generous with their time and knowledge and publish a lot of
really useful libraries. These libraries can save us plenty of time both in our professional work, but also
on our side projects that we may do for fun.
Here are some of the modules with very useful methods that you can immediately start using in your
projects:
import os
Once we have imported a module, we can start using methods that are inside it.
import math
print(math.sqrt(81)) # 9.0
We can also use new names for our imported modules by specifying an alias for them as alias where
alias is any variable name that you want:
result = math_module_that_i_just_imported.sqrt(4)
print(result) # 2.0
There are times when we do not want to import a whole package with all its methods. This is because
we want to avoid overriding methods or variables that are in the module with the ones that we want to
implement ourselves.
We can specify parts that we want to import by using the following form:
Let's take an example of importing only the square root function from math module:
print(sqrt(100)) # 10.0
We can also import everything from a module, which can turn out to be a problem. Let's illustrate this
with an example.
Let's assume that we want to import everything that is included in the math module. We can do that by
using the asterisk like this:
from math import * # The asterisk is an indicator to include everything when importing
sqrt = 25
When we try to call the function sqrt() from the math module, we are going to get an error, since the
interpreter is going to call the latest sqrt variable that we have just declared in the previous code block:
print(sqrt(100))
When we are implementing Python scripts or doing any type of implementation, we are going to get
many errors that are thrown even when the syntax is correct.
These types of errors that happen during execution are called exceptions.
We indeed do not have to surrender and not do anything regarding them. We can write handlers that
are there to do something so that the execution of the program does not stop.
Here are some of the most common exceptions that happen in Python with definitions taken from the
Python documentation:
Exception – This is a class that is as a superclass of most other exception types that happen.
TypeError – Raised when an operation or function is applied to an object of inappropriate type. The
associated value is a string giving details about the type mismatch.
ZeroDivisionError – Raised when the second argument of a division or modulo operation is zero.
IOError – Raised when an I/O operation (such as a print statement, the built-in open() function or a
method of a file object) fails for an I/O-related reason, e.g., “file not found” or “disk full”.
ImportError – Raised when an import statement fails to find the module definition or when a from …
import fails to find a name that is to be imported.
KeyError – Raised when a mapping (dictionary) key is not found in the set of existing keys.
ValueError – Raised when a built-in operation or function receives an argument that has the right type
but an inappropriate value, and the situation is not described by a more precise exception such as
IndexError.
There are many other error types, but you don't really need to see about them now. It is also very
unlikely that you are going to see all types of errors all the time.
You can see more types of exception in the Python documentation.
Let's start with a very simple example and write a program that throws an error on purpose so that we
can then fix it.
We are going to do a division by zero, which is something that you have probably seen at school:
print(5 / 0)
If we try to execute that, we are going to be greeted with the following error in the console:
If we were to have such occurrences inside a Python program of any kind, we should catch and wrap this
error inside a try/except block.
We need to write inside the try block the part of the code that we expect is going to throw errors. We
then catch those types of errors inside the except block by also specifying the type of error that we
except to happen.
Let's see how we can deal with that error so that we also get informed that such error happened:
try:
5/0
except ZeroDivisionError:
try:
5/0
except:
However, this is not recommended, since we are catching all types of errors in a single except block and
we are not sure what type of errors are being caught (which would be quite useful for us).
name = 'User'
try:
except NameError:
In the previous example, we have used variable surname before declaring it, therefore a NameErroris
going to be thrown.
my_list = [1, 2, 3, 4]
try:
print(my_list[5])
except IndexError:
We can also use a single try block with multiple except errors:
my_list = [1, 2, 3, 4]
try:
print(my_list[5])
except NameError:
except ZeroDivisionError:
except IndexError:
In the previous example, we try to initially catch whether there is any variable that is used but not
declared. If this error happens, then this except block is going to be taking over the execution flow. This
execution flow is going to stop there.
Then, we try to check whether we are dividing by zero. If this error is thrown, then this except block is
going to take over the execution and everything that is inside it is going to be executed. Similarly, we
continue with the rest of the errors declared.
We can also put more than one error inside parenthesis to catch multiple exceptions. But this is not
going to be helpful for us, since we do not know what specific error has been thrown. In other words,
the following method does work, but it is not recommended:
my_list = [1, 2, 3, 4]
try:
print(my_list[5])
After the try and except are passed, there is another block that we can declare and execute. This block
starts with the finally keyword and it is executed no matter whether we have an error is being thrown or
not:
try:
print(my_list[0])
except IndexError:
print('An IndexError occurred')
finally:
If we execute the previous block of code, we are going to see the following in the console:
We usually write code that we want to be as a cleanup inside the finally block. This includes things like
closing a file, stopping a connection with a database, exiting the program entirely, and so on.
We can write statements inside try and except, but we can also use an else block where we can write
code that we want to be executed if there are no errors being thrown:
try:
print(my_list[0])
except IndexError:
else:
If we execute the code above, we are going to get the following printed in the console:
Exception wrap up
Hopefully you now understand exceptions and the various ways that you can use to handle them. If you
handle them correctly, there shouldn't be any sudden interruptions that cause your program to fail
unexpectedly.
When you want to develop an interactive program and get user input in the command line, you can call a
function called input().
It is very simple and all you have to do is declare a variable where you want to save the value that the
user types:
Wrapping Up
This book represents my attempt to make it quick and easy for you to learn the essentials of Python.
There are many other things to know about Python that I didn't cover in this book, but we will leave it
here.
Now that you have had the chance to learn how to write Python, go out there and make a positive
impact with your lines of code.
Fatos Morina
Fatos Morina
If you read this far, tweet to the author to show them you care. Tweet a thanks
Learn to code for free. freeCodeCamp's open source curriculum has helped more than 40,000 people get
jobs as developers. Get started
ADVERTISEMENT
Our mission: to help people learn to code for free. We accomplish this by creating thousands of videos,
articles, and interactive coding lessons - all freely available to the public. We also have thousands of
freeCodeCamp study groups around the world.
Donations to freeCodeCamp go toward our education initiatives, and help pay for servers, services, and
staff.
Trending Guides
Learn JavaScript
Linux ln Example
JS document.ready()
Rust Lang
Python Sets
C++ Strings
Python map()
Python .pop()
Python arrays
npm Uninstall
Insertion Sort
Python If-Else
parseInt() in JavaScript
About
Alumni Network
Open Source
Shop
Support
Sponsors
Academic Honesty
Code of Conduct
Privacy Policy
Terms of Servi