Python is an unbelievably powerful programming language that is used by millions of developers in production systems around the world. It's easy to learn, free to use, and has a huge community of developers that are always willing to help.
If you're interested in back-end web development, data science, data engineering, or just want to automate some of your boring tasks, Python is a great place to start.
Would you rather learn by doing?
I've included all the static read-only material you'll need here in this tutorial, but if you would like a more hands-on experience, you can take the interactive version of this course, complete with coding challenges and projects on Boot.dev here.
Chapter 1: Introduction
Thousands of students start their coding journey right here with Python. We think it's the best programming language to get started with. Python is famous for being a simple language that's easy to read and write.
However, just because it's simple that doesn't mean it's not useful! Python is an extremely popular language in the industry, and is well-known for:
- Backend web servers
- DevOps and cloud engineering
- Machine learning
- Scripting and automation
- etc...
On the other hand, it's not particularly well-known for front-end work. While it's possible to do so, Python isn't typically used to build visual user interfaces.
Setup a Local Development Environment
To get started with Python, you'll need to install the python
command on your computer, and then install a text editor or IDE. If you're not already familiar with how to do that, I have a full step-by-step project guide here that you can follow.
If you're able to edit and run Python code on your computer, you're ready to continue!
What is "Code"?
Code is just a series of instructions that computers can follow. Computers obey each instruction, one after another.
Programs can be comprised of many instructions. Like many. Like millions.
Addition is one of the most common instructions in coding.
Printing numbers
print()
can print text using quotes:
print("some text here")
but it can also print numbers without quotes:
print(1)
and you can do math directly inside the parentheses:
print(1 + 2)
Try some of these code snippets in your editor! For example, you could create a file called hello.py
and write the following code:
print("Hello, world!")
print(1 + 2)
Then, run the file with python hello.py
and see what happens.
Multiple Instructions
Code runs in order, starting at the top of the program. For example:
print("this prints first")
print("this prints second")
print("this prints last")
Syntax Errors
Syntax is jargon for "valid code that the computer can understand". For example,
prnt("hello world")
is invalid syntax because prnt()
is not a valid function, "print" is spelled incorrectly. As a result, an error will be thrown and the code won't execute.
Syntax varies from langauge to language
A coding language's syntax makes up the rules that define what properly structured expressions and statements look like in that language. For example, in Python, the following would be considered correct syntax:
print("hello world")
While in a different programming language, like Go, the correct syntax would be:
fmt.Println("hello world")
Code can have many different problems that prevent it from working as intended. Some examples include:
- A bug in the logic. For example, a program that should add numbers multiplies them instead
- A problem with speed. A program that calculates how to play the perfect game of chess might never be able to finish because it requires too many calculations.
- A problem with syntax. This is the most common problem for new developers. Luckily the Python interpreter will try to give you a descriptive error message in the console to help you find the problem.
Chapter 2: Variables
Variables are how we store data in our program. So far we've been directly printing data by passing it directly into the print()
function.
Now we are going to learn to save the data in variables so we can use and change it before we need to print it.
A variable is a name that we define that will point to some data. For example, I could define a new variable called my_height
and set its value to 100. I could also define a variable called my_name
and set it equal to "Lane".
Creating variables
To create a new variable in Python we use the following syntax:
my_new_variable_two = 2
this_can_be_called_anything = 3
Variables Vary
Variables are called "variables" because they can hold any value and that value can change (it varies).
For example, the following will print 20
:
acceleration = 10
acceleration = 20
print(acceleration)
The line acceleration = 20
reassigns the value of acceleration
to 20. It overwrites whatever was being held in the acceleration
variable before.
Let's do some math
Now that we know how to store and change the value of variables let's do some math!
Here are some examples of common mathematical operators in Python syntax.
sum = a + b
difference = a - b
product = a * b
quotient = a / b
Comments
Comments don't run like code, they are ignored by the computer. Comments are useful for adding reminders or explaining what a piece of code does in plain English.
Single line comment
# speed is a variable describing how fast your player moves
speed = 2
Multi-line comments (aka docstrings)
You can use triple quotes to start and end multi-line comments as well:
"""
the code found below
will print 'Hello, World!' to the console
"""
print('Hello, World!')
This is useful if you don't want to add the #
to the start of each line when writing paragraphs of comments.
Variable Names
Variable names can't have spaces, they're continuous strings of characters.
In Python you should use "snake_case" when creating variable names - it's become the "rule of thumb" for the language. By way of comparison, "camel case" is where the beginning of each new word except the first is capitalized.
No casing (pure insanity)
somevariablehere = 10
Camel Case
someVariableHere = 10
Snake Case
some_variable_here = 10
Basic Variable Types
In Python there are several basic data types.
String Type
"Strings" are raw text in coding speak. They are called "strings" because they are a list of characters strung together. Strings are declared in Python by using single quotes or double quotes. That said, for consistency's sake, we prefer double quotes.
name_with_single_quotes = 'boot.dev'
name_with_double_quotes = "boot.dev"
Numeric Types
Numbers aren't surrounded by quotes when created, but they can have decimals and negative signs.
Integers are numbers without a decimal
x = 5
y = -5
A "Float" is a number with a decimal
x = 5.2
y = -5.2
Boolean Type
A "Boolean" (or "bool") is a type that can only have one of two values: True
or False
. As you may have heard computers really only use 1's and 0's. These 1's and 0's are just Boolean
values.
0 = False
1 = True
is_tall = True
NoneType Variables
Not all variables have a value. We can declare an "empty" variable by setting it to None
.
empty = None
The value of empty
in this instance is None
until we use the assignment operator, =
, to give it a value.
None is NOT a specific string
Note that the None
type is not the same as a string with a value of "None":
my_none = None # this is a None-type
my_none = "None" # this is a string
Dynamic Typing
Python is dynamically typed. All this means is that a variable can store any type, and that type can change.
For example, if I make a number variable, I can later change that variable to a string:
This is valid:
speed = 5
speed = "five"
Just because you can doesn't mean you should!
In almost all circumstances, it's a bad idea to change the type of a variable. The "proper" thing to do is to just create a new one. For example:
speed = 5
speed_description = "five"
What if it weren't dynamically typed?
Statically typed languages like Go (which you'll learn in a later course) are statically typed instead of dynamically typed. In a statically typed language, if you try to assign a value to a variable of the wrong type, an error would crash the program.
If Python were statically-typed, the first example from before would crash on the second line, speed = "five"
. The computer would give an error along the lines of you can't assign a string value ("five") to a number variable (speed)
Math With Strings
Most of the math operators we went over earlier don't work with strings, aside from the +
addition operator. When working with strings the +
operator performs a "concatenation".
"Concatenation" is a fancy word that means the joining of two strings.
first_name = "Lane "
last_name = "Wagner"
full_name = first_name + last_name
full_name
now holds the value "Lane Wagner".
Notice the extra space at the end of "Lane "
in the first_name
variable. That extra space is there to separate the words in the final result: "Lane Wagner"
.
Multi-Variable Declaration
We can save space when creating many new variables by declaring them on the same line:
sword_name, sword_damage, sword_length = "Excalibur", 10, 200
Which is the same as:
sword_name = "Excalibur"
sword_damage = 10
sword_length = 200
Any number of variables can be declared on the same line, and variables declared on the same line should be related to one another in some way so that the code remains easy to understand.
We call code that's easy to understand "clean code".
Chapter 2: Computing Basics
Python Numbers
In Python, numbers without a decimal part are called Integers
- just like they are in mathematics.
Integers are simply whole numbers, positive or negative. For example, 3
and -3
are both examples of integers.
Arithmetic can be performed as you might expect:
Addition
2 + 1
# 3
Subtraction
2 - 1
# 1
Multiplication
2 * 2
# 4
Division
3 / 2
# 1.5 (a float)
This one is actually a bit different - division on two integers will actually produce a float
. A float is, as you may have guessed, the number type that allows for decimal values.
Integers
In Python, numbers without a decimal part are called Integers
. Contrast this to JavaScript where all numbers are just a Number
type.
Integers are simply whole numbers, positive or negative. For example, 3
and -3
are both examples of integers.
Floats
A float is, as you may have guessed, the number type that allows for decimal values.
my_int = 5
my_float = 5.5
Floor division
Python has great out-of-the-box support for mathematical operations. This, among other reasons, is why it has had such success in artificial intelligence, machine learning, and data science applications.
Floor division is like normal division except the result is floored afterward, which means the remainder is removed. As you would expect, this means the result is an integer
instead of a float
.
7 // 3
# 2 (an integer)
Exponents
Python has built-in support for exponents - something most languages require a math
library for.
# reads as "three squared" or
# "three raised to the second power"
3 ** 2
# 9
Changing In Place
It's fairly common to want to change the value of a variable based on its current value.
player_score = 4
player_score = player_score + 1
# player_score now equals 5
player_score = 4
player_score = player_score - 1
# player_score now equals 3
Don't let the fact that the expression player_score = player_score - 1
is not a valid mathematical expression be confusing. It doesn't matter, it is valid code. It's valid because the way the expression should be read in English is:
Assign to player_score the old value of player_score minus 1
Plus Equals
Python makes reassignment easy when doing math. In JavaScript or Go you might be familiar with the ++
syntax for incrementing a number variable. In Python, we use the +=
operator instead.
star_rating = 4
star_rating += 1
# star_rating is now 5
Scientific Notation
As we covered earlier, a float
is a positive or negative number with a fractional part.
You can add the letter e
or E
followed by a positive or negative integer to specify that you're using scientific notation.
print(16e3)
# Prints 16000.0
print(7.1e-2)
# Prints 0.071
If you're not familiar with scientific notation, it's a way of expressing numbers that are too large or too small to conveniently write normally.
In a nutshell, the number following the e
specifies how many places to move the decimal to the right for a positive number, or to the left for a negative number.
Underscores for readability
Python also allows you to represent large numbers in the decimal format using underscores instead of commas to make it easier to read.
num = 16_000
print(num)
# Prints 16000
num = 16_000_000
print(num)
# Prints 16000000
Logical Operators
You're probably familiar with the logical operators AND
and OR
.
Logical operators deal with boolean values, True
and False
.
The logical AND
operator requires that both inputs are True
to return True
. The logical OR
operator only requires that at least one input is True
to return True
.
For example:
True AND True = True
True AND False = False
False AND False = False
True OR True = True
True OR False = True
False OR False = False
Python Syntax
print(True and True)
# prints True
print(True or False)
# prints True
Nesting with parentheses
We can nest logical expressions using parentheses.
print((True or False) and False)
First, we evaluate the expression in the parentheses, (True or False)
. It evaluates to True
:
print(True and False)
True and False
evaluates to False
:
print(False)
So, print((True or False) and False)
prints "False" to the console.
Binary Numbers
Binary numbers are just "base 2" numbers. They work the same way as "normal" base 10 numbers, but with 2 symbols instead of 10.
Each 1
in a binary number represents a greater multiple of 2. In a 4-digit number, that means you have the eight's place, the four's place, the two's place, and the one's place. Similar to how in decimal you would have the thousandth's place, the hundredth's place, the ten's place, and the one's place.
-
0001
= 1 -
0010
= 2 -
0011
= 3 -
0100
= 4 -
0101
= 5 -
0110
= 6 -
0111
= 7 -
1000
= 8
Bitwise "&" Operator
Bitwise operators are similar to logical operators, but instead of operating on boolean values, they apply the same logic to all the bits in a value. For example, say you had the numbers 5
and 7
represented in binary. You could perform a bitwise AND
operation and the result would be 5
.
0101 = 5
&
0111 = 7
=
0101 = 5
A 1
in binary is the same as True
, while 0
is False
. So really a bitwise operation is just a bunch of logical operations that are completed in tandem.
&
is the bitwise AND
operator in Python. 5 & 7 = 5
, while 5 & 2 = 0
.
0101 = 5
&
0010 = 2
=
0000 = 0
Binary notation
When writing a number in binary, the prefix 0b
is used to indicate that what follows is a binary number.
-
0b0101
is 5 -
0b0111
is 7
Example: Guild Permissions
It's common practice in backend development to store user permissions as binary values. Think about it, if I have 4
different permissions a user can have, then I can store that as a 4-digit binary number, and if a certain bit is present, I know the permission is enabled.
Let's pretend we have 4 permissions:
-
can_create_guild
- Leftmost bit -
can_review_guild
- Second to left bit -
can_delete_guild
- Second to right bit -
can_edit_guild
- Rightmost bit
Which are represented by 0b0000
. For example, if a user only has the can_create_guild
permission, their binary permissions would be 0b1000
. A user with can_review_guild
and can_edit_guild
would be 0b0101
.
To check for, say, the can_review_guild
permission, we can perform a bitwise AND
operation on the user's permissions and the enabled can_review_guild
bit (0b0100
). If the result is 0b0100
again, we know they have that specific permission!
Bitwise "|" Operator
As you may have guessed, the bitwise "or" operator is similar to the bitwise "and" operator in that it works on binary rather than boolean values. However, the bitwise "or" operator "ORs" the bits together. Here's an example:
-
0101
is 5 -
0111
is 7
0101
|
0111
=
0111
A 1
in binary is the same as True
, while 0
is False
. So a bitwise operation is just a bunch of logical operations that are completed in tandem. When two binary numbers are "OR'ed" together, the result has a 1
in any place where either of the input numbers has a 1
in that place.
|
is the bitwise OR
operator in Python. 5 | 7 = 7
and 5 | 2 = 7
as well!
0101 = 5
|
0010 = 2
=
0111 = 7
Not
We skipped a very important logical operator - not
. The not
operator reverses the result. It returns False
if the input was True
and vice-versa.
print(not True)
# Prints: False
print(not False)
# Prints: True
Chapter 3: Comparisons
Comparison Operators
When coding it's necessary to be able to compare two values. Boolean logic
is the name for these kinds of comparison operations that always result in True
or False
.
The operators:
-
<
"less than" -
>
"greater than" -
<=
"less than or equal to" -
>=
"greater than or equal to" -
==
"equal to" -
!=
"not equal to"
For example:
5 < 6 # evaluates to True
5 > 6 # evaluates to False
5 >= 6 # evaluates to False
5 <= 6 # evaluates to True
5 == 6 # evaluates to False
5 != 6 # evaluates to True
Evaluations
When a comparison happens, the result of the comparison is just a boolean value, it's either True
or False
.
Take the following two examples:
is_bigger = 5 > 4
is_bigger = True
In both of the above cases, we're creating a Boolean
variable called is_bigger
with a value of True
.
Since 5 > 4
, is_bigger
is always assigned the value of True
.
Why would I use the comparison if I can just set it to "True"?
You wouldn't in this case. However, let's imagine that instead of hard-coding the numbers 5
and 4
, we had some dynamic variables that we don't know the values of. For example, perhaps you're making a video game and need to keep track of player scores.
To calculate who wins, you would need to write something like:
# player_one_points and player_two_points are defined and change somewhere else in the game's code
player_one_wins = player_one_points > player_two_points
print(player_one_wins)
# prints "True" when player one is winning, otherwise prints "False"
Increment / Decrement
If we're changing a number and simply want to increment (add to) or decrement (subtract from) there are special operators for that.
shield_armor = 4
shield_armor += 1
# shield_armor now equals 5
shield_armor += 2
# shield_armor now equals 7
shield_armor = 4
shield_armor -= 1
# shield_armor now equals 3
shield_armor -= 2
# shield_armor now equals 1
Notice that shield_armor+=1
is just short-hand for shield_armor = shield_armor + 1
If Statements
It's often useful to only execute code if a certain condition is met:
if CONDITION:
# do some stuff here
for example:
if bob_score > bill_score:
print("Bob Wins!")
If-Else
An if
statement can be followed by zero or more elif
(which stands for "else if") statements, which can be followed by zero or one else
statement. For example:
if score > high_Score:
print('High score beat!')
elif score > second_highest_score:
print('You got second place!')
elif score > third_highest_score:
print('You got third place!')
else:
print('Better luck next time')
First the if
statement is evaluated. If it is True
then the if statement's body is executed and all the other else
s are ignored.
If the first if
is false then the next elif
is evaluated. Likewise, if it is True
then its body is executed and the rest are ignored.
If none of the if
statements evaluate to True
then the final else
statement will be the only body executed.
If-Else Rules
- You can't have an
elif
or anelse
without anif
- You can have an
else
without anelif
Chapter 5: Loops
Loops are a programmer's best friend. Loops allow us to do the same operation multiple times without having to write it explicitly each time.
For example, let's pretend I want to print the numbers 0-9.
I could do this:
print(0)
print(1)
print(2)
print(3)
print(4)
print(5)
print(6)
print(7)
print(8)
print(9)
Even so, it would save me a lot of time typing to use a loop. Especially if I wanted to do the same thing one thousand or one million times.
A "for loop" in Python is written like this:
for i in range(0, 10):
print(i)
In English, the code says:
- Start with
i
equals0
. (i in range(0
) - If
i
is not less than 10 (range(0, 10)
), exit the loop. - Print
i
to the console. (print(i)
) - Add
1
toi
. (range
defaults to incrementing by 1) - Go back to step
2
The result is that the numbers 0-9
are logged to the console in order.
Whitespace matters in Python!
The body of a for-loop must be indented, otherwise you'll get a syntax error.
Example
This code print the numbers 0-9 to the console.
for i in range(0, 10):
print(i)
Range Continued
The range()
function we've been using in our for
loops actually has an optional 3rd parameter: the "step".
for i in range(0, 10, 2):
print(i)
# prints:
# 0
# 2
# 4
# 6
# 8
The "step" parameter determines how much to increment i
by in each iteration of the loop. You can even go backwards:
for i in range(3, 0, -1):
print(i)
# prints:
# 3
# 2
# 1
F-strings in Python
You can create a string with dynamic values by using f-strings
in Python. It's a beautiful syntax that I wish more programming languages used.
num_bananas = 10
print(f"You have {num_bananas} bananas")
# You have 10 bananas
The opening quotes need to be proceeded by an f
, then any variables within curly brackets have their values interpolated into the string.
Chapter 6: Lists
A natural way to organize and store data is in the form of a List
. Some languages call them "arrays", but in Python we just call them lists. Think of all the apps you use and how many of the items in the app are organized into lists.
For example:
- A twitter feed is a list of posts
- An online store is a list of products
- The state of a chess game is a list of moves
- This list is a list of things that are lists
Lists in Python are declared using square brackets, with commas separating each item:
inventory = ["Iron Breastplate", "Healing Potion", "Leather Scraps"]
Arrays can contain items of any data type, in our example above we have a List
of strings.
Vertical syntax
Sometimes when we're manually creating lists it can be hard to read if all the items are on the same line of code. We can declare the array using multiple lines if we want to:
flower_types = [
"daffodil",
"rose",
"chrysanthemum"
]
Keep in mind this is just a styling change. The code will run correctly either way.
Counting in Programming
In the world of programming, counting is a bit strange!
We don't start counting at 1
, we start at 0
instead.
Indexes
Each item in an array has an index that refers to its spot in the array.
Take the following array as an example:
names = ["Bob", "Lane", "Alice", "Breanna"]
- Index 0:
Bob
- Index 1:
Lane
- Index 2:
Alice
- Index 3:
Breanna
Indexing into Lists
Now that we know how to create new lists, we need to know how to access specific items in the list.
We access items in a list directly by using their index. Indexes start at 0 (the first item) and increment by one with each successive item. The syntax is as follows:
best_languages = ["JavaScript", "Go", "Rust", "Python", "C"]
print(best_languages[1])
# prints "Go", because index 1 was provided
List length
The length of a List can be calculated using the len()
function. Again, we'll cover functions in detail later, but this is the syntax:
fruits = ["apple", "banana", "pear"]
length = len(fruits)
# Prints: 3
The length of the list is equal to the number of items present. Don't be fooled by the fact that the length is not equal to the index of the last element, in fact it will always be one greater.
List Updates
We can also change the item that exists at a given index. For example, we can change Leather
to Leather Armor
in the inventory
array in the following way:
inventory = ["Leather", "Healing Potion", "Iron Ore"]
inventory[0] = "Leather Armor"
# inventory: ['Leather Armor', 'Healing Potion', 'Iron Ore']
Appending in Python
It's common to create an empty list then fill it with values using a loop. We can add values to the end of a list using the .append()
method:
cards = []
cards.append("nvidia")
cards.append("amd")
# the cards list is now ['nvidia', 'amd']
Pop Values
.pop()
is the opposite of .append()
. Pop removes the last element from the array and returns it for use. For example:
vegetables = ["broccoli", "cabbage", "kale", "tomato"];
last_vegetable = vegetables.pop()
# vegetables = ['broccoli', 'cabbage', 'kale']
# last_vegetable = 'tomato'
Counting the items in a list
Remember that we can iterate (count) over all the items in an array using a loop. For example, the following code will print each item in the sports
array.
for i in range(0, len(sports)):
print(sports[i])
No-index Syntax
In my opinion, Python has the most elegant syntax for iterating directly over the items in a list without worrying about index numbers. If you don't need the index number you can use the following syntax:
trees = ['oak', 'pine', 'maple']
for tree in trees:
print(tree)
# Prints:
# oak
# pine
# maple
tree
, the variable declared using the in
keyword, directly accesses the value in the array rather than the index of the value. If we don't need to update the item, and only need to access its value then this is a more clean way to write the code.
Find an item in a list
Example of "no-index" or "no-range" syntax:
for fruit in fruits:
print(fruit)
Modulo operator in Python
The modulo operator can be used to find a remainder:
For example, 7
modulo 2
would be 1
, because 2 can be multiplied evenly into 7 at most 3 times:
2 * 3 = 6
Then there is 1 remaining to get from 6
to 7
.
7 - 6 = 1
The d operator is the percent sign: %
. It's important to recognize modulo is not a percentage though! That's just the symbol we're using.
remainder = 8 % 3
# remainder = 2
An odd number is a number that when divided by 2
, the remainder is not 0
.
Slicing lists
Python makes it easy to slice and dice lists to work only with the section you care about. One way to do this is to use the simple slicing operator, which is just a colon :
.
With this operator, you can specify where to start and end the slice, and how to step through the original. List slicing returns a new list from the existing list.
The syntax is as follows:
Lst[ Initial : End : IndexJump ]
scores = [50, 70, 30, 20, 90, 10, 50]
# Display list
print(scores[1:5:2])
# Prints [70, 20]
The above reads as "give me a slice of the scores
list from index 1, up to but not including 5, skipping every 2nd value. All of the sections are optional.
scores = [50, 70, 30, 20, 90, 10, 50]
# Display list
print(scores[1:5])
# Prints [70, 30, 20, 90]
scores = [50, 70, 30, 20, 90, 10, 50]
# Display list
print(scores[1:])
# Prints [70, 30, 20, 90, 10, 50]
List Operations - Concatenate
Concatenating two lists (smushing them together) is really easy in Python, just use the +
operator.
all = [1, 2, 3] + [4, 5, 6]
print(all)
# Prints: [1, 2, 3, 4, 5, 6]
List Operations - Contains
Checking whether a value exists in a list is also really easy in Python, just use the in
keyword.
fruits = ["apple", "orange", "banana"]
print("banana" in fruits)
# Prints: True
Tip: Quotes within quotes
To use quotes within quotes, they either need to be escaped or you need to use the other kind of quotes. Because we usually use double quotes, we can nest strings with single quotes:
f"banana is in fruits list: {'banana' in fruits}"
List deletion
Python has a built-in keyword del
that deletes items from objects. In the case of a list, you can delete specific indexes or entire slices.
nums = [1, 2, 3, 4, 5, 6, 7, 8, 9]
# delete the fourth item
del nums[3]
print(nums)
# Output: [1, 2, 3, 5, 6, 7, 8, 9]
# delete items from 2nd to 3rd
nums = [1, 2, 3, 4, 5, 6, 7, 8, 9]
del nums[1:3]
print(nums)
# Output: [1, 4, 5, 6, 7, 8, 9]
# delete all elements
nums = [1, 2, 3, 4, 5, 6, 7, 8, 9]
del nums[:]
print(nums)
# Output: []
Tuples
Tuples are collections of data that are ordered and unchangeable. You can think of a tuple as a List
with a fixed size. Tuples are created with round brackets:
my_tuple = ("this is a tuple", 45, True)
print(my_tuple[0])
# this is a tuple
print(my_tuple[1])
# 45
print(my_tuple[2])
# True
While it's typically considered bad practice to store items of different types in a List it's not a problem with Tuples. Because they have a fixed size, it's easy to keep track of which indexes store which types of data.
Tuples are often used to store very small groups (like 2 or 3 items) of data. For example, you might use a tuple to store a dog's name and age.
dog = ("Fido", 4)
Because Tuples hold their data, multiple tuples can be stored within a list. Similar to storing other data in lists, each tuple within the list is separated by a comma.
my_tuples = [("this is the first tuple in the list", 45, True),("this is the second tuple in the list", 21, False)]
print(my_tuples[0][0])
# this is the first tuple in the list
Chapter 7: Functions
Functions allow us to reuse and organize code. For example, let's pretend we need to calculate the area of a circle. We can use the formula area = pi * r^2
, or in code:
r = 5
area = 3.14 * r * r
This works great! The problem arises when multiple places in our code need to get the area of a circle
r = 5
area1 = 3.14 * r * r
r2 = 7
area2 = 3.14 * r2 * r2
r3 = 11
area3 = 3.14 * r3 * r3
We want to use the same code, why repeat the work?
Let's declare a new function area_of_circle()
. Notice that the def
keyword is written before the function name, and tells the computer that we're declaring, or defining, a new function.
def area_of_circle(r):
return 3.14 * r * r
The area_of_circle
function takes one input (which can also be called a parameter or argument), and returns a single output. We give our function the radius of a circle and we get back the area of that circle!
To use or "call" the function we can pass in any number as the input, and capture the output into a new variable:
radius = 5
area = area_of_circle(radius)
Let's talk through this code example step by step.
- The
radius
variable is created with a value of5
. - The
area_of_circle
function is called with a single argument:radius
- The
area_of_circle
function is executed, withr
being equal to5
- The result of
3.14 * r * r
is returned fromarea_of_circle
, which happens to be78.75
-
area_of_circle(radius)
resolves to the returned value of78.75
- The
area
variable is created with a value of78.75
Multiple Parameters
Functions can have multiple parameters, or inputs:
def subtract(a, b):
return a - b
Where to Declare Functions
You've probably noticed that a variable needs to be declared before it's used. For example, the following doesn't work:
print(my_name)
my_name = 'Lane Wagner'
It needs to be:
my_name = 'Lane Wagner'
print(my_name)
Lines of code execute in order from top to bottom, so a variable needs to be created before it can be used. That means that if you define a function, you can't call that function until after the definition.
The main()
function is a convention used in many programming languages to specify the entrypoint of an application. By defining a single main
function, and only calling main()
at the end of the entire program we ensure that all of our function are defined before they're called.
Order of functions
All functions must be defined before they're used.
You might think this would make structuring Python code difficult because the order in which the functions are declared can quickly become so dependent on each other that writing anything becomes impossible.
As it turns out, most Python developers solve this problem by simply defining all the functions first, then finally calling the entrypoint function last. If you do that, then the order that the functions are declared in doesn't matter. The entrypoint function is usually called "main".
def main():
func2()
def func2():
func3()
def func3():
print("I'm function 3")
main() # entrypoint
Scope
Scope refers to where a variable or function name is available to be used. For example, when we create variables in a function (by giving names to our parameters for example), that data is not available outside of that function.
For example:
def subtract(x, y)
return x - y
result = subtract(5, 3)
print(x)
# ERROR! "name 'x' is not defined"
When the subtract
function is called, we assign the variable x
to 5, but x
only exists in the code within the subtract
function. If we try to print x
outside of that function then we won't get a result, in fact we'll get a big fat error.
Global Scope
So far we've been working in the global scope. That means that when we define a variable or a function, that name is accessible in every other place in our program, even within other functions.
For example:
pi = 3.14
def get_area_of_circle(radius):
return pi * radius * radius
Because pi
was declared in the parent "global" scope, it is usable within the get_area_of_circle()
function.
Infinity
The built-in float()
function can be used to create a numeric floating point value that represents the negative infinity value. I've added it for you as a starting point.
negative_infinity = float('-inf')
positive_infinity = float('inf')
None Return
When no return value is specified in a function, (for example, maybe it's a function that prints some text to the console, but doesn't explicitly return a value) it will return None
. The following code snippets all return exactly the same thing:
def my_func():
print("I do nothing")
return None
def my_func():
print("I do nothing")
return
def my_func():
print("I do nothing")
Parameters vs arguments
Parameters are the names used for inputs when defining a function. Arguments are the names of the inputs supplied when a function is called.
To reiterate, arguments are the actual values that go into the function, say 42.0
, "the dark knight"
, or True
. Parameters are the names we use in the function definition to refer to those values, which at the time of writing the function, could be anything.
That said, it is important to understand that this is all semantics, and frankly developers are really lazy with these definitions. You'll often hear the words arguments and parameters used interchangeably.
# a and b are parameters
def add(a, b)
return a + b
# 5 and 6 are arguments
sum = add(5, 6)
Multiple return values
In Python, we can return more than one value from a function. All we need to do is separate each value by a comma.
# returns email, age, and status of the user
def get_user():
return "name@domain.com", 21, "active"
email, age, status = get_user()
print(email, age, status)
# Prints: "name@domain.com 21 active"
def get_user():
return "name@domain.com", 21, "active"
# this works, and by convention you should NOT use the underscore variable later
email, _, _ = get_user()
print(email)
# Prints: "name@domain.com"
print(_)
# Prints: "active"
Default values for function arguments
Python has a way to specify a default value for function arguments. This can be convenient if a function has arguments that are essentially "optional", and you as the function creator want to use a specific default value in case the caller doesn't provide one
A default value is created by using the assignment (=
) operator in the function signature.
def get_greeting(email, name="there"):
return f"Hello {name}, welcome! You've registered your email: {email}"
msg = get_greeting("lane@example.com", "Lane")
# Hello Lane, welcome! You've registered your email: lane@example.com
msg = get_greeting("lane@example.com")
# Hello there, welcome! You've registered your email: lane@example.com
If the second parameter is omitted, the default "there"
value will be used in its place. As you may have guessed, for this structure to work, optional arguments that have defaults specified come after all the required arguments.
Chapter 8: Dictionaries
Dictionaries in Python are used to store data values in key
-> value
pairs. Dictionaries are a great way to store groups of information.
car = {
"brand": "Tesla",
"model": "3",
"year": 2019
}
Duplicate keys
Because dictionaries rely on unique keys, you can't have two of the same key in the same dictionary. If you try to use the same key twice, the associated value will simply be overwritten.
Accessing Dictionary Values
Dictionary elements must be accessible somehow in code, otherwise they wouldn't be very useful.
A value is retrieved from a dictionary by specifying its corresponding key in square brackets. The syntax looks similar to indexing into a list.
car = {
'make': 'tesla',
'model': '3'
}
print(car['make'])
# Prints: tesla
Setting Dictionary Values
You don't need to create a dictionary with values already inside. It is common to create a blank dictionary then populate it later using dynamic values. The syntax is the same as getting data out of a key, just use the assignment operator (=
) to give that key a value.
names = ["jack bronson", "jill mcarty", "john denver"]
names_dict = {}
for name in names:
# .split() returns a list of strings
# where each string is a single word from the original
names_arr = name.split()
# here we update the dictionary
names_dict[names_arr[0]] = names_arr[1]
print(names_dict)
# Prints: {'jack': 'bronson', 'jill': 'mcarty', 'john': 'denver'}
Updating Dictionary Values
If you try to set the value of a key that already exists, you'll end up just updating the value of that key.
names = ["jack bronson", "james mcarty", "john denver"]
names_dict = {}
for name in names:
# .split() returns a list of strings
# where each string is a single word from the original
names_arr = name.split()
# we're always setting the "jack" key
names_dict["jack"] = names_arr[1]
print(names_dict)
# Prints: {'jack': 'denver'}
Deleting Dictionary Values
You can delete existing keys using the del
keyword.
names_dict = {
'jack': 'bronson',
'jill': 'mcarty',
'joe': 'denver'
}
del names_dict['joe']
print(names_dict)
# Prints: {'jack': 'bronson', 'jill': 'mcarty'}
Deleting keys that don't exist
Notice that if you try to delete a key that doesn't exist, you'll get an error.
names_dict = {
'jack': 'bronson',
'jill': 'mcarty',
'joe': 'denver'
}
del names_dict['unknown']
# ERROR HERE, key doesn't exist
Checking for existence
If you're unsure whether or not a key exists in a dictionary, use the in
keyword.
cars = {
'ford': 'f150',
'tesla': '3'
}
print('ford' in cars)
# Prints: True
print('gmc' in cars)
# Prints: False
Iterating over a dictionary in Python
fruit_sizes = {
"apple": "small",
"banana": "large",
"grape": "tiny"
}
for name in fruit_sizes:
size = fruit_sizes[name]
print(f"name: {name}, size: {size}")
# name: apple, size: small
# name: banana, size: large
# name: grape, size: tiny
Ordered or Unordered?
As of Python version 3.7
, dictionaries are ordered. In Python 3.6
and earlier, dictionaries were unordered.
Because dictionaries are ordered, the items have a defined order, and that order will not change.
Unordered means that the items used to not have a defined order, so you couldn't refer to an item by using an index.
**The takeaway is that if you're on Python 3.7
or later, you'll be able to iterate over dictionaries in the same order every time.
Chapter 9: Sets
Sets are like Lists, but they are unordered and they guarantee uniqueness. There can be no two of the same value in a set.
fruits = {'apple', 'banana', 'grape'}
print(type(fruits))
# Prints: <class 'set'>
print(fruits)
# Prints: {'banana', 'grape', 'apple'}
Adding values to a set
fruits = {'apple', 'banana', 'grape'}
fruits.add('pear')
print(fruits)
# Prints: {'banana', 'grape', 'pear', 'apple'}
Empty set
Because the {}
syntax creates an empty dictionary, to create an empty set, just use the set()
function.
fruits = set()
fruits.add('pear')
print(fruits)
# Prints: {'pear'}
Iterate over values in a set (order is not guaranteed)
fruits = {'apple', 'banana', 'grape'}
for fruit in fruits:
print(fruit)
# Prints:
# banana
# grape
# apple
Removing values from a set
fruits = {'apple', 'banana', 'grape'}
fruits.remove('apple')
print(fruits)
# Prints: {'banana', 'grape'}
Chapter 10: Errors
You've probably encountered some errors in your code from time to time if you've gotten this far in the course. In Python, there are two main kinds of distinguishable errors.
- syntax errors
- exceptions
Syntax errors
You probably know what these are by now. A syntax error is just the Python interpreter telling you that your code isn't adhering to proper Python syntax.
this is not valid code, so it will error
If I try to run that sentence as if it were valid code I'll get a syntax error:
this is not valid code, so it will error
^
SyntaxError: invalid syntax
Exceptions
Even if your code has the right syntax however, it may still cause an error when an attempt is made to execute it. Errors detected during execution are called "exceptions" and can be handled gracefully by your code. You can even raise your own exceptions when bad things happen in your code.
Python uses a try-except pattern for handling errors.
try:
10 / 0
except Exception as e:
print(e)
# prints "division by zero"
The try
block is executed until an exception is raised or it completes, whichever happens first. In this case, a "divide by zero" error is raised because division by zero is impossible. The except
block is only executed if an exception is raised in the try
block. It then exposes the exception as data (e
in our case) so that the program can handle the exception gracefully without crashing.
Raising your own exceptions
Errors are not something to be scared of. Every program that runs in production is expected to manage errors on a constant basis. Our job as developers is to handle the errors gracefully and in a way that aligns with our user's expectations.
Errors are NOT bugs
When something in our own code happens that we don't expect, we should raise our own exceptions. For example, if someone passes some bad inputs to a function we write, we should not be afraid to raise an exception to let them know they did something wrong.
An error or exception is raised when something bad happens, but as long as our code handles it as users expect it to, it's not a bug. A bug is when code behaves in ways our users don't expect it to.
For example, if a player tries to forge an iron sword out of bronze metal, we might raise an exception and display an error message to the player. However, that's the expected behavior of the game, so it's not a bug. If a player can forge the iron sword out of bronze, that may be considered a bug because that's against the rules of the game.
raise Exception("something bad happened")
Software applications aren't perfect, and user input and network connectivity are far from predictable. Despite intensive debugging and unit testing, applications will still have failure cases.
Loss of network connectivity, missing database rows, out of memory issues, and unexpected user inputs can all prevent an application from performing "normally". It is your job to catch and handle any and all exceptions gracefully so that your app keeps working. When you are able to detect that something is amiss, you should be raising the errors yourself, in addition to the "default" exceptions that the Python interpreter will raise.
raise Exception("something bad happened")
Different types of exceptions
We haven't covered classes and objects yet, which is what an Exception
really is at its core. We'll go more into that in the object-oriented programming course that we have lined up for you next.
For now, what is important to understand is that there are different types of exceptions and that we can differentiate between them in our code.
More syntax for errors
try:
10/0
except ZeroDivisionError:
print("0 division")
except Exception:
print("unknown exception")
try:
nums = [0, 1]
print(nums[2])
except ZeroDivisionError:
print("0 division")
except Exception:
print("unknown exception")
Which will print:
0 division
unknown exception
Chapter 11: Python Facts
The Zen of Python
Tim Peters, a long time Pythonista describes the guiding principles of Python in his famous short piece, The Zen of Python.
Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than right now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!
Why Python?
Here are some reasons we think Python is a future-proof choice for developers:
- Easy to read and write - Python reads like plain English. Due to its simple syntax, it's a great choice for implementing advanced concepts like AI. This is arguably Python's best feature.
- Popular - According to the Stack Overflow Developer Survey, Python is the 4th most popular coding language in 2020.
- Free - Python, like many languages nowadays, is developed under an open-source license. It's free to install, use, and distribute.
- Portable - Python written for one platform will work on any other platform.
- Interpreted - Code can be executed as soon as it's written. Because it doesn't need to take a long time to compile like Java, C++, or Rust, releasing code to production is typically faster.
Why not Python?
Python might not be the best choice for a project if:
- The code needs to run fast. Python code executes very slowly, which is why performance critical applications like PC games aren't written in Python.
- The codebase will become large and complex. Due to its dynamic type system, Python code can be harder to keep clean of bugs.
- The application needs to be distributed directly to non-technical users. They would have to install Python in order to run your code, which would be a huge inconvenience.
Python 2 vs Python 3
One thing that's important to keep in mind as you continue your Python journey is that the Python ecosystem suffers from split personality syndrome. Python 3 was released on December 3rd, 2008, but over a decade later the web is still full of Python 2 dependencies, scripts and tutorials.
In this course, we used Python 3 - just like any good citizen should these days.
One of the most obvious breaking changes between Python 2 and 3 is the syntax for printing text to the console.
Python 2
print "hello world"
Python 3
print("hello world")
Congratulations on making it to the end!
If you're interested in doing the interactive coding assignments and quizzes for this course you can check out the Learn Python course over on Boot.dev.
That course is a part of my full back-end developer career path, made up of other courses and projects if you're interested in checking those out.