Professional Documents
Culture Documents
Py2o1 Course
Py2o1 Course
Py2o1 Course
Programming Paradigms
There are a few different ways to write programs that approach this
challenge differently. Sometimes, these different ways are called
programming paradigms and are often divided into the following three
paradigms:
Procedural programming
Object-oriented programming
Functional programming
Python is different. Python makes it relatively easy to write your code in any
of these three paradigms. This can be nice, because it allows you to tackle
challenges in different ways, but it can also muddy the waters and make it
harder to understand what either of these paradigms is, and when you would
want to use one over another.
But Python is pragmatic, and this set of modules will introduce you to each
of these paradigms and show you how you can follow them using Python.
This will give you a great start to learn a wide variety of other, more focused
programming languages if you ever feel the need to.
This module of the course will continue to focus on the procedural way of
writing code. You'll write scripts with your code instructions, and Python will
execute them from top to bottom.
In this course, you'll see additional ways that the flow of your program can
be altered by introducing functions. Functions are a common and important
aspect of programming. However, just because you'll learn to write
functions, doesn't mean that you're doing functional programming yet. You'll
learn more about functional programming towards the end of the course.
Strap on your learning boots and crack your knuckles. This second module
on procedural programming in Python packs a punch and by the end, you'll
have a good working understanding of Python. This module will fill your
Python knowledge so you'll feel as if you've gotten to know both snakes that
make up the Python logo:
After finishing this module, you'll know how to tackle almost any challenge
that you can handle with programming in Python. Your code won't be the
most effective, and it might not be the most elegant yet, but it'll work and
get the job done. You also won't understand everything that's happening yet,
but you'll know how to use Python to solve puzzles and build solutions.
Beyond this module, you'll dive deeper and keep pulling off more layers to
get a better understanding of programming in general and the Python
language specifically.
In the next lesson, you'll start by having a bit of fun with Python :)
Additional Data Types
In the previous course module you mainly got to know three foundational
data types:
In the following lessons, you'll get to know additional common data types in
Python. You'll read over a description of the data type, and then you'll have
access to a concise cheat sheet that sums up the most important aspects of
it.
Info: Don't attempt to remember everything. Get to know the data types,
spend some time with them, and then come again for a visit whenever you
feel like it.
So what are additional data types in Python, and why would you even need
them? You already got text and numbers covered, and you learned that you
can use them for different scenarios. The additional data types are just like
that. It makes sense for you to use them in specific situations because of
their specific characteristics.
One thing you'll remember about strings is that they are sequences of
characters. Strings are not the only sequential type of data in Python. In the
next lesson, you'll learn about another sequential data type called tuples.
Hello Tuples
Another data type that represents a sequence in Python is the tuple.
Info: The jury is out on how to correctly pronounce the word tuple. Some call
it a [toopl], others [tapl]. Seems like either one is fine ¯\_(ツ)_/¯
However, while strings are made up only of characters, tuples can consist of
elements of any data type, as described in the Python docs on Tuples and
sequences:
You've already learned about indexing strings using square brackets ([]),
and you'll see that it works the same with tuples. You'll learn about the new
concept of unpacking tuples in this section. But before that, you'll read
about how to create a tuple.
Creating A Tuple
# Commas
tup1 = 1, 42, "hello!"
print(tup1) # (1, 42, "hello!")
# Parentheses
tup2 = (1, 42, "hello!")
print(tup2) # (1, 42, "hello!")
# Tuple Function
tup3 = tuple([1, 42, "hello!"])
print(tup3) # (1, 42, "hello!")
As you can see, each of these ways to create a tuple gives you the same
result. You may also notice that, unlike with strings, your tuple can consist of
all sorts of different types of elements. In the case shown above, your tuples
consist of three elements each, two of the type int and one of the type str.
Note that you have to pass a sequence if you want to use the tuple()
constructor, which you do in this example with square brackets ([]). For now,
feel free to disregard this and just create your tuples using commas or
parentheses.
Since tuples are sequences, just like strings, they also have a length that
you can access using len():
empty_tuple = ()
print(len(empty_tuple)) # OUTPUT: 0
tup = ("hello", "there")
print(len(tup)) # OUTPUT: 2
You can use len() just like you did with strings, in order to find out how
many elements are in a given tuple.
Because tuples are sequences, you can also iterate over their elements
using loops. The syntax for that is the same as if you'd iterate over a string.
However, instead of accessing each character of a string, you'll access each
element of your tuple:
This code snippet creates a tuple using parentheses and iterates over each
element using a for loop. The variable name element that you're temporarily
assigning each item in the tuple to is an arbitrary variable name that you
should change to make it more descriptive for your specific use case.
For example, if you had a tuple of usernames and wanted to print each of
them out, you could write the same for loop more descriptively:
This loop construct does the same as the one further up. You're creating a
tuple and iterating over its elements to print each of them out. This is
possible because tuples are sequences, just like strings.
In the code snippet above, you first create a tuple with three elements, then
access the second element through its index, 1. To do this, you use the
square-bracket syntax you got to know earlier when working with strings.
Recap
Tuples are another immutable sequence data type in Python that behaves
similarly to strings in some aspects:
Tuples can contain any other data type as an element, which makes them
very versatile and useful.
Another aspect that tuples and strings have in common is that they are both
immutable. You'll revisit what that means and how it applies to tuples in the
upcoming lesson.
Tuples Are Immutable
Another aspect that connects strings and tuples is that they are both
immutable. Just like you can't replace a character in a string, you also can't
replace an element in a tuple.
Just like with strings, you can also use the + operator to add two tuples
together and create a new tuple with the combined elements:
As you can see, the new tuple tup3 contains all elements of tup and tup2
stuck together just like you'd expect it with a string.
Keep in mind that neither tup nor tup2 has changed at all. They are
immutable and can't change, but you can use them to create new tuple
elements. Like with strings, you can also overwrite the variable reference to
an existing tuple with a new tuple object:
Here you did the same thing as in the code snippet above, but instead of
assigning the output of the tuple concatenation to a new variable, tup3, you
overwrote the existing variable tup with a new tuple object value.
Info: The tuple object that tup was originally referring to never changed.
Instead, you discarded the reference to it and assigned the variable name
tup to a different tuple object. Python automatically finds and deletes such
abandoned objects that don't have any reference to them anymore.
You can train your understanding of this new data type by mobilizing your
knowledge about strings. Both data types behave similarly. However, you'll
soon notice that they have different use cases since tuples can be used as
containers for other data structures.
Tasks
Create a for loop and iterate over one of your tuples. Print out each
element.
Create a tuple that is a collection of only str elements. Access the
second letter of the second element in your tuple using indexing.
Iterate over your tuple full of strings and print out the last letter of each
string only. For this, you'll need to combine iteration and indexing.
Recap
Tuples are another immutable sequence data type in Python that behaves
similarly to strings. You can't change a tuple, but you can create a new tuple
object with changed content.
Unlike strings, tuples can also contain any other data type as an element,
which makes them very versatile and useful.
In the next lesson, you'll meet another sequence data type in Python called
list, which is similar to tuples in that it can contain any other data type, but
different because you can change the elements in a list.
Additional Resources
You can create lists of strings, lists of strings and numbers, lists of tuples
and integers, and even lists containing more lists, each of which contains a
string with the value "inception"... You get the point!
However, unlike tuples, lists are not carved in stone. You can change the
elements in a list, you can add to the same element without needing to
create a new one, as well as remove items from it. You'll start by creating a
new list that you'll work with for the rest of this lesson.
Creating Lists
The code snippet below will show you both of these ways of creating a list:
# Square Brackets
list1 = [1, 42, "hello"]
print(list1) # OUTPUT: [1, 42, "hello"]
# List Function
tup = (1, 42, "hello")
list2 = list(tup)
print(list2) # OUTPUT: [1, 42, "hello"]
Using list() like in the example above is a type conversion similar to the
ones you have seen using str() and int(). To create a list from scratch,
you'll generally use the square brackets.
As you can see in the code snippet above, lists seem very similar to tuples so
far. They are sequences of elements, where the elements can be whatever
you want.
Info: Many programming languages enforce the data type of all the elements
in a sequence so that you wouldn't be able to mix them in the first place.
Python is more chill about it, but that doesn't mean that you need to abuse
the freedom that Python gives you! Often it can be helpful to apply some
limitations, even if they aren't enforced.
That means that you should avoid mixing data types inside a list, and instead
only use elements of one data type, e.g. only integers, or only strings, in a
single list.
Info: When you feel you need to combine different data types in one
sequence, it's often better to use a tuple instead. The fact that tuples are
immutable makes it less likely that you'll end up with unexpected bugs.
In the code snippet below, you'll create a homogenous list that contains only
strings, a bucket list of things a person might want to do in their life:
Just like tuples and strings, list elements have indices that allow you to
access each element by its unique index position. Lists support indexing
through the same syntax that you've already gotten to know:
You can also use the familiar square-bracket notation to create slices of your
lists or access them in steps.
Tasks
Revisit the lesson on string slicing and apply the concepts to your list.
Access the last element in a list using negative indexing.
Create a longer bucket list with at least six items. Then use slicing and
steps to pick out only every second element in the list. Don't use a loop
for this!
As you can see, both indexing and slicing work the same on lists as it does
on other data types that you've already gotten to know. Since lists are
sequences, there is yet another aspect that works the same as with these
data types.
You can iterate over a list using loops in the same way you did with strings
and tuples:
for task in bucket_list:
print(task)
Of course, there are more interesting things you can do with each list item
than just printing it out, but for now, you're just learning about the basic
concepts and you are always encouraged to go ahead and play around with
the concepts with your own ideas :)
Tasks
Recap
Lists are sequences of elements that can be of any data type. However, it is
often useful to keep lists homogenous and only consist of elements of the
same data type.
You can index list elements, create slices, and iterate over lists, using the
same syntax as for strings and tuples.
After revisiting how lists are the same as these other data types, you are now
ready to find out how lists are different.
Additional Resources
First, you'll look at some of the cool things that you can do due to the fact
that lists are mutable. Remember your bucket list:
After spending a month in solitude and pondering about life, the universe,
and everything, you realized that the second entry doesn't quite cover what
you tried to express with it. You want to keep your bucket_list object intact,
but now you want to reach in there, grab the second item, and change it to
match what you wanted to say. Because lists are mutable, you can do just
that:
In this code snippet, you're using indexing to access the second element in
your bucket_list. This is the same process that you've seen in the previous
lesson. However, now you're not just accessing the element, but you are
changing it. Using the += operator, you're creating a new string object that
consists of the content of your old string ("eat fruits from a tree") and a
new string that you concatenated to the end of it (" that I planted").
You can see both types of mutability in action here:
By changing an element in your list, you effectively changed the existing list
object. If you attempted to do the same with a tuple, you'd run into a
descriptive TypeError.
Go ahead and give it a try! You might also find out that what you're doing
here is called item assignment. It means that you're assigning a value to an
item in your list.
Working with mutable objects can be useful, but it also has some drawbacks.
Sometimes strange things can happen when you forget that a list has
changed due to some action you took in another part of your code.
Aliasing
The mutability of lists can be especially tricky when you have created an
alias of a list.
Variables are pointers to values. You can have more than one pointer that
each point to the same value. This is called aliasing, and in the case of
mutable objects, such as lists, it can lead to undesired results:
a = [1, 2, 3]
b = [1, 2, 3]
print(a == b) # True
print(a is b) # False
In this code snippet, you've created two lists, a and b, that both have the
same values. However, they are pointing to different objects in memory.
You're testing this with the equality operatory (==) and the identity operator
(is).
Info: If you want a refresher on the different Python operators and how they
work, check back to the relevant section in the first module of this course.
So you've established that you have two different lists that both hold the
same values. Now you're reassigning one of your variables to the first list so
that both variables a and b now point to the same list object:
b = a
print(a == b) # True
print(a is b) # True
There's nothing wrong with doing this, but it can introduce subtle bugs if you
don't stay aware of what you're doing here. You might think that you're still
referencing different objects with a and b, but in reality, they point to one and
the same list. Therefore, and because you can change lists, if you now make
a change to b, then it'll be reflected also in a:
b[0] = 4
print(a) # OUTPUT: [4, 2, 3]
After reassigning b to a, you were only dealing with one list object anymore.
However, you had two references to the same object. This is called aliasing.
Now when you used the variable b to change something in your list object,
you'll see the same changes also when you access the list through your
other reference to it, a.
Tasks
Check out this great interactive visual explanation to drill your understanding
of how mutability can have confusing effects when you alias lists.
Recap
Lists are mutable, which means that you can change a list object in place.
This can be helpful if you want your list to change state throughout your
program. However, it's also a common source of confusion and bugs,
especially when you're using aliasing to refer to the same list object with
different variables.
Lists are a commonly used data type in Python programs, so you'll get to
know some useful list methods in the next lesson.
List Methods
You've gotten to know string methods in a previous lesson. Many Python
objects you'll work with have methods associated with them. You'll learn
more about what methods are in an upcoming lesson. For now, you can think
of a list method as a way to do something with your list.
You can use list methods to change your list, for example, to add elements to
the end of your list, and remove them as well:
bucket_list = ["climb Mt. Everest", "eat fruits from a tree that I planted"]
# Add an element
bucket_list.append("sail across the Atlantic ocean")
print(bucket_list) # OUTPUT: ["climb Mt. Everest", "eat fruits from a tree that I pl
# Remove an element
bucket_list.pop()
print(bucket_list) # OUTPUT: ["climb Mt. Everest", "eat fruits from a tree that I pl
Using list.append() you can add elements to the end of your list, and with
list.pop() you can remove the last item in your list.
bucket_list = ["climb Mt. Everest", "eat fruits from a tree that I planted"]
next_task = bucket_list.pop()
You've successfully removed the last item from your bucket_list and
assigned it to a new variable next_task.
Sometimes, however, you might want to remove a different item than the last
one. For this you can use a different list method called list.remove():
bucket_list = ["climb Mt. Everest", "eat fruits from a tree that I planted"]
bucket_list.remove("climb Mt. Everest")
print(bucket_list) # OUTPUT: ["eat fruits from a tree that I planted"]
When using list.remove() you can specify the value of the element that you
want to remove from your list. Keep in mind that you'll need to pass it the
exact value of the item you want to take out. You can make sure to avoid any
typos by indexing your bucket_list, which returns the value of the element:
bucket_list = ["climb Mt. Everest", "eat fruits from a tree that I planted"]
bucket_list.remove(bucket_list[0])
print(bucket_list) # OUTPUT: ["eat fruits from a tree that I planted"]
Using indexing to access the value of a list element and passing this value to
.remove(), allows you to effectively target the item that you want to take out
of your list.
Tasks
Similarly to removing elements from specific positions in your list, you can
also insert them at specific positions. For that, you'll use list.insert(),
and you'll need to specify both the index where you want to insert the new
element, as well as its value:
bucket_list = ["climb Mt. Everest", "eat fruits from a tree that I planted"]
# Add an element
bucket_list.insert(1, "sail across the Atlantic ocean")
print(bucket_list) # OUTPUT: ["climb Mt. Everest", "sail across the Atlantic ocean",
You'll first add a number to specify where your new item should be inserted.
In this case, you chose the number 1, which means the value will be inserted
after the first position, which has the index of 0. The inserted element will be
at the index you specify here.
The second argument to list.insert() is the value of the element that you
want to add to your list. In the code snippet above, that is the string "sail
across the Atlantic ocean". Run this example in your Python interpreter
and confirm that you're getting the same result.
Sorting Lists
Another useful list method is list.sort(), which allows you to sort the items
in your list. You can apply this to lists of strings, which will be sorted
alphabetically by the beginning letters of your elements, or also to lists of
numbers:
a = [3, 0, 999, 42]
a.sort()
print(a) # OUTPUT: [0, 3, 42, 999]
When you call .sort() on your list, the items in the list get sorted in place.
This means that your list a has changed. This is only possible because lists
are mutable. You can confirm that your list has been sorted by printing it out.
Some of the Python operators that you covered earlier can also be used with
lists. Revisit the previous lessons to refresh your knowledge on Python
operators, then use them to better get to know your new data type friend,
list.
Tasks
Try to apply Python operators such as +, *, /, etc. that you used with
numbers and strings to lists. Which ones work and which produce
errors?
Which operators do what you'd expect them to do?
Which operators work but produce unexpected results?
Many use cases of Python operators that apply to lists are intuitive and make
working with lists quicker.
Recap
You can perform actions on list objects via list methods. Some especially
useful methods are:
list.append(item)
list.pop()
list.remove(item)
list.insert(index, item)
list.sort()
All of these methods change the list that you are calling them on, which is
possible because lists are mutable.
You can also apply some of the Python operators you've gotten to know
earlier on lists.
Additional Resources
In this code snippet, you define a list called names that contains a few names
as strings. In the second line, you're assigning the output of a list
comprehension to the variable lowercase_names. If you printed the result,
you'd see that it consists of a list with the same names in lowercase:
# OUTPUT:
['ada', 'bertha', 'carol']
This might look like a strange sequence of code that you've never
encountered before. But you might also notice some parts of it that could
seem familiar:
In fact, list comprehensions are only one-liner shortcuts for for loops in
Python. Anything you can do with a list comprehension, you can also do with
a for loop. It just takes you a couple more lines of code:
lowercase_names = []
for x in names:
lowercase_names.append(x.lower())
As you can see, the for loop did the same thing as the list comprehension
further up, the list comprehension was just more compact in achieving the
task.
Unless you've already fallen in love with the concept of list comprehensions,
stick with writing your for loops in full length for now.
You'll revisit list comprehensions later on in this course when you'll also learn
about some other similar constructs that are generally known by the name of
comprehensions.
For now, it's enough that you understand what list comprehensions do and
that they're no mystery when you see them in a StackOverflow answer.
Tasks
Recap
List comprehensions are a concise way to write for loops in Python. They
don't introduce any new functionality but can be an elegant way to avoid
writing verbose loops for short and uncomplicated code logic.
In the next lesson, you'll return to looking at data types as you'll get to know
another sequence type, called set.
Additional Resources
Yes, that's right! And yes, that's a bad joke! But apart from that, it's also a
great metaphor. The name for this Python data type was chosen on purpose.
As you might see, this is equivalent to how you can use a word in a real-
world dictionary to access that word's definition.
Another metaphor that you can think about, is comparing keys and values in
dictionaries to variables and values in programming. The dictionary key
represents a reference to the associated value in the dictionary.
Creating A Dictionary
You can create a dictionary using curly braces ({}) and adding keys and
values that are separated by a colon (:). Multiple entries are separated by
commas (,), just like elements in collections:
1. "greeting": "hello"
2. "name": "martin"
Each of these two items consists of a key and a value. If you take apart the
first element, then you'll see that it consists of:
1. key: "greeting"
2. value: "hello"
Tasks
What are the key and value of the second item in your dictionary?
In this example, you're using strings both as keys as well as values of your
dictionary. But that's not a requirement. You can use any Python data type as
values in your dictionary, however, you can only use immutable data types as
keys:
Keys need to be immutable data types, such as str, int, tuple, and
need to be unique
Values can be any Python data type and can contain duplicates
Dictionaries are extremely useful data structures that allow you to build
complex programs and interactions. Now that you've created a new
dictionary, how do you access a value from it?
print(users["mary"]) # OUTPUT: 22
You can access any of the other integer values with their respective keys.
Editing A Value
You can use a similar syntax as for a dictionary lookup also to change a value
in the dictionary:
However, it also means that you can run into the same issues with aliasing
as you did when using lists. Return to the lesson on mutability in lists to
refresh your memory on what that means and why it's important to keep
track of.
You can again use a similar syntax to add a new key-value pair to the
dictionary:
users['ludvik'] = 9
print(users['ludvik']) # OUTPUT: 9
print(users) # OUTPUT: {'mary': 22, 'caroline': 99, 'harry': 24, 'ludvik': 9}
If you're using the syntax to change the value of a key that doesn't exist yet,
it gets added as a new entry to your dictionary.
Recap
Dictionaries are a commonly used data structure that gives you a lot of
flexibility to build complex programs with.
In the next lesson, you'll learn more about dictionaries as iterables, and how
you can iterate over their keys, values, or both.
Hello Sets
Another type of mutable collection in Python is a set. The Python Docs on
Sets use the following paragraphs to describe what sets are and how they
are used:
If you're familiar with mathematical terms, then a set might seem already
intuitive to you. If not, then don't worry, you'll go over how to create one and
what you can use them for in this lesson.
Creating A Set
You can create a set in Python using set() and pass it to another collection
object. You can also create a new set by wrapping comma-separated values
in curly braces ({}):
Note: If you want to create an empty set, then you need to use set(), not {},
as the latter creates an empty dictionary. You'll learn more about dictionaries
in an upcoming lesson.
After you've learned how to create a set, you'll now look at an example of
how sets can be useful.
Eliminating Duplicates
Imagine that you fetched all links from a website with your web scraping
script, and now you want to follow those links programmatically. However,
you only want to visit each destination once and some pages have multiple
paths to get to the same page, which is why you'll end up with multiple URLs
in your list.
You can convert the list of URLs to a set, which will remove all duplicates
and present you with a collection of unique URLs:
unique_urls = set(url_list)
print(unique_urls) # OUTPUT: {'http://www.example.com', 'http://www.setsareuseful.co
With a single line of code, you effectively removed all duplicate links from
your list. Your result set is also an iterable collection, which means that you
can use it in the same way you'd use a list to access each of the URLs in
your script.
Set Operator
Set Methods Syntax Effect
Syntax
Returns a new set that contains all
A | B A.union(B)
elements of A and B.
A |= B A.update(B) Adds all elements of B to the set A.
Returns a new set that contains the
A & B A.intersection(B)
elements that are in both A and B.
Returns the the elements that are
A - B A.difference(B)
included in A, but not included in B.
Removes all elements of B from the set
A -= B A.difference_update(B)
A.
There are even more set operations. You don't have to learn all of them, but
rather keep aware that they exist and look them up if you need them.
Tasks
Recap
Python sets work similarly to mathematical sets. In practice, you'll likely use
them to quickly remove duplicates from a collection. You can create sets
with curly braces ({}) or the set() function. You can use set operations and
set methods to perform actions with sets.
In the next lesson, you'll get to know another very important data type that
you'll use frequently in programming. This data type is called a dictionary.
Additional Resources
The dict.items() method gives you a list of tuple objects that you can
iterate over.
While there's also another way of accessing items in a dictionary, the pattern
shown in the code snippet above is the most straightforward to read. It
clarifies that you're iterating both over the keys and values of the dictionary.
But what if you want to only access the keys or the values?
You can also iterate over only the keys or only the values in a dictionary. In
fact, if you directly iterate over a dictionary without calling a method, such as
.items() on it, Python will iterate over the dictionary's keys:
for k in users:
print(k)
# OUTPUT:
# mary
# caroline
# harry
Alternatively, you can use the distinct dictionary method .keys() to iterate
over just the keys:
for k in users.keys():
print(k)
# OUTPUT:
# mary
# caroline
# harry
And if you need to access all values in a dictionary, you'll need to use
.values():
for v in users.values():
print(v)
# OUTPUT:
# 22
# 99
# 20
As you can see, dict.keys() allows you to iterate over only the keys of your
dictionary, while dict.values() allows you to access all the values.
Tasks
Just like with other data types, there are a lot of methods you can use on
dictionaries to do something with them. You invoke these methods by calling
them on the dictionary object. For example, this is how you call the .clear()
method on your dictionary:
dict.get()
dict.pop()
dict.update()
What can you accomplish when using these methods? Remember to keep
the documentation handy and practice reading it. It'll feel complicated and
unfamiliar at the beginning, but learning to be comfortable with reading
documentation is a vital skill for becoming a developer. So keep coming back
to it and don't let yourself get too frustrated if you don't understand
everything right from the start.
Recap
Like other data types, dictionaries also come with predefined methods that
allow you to perform common actions on your dictionaries. You can learn
more about them in the dictionary documentation.
In the next lesson, you'll recap all the new data types you've gotten to know,
before applying them to your projects.
Additional Resources
You were already able to do quite a lot by knowing just these data types and
some programming logic! But new data types expand your possibilities of
what you can accomplish with your programs.
In this module, you've learned about some additional important data types:
There are many more data types, and you'll later learn that anything in
Python can be its own data type. What you got to know in this section are
just some common data types that you'll use over and over again.
Info: A data type in Python is nothing special and you'll later learn how to
build your own data types. Think of them as standardized and useful ways to
group-specific functionality, that allows you to tackle certain challenges.
Data types are a helpful concept to categorize types of data into separate
buckets. For example, there's something that's similar between all textual
data. And often, you'll want to be able to treat some types of categories in a
similar manner.
This is what data types were created for. They allow you to lump similar data
together, and perform common actions on that type of data.
Over your journey across the lands of Python, you'll keep encountering new
custom data types that someone has written to fulfill a specific goal. Often
it'll be useful to learn how to work with that data type and use the
abstraction that it provides for you.
On the other hand, by becoming familiar with the built-in data types that
you've gotten to know up to now, you'll be able to handle almost any
programming challenge that the world might throw at you.
Rejoice, give yourself a pat on the back, and get ready to apply some of the
built-in data types you got to know in this section to your projects next.
PROJECT: Adventure Game
In the previous module of this course, you built a text-based adventure game
that allowed you to interact with a few rooms through your command line.
If you haven't built the first version of this project yet, then make sure to
head back to the instructions in the previous module and give it a go! If
you're already done, then great work building it out!
Equipped with a new diamond sword and a couple of additional data types,
you're about to venture back into your text-based RPG to extend its
functionality and make it more immersive.
The game will continue in the fashion of a classic text-based Dungeons and
Dragons game. But, with the help of the additional data types you've gotten
to know, you'll start giving the player more choices:
Here are the tasks that you'll code up for this project. You'll read them below
in a list form, and as a first step, you can again copy that content over into a
new Python file as pseudocode:
Save the user input options you allow e.g. in a set that you can check
against when your user makes a choice.
Create an inventory for your player, where they can add and remove
items.
Players should be able to collect items they find in rooms and add them
to their inventory.
If they lose a fight against the dragon, then they should lose their
inventory items.
Add more rooms to your game and allow your player to explore.
Some rooms can be empty, others can contain items, and yet others
can contain an opponent.
Implement some logic that decides whether or not your player can beat
the opponent depending on what items they have in their inventory
Use the random module to add a multiplier to your battles, similar to a
dice roll in a real game. This pseudo-random element can have an effect
on whether your player wins or loses when battling an opponent.
Once you've got a working version done, show it again to your friends and
family and let them give it another spin. Feel free to share a link to your game
on the forum in the thread on Command-Line Games.
PROJECT: File Counter
In the previous module of this course, you wrote a script that was able to
move all your screenshots from your messy desktop into a new folder. In this
project, you'll revisit your file mover code and expand on it with the
knowledge of some additional data types, in order to make it more flexible
and powerful.
When you built your file mover, you used the pathlib module that worked
with Path() objects. You can consider Path() objects as yet another data
type, one that is more custom and not considered a built-in type. As you
might remember, you'll have to import it from the standard library in order to
work with it.
In this project, you'll continue to work with pathlib and files, but you'll also
use some of the additional built-in data types that you got to know in this
section.
Path() objects have an attribute called .suffix that allows you to get the file
extension of each file that you're working with. You used it previously to
decide which files are screenshots:
if filepath.suffix == '.png':
pass # Do something
Now, your desktop has gotten quite messy again, but this time you want to
know what's on there and what keeps cluttering it up!
To get that information, write a script that locates your Desktop, fetches all
the files that are on there, and counts how many files of each different file
type are on your desktop. Use a dictionary to collect this data, and print it to
your console at the end in order to get an overview of what is there.
Info: You can use another package from the standard library called pprint,
which stands for "pretty print", in order to display your output nicely
formatted.
You can now expand on your file mover script and add logic that moves all
file types that have e.g. more than five files on the desktop into their own
separate folder. Will it help to keep your desktop cleaner?
You could copy the dictionary and save it in a text document. But that
sounds repetitive and also like something you might be able to automate
using Python. In the next section, you'll learn about File Input/Output so you
can write data to files, and read it back from there.
Introduction to File Input/Output
Working with files can seem very normal from the perspective of a computer
end-user. You double-click a program, you create a new file by selecting a
context menu, and you're ready to start typing your novel:
However, you've already learned that there are also programmatic ways to
handle these familiar tasks. You did something similar when you learned how
to move files with a Python script. Before that, you had probably only done
that using your operating system's graphical user interface.
# OUTPUT
{'': 8, '.csv': 2, '.md': 2, '.png': 11}
Your count dictionary shows that you've got the following content cluttering
your desktop:
Now, that's some juicy information, but how will you keep track of it until the
next time you'll run your script to analyze your desktop contents? Most likely,
you'll have forgotten about it, unless you write it down. Python can keep
track of the data for you.
If you think of files from the perspective of a programmer, where they are
just a place to persist some data across multiple executions of your
programs, then you're opening up a whole new world. File extensions are just
some additional information that gives your operating system a hint as to
which program it should use to access the data saved in that file.
Keep that script you just wrote around, and get ready to adapt it in order to
write your desktop analysis data to a new file.
Writing To Files
Applications that need to save data or their current state rely on the ability to
persist data. You can do this with files. By writing to and reading from files,
you are able to save information about an application and use it later.
Continuing with the count of the types of files on your desktop from the
previous section, you'll start by writing information to a file.
If you map out the process of writing to a file in pseudocode, you might
come up with three distinct steps:
You'll mirror these steps using Python code to achieve just what you're
trying.
In the code snippet above, you're opening a file called output.txt in write
mode ("w") and assigning that to the variable file_out.
If there's no file called output.txt in the directory you're running your script
from, then it will be created.
Note: If a file called output.txt already exists, all of its content will be
deleted instantly if you open it up in write mode as shown above.
After opening the file, you can write data to it using the .write() method on
your file_out variable:
Finally, you should always close the file after you're done working with it, or
you might run into unexpected effects. For example, you might not be able
to delete the file on some systems, or you might see an older version of the
file content, since changes are committed once the file is closed. You can
close a file using the .close() method on your file_out variable:
file_out.close()
Using these three steps, you can create or open files from within your
Python scripts, and write any content to them.
Tasks
Edit the desktop file counter script that you built in the previous section
to write its output to a file.
Run the script and confirm that you can read the output in your new file.
Take a new screenshot, or add another file to your desktop, then run the
script again.
Which possible issues can you identify?
Recap
You can use Python to write data to a file to persist the information also after
your script has finished running. To do this, you need to:
In the example code snippet, you wrote a string to a .txt file, but you can
also write Python objects to your files.
When you applied this process to your file counter script, you might have run
into some quirks, such as that the data wasn't nicely formatted, and got
overwritten each time you ran the script.
In the next lesson, you'll learn how you can improve this.
Appending And Formatting
When you edited your file counter script to write to a file, you probably
added code similar to the one shown below:
Note that you had to convert the count dictionary into a str() in order to
write it to the file. If you don't do that, then Python will let you know by
showing you a TypeError with clear instructions:
If you did do the conversion, then you'll see a new file in your current folder
that shows the expected content:
cat filecounts.txt
{'': 8, '.csv': 2, '.md': 2, '.png': 11}
Great! However, as soon as you run this script again, Python will overwrite all
the existing content in your file. Also, if you want to display a longer
dictionary, it'll become quite hard to read like this.
Appending To A File
You can open file objects in different modes. You've opened it in write mode
("w"), which always starts with a blank slate.
Instead, you can use the append mode ("a") to add new data to an existing
file:
The rest of your code stays the same. If you now run your script a second
time, you'll see that Python added a new dictionary at the end of the file:
cat hello.txt
{'': 8, '.csv': 2, '.md': 2, '.png': 11}{'': 8, '.csv': 2, '.md': 2, '.png': 11}
By only changing the mode that the file object operates as you were able to
keep adding new information on each run.
However, it looks messy and will be really hard to read after 20 runs if you
leave the script as-is.
# -- snip --
count = {'': 8, '.csv': 2, '.md': 2, '.png': 11}
In this example, you add a newline character ("\n"), which represents a line
break, after you've written the string representation of your dictionary. That
way, each run will write to a new line in your output file.
Since you're just working with text representation, you can apply any string
formatting technique to the content you're writing to the file.
Recap
File objects have different modes. If you want to add data at the end of a file
object without overwriting the existing content, then you can use the
append mode ("a") when opening the file object.
Since you're just writing text to your files, you can format the text as you
wish using string formatting techniques.
In the next lesson, you'll use the persistent file memory that you created, and
access the data in there.
Reading From Files
After you've learned how to write information to a file, you'll now read that
information back into your program. Reading from a file has a very similar
structure to writing to a file, and requires similar steps:
First, you have to open the file. This time, you'll use the default read mode
("r"):
After creating a file object in read mode, you can use .read() to read the
entire content of your file into memory:
contents = file_in.read()
This line of code saves the content of your file in the variable contents.
Tasks
Try to answer the questions posed above and take a moment to think it
through. Write your thoughts in your notebook, and brainstorm some
possible solutions.
Info: You'll learn about better ways to handle input data in just a bit.
After you're done reading the data from your file, you must again close the
file, just like you did when writing to it:
file_in.close()
Keep in mind that you need to call the .close() method on the file object
that you've opened. In this example, you named the file file_in, so that's
also the name you need to use when closing the file.
If you followed the instructions through the last few lessons, then you'll end
up with a value of content that looks similar to this:
# OUTPUT
"{'': 8, '.csv': 2, '.md': 2, '.png': 11}\n{'': 8, '.csv': 2, '.md': 2, '.png': 11}\n
It's a str value that shows the representation of two dictionaries separated
by newline characters.
Now, what would you do if you wanted to get a total count of all the .png files
that you ever had on your desktop?
Tasks
Train your string manipulation skills and write a script that can parse this
string input and convert it back into a list of dictionaries that you can use to
access information.
You can do that, but there are better ways that allow for better generalization
and will make your collaborators happier.
Especially if you are planning to use the data your script produces
programmatically, or together with other people, there is a better way to
achieve good structure and reusability of your data.
While any data you'll be writing could be represented as text or binary data,
and you could write custom code to parse the data correctly, you can make
your life much easier by using common file formats for data storage and
data exchange.
1. .csv
2. .xml
3. .json
All three of these file formats are commonly used to store and retrieve
information programmatically.
Python has modules to handle all of them. In this section, you'll learn to work
with the CSV format because it is extremely common and human-readable.
Later in this course, you'll also get to know the JSON format in more detail,
as you'll learn how to receive data from the Internet, and JSON is a common
standard for data exchange over the Internet.
Recap
You can read data from a file using open() with the default read mode ("r").
To get the full content of a file, you next call .read() on the file object.
However, there are also other Methods of File Objects that you can use to
read from a file object, for example, line-by-line.
To make it easier to retrieve the stored information and work with it, there are
certain conventions around how to format data in a file. Common formats
are:
1. CSV
2. XML
3. JSON
You'll learn how to write a CSV file using Python in the next lesson.
Context Managers And CSV Files
In this lesson, you'll learn how to use Python's csv module to convert your
dictionary data to a common file format, and save it, so that you'll be able to
retrieve it later on.
Before using the csv module, however, you'll learn a common shortcut when
writing to, or reading from, files with Python.
Context Manager
You've already learned how to interact with a file using the open() function
and the .close() method.
When you're working with files, you might often run into some unexpected
errors. Sometimes the contents of the file are not what you expect or the file
doesn't even exist at all. You want to be sure that the file object you opened
will be closed in any case. You can achieve this by using a context manager
when opening the file:
Using the above construct with the with keyword, which is called a context
manager, you'll automatically close the file object after the indented part of
your code is done running.
Python knows how to safely close the file once it reaches the end of the
indented code block.
Info: You'll most often see a context manager when you're working with files,
and it's encouraged to use them over the three-step process described in
the previous lessons.
The with context manager is a convenient and safe way to handle your file
interactions. You can use it in the same way when writing to a file.
Now you'll use the context manager and the csv module to save your file
counter output to a .csv file, so that it's in a format that'll be accessible to
other developers:
import csv
# -- snip --
count = {"": 8, ".csv": 2, ".md": 2, ".png": 11}
Some parts of this code snippet will be unfamiliar, so you'll take a look at
each line and read about what it does:
First, you're importing the csv module from Python's standard library.
Then you're using a context manager to open up a file in append mode
("a"), and saving the file object to the variable csvfile.
Next, you're creating a CSV writer object with the opened csvfile as its
input, and you save it to the variable csvwriter.
Now you're preparing the row of data that you want to write to the file.
For this, you're accessing each piece of information through a
dictionary lookup and adding them to a list.
file_inally, you're using the csvwriter to write the row of data to your file
using .writerow().
As you can see, the process is similar to when you were writing plain text to a
file before.
Tasks
Run your script and check the output. What does it write to your file?
Run the script a second time and check the output.
Rewrite the functionality of the script without using the csv module.
Create the correct formatting only with proper string formatting.
What are the advantages of using the csv module over plain string
formatting? What do you think is missing from your output file? How
could you add it? Explore the csv module's documentation.
Because CSV only separates values with commas, you can write your own
CSV files even without using Python's csv module. However, the module
introduces some quality of life improvements, such as adding headers and
choosing between different flavors. It also prevents you from accidentally
messing up the correct syntax.
Other formats for storing your data are more difficult to manually reproduce,
which means that there are more opportunities to mess up the correct
syntax. That's why it's a good idea to use the existing modules to help you
with creating the right formatting, as well as with reading it back.
Later in this module, you'll also learn to work with JSON, a common format
used for data exchange over the Internet.
Recap
You can use a context manager through the with keyword to safely open
file objects and rest assured that they'll be closed once your script exits the
indented code block.
In the next lesson, you'll learn how to read the data you saved back into your
program so that you can work forward with it.
Reading CSV Data
After running your file counter script twice and saving the output to a CSV
file, you'll end up with a file that might look something like this:
8,2,2,11
8,3,2,14
Your numbers will be different, but if you changed the contents of the
desktop, for example by taking a few more screenshots before running the
script again, you'll see that the numbers have changed.
Info: Your script is only recording very specific file types in your CSV file,
namely the ones that you have explicitly added to the list that gets written to
your CSV file. Adding new file types will be noticed by your file counter
script, but with the current setup, it won't make it into your CSV file.
Every time you'll run your script, it'll add another line to this file.
Tasks
Improve your data by writing the headers to your CSV file during the
first run of your script.
Think about how you could improve the CSV writer so that it records
more file types. This is not a trivial task and you'd have to approach the
organization of your script differently. Map it out in your head and on
paper to train thinking through designing a program.
So you've collected some data, and you can keep collecting it with your file
counter script. Now you'll also want to do something with the collected data!
Time to start a new script and read in the data:
# analyze.py
import csv
print(counts)
Some of this code is similar to when you were using the csv module to write
the data to your file. However, you can see a few differences that you'll look
at in more detail:
After running the script, you should see output similar to below:
[
{'Folder': '8', 'CSV': '2', 'MD': '2', 'PNG': '11'},
{'Folder': '8', 'CSV': '3', 'MD': '2', 'PNG': '14'}
]
Your output might be formatted a bit differently, but you can see that you're
now working with a list of dictionaries. You can access each value through
list indexing followed by a dictionary lookup.
Tasks
Give it a try and train your list indexing and dictionary lookup skills!
Write code to access the amount of folders that was collected during
the first run of your file counter script.
Access the number of PNGs during the second run of the script.
After reading in your CSV data and converting it back to a familiar data type
that you know how to work with, you're all set to analyze the changing
contents of your desktop over time.
Recap
You can use a context manager to read data from files in the same way as
you used it before to write data to a file. Python's csv module comes with
some convenient abstractions, such as the csv.DictReader(), that you can
use to create familiar data structures from your CSV data.
In the next lesson, you'll learn how to include Path() objects from the
pathlib module into your File I/O operations.
Additional Resources
The relative path of a file is the path from the Python script to the file.
When you use the absolute path of a file, you can run the Python script
anywhere on your machine and it would file_ind the file input.txt in the
folder /Users/yourname/Desktop and be able to open it up.
File paths are a tricky subject. One reason is that it might be hard to keep
track of the exact absolute path of a file. Most of all, however, they get
handled differently by different operating systems. Windows paths look
completely different than UNIX paths, so if you write a script that works with
UNIX style paths, half of your users would probably not be able to use it.
There are, of course, clever ways around this challenge, and the most
straightforward one is using Python's pathlib module. You've worked with it
before in the previous module of this course, and now you'll apply it to File
Input/Output.
data_path = Path("/Users/yourname/Desktop")
You've imported Path from pathlib, and used it to create a Path() object
that points to the folder location of where your data is located. You saved
this information in the variable called data_path.
After defile_ining your data_path, you can use it as usual inside your context
manager where you're providing the file name. With .joinpath() you're
adding the file name to the Path() object, which provides open() with the
location of the file you want to open. The rest of the code stays exactly the
same.
The pathlib module and the Path() objects provide you with a couple of
convenient shortcuts. While you can open and read a file as you did above,
you can do it even faster when using methods on your Path() objects:
from pathlib import Path
filepath = Path("/Users/yourname/Desktop/input.txt")
with filepath.open() as f:
print(f.read())
You can use .open() on a Path() object to open the file in the same way as
you would when using Python's built-in open().
You can also read from and write to files with quick Path() methods:
p = Path("hello.txt")
p.write_text("Hello world!")
p.read_text() # OUTPUT: "Hello world!"
pathlib is a great module that makes working with File Input/Output faster
and more secure.
Recap
1. Relative path: This is the location as seen from the directory that your
Python script executes from.
2. Absolute path: This is the location from the root directory of your
operating system.
You can also use the pathlib module and work with Path() objects instead
of plain strings. This has the advantage that it makes your paths operating
system agnostic, and you can use the many convenience methods that the
module provides to speed up and simplify your File I/O tasks.
Practice reading and writing to files with your lab exercises and think about
something you'd like to build that involves reading from and writing to files.
Take a note in your journal to keep track of your ideas.
In the next section of this module, you'll learn more about functions, a
structure that you've already used before. You'll learn what functions are,
why they are useful, and how you can write your own functions.
Additional Resources
In this lesson, you'll look closer at what scopes are and how they apply to
functions. Your greet() function contains three variables:
1. greeting
2. name
3. sentence
greeting and name are two parameters that get filled with the value of the
arguments a user passes when calling your function. sentence gets created
within the body of your function.
In this lesson, you'll start to learn about scope, which influences where you
have access to the values of these variables.
Function-internal variables live only inside the function. You can refer to the
function parameters inside your function as much as you want to, but you
won't be able to access them outside of it:
Info: Even though you are returning the value of your function-internal
sentence variable, that doesn't make the variable accessible in your global
scope. All you do is to expose the value of that variable, and you'll need to
assign it to a new variable in the global scope.
In the next lesson, you'll dive deeper into the concept of scopes, and walk
through a step-by-step example to better understand how they work.
Recap
Ufff! That's a heavy definition and doesn't seem that straightforward. Let's
take another approach and try this again from the beginning.
These boxes are similar to how scopes work. You can fill different scopes
with different variables. So you can have something called a candy inside the
outer box, and you can have something else that is also called candy in the
inner box. Even though they have the same name, they will be two different
candies.
Just like in the example in the previous lesson, you won't be able to access
the candy in the inner box from the outer box. No matter how hard you try:
That covers the very basics of scopes, but the metaphor doesn't entirely
hold up. It might be counterintuitive to know that you can access things
inside the outer box from the one that's nested inside of it. So scopes aren't
just ordinary boxes, and you'll now learn about two different types of scopes:
1. Global scope
2. Local scope
Most of the time up to now you've worked in the global scope of your script.
In the previous lesson, you opened a local function scope and defined
variables that existed only in there.
Global Scope
Each Python session that you start, for example by running a script or an
interpreter session, has a global scope.
Any variable that you define in the global scope is accessible within any of
the inner scopes you might create in that session. That's where the box
metaphor stops making a whole lot of sense.
Even a function within a function within a function (etc.) can still use a
variable that has been defined in the global scope without needing to pass it
as an argument. Nested scopes have access to anything defined in their
outer scopes:
name = "Mycroft"
def print_name_box():
print(name)
def smaller_box():
print(name)
def smallest_box():
print(name)
smallest_box()
smaller_box()
print_name_box()
However, this is a one-way street and doesn't work the other way around, as
you've seen in the previous lesson.
In the following graphic, you'll see a visualization of the different scopes
within this code snippet. Each new scope is surrounded by a colored square.
Even though you're opening up some new scopes, the value for name stays
accessible as "Mycroft" in all scopes. This is indicated in the graphic with
the red background behind all scopes:
As you can see, variables defined in the global scope are accessible in all
inner nested scopes of your script, unless they get overwritten.
Local Scope
Variables that are defined within a local scope are available in that local
scope, and any scopes nested within it. A global variable will only exist within
a local scope if there's no variable with the same name in the local scope. If a
local variable has the same name as a global variable the local variable will
always take precedence.
To exemplify this, you'll take another look at your previous example with the
name variable:
name = "Mycroft"
def print_name_box():
print(name)
def smaller_box():
# (Re)assigning a variable within a local scope
# overwrites the same variable from an outer scope
# You also can't use the global variable *before*
# assigning it, if you assign it anywhere in that scope.
# print(name)
name = "Sherlock"
def smallest_box():
# Inner scopes always draw from the next-outer layer.
# After `name` got overwritten, the name that will
# be printed is NOT the global-scope name anymore
print(name)
smallest_box()
smaller_box()
print_name_box()
Copy this code into your local IDE and run it to see the output, or use this
online playground. Just like in the previous example, you can see that the
value of name cascades down into the inner scopes.
In the scope of smaller_box(), the name variable gets assigned a new value,
"Sherlock". It keeps that new value further in any deeper-down inner
scopes:
In the graphic above, you can again see all new scopes surrounded by a
colored box. The name variable has the value "Mycroft" in all scopes that
have a red background, and it has the value "Sherlock" in all scopes that
have a blue background.
Tasks
1. Composition: You use functions to break your code into smaller chunks
that you can arrange and compose together.
2. DRY (Don't Repeat Yourself): You write functions to compartmentalize
and generalize a sequence of instructions so that you can use the same
code for it in multiple places in your script.
Until now, you've mostly written procedural scripts. Python files that execute
instructions from top to bottom. Sometimes, your script would do
something, sometimes it would produce some output and print it to your
command line.
You can think of a function like a script, only smaller. Just like your script file,
your function will contain a set of instructions. When you call your function,
these instructions will be executed, and the function will return some
output. This is similar to how you can run your script, which makes it execute
the instructions, and often give you back some sort of result.
One reason for building functions is also to generalize tasks and help you to
avoid repeating code. Instead, you can write the instructions once and then
use them multiple times. Ready to start writing your own functions?
Function Definition Syntax
def my_func(parameter):
pass # Replace with code that does something
return # Followed by a value that the function gives back
Tasks
Before turning to the next lesson, take a moment and write down a
couple of functions that you've already used throughout this course.
Can you identify which functions required parameters and what these
parameters were?
How could you find out which of the functions you used returned a
value, and which ones returned None?
Additional Resources
print(random.randint(0, 100))
hello()
import random
def hello():
print("hello")
Even though this code snippet consists entirely of valid Python code, you're
still running into errors:
# OUTPUT
NameError: name 'random' is not defined
Order matters, because your Python script will execute from top to bottom.
You'll run into two NameErrors, one about random and the other one about
hello because you are attempting to use them before you defined them.
# Import statements
import random
# Function definitions
def hello():
print("hello")
# Code execution
print(random.randint(0, 100))
hello()
When you follow this basic order, it helps to avoid errors related to execution
order, where you'd for example try to call a function before it's been defined.
This structure also helps you and other coders to quickly orientate within
your code and be able to read it faster.
Recap
Python code executes from top to bottom. When writing your scripts, you
should stick to a basic order:
1. import statements
2. function definitions
3. code execution
This helps to avoid errors and keep your code organized, accessible, and
maintainable.
With this structure in mind, you're ready to dive into building some practical
projects to apply your newly learned knowledge.
Familiar Functions
You've heard the word function many times before, and you probably already
identified a couple of functions you've been using in this course. Here are
some of them:
You didn't have to define any of these functions, because they're already
part of the Python language. But they aren't special beyond the fact that
they come pre-installed, and you could write your own versions of any of
these functions.
Fruitful Functions
When you think about what most of these functions have in common, you
might notice that you've mostly used them on the right side of assignment
statements:
nstr = "42"
n = int(nstr)
You can do that because you are interested in the return value of the
function.
Instead of allowing the return value to float off into some digital abyss, you
capture it and assign it to a variable, in this case, n.
Most functions that you'll work with, and most functions that you'll write, will
return a value that you want to use somewhere else in your program. In this
course, you'll read about functions that have a return value as fruitful
functions.
Void Functions
Functions that don't return a value you defined, always return None in
Python. These functions are also called void functions.
You might wonder what's the point of a void function. But just because a
function doesn't return anything, that doesn't mean that it can't still have an
effect.
However, print() is a void function and returns None. The output that is
printed to your console is not its return value, it's what is sometimes called a
side effect.
If you assign the output of a call to print() to a variable, as you did with the
fruitful functions discussed above, and print out the value of your new
variable, you'll get the proof:
result = print("hello void!")
print(result) # None
You'll see that the message gets printed to your console just as you're used
to. When printing the value of the result variable, however, you'll see that
the print() function returned None. It's a void function that does something
in its function body but doesn't explicitly return a value.
Most of the time you'll want to write fruitful functions, and there is even a
programming paradigm called functional programming that aims to only
use functions that have no side effects and always create the same output
when given the same input.
You can write programs in a functional style in Python, but you don't have to.
For now, you'll stick with getting stuff to work without worrying about
programming paradigms.
Recap
Functions can be fruitful if they return a specific value, or void if they return
the default None.
Tasks
Print out the return value of each of the functions you've gotten to
know.
What does their output tell you about them?
In the next lesson, you'll take a second look at the different parts that make
up a function definition, before starting to write your own functions.
Calling Functions
You've successfully defined a new function, greet():
Now you want to use the code you wrote to accomplish something. In this
lesson, you'll learn how to execute your functions.
Function Calls
Functions help you to reduce repeating code, which makes it easier to write
and maintain your code over the long term. This is true because once you've
defined your function, you can now use it as often as you want to:
To execute the code inside your function, you need to call the function.
You've done this before, so it might not look all that new to you.
To call a function, you write its name followed by opening and closing
parentheses. Optionally, if the function requires arguments, you need to pass
the right amount of arguments in between the parentheses:
What happens when you call a function without passing the right amount of
arguments?
Tasks
What happens when you call greet() without passing any arguments to
it?
What if you pass only one argument?
What happens if you pass numbers instead of strings to greet()?
Keep in mind that with your function definition you're creating a blueprint for
the function calls. You'll then need to stick to this blueprint and call your
function as it was defined.
However, there are situations when the number of arguments you want to
pass to a function might change in different use cases. Python has a solution
for that through a language feature called *args and **kwargs. You'll learn
more about it in the upcoming lesson.
Recap
To execute a function, you need to call it. You do that by writing the
function's name followed by opening and closing parentheses. If your
function requires parameters, you need to pass the right amount of
arguments in between the parentheses.
Parts Of A Function
After you've revisited a couple of the functions you've encountered up to
now, it's time to dive deeper into the structure of a function definition. Here
are once again the parts that make up a function:
1. def keyword
2. Function name
3. Parameters
4. Function body
5. return statement
In this and the following lesson, you'll look into each of these parts in more
detail, while you build out your first custom function from scratch. You can
use the function you'll build to greet people:
You'll look into the first three elements of the function definition below, and
you'll learn about the final two on the next page.
If you look at the first word in the first line of the code snippet, you'll see the
magic that starts it all off:
def
The def keyword is what tells Python that you want to define a new function.
Python expects a specific syntax after it. def needs to be followed by a
whitespace, then a function name, optionally some parameters, a colon, and
finally an indented function body.
The second word in the first line is the name of the function:
def greet
This name is just a variable name and can be any valid Python variable name.
It'll be the name you use to refer to the function object you're creating. Aim
to make your function names descriptive of what the function does.
For example, the function you're building here aims to greet people,
therefore a good descriptive name for it could be greet.
Following the function name, inside of the parentheses, you'll find the
function parameters:
Parameters are optional, but parentheses are required for your function
definition. So if you were to define a function that doesn't take any
arguments, this line would look like below:
def greet():
Before moving on to the next line, take a moment to consider the difference
between parameters and arguments. The two terms are often used
interchangeably, and while they are related to each other, it's worth to be
precise and understand how they really are different.
What that means is, that while there's a semantic difference between them,
parameters and arguments are two sides of the same coin.
When you define a function, you are writing parameters, such as greeting
and name in this example. When you call a function you are passing
arguments to the function call, for example, the strings "Hello" and
"Martin":
Whatever you pass to the function call as an argument gets assigned to the
corresponding parameter inside the function body.
Info: Order matters when using positional arguments like in this case. You
need to pass the arguments in the right order, or they'll be assigned to the
wrong parameters in your function and you'll get surprising results.
You can also pass arguments together with their parameter names as
keyword arguments, which allows you to pass them in any order and still
have them assigned to the right parameters:
Info: The keywords need to match the parameter name that your argument
will be referred to in the function body.
They have the same effect as positional arguments, only that you'll be able
to call your function also without providing all the input. If you omit a
parameter that has a default value, then it'll be used instead:
print(greet())
print(greet(name="Fievel" greeting="Hello"))
# OUTPUT:
# Hi, User! How are you?
# Hello, Fievel! How are you?
One aspect that makes functions so versatile is that you can pass different
arguments to a function call. They'll be assigned to parameters in the
function body and that's how the execution can work for different inputs.
Recap
1. def keyword
2. Function name
3. Parameters
4. Function body
5. return statement
1. the def keyword, which informs Python that you want to define a
function
2. the function name, which is the variable name that you'll refer to when
working with your new function
3. parameters and arguments
1. positional arguments
2. keyword arguments
You can also mix both types, only keep in mind that they have an inherent
order and you need to first provide all positional arguments before providing
any of the keyword arguments.
In the next lesson, you'll continue to learn more about the different parts that
make up a function definition, specifically the function body and the return
keyword.
Parts Of A Function Part 2
In the previous lesson, you've started to dive deeper into the different parts
that make up a function definition. Specifically, you've learned about:
Especially the topic of parameters and arguments is quite a lot to take in,
which is why you'll learn about the next two parts that make up a function
definition in this lesson.
Until now, you've described the function you've defined would only contain
the first three parts, which are all congregated in the first line of code:
This doesn't yet make for a useful function, and in fact, would still cause a
SyntaxError if you tried to do anything with it until now.
After finishing the first line of code of a function definition, and ending it with
a colon (:), Python expects an indented code block. You've already
encountered similar syntax before with loops and conditional statements.
Functions use a similar syntax, where indentation has meaning and defines
which lines of code are part of your function. This code is also called the
function body.
Inside the function body is where all your functional code goes:
def greet(greeting, name):
sentence = f"{greeting}, {name}! How are you?"
In this example, your code logic works with the two parameters to construct
a new string that you assign to the variable sentence.
The function body is the place to work your code magic. As you can see
above, you can use parameters that you've defined in the first line of your
function definition. You can use, mix, and re-calibrate them, and finally add
your special spices until you come up with the perfect value.
The final value that you've created in your function body is what you want to
return from the function using the return statement:
def void_func(message):
print(message)
You can see that you didn't even need to write the return statement at all. If
it's not there, Python will automatically add return None to your code.
def void_func(message):
print(message)
return None
Avoiding the return statement is equivalent to adding the line return None.
Note: Keep in mind that even though this function does something by
printing to the console, the printed message isn't the return value of the
function.
def fruitful_func(message):
return message
This function returns a value that you can assign to a variable and work
forward with. In the example above, the value of message would just be
whatever argument you passed to your function when executing it. There's
not much functionality going on here, but the function is still fruitful since it
has a return value.
# OUTPUT:
#
Currently, this code snippet doesn't print any output, but you could print()
the return value of your function call, instead of assigning it to a variable:
print(greet("Hello", "World"))
# OUTPUT:
# Hello, World! How are you?
As you can see, greet() now returns a value that you can continue to work
with, for example by assigning it to a variable name or printing it out.
The return value is an essential part of your function because it defines what
you'll have access to from all the magic that happened inside your function.
Variables and parameters that you define inside the function body won't be
accessible outside the function.
The return value is the only output that you can continue working with.
Info: If you want to return more than one value from a function, then you
need to wrap your data in a collection. Most of the time, you'll use tuples for
this. For example, the built-in enumerate() function returns a tuple consisting
of an index number and the element's value.
Play around with this in the code playground below to make sure you
understand the concept of void and fruitful functions, and what role the
return statement has in this:
Recap
1. def keyword
2. Function name
3. Parameters
4. Function body
5. return statement
The function body needs to be indented and contains all the code logic
that your function will compute.
If you want to continue to work with a value that your function computed,
then you need to include a return statement. You'll only have access to the
return value after your function has finished execution.
Info: The reason why you won't have access to any function parameters
outside of the function body is related to the concept of scopes that you'll
learn about later in this section.
You've read about executing functions and function calls in this lesson, on
the next page you'll learn more about what that means and how you can call
your brand new greet() function.
Args And Kwargs
During the last times when you were floating around StackOverflow
researching Python topics in your free time, you might have come across the
words *args and **kwargs. They arguably sound like guttural sounds that a
frog might make, rather than the elegant zssssh of a python. So, it's no
wonder that these special arguments can cause a bit of confusion:
But when it comes down to it, frogs are just animals, and *args and **kwargs
are just another useful feature of the Python language.
The syntax that actually provides the functionality of this feature are the
asterisks (* and **).
When you define a function, you can prefix a parameter name, e.g. args and
kwargs, with the asterisks. By doing this, you implement a language feature
that allows your function to now take arbitrary amounts of arguments.
But exactly does that mean, and what's the difference between *args and
**kwargs?
If you add *args as a parameter to your function definition, it will package all
passed arguments into a collection. You can then access each item like you
would in a normal list. More commonly, however, you'll just iterate over all
items in args:
def print_args(*args):
for a in args:
print(a)
# OUTPUT:
# barcelona
# tahoe
# ubud
# koh tao
In this code snippet, you're using *args in the first line of your function
definition, just like you would otherwise define a single parameter that your
function takes as its input.
However, by prepending the parameter name with the single asterisk (*), you
invoke the special language feature that allows you to pass as many
arguments to your function call as you want to.
Tasks
You might have noticed that the asterisk is only used inside the parentheses
when you define your function. When you use the packaged arguments
inside your function body, you only use the variable name args.
This means that you need to pass the arguments as keyword arguments,
and access them in your function body as you would with a dictionary:
def print_kwargs(**kwargs):
for k, v in kwargs.items():
print(k, v)
print_kwargs(country='ukraine', city='odessa')
# OUTPUT:
# country ukraine
# city odessa
Here you added **kwargs in the first line of your function definition.
By prepending the parameter name with the double-asterisk (**), you invoke
the special language feature that allows you to pass as many keyword
arguments to your function call as you want to.
Use Cases
To think of a practical use case of this feature, and tie it back to your greet()
function, imagine that you're handling a lot of users in your web app. You
want to send them all a short email every month in order to check-in and see
how they like your product.
For this, you'll write a new function, greet_many(), based on greet(), with a
few changes to incorporate the use of *args and allow the function to greet
an arbitrary amount of users:
Info: Note that if you are mixing defined positional parameters, such as
greeting, with *args, then *args needs to come after any defined
parameters in your function definition.
Because your function will only be able to return one value, you're creating a
new string named greetings that you later concatenate each individual
greeting sentence to, followed by a newline character ("\n") for better
formatting.
When you collect arguments with *args, they get put into a collection, which
is why you're introducing a for loop to access each item in the collection.
With greet_many(), you are now able to greet all your users with the same
greeting message, and it doesn't matter how many users currently are in
your app. *args will handle them whether it's only one or 1000 of them.
In this example, you can also instead explicitly use a list or a tuple to pass
all the names in a single argument that you could have called names. This
approach would be more descriptive in this specific situation and goes to
show that there are always multiple ways to achieve your aim when you're
programming.
Info: You'll revisit the use of *args and **kwargs in another example later in
the course when you'll learn about decorators.
Run the code in your local IDE and make sure that you can get your adapted
greet_many() function to greet different amounts of users before moving on.
Tasks
Recap
In this lesson, you learned about using *args and **kwargs to allow your
functions to take arbitrary amounts of arguments:
You can also use the * and ** syntax to unpack collections and mappings
when calling a function:
Maybe you noticed that it's helpful that you named your function
descriptively as greet(). This helps to know what the function is about when
you need to call it.
Most of the time, however, you'll want your functions to be called in a very
specific way, with a specific amount of inputs. greet() is meant to be called
with exactly one greeting and exactly one name, both of which should be of
the str data type. How can you communicate this to other developers?
Docstrings
To better document what your functions do, you should add docstrings to
the function definition. In this lesson, you'll learn what docstrings are, how to
write them, and how to use the information they provide.
Special Comments
Note: You'll learn about classes and methods in the next module of this
course. For now just keep in mind that docstrings apply to them in the same
way as they do to functions.
The rationale for docstrings is to help to know how to work with your
functions. Docstrings should describe at least three aspects of your
function:
1. what it does
2. what arguments it takes
3. what it returns
Write A Docstring
Docstrings have a specific syntax that you need to follow, and additionally
there exist a couple of conventions on how to write good docstrings. The
two essential aspects of a docstring are:
To walk through creating a docstring for a function, you'll add one to the
greet() function you've written earlier:
Args:
greeting (str): The greeting to use, e.g. "Hello"
name (str): The name of the person you want to greet
Returns:
str: A personalized greeting message
"""
sentence = f"{greeting}, {name}! How are you?"
return sentence
Read A Docstring
After writing your docstring, you're now able to quickly get information about
how to use your function anytime you need it.
Open up a new interpreter session and copy greet() including its docstring
in there. You now have two options to read your docstring:
Try both of them and see what they show you. When calling help(greet) you
should see a full-screen description show up in your terminal that displays
the information from your docstring:
greet(greeting, name)
Generates a greeting.
Args:
greeting (str): The greeting to use, e.g. "Hello"
name (str): The name of the person you want to greet
Returns:
str: A personalized greeting message
You can exit this screen by pressing the q character on your keyboard.
When you print the .__doc__ attribute of your function, you should see the
same output, but right in your console:
print(greet.__doc__)
As you can see, having defined a descriptive docstring gives any developer
who might use your function a good description of what it is about and how
they can use it. This information is always accessible to them, given that
they've defined or imported your function.
This also works for functions and classes from the standard library, or with
code that other developers wrote. As long as they've added docstrings!
Tasks
Args:
greeting ([type]): [description]
name ([type]): [description]
Returns:
[type]: [description]
"""
sentence = f"{greeting}, {name}! How are you?"
return sentence
You can jump between the different blanks you need to fill using your Tab
character, which makes creating comprehensive docstrings much faster.
If you're not using VSCode, make sure to check the extensions of your
favorite IDE, it'll most likely have a similar product that you can use.
PyCharm, for example, includes this auto-completion for docstrings by
default.
Recap
Docstrings are special comments that you write right after starting a function
definition. They start and end with triple-double quotes ("""), and there are
various guidelines on how to write good docstrings.
The style you should use depends on what the organization you're working
with uses as their standard, however, a good docstring should describe at
least:
You can follow Google's docstring guidelines and install an extension in your
IDE that helps you to quickly auto-generate the basic structure of your
docstrings.
Additional Resources
In your IDE, you can collapse parts of your code, usually by clicking a small
arrow displayed in the margins next to your line numbering. But what if you
ever only wrote a short docstring like the one above?
Dynamic typing, also called "duck typing", evaluates variables and inputs at
runtime of your script. There is no need to declare the type of a variable
when you initialize it. Your variable, e.g. a, could be of any type, and change
types throughout its lifetime:
a = 3
a = "hello"
a = 4.2
a = True
Static typing, on the other hand, gives every variable a specific type when
you create it. In a statically-typed language, you can't change the variable's
type once it's declared in the same way you can in Python.
As you can see, Python doesn't throw an error. It's able to handle the input,
even though it might not be what you wrote the function for. This can
potentially cause troubles in the applications you're building, but it also
opens up new possibilities and makes it faster and more convenient to
develop with.
Type Hinting
To add type hints to your function definition, you add the expected type of a
parameter after a colon (:). You can also define the expected type of your
return value with an arrow (->) at the end of the first line of your function
definition:
With the type hints added to greet(), it's quicker to see what type of input
the function expects, and what type of data it returns.
But Python stays true to its dynamic typing and won't enforce these type
hints. Even if you added them, and you pass an int to greet(), you still won't
get an error. Type hints are mainly meant to improve the documentation of
your functions.
There are external packages, such as mypy, that make it possible to enforce
static typing in Python. mypy uses the type hints and throws errors if you
attempt to pass any data type that contradicts them. By using type hints
together with mypy to check your code, you can benefit from some of the
advantages of static typing in Python.
Info: mypy is an external package, which means you need to install it before
you can use it. If you haven't done this before, read over the next sections
about virtual environments and external packages before completing the
exercise below.
To check your code with mypy, you need to first install the package, and then
run mypy from your CLI and pass it the path to your Python script:
mypy yourscriptname.py
If mypy doesn't identify any issues where your type-hinted function might be
called with the wrong data types as input, it'll show you a success message.
Otherwise, you'll get to meet a new error-message friend:
$ mypy typechecker.py
typechecker.py:7: error: Argument 1 to "greet" has incompatible type "List[int]"
typechecker.py:7: error: Argument 2 to "greet" has incompatible type "int"; expected
typechecker.py:8: error: Argument 1 to "greet" has incompatible type "bool"; expected
typechecker.py:8: error: Argument 2 to "greet" has incompatible type "bool"; expected
Found 4 errors in 1 file (checked 1 source file)
mypy successfully analyzed your code and identified that it included some
use cases that contradict the information encoded through your function's
type hints.
Tasks
You don't need to add type hints to your functions. It's good to know that
this feature exists, but it doesn't need to be part of your workflow. If you like
the additional descriptiveness that it gives to your functions, then it's a great
habit to get into adding type hints as a part of documenting your functions.
Recap
Python is a dynamically typed language, which gives you freedom but also
introduces potential errors. You can add type hints to your functions to
better document them. You can even use external packages, such as mypy,
to enforce type hints and use Python more like a statically typed language.
To add type hints to your function definition, you add the expected type of a
parameter after a colon (:). You can also define the expected type of your
return value with an arrow (->) at the end of the first line of your function
definition.
Additional Resources
You'll get to know the enumerate() function, and then venture off to create
the function from scratch by yourself.
# OUTPUT:
# 0: Intro Python
# 1: Intermediate Python
# 2: Advanced Python
# 3: Professional Python
In the code snippet above you can see enumerate() in action. You're first
defining a list called courses that holds a couple of string values.
Then you create a for loop that is a bit different than it would be if you were
looping directly over the list. Instead of defining only one loop variable,
you're working with two of them, index and course.
You have access to two loop variables because of what enumerate() does to
your courses list. Instead of providing only the current item in your iterator,
enumerate() packages each item together with an incrementing number into
a tuple:
(0, 'Intro')
(1, 'Intermediate')
(2, 'Advanced')
(3, 'Professional')
Your for loop then implicitly uses tuple unpacking to take the tuples
generated by enumerate() apart again and provide them as two separate
variables that you descriptively named index and course.
Alternatives
As you can see, there's more than one way to solve this challenge, but
Python's enumerate() is a well-liked function in the Pythonsphere. So why
not get to know it more closely?
This project will train you in your understanding of data types, writing
functions, and reading documentation.
Don't just copy the range(len(sequence)) example and work off of it.
Instead, go online, train your search engine skills, and research how
enumerate() works internally. Then use this to inform your design decisions
for your custom my_enumerate() function.
def my_enumerate():
# You implement this function
pass
Specifically, there are two new big concepts that you can incorporate into
your CLI game project to make it better:
1. File Input/Output
2. Functions
Take a moment and brainstorm with your notebook about ways that you can
include these concepts to improve your game.
When you're done, here are some broad suggestions to consider:
State: Write the state of your gameplay to a file at the end of a session.
You could use it to keep track of your player's inventory across multiple
instances of your game.
Functions: Refactor your code to use functions for the different actions
that your player can take. Then use your game loop to call the functions
that the player chooses.
Documentation: Add docstrings and type hints to your functions.
Explain what they do and learn to use good practices for writing
readable and reusable functions right from the start.
Please share your updated game on the forum in the Command-Line Games
thread.
More Than Just Python
Wow! If you've started this course from the beginning and made it all the way
here, you've already come a long way! Congratulations on your progress :)
Install Python
Understand what programming is and why it is useful
Read, write, and understand Python code
Use Python as a scripting language
Write Python in the REPL and as a script
Create variables and assign values to them
Work with text and numbers
Identify and use common data types, such as:
int
float
str
tuple
list
set
dict
bool
None
Plan out your task with pseudocode
Document your reasoning and decisions using code comments
Write loop logic to tackle repetitive tasks with:
for loops
while loops
list comprehension
Make comparisons and calculations using operators:
Assignment operator
Arithmetic operators
Membership operator
Relational operators
Logical operators
Identity operator
Make decisions with conditional logic and control flow using:
if, elif, else statements
Looping keywords: break, continue, and return
Collect user input with input()
Format your strings with f-strings
Write and call functions to avoid repetition and generalize your code
logic
Provide input to your functions with arguments, and return values to
reuse them
Use positional and keyword parameters
Document your functions with docstrings and type hinting
Understand scopes in programming
Automate repetitive tasks on your file system
Work with File input/output on your computer
Projects
You've also applied code logic concepts, correct Python syntax, and your
creativity to build out projects:
File Renamer: Move and rename files in a folder on your file system
Automate renaming and moving files
Using the pathlib module to handle file paths
CLI Games: Build a small CLI game
Guess My Number
Hangman
Adventure role-playing game
At this point, you've learned enough about Python and programming so you
are ready to handle any scripting task.
World Building
But there are also other concepts that you learned on your way.
Programming isn't just programming, after all, and in this course, you're
learning more than just programming.
Next Steps
In the upcoming sections of this course, you'll spend more time on topics
that aren't exclusively related to the Python programming language.
However, these topics are important for your path to becoming a capable
and versatile developer, whether you want to use your skills privately or as a
profession.
At the end of this course module, you'll build a project that uses Python to
interact with data provided over the internet. You'll programmatically
connect and retrieve this data, and include it in programs that you build
locally on your computer.
You'll venture out into the world-wide-web, and the Internet is a messy place
that consists of thousands of different technologies. You'll keep using
Python to accomplish your tasks, but there are some more general
software development topics that you'll learn to be able to do so safely
and productively.
You'll also keep learning more about Python and how you can debug your
Python programs, as well as how to use specific Python tools to intersect
with and tackle some of these challenges.
Recap
You've already learned a lot up to this point, both about Python, and
programming, as well as related concepts that you might not encounter in
pure Python-focused courses.
Moving forward, this module will help you learn more Python, but it'll also
start to focus on more general concepts that are part of your tasks as a
developer, as well as highlighting how to intersect them with your Python
skills.
In this section, you'll learn about external Python packages, as well as how
you can install them in virtual environments (venvs), and use these venvs to
automate some of your project-specific settings.
External Packages
In the lesson about type hinting in Python, you've learned about an external
package called mypy. If you tried to run the mypy command without first
installing it, you'll have encountered a new error friend. If you went to check
it out on GitHub and read over their installation instructions, then you've
probably come across the line:
While Python already comes with a lot of code in its standard library, there
are a vast amount of additional packages available that you can install from
external sources. mypy is one such package.
You've used packages from the standard library before when importing
pathlib and random.
However, another reason that Python is so widely used is its rich ecosystem
of packages and modules. It also helps that it's straightforward to install and
use an external codebase.
Third-Party Packages
The Python Package Index (PyPI) is a central repository where all Python
users can upload their own code as a package, and where everyone can go
to download those packages.
PyPI contains the most well-known third-party packages that you might
want to work with. Because so many packages are hosted in this central
place, there's also a common tool that comes with your Python installation
that allows you to quickly install a package from PyPI. This tool is called pip.
Python ships with a tool to install packages that is called pip. pip stands for
"pip installs packages" and it makes installing modules and packages quite
straightforward:
For example, in order to install mypy locally on your computer, you only need
to type this one line of code into your terminal:
python3 -m pip install mypy
This command would install mypy system-wide for your python3 installation.
Note: You most likely don't want to do that. Installing external packages
system-wide is a bad practice because it can lead to version clashes down
the line.
Recap
Python comes with a lot of great packages, and you can install even more of
them from a central location called PyPI with the pre-installed package
manager pip.
Additional Resources
With a bustling activity as a developer, when the gears are oiled and grinding
feels like a morning jog, it happens quickly that your dependencies go out
of hand.
Dependencies
Eventually, you'll want to build Python projects that use the rich ecosystem
of modules and packages that are out there.
A dependency is code that someone else wrote and that you use in your
own project. The functionality of your own code depends on the code you
pulled in from an external source.
The developers who wrote the package put a lot of effort into writing code
that abstracts away implementation details and makes it possible for you to
build on top of that. They did that so that you won't have to re-invent the
wheels they've already built.
Using dependencies is very common when you build out more complex
programs, and it's extremely helpful for a quicker and more productive
development process.
For example, you might work in web development and use Django, a Python
web framework that you can install as an external package through pip.
Info: Django is just an example for a dependency you might have in your
code. Anything that you python3 -m pip install is a dependency.
Your system-wide Python can have only one version of Django installed at a
time, so you'd quickly run into trouble in such a situation.
The Concept
Virtual environments are a similar concept, only that they don't box up a
complete operating system, but instead just the following:
If you keep your Python inside a virtual environment, then you're keeping it
and all the packages it needs for that specific project, safe from other
environments. You also keep the rest of your computer safe from whatever
goes on inside your terrarium, ahem, virtual environment.
The Reality
.
├── bin
│ ├── Activate.ps1
│ ├── activate
│ ├── activate.csh
│ ├── activate.fish
│ ├── dmypy
│ ├── easy_install
│ ├── easy_install-3.8
│ ├── mypy
│ ├── mypyc
│ ├── pip
│ ├── pip3
│ ├── pip3.8
│ ├── python -> /Users/martin/.pyenv/versions/3.8.5/bin/python
│ ├── python3 -> python
│ ├── stubgen
│ └── stubtest
├── include
├── lib
│ └── python3.8
│ └── site-packages
│ ├── 8c8c5116bc4884237d33__mypyc.cpython-38-darwin.so
│ ├── __pycache__
│ │ ├── easy_install.cpython-38.pyc
│ │ ├── mypy_extensions.cpython-38.pyc
│ │ └── typing_extensions.cpython-38.pyc
│ ├── easy_install.py
│ ├── mypy
On Windows, the folder structure is similar but not quite the same. For now,
the differences between the folder structures on the different operating
systems don't matter.
What you can see in the tree structure above is just a small part of all the
files and folders that make up such a virtual environment. If you're curious,
you can look at the full contents of an example virtual environment.
Whether you ventured bravely to follow the link or not, you'll see that this
folder structure is huge. That might seem daunting, and it would be if you
had to build it yourself.
But instead, Python comes bundled with a command that creates all of that
for you. You'll get to know it in the next lesson.
Recap
Starting to use them early on can be a huge time-saver. In the next lesson,
you'll create your first virtual environment.
Working With Virtual Environments
You can automatically generate the folder structure for a virtual environment
(venv) in one line of code, activate it with a second command, and
deactivate it with yet another one. To get started working with virtual
environments, you only need to know these three commands, and you'll
learn them in this lesson.
To create a new virtual environment that is set up for all your Python
development needs, you'll need to run the following command:
This is a Bash command, where you're using the program python3, which is
your system-wide installation of Python 3, to call one of its built-in modules
(-m) called venv. The venv module that comes with Python's standard library
allows you to create virtual environments. As the final part of this command,
you need to provide a name for your new virtual environment.
It's a default to call your virtual environments env or venv but you could name
them anything you want. You'll just need to remember what you called it
because you need its name to activate it in the next step.
Note: Make sure that your virtual environment's path does not include any
whitespace. Otherwise, the executables won't be found and you might end
up accidentally using the system-wide install of pip. See this StackOverflow
answer for more information.
It's strongly suggested to stick with a default name, and this course will use
venv as a name for any virtual environments created. Therefore, to create a
virtual environment with that default name, you'd run the following
command:
Give it a try, wherever you currently are in your CLI. You won't break
anything.
After you successfully ran the command, navigate to the new folder that was
created. It'll be named venv if you followed the instructions above.
Inspect the folder using your Finder, Explorer, or CLI. Take a look into the
bin/ subfolder on macOS and Linux, or the Scripts\ folder on Windows,
respectively. You might find that every freshly minted virtual environment
comes with executable files for the Python interpreter, as well as pip.
You are now the proud owner of a virtual environment. But beware! You are
not yet inside this safe space that you created! In order to enter your new
venv, you'll have to activate it. You can activate a virtual environment that
you named venv by typing the following command on macOS and Linux:
source venv/bin/activate
The source command runs the activate script of your venv. You might have
seen it inside of your venv's bin/ folder.
On Windows, the command looks a little bit different, because the activation
script is located in a different folder:
venv\Scripts\activate
Just like on UNIX systems, however, you're also executing a script called
activate. On Windows, that script lives in a directory that's aptly named
Scripts\.
The activation script grants you access to the safe kingdom of venv. After
the command has finished executing, you'll see that your command prompt
has slightly changed. As an indicator that you are now inside of the virtual
environment, you'll see the name of your venv in brackets in front of each
new line in your terminal:
Depending on the shell that you are using in your terminal, it might look
different. But the concept is the same. As soon as you can see your virtual
environment's name in brackets next to your command line prompt, it means
that you can finally start breathing again. You have arrived in the safe
kingdom of venv.
Now you're ready to do all the work you need to do on the project that you
created the venv for. From the terminal window where you activated your
venv, you can now install all the packages you need:
The external packages install inside your venv. This means that when you
deactivate your venv, they won't interfere with other packages you installed
system-wide. It also means they'll be waiting for you when you re-activate
your venv the next time you want to work on this project. And if you wanted
to get rid of everything, all you need to do is to delete the venv folder.
To return to your normal system's environment you only need to type one
passphrase into the console:
deactivate
The indication that you're in the venv will disappear and your shell will show
your normal prompt again. That's all there is to deactivating a venv. Welcome
back!
Tasks
Many IDEs, such as VS Code and PyCharm, can help you automate working
with venvs. PyCharm, for example, automatically creates and activates a new
virtual environment for you when you create a new project. If you use these
features, you won't need to go through the process of creating and
activating a virtual environment yourself.
Tasks
Research the documentation of your favorite IDE and find out how it can
help you to make working with virtual environments easier.
You'll notice whether or not your venv is activated in your IDE's terminal in
the same way as you do in your normal Bash terminal:
The screenshot above shows a terminal window in VS Code with an
activated virtual environment named venv.
Recap
There are three commands you need to know to start working with venvs:
You'll want to create a new virtual environment for any project that uses
external third-party packages. You can install them into your venv after
creating and activating it. When you're done working on a project, you
deactivate your venv to return to your normal system-wide environment.
Introduction To Environment
Variables
In this lesson, you'll learn about environment variables in Bash on UNIX
operating systems, but Windows has the same concept with slightly
different commands that you can read about in this external guide on
configuring environment variables in Windows. The concept of environment
variables is not specific to Python. Instead, it's a common standard used
across different areas of software development.
In UNIX systems, the most famous one of them is $PATH, which specifies file
paths where your system looks for executable files. Windows has a similar
variable called Path.
Use Cases
You can access the value of your environment variables anywhere in your
project without ever spelling out the actual value of that variable. Instead,
you can refer to it through the environment variable.
That way you can work with secrets and passwords throughout your project,
and commit all project-relevant code to GitHub while keeping your sensitive
information safe and to yourself.
Many larger programs that you'll build will include some setting information
that you don't want to share with the world. Think about API keys for web
service calls, database login credentials, or the ingredients to your secret
sauce in your recipe generator. However, you might still want to be able to
collaborate online with other developers on your code through GitHub.
Info: While Git and GitHub are great, sensitive information should never
make its way to the open-source community.
Environment variables can help you with generalizing the setup of your
applications, as well as with separating out sensitive information so that
you'll have an easier time keeping that information safe.
Horror Scenarios
The web is full of horror stories of accidentally posted API key secrets that
ended up costing the owner a lot of money. If you need some extra
convincing, or just want to stay up late tonight, check out the following
posts:
The quick takeaway is that you should never post your sensitive information
to GitHub.
Info: Bots are quick, and one compromised commit is one too many.
Keep in mind that there are multiple ways to keep your sensitive information
safe. In this course, you'll learn how you can use environment variables to
separate out sensitive information and make it less likely you'll end up with
an accidental horror story.
Recap
You can use environment variables to help with your project setup and
separate out sensitive information. In the upcoming lessons, you'll learn how
to:
Even though the skills you're learning here are not specific to Python,
knowing how to work with environment variables is a standard skill for
software developers that'll come in handy especially when you'll work on a
team. It's also helpful when you build projects and store your code on
remote version control sites, such as GitHub.
Working With Environment Variables
In this first lesson about environment variables, you'll learn how to inspect,
add, and remove them directly from the command line.
Note: These lessons focus on using the Bash command language, which
you'll have available if you're on a UNIX system or use WSL on Windows. If
you're working directly on Windows, then setting environment variables
takes less effort if done through the graphical user interface. You can read all
about it in this detailed guide on configuring environment variables on
Windows.
Open up your terminal and type the Bash command printenv. This will give
you a list of all the current environment variables present in your system:
HOME=/Users/Martin
LOGNAME=Martin
USER=Martin
PATH=/Library/Frameworks/Python.framework/Versions/3.9/bin:/usr/bin:/bin
This example output shows you a couple of environment variables that are
currently defined on your local machine. You'll probably see a different name
and some additional lines in your own output.
You can check the value of each variable with echo $<NAME>. For example, to
check the value of LOGNAME, you can type the following command:
echo $LOGNAME
The output you receive when running the same command will be what your
LOGNAME variable points to. You can confirm this value also by running
printenv and looking for LOGNAME in your output.
With these two commands, you can inspect all the environment variables
that are currently defined in your system. But what if you want to change,
add, or remove one using Bash?
Using Bash in your CLI, you can add a new environment variable with the
following command:
export <NAME>=<VALUE>
In this example, you'll have to replace <NAME> with the new environment
variable that you want to add. You also need to replace <VALUE> with the
value you want to assign to that environment variable.
For example, to add a new variable with the name DAY and the value Sunday,
you would spell it out as follows:
export DAY=Sunday
After executing this command, you can see that a new variable has been
added to your environment. Take a look at it using printenv and echo as
described above.
In order to remove an environment variable with Bash, you'll have to call the
following command:
unset <NAME>
Again, you'll have to add the actual name of the variable you want to remove
instead of the placeholder <NAME>:
unset DAY
This Bash command removes the DAY variable you set before.
Try adding and removing some environment variables using these Bash
commands. Remember you can always check what’s happened using
printenv or echo <NAME>.
However, when you are working on a project that requires a specific API key,
you don't want to set your environment variables across your whole system
environment. The value will be project-specific, which is why you should
compartmentalize your environment variables just like you did for your
dependencies by including them into your virtual environments.
Recap
You can interact with system-wide environment variables directly from your
Bash command line:
In the next lesson, you'll learn how to combine the concept of environment
variables with what you learned about virtual environments, to create
environment-specific virtual environment variables.
Additional Resources
It's a much better idea to set these types of environment variables inside of
your virtual environment. In this course, these types of variables will be
called virtual environment variables.
Info: You can find this file inside of the venv folder that got created by
running the command shown above. The relative path of this script is
venv/bin/activate on UNIX systems and venv\Scripts\activate on
Windows.
This script runs every time your venv gets activated, which makes it a good
place to let your computer know which environment variables you would like
to have access to, and which ones to get rid of once you exit the virtual
environment.
You'll now edit the activate script and hard-code the values you want in
there. First, you need to make sure that your virtual environment variables
won’t stick around once you deactivated them, so you start by unsetting the
environment variable that you haven't even created yet.
deactivate () {
unset MY_SUPER_SECRET_SECRET
# Lots of other code
}
Adding this line in the deactivate section makes sure that your virtual
environment variables won't leak into your system environment. Instead,
they'll exist only within your virtual environment.
Once you wrote the code to unset your variable, it's time to make sure you
also set it, so it'll exist in the first place.
You can set a virtual environment variable in the same way as you practiced
before with your Bash CLI. However, instead of typing the export command
directly in your terminal, you'll add it as a new line of code at the end of the
activate script:
Info: You'll need to wrap the value of your virtual environment variable inside
of double quotes ("").
Save the Bash script and close it. Now you can activate your virtual
environment:
source venv/bin/activate
Once the virtual environment has been successfully activated, you can now
run the printenv command to inspect the state of your environment
variables in your current environment.
After you confirmed that activating your virtual environment brings your
virtual environment variable into existence, go ahead and deactivate it. Use
printenv once again. Your secret should be gone.
As you can see, this setup can keep your project-specific secrets safe within
their own comfy virtual environments.
secret = os.environ['MY_SUPER_SECRET_SECRET']
print(secret)
You'll be able to run this code from any file in your project, as long as your
virtual environment is activated, and an environment variable called
MY_SUPER_SECRET_SECRET is defined.
If you don't want your script to terminate with an exception when you didn't
define the environment variable, then you can use Python's dict.get()
method instead of doing a direct lookup.
Recap
Note: Make sure that you add your virtual environment folder to your
.gitignore file, or you'll end up pushing your secrets to GitHub after all.
Don't worry too much about GitHub and version control if you're not yet
familiar with it. You can head over to the Git and GitHub course to study up
any time you feel like it. For now, it's not essential to understand it in order to
continue with this course.
In the next section, you'll dive a bit deeper into more advanced Python
concepts before later learning about APIs and databases, to prepare you for
using Python to interact with the world-wide web.
Intro To New Concepts
In the upcoming section, you'll learn about some more advanced topics of
the Python language in small and self-contained lessons.
You might not need to work with these concepts yet, but it's still good to
know about them and spend some time practicing them, too.
codingnomads
├── ingredients.py
└── soup.py
If you're working in soup.py, you can get access to anything you've defined
inside of ingredients.py with a familiar import statement:
# soup.py
from ingredients import carrot
Info: You can create the folder structure and files to work alongside this
lesson. To make the import work in soup.py, you'll need to define a variable
called carrot in ingredients.py.
After you've imported the code, you can now use it in your other script:
# soup.py
from ingredients import carrot
print(carrot)
You can use the carrot variable in any way you want to, just as if you had
declared it directly in the soup.py file.
Namespace Preservation
# soup.py
import ingredients
print(ingredients.carrot)
With this type of import, you need to call any variables or functions defined
in ingredients.py through the ingredients. namespace.
Alias Imports
Sometimes you want to preserve the namespace, but you don't want to keep
typing out a long word such as ingredients over and over again. You can
assign an alias to your code module during the import:
# soup.py
import ingredients as i
print(i.carrot)
If you assign an alias to your module name during import, using the as
keyword, then you can access its namespace through the alias you gave it. In
the code snippet above, the alias for ingredients is just the single letter i.
When you'll work with some popular external Python packages, you'll notice
that some of them are by default aliased during import, for example:
import pandas as pd
import numpy as np
These aliases are only conventions that help to preserve the namespace of
the packages, but allow the developers to type less code. If you haven't
encountered these packages during your online research, look them up so
you have an idea of what they are used for.
Recap
For code files that share a folder with each other, you can import your own
custom code in the same way that you can import modules from the
standard library or even external packages.
During import, you can decide whether you want to retain the namespace
and import everything or pick only specific pieces of information that were
defined in the original module. You can also assign an alias to your imports,
to reference them in a more convenient way.
But how can you import your own code from files that aren't right in the
same folder as your current script?
Nested Namespaces
In the previous lesson, you learned that you can import your own code in the
same way that you can import modules from the standard library, as well as
external packages that you've installed with pip.
codingnomads
├── ingredients.py
├── recipes
│ └── soup.py
└── cook.py
How can you access the logic you wrote in soup.py from your cook.py
script?
Deeper Namespaces
# cook.py
import ingredients as i
import recipes.soup as s
c = i.carrot
s.make_soup(c)
In this example, you can see that Python treats the additional folder as an
extra namespace. Depending on how you choose to import your code, you
can access it through the full namespace, recipes.soup, or through an alias,
as shown in the code snippet above.
Note: For the code snippet to work, you need to have both a carrot variable
defined in ingredients.py, as well as a make_soup() function in
recipes/soup.py that takes one argument. You can only import and use
what you've actually defined.
Python allows you to handle namespaces in a way that can seem intuitive
when you're used to working with folder structures.
In the next lesson, you'll run into a little side-effect that you might encounter
when you're importing code from your custom modules.
Unexpected Messages
You've defined a couple of yummy ingredients in your ingredients.py
module:
# ingredients.py
def prepare(ingredient):
return f"cooked {ingredient}"
carrot = "carrot"
salt = "salt"
potato = "potato"
print(prepare(potato))
Because you spent some time writing your code and you were also checking
its functionality, you kept a print() at the bottom of your file:
print(prepare(potato))
So far that all seems fine, and if you execute ingredients.py, it all seems to
work normally.
You head over to soup.py and import just your carrot once again:
# soup.py
from ingredients import carrot
However, now you're getting some unexpected output in your console:
# OUTPUT:
cooked potato
Huh?! Where did the potato come from, and why is it already cooked?
You've explicitly imported only the carrot so far. Does that mean you can
access the potato variable in soup.py:
# soup.py
from ingredients import carrot
print(carrot)
print(potato)
The output of running this doesn't make the mystery less mysterious:
# OUTPUT:
cooked potato
carrot
Traceback (most recent call last):
File "/Users/martin/codingnomads/cook.py", line 9, in <module>
print(potato)
NameError: name 'potato' is not defined
Your CLI clearly prints the cooked potato, but Python doesn't know anything
about the potato variable and throws its digital hands in the air with a sigh of
NameError. What's going on here?
Info: When you import code from a module, you execute the whole script. If
there's any code execution written in the script, it'll run and potentially
produce some unexpected output.
Python runs the call to your print() function inside of ingredients.py when
you're importing even just the carrot variable in a different script. But that's
not what you want! You just want access to the carrot, and leave the potato
uncooked and in storage over in ingredients.py.
Recap
When you import code from a module, Python executes the whole script.
Any leftover function calls will also execute, and they might produce
unexpected results.
In the next lesson, you'll learn how you can avoid running into this.
Dunder Name
To avoid code execution in your source module during import, you can do
two things:
1. Remove any code execution from the file and make sure it only defines
functions, classes, and variables without doing anything else.
2. Use the __name__ namespace and nest your code execution there.
If you nest your code execution in a specific indented block, you'll still be
able to run the file directly and have it produce output, but avoid unexpected
code execution when importing some values from the module.
Dunder Name
To avoid executing code during imports, but leaving it in the original module
file, you can add the following code to the bottom of your module:
if __name__ == "__main__":
# your code execution goes here
For example, when you want to keep checking for cooked potatoes in
ingredients.py, but you don't want to cook when you're just importing the
carrot in soup.py, you can change the code in ingredients.py like so:
# ingredients.py
def prepare(ingredient):
return f"cooked {ingredient}"
carrot = "carrot"
salt = "salt"
potato = "potato"
if __name__ == "__main__":
print(prepare(potato))
If you add this line of code and indent the code execution underneath it, you
can run ingredients.py as you normally would and receive your cooked
potato:
At the same time, if you now head back to soup.py and run the code, you'll
see that Python won't cook your potato and instead it'll only print back the
carrot that you wanted to:
# soup.py
from ingredients import carrot
Then, in the indented code block in the next line, you're calling the prepare()
function and print its result.
Recap
When you're working with different files and larger programs, this code
snippet can come in handy.
Don't worry if this is confusing and no need to break your head with it. If
you're interested to learn more, check out the linked resources below.
Python Comprehensions
You've learned about list comprehensions in an earlier section. In this lesson,
you'll get to know other Python comprehensions, which work with a similar
syntax:
You'll see that the different comprehensions do what you'd expect them to
do, and mainly present a shortcut syntax to writing for loop logic.
List Comprehensions
Python comprehensions are a concise way of writing some code logic that
you could also express in a for loop. You've already encountered list
comprehensions before:
The list comprehension allows you to encode the logic you'd otherwise apply
in a for loop into a single line of code. In the example above, you're
multiplying each number provided by the range() object by 2, and you end
up with a new list object containing the results.
Aside from the most commonly used list comprehensions, Python also
provides comprehension syntax for other data types.
Set Comprehensions
You can use set comprehensions with a slight change to the syntax you're
used to, by using curly braces ({}) instead of the square brackets:
This code snippet creates a new set() that contains the results of the
calculation applied to each number provided by range().
Dictionary Comprehensions
This code snippet creates a new dict() that contains the results of the
calculation applied to each value of the original dict_1 while keeping the
keys intact.
Recap
In the next lesson, you'll get to know generator objects, which follow a
similar syntax to Python comprehensions, but actually create something
slightly different.
Additional Resources
However, when you look at the output, you might be surprised. You don't
seem to be able to look into the generator object, and you can see that it
created neither a set() nor a list(), but something called a 'generator'.
Info: You'll dive much deeper into classes and objects in the third module of
this course, where you'll learn about object-oriented programming, and why
you keep encountering classes and objects in Python.
So what is this generator object, what's its use, and how can you work with
it?
Generators
When Python creates this list object, it needs to assign computer memory in
order to be able to store it somewhere. If you're working with huge lists, this
can potentially become an issue.
Info: You're nowhere near running into memory issues due to list creation
with the code you're writing in this course. However, when you're working
with large datasets, this challenge is real.
In order to actually create and work with the items whose potential existence
the generator contains, you'll have to iterator over the generator object:
# OUTPUT:
# 0
# 2
# 4
# 6
# 8
Avoiding creating and storing objects that you won't actually need again can
lead to performance improvements by freeing up memory space.
Recap
Generators are especially useful when working with large amounts of data.
Additional Resources
Sentdex: List comprehensions vs. generators
Dan Bader: Python Iterators
Python Wiki: Generators
Jeff Knupp: Improve Your Python: 'yield' and Generators Explained
Lambda Expressions
Lambda expressions, also called anonymous functions, allow you to
define small functions in a single line of code. They can make your code
more concise, but they can be a difficult concept to grasp, so there is a
trade-off in keeping your code human-friendly and readable.
In this lesson, you'll take a look at a lambda expression to learn how it relates
to a normal function:
def square_root(x):
return x**2
In the code snippet above, you defined a function, square_root() that takes
an integer, x, as its argument and returns that number squared.
As you can see, lambda expressions are just another way to write a function
in Python.
Info: If you're planning to assign a name to your function object, then you
should always opt for defining the function using the def keyword. While it's
possible to give a name to a lambda expression, this is not how they're
intended to be used. They are called "anonymous functions" for a reason!
So if it's just another way to write a function, why bother with lambda
expressions at all?
Anonymous Functions
You've probably used Python's sorted() function before. This function has
an optional key parameter that allows you to pass a function to apply some
logic to each element before performing the sort. For example, if you wanted
to sort the ingredients list of tuples shown below by their amounts, you can
do this by passing an anonymous function to the key parameter:
The sorted() function will apply the lambda expression to each item of the
iterable, in this case, each tuple of the shape ("name", number). Similar to a
loop variable, you can think that x will be each of the tuples contained in the
list. You define that with the first part of the lambda expression, lambda x:.
Then, you're defining what the anonymous function should do with each of
the tuples, x, and in this case, you're telling the code to pick out the second
item of each tuple with x[1].
This means that your lambda expression will pick the second item of each
tuple, which are the numerical amounts, use them to create a new iterable,
and sort your original values based on the sorted order of that new iterable:
# OUTPUT:
# [('peppers', 1), ('carrots', 2), ('potatoes', 4)]
As you can see, this code outputs a new list that is sorted ascending by the
second element in the tuples, which would be hard to achieve without using
the lambda expression.
Functional Programming
At the same time, this fact also explains why lambda expressions can
sometimes appear foreign. You're used to working with Python in an object-
oriented pattern, and from that perspective, lambda functions don't quite fit
the mold.
While you won't need to use lambda expressions in your programs, it's still
useful to understand the basics about them, and when it might be helpful to
use one.
Other examples of functions that can take function objects as arguments are
Python's filter(), map(), and reduce() functions. They are often used with
lambda expressions to define the logic that'll be applied to all elements in an
iterable. These functions are also a common way to start thinking about
concepts that are common in functional programming:
Using map() with a lambda expression as its first input, and an iterable
range() object as the second input, you just calculated all square numbers
from 0 to 10 and added them to a new list. Pretty neat and compact!
Note: You need to explicitly convert the output of map() to a list, because
filter(), map() and reduce()return generator objects instead of lists in
Python 3.
If you're thinking that you could have done the same thing more concisely
with a list comprehension, then you're right in thinking so! The list
comprehension can do the same thing and is arguably more readable for
many developers:
Recap
Additional Resources
In this lesson, you'll learn about the difference between an expression and a
statement.
Expressions
2 + 3 # OUTPUT: 5
In the code snippet above, you can see that 2 + 3 has a result, 5. When you
execute the expression 2 + 3, you'll hold a value in your digital hand. This
makes 2 + 3 an expression.
Statements
Statements are a more general term for all sorts of pieces that make up
your code. In programming, statements are the building blocks that
constitute a program.
x = 2
In any complex program that you'll write, you'll use both expressions and
statements together. A basic example of a statement that comes in
combination with an expression is:
x = 2 + 3
If you pick this short code snippet apart, you'll see that while the complete
line of code is a statement, it also contains an expression:
Your code will be made up of both, but sometimes it's helpful to be able to
make a distinction between the two. When?
Info: Keep training in the habit of researching any questions you might have.
It's extremely important for your continuous learning journey. The skill of
answering your own questions through effective online research will stay
relevant throughout your work as a software developer.
Recap
Additional Resources
Collect the same information you saved to a CSV file before into a database
instead. You can use a file-based SQLite database or a server-based
database such as PostgreSQL or MySQL.
Additionally, look into Python's datetime module and attempt to track the
times when you ran your script.
Data Analysis
Once your script is set up to save its run data into your database, you'll next
write a script to analyze your collected desktop statistics.
The total number of files that was on your desktop since you started
tracking it
The total number for each file type
On what day you had the most items on your desktop
The most common file type ever to clutter your desktop
Write your results to a new table in your database and set your analysis
script up so that it keeps updating this table on each execution.
Tasks
Write a script that stores the information returned from your file counter
script in a SQL database.
Read the saved data from your database and analyze it.
Write the results of your analysis to another table in your database.
Your program should move away from using CSV or JSON files, but you
should still be able to track your data over multiple executions.
Limits Of The Mighty Print
Until now, you've used the mighty print() function to inspect the state of
your code at specific points and used it to figure out what's going on.
Printing variables gives you insight into what your variables are like at any
stage of the execution of your script. Using print() is a straightforward and
powerful ally when debugging your code.
However, once you'll start dealing with larger applications, you'll sooner or
later hit the ceiling of possibilities with print(). You might have already been
there while learning about APIs and databases in the previous section.
And this is completely normal! The more code you'll write, and the more
complex it'll get, the more errors ("bugs") will find their way in there.
Recap
Complex programs are likely to have multiple software bugs in them. In this
section of your course, you'll take a look at tools that can help debug your
Python programs.
And even if bugs may be annoying sometimes, don't be mean to them! They
are often pretty cute, and there's a lot you can learn from them:
More Visual Debuggers
The text-heavy black-and-white style of the pdb debugger can seem
unintuitive to some people. If you prefer a more visual interface, you can
swap out pdb for another debugger, for example:
In this lesson, you'll look at these two alternatives to pdb and learn how you
can swap your debugger without needing to change your code.
The first alternative debugger, pudb, sounds very similar to pdb. However, if
you use it you'll see that it's a whole different visual experience.
pudb is a third-party package, which means that you need to install it with
pip before you'll be able to use it:
After installing pudb, you need to edit the relevant environment variable,
PYTHONBREAKPOINT. This will instruct Python to use the newly installed
debugger instead of the default one:
export PYTHONBREAKPOINT=pudb.set_trace
After setting the PYTHONBREAKPOINT environment variable to pudb.set_trace,
you can execute your script in the same way you did before:
python3 your_file_name.py
Python will now launch pudb for you where it launched pdb before:
As you can see, this looks quite different from the strictly text-based pdb that
you encountered in the previous lessons. A notable improvement is the
variable inspector in the top right of your terminal, where you can see all
currently defined variables and their values at the line where pudb is currently
pointing to.
You can use the same commands you learned before to step through your
program and investigate what's going on in there.
Web-Based Debugger With Clickable User Interface
Start by installing the package using pip inside of your virtual environment:
Just as you did with pudb before, you'll need to edit the PYTHONBREAKPOINT
environment variable to point to web-pdb instead:
export PYTHONBREAKPOINT=web_pdb.set_trace
Now you can execute your script in the same way you did before:
python3 your_file_name.py
Your console will show you some output that indicates that the web server
has been started:
When you see this output, you can open up your browser and go to the
following URL:
http://localhost:5555/
Once the page has loaded, you should see the interface of a modern user-
friendly debugger:
You'll notice that there's also a variable inspector at the top right of the
interface and it generally looks quite similar to pudb. However, you can use
buttons to interact with your interactive debugger session.
Recap
This can allow you to debug with different tools in different environments.
For example, you could use a debugger with a graphical user interface on
your local development machine, and use a plain text one on your server
without needing to change the code.
Here's an example of how you can change the relevant environment variable:
export PYTHONBREAKPOINT=web_pdb.set_trace
In the next lesson on debugging, you'll see how you can use a built-in
debugger that comes with most modern IDEs.
Additional Resources
From seeing the more visual debuggers, such as pudb and web-pdb, you're
already familiar with the general idea of an interface that these visual
debuggers provide. You can do everything that you did with the pdb-based
debuggers, and often much more.
Info: IDEs with integrated debuggers usually also include a visual way to set
breakpoints, so that you don't need to add any additional lines of code into
your script. In VS Code you can do this by clicking into the margin next to the
line numbers.
To access VS Code's debugger, you can press the dropdown next to the Play
symbol on the top-right corner of the app, then select Debug Python File:
Alternatively, you can also access it by opening the command palette by
pressing Cmd+Shift+p on macOS, or Ctrl+Shift+p on Linux and Windows,
typing debug, and pressing Enter:
This will execute your script as a debugging session, which means that any
visual breakpoints you set in the margins of your script will take effect. The
built-in debugger will stop execution at the breakpoints and launch the
interactive session.
However, just like the visual debuggers you've encountered in the previous
lesson, it works in a more user-friendly manner. You have access to buttons
instead of text commands.
You'll also notice the Variables panel, and how it displays the current variable
values. VS Code's debugger also uses color-highlighting to show eventual
changes:
Don't get overwhelmed if there seems to be too much lot going on. Try to
focus on what you're interested in right now and recreate the more basic
functionality of pdb. You can keep learning your built-in debugger slowly
Introduction To Your Task
In this section, you'll combine the skills you have learned so far to continue
to build out your game project with information that you'll gather from the
Internet through an API. You will:
Each piece of the project will build on concepts that you have covered in
earlier sections of the course. This section will walk you through adding an
API call to a name generator API to your game.
At the end of the section, you'll be tasked to integrate another API of your
own choice into your gameplay.
The Name API
Just like in the APIs and Databases course, you'll use the requests library to
connect to an API and receive the responses.
For this walkthrough, you'll use the Uzby name generator API to create
random names for your players. You'll work with this specific API because
it's relatively straightforward and is very focused on doing just this one thing.
Before you can start working with an API, you'll always need to read through
its documentation to know how to interact with it. The Uzby API
documentation is quite short and fits on one page:
Essentially, you'll make your request to the following URL, providing a
number as a parameter for MINIMUM_LENGTH and MAXIMUM_LENGTH:
https://uzby.com/api.php?min=MINIMUM_LENGTH&max=MAXIMUM_LENGTH
If you wanted to create a random pronounceable name between a length of 4
and 8 characters, you can visit this URL in your web browser:
https://uzby.com/api.php?min=4&max=6
You'll see a very basic result in your web browser. It's just what you asked
the API to get for you:
You can make the same request that you just made with your web browser
with Python instead. You'll revisit how to do that in the next lesson.
Explore The API
Create a new virtual environment for your game project and install the
requests library. Then, create a new Python script to test making the API
request and receive a randomly generated name.
import requests
min_len = 4
max_len = 6
URL = f"https://uzby.com/api.php?min={min_len}&max={max_len}"
response = requests.get(URL)
print(response.text)
After you run this short script, you'll get a name as a string printed out to
your console:
Zaiki
Keep in mind that these names are randomly generated, which is why your
output will be different, even if you use the same numbers for min_len and
max_len.
Tasks
Explore the API some more by making more calls with different values
for min_len and max_len.
Can you change the arguments the API takes to receive an error
message from the API? What does it look like?
In the next lesson, you'll incorporate this API call into your CLI game code to
generate a random name for your player, based on the real name they
provide.
PROJECT: Include More APIs
You've just stepped through the process of including an API call into your
local game script. Way to go!
As you know by now, you'll have to train each skill a lot to solidify your
knowledge. This is why you'll pick another API of your own choice for your
next task and incorporate its functionality into your game as well.
The Internet is full of data and offers a large chunk of it through freely
accessible APIs. You'll find some links to aggregated API resources at the
bottom of this page.
If you're stuck coming up with an idea for an API call that you could include
in your project, then you can get some inspiration from the suggestions
below. Feel free to use something completely different, however! The most
important part is that it's interesting for you to work with and that it adds a
new aspect to your game, even if it's just a very small one like the random
name generator that you implemented earlier in this section.
Some of these tasks will be easier to implement than others, and it'll depend
to a large degree on the API that you choose. To get started, pick an API
that:
is free to use
doesn't require authentication
has well-made and well-maintained documentation
You'll make your task easier if you select your API according to these criteria.
Of course, if you feel brave and curious, and you have an idea in mind, then
go ahead and implement it even if the API you want to use doesn't make it
quite as straightforward as the Uzby API you've worked with before.
Note: If you choose to work with an API that requires authentication, make
sure to keep your API key information out of your production code. You can
do this for example by adding it as an environment variable.
The best way to learn new skills and train them is to follow your interest and
keep putting in the time and effort.
Additional Resources
Info: Keep in mind that there are multiple ways to solve this challenge, and
that writing code is a creative activity where you get to express your
thoughts. If you've solved the challenge in a different way, then that is
perfectly fine!
You could incorporate the API call into your game like so:
import requests
name_length = 0
# Check whether it meets the length requirements for the API call
while not (name_length >= 2 and name_length <= 40):
# Collect the name from your player
original_name = input("Enter your name: ")
name_length = len(original_name)
# Ping the Uzby API to create a new random name for your player,
# using the length of their given name as input
URL = f"https://uzby.com/api.php?min={name_length}&max={name_length}"
response = requests.get(URL)
name = response.text
When you add this code to your CLI game, it now interacts with the web to
gather data from an API based on the input of your players. Nice work! This
might not be enough for your game to qualify as an online immersive game,
but you're learning to use Python to interact with data on the Internet.
Tasks
Switch off your Internet connection and try running your game script.
What error do you receive?
Can you identify the name of the error in your traceback?
Research the error online in the documentation of requests.
How can you avoid your players running into that error?
In the next module of this course, you'll learn how you can write code to
catch errors that your programs run into. You'll learn about exception
handling, and you'll get the chance to improve your scripts and make sure
that your game is playable also without an active connection to the Internet.
Before you move on to the next module, however, you'll learn about another
important concept in software development that isn't exclusive to Python. In
the final section of this module, you'll learn the essentials of version control
with Git and GitHub.
Info: If you want to dive deeper, check out the dedicated course on Version
Control with Git and GitHub.
In order to be able to share your game with the world and allow others to
collaborate on your code, you'll need to put it up on the Internet. In the next
section, you'll learn the basics of how you can do that with Git and GitHub,
as well as what it means to put your code under version control.
Wrap Up Module Two
After completing your API project and putting it under version control on
GitHub, you've successfully completed the second module of this Python
course. Congratulations on your progress :)
Install Python
Understand what programming is and why it is useful
Read, write, and understand Python code
Use Python as a scripting language
Write Python in the REPL and as a script
Create variables and assign values to them
Work with text and numbers
Identify and use common data types, such as:
int
float
str
tuple
list
set
dict
bool
None
Plan out your task with pseudocode
Document your reasoning and decisions using code comments
Write loop logic to tackle repetitive tasks
for loops
while loops
list comprehension
Make comparisons and calculations with operators
Assignment operator
Arithmetic operators
Membership operator
Relational operators
Logical operators
Identity operator
Make decisions with conditional logic and control flow
if, elif, else statements
Looping keywords: break, continue, and return
Collect user input with input()
Format your strings with f-strings
Write and call functions to avoid repetition and generalize your code
logic
Provide input to your functions with arguments, and return values to
reuse them
Use positional and keyword parameters
Document your functions with docstrings and type hinting
Understand scopes in programming
Automate repetitive tasks on your file system using the pathlib module
Work with File input/output on your computer
Install and use third-party Python packages
Use sqlalchemy to interact with any SQL database through Python
Use the requests package to interact with APIs on the Internet
You've also learned about additional concepts and techniques that are
important in your day-to-day work as a software developer, such as:
Growth mindset: you'll have to always keep learning, and that's a good
thing
Error messages: friendly messages from Python or your operating
system, that help you to identify any miscommunications you had with
your computer
Operating systems: they are different from each other, but there are
also many similarities
Development environment: installing and working with the tools you
need to write code productively on your local machine
Virtual environments: compartmentalize your development
environment
Environment variables: separate sensitive information and settings
from your production code
Databases: high-performance software built to persistently store and
retrieve your data
APIs: structured data provided over the Internet that you can
programmatically consume
Debugging: find and fix bugs in your code, and use tooling to make that
process more productive
Version control: with Git and GitHub to save snapshots of your projects
and open your codebase up for collaboration
Refactoring: go back into the code you wrote earlier and improve it
At this point, you know enough Python and you have the additional
necessary software development skills to tackle a wide variety of projects.
Speaking about projects, you've already completed a few.
Projects
You've also applied code logic concepts, correct Python syntax, and your
creativity to build out projects:
File Renamer: Move and rename files in a folder on your file system
CLI Games: Build CLI games
Guess My Number
Hangman
Adventure role-playing game with API interactions
You've already created fun games and useful automation scripts that you
can be proud of and that you can showcase on your personal GitHub
account.
Next Steps
The next course in this series is Python 301 which shifts focus to learning
Python as an object-oriented programming language. You'll get to know
what exactly it means that:
Everything's an object in Python
You'll finally dig deeper into the concept of objects and learn about their
attributes and methods. You've already used a lot of the concepts you'll
learn about, but now you'll finally better understand why things worked as
they did.