├── .gitignore ├── 01 - Install and Run Python ├── README.md └── hello.py ├── 02 - Variables and Datatypes ├── README.md └── solution_02.py ├── 03 - Operators ├── README.md └── solution_03.py ├── 04 - Conditions ├── README.md └── solution_04.py ├── 05 - Lists ├── README.md └── solution_05.py ├── 06 - Loops ├── README.md └── solution_06.py ├── 07 - Dictionaries ├── README.md └── solution_07.py ├── 08 - Tuples and Sets ├── README.md └── solution_08.py ├── 09 - Functions ├── README.md └── solution_09.py ├── 10 - User Input ├── README.md └── solution_10.py ├── 11 - Classes ├── README.md └── solution_11.py ├── 12 - Final Project ├── README.md ├── expense.py ├── expense_cli.png ├── expense_drive.png ├── expense_tracker.py └── expenses.csv ├── README.md └── images └── learn-python-preview.png /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.pyc -------------------------------------------------------------------------------- /01 - Install and Run Python/README.md: -------------------------------------------------------------------------------- 1 | # 01: How to Install and Run Python 2 | 3 | Python is available for all OS platforms (Windows, Mac and Linux). 4 | 5 | ## Windows 6 | 1. Download the latest version of Python from the [Python downloads page](https://www.python.org/downloads/) 7 | 2. Run the installer and follow the instructions. Make sure to check the box to add Python to your `PATH`. 8 | 9 | ## Mac and Linux 10 | 11 | If you use Mac or Linux, your computer should already come with Python, so you should be able to skip this step. 12 | 13 | Otherwise, you can follow the same instructions as above, just make sure to download the correct version for your system. 14 | 15 | ## Checking Python Version 16 | 17 | To check the version of Python installed on your system, open a terminal and type: 18 | 19 | ```bash 20 | python --version 21 | ``` 22 | 23 | You should see it print out your Python version. For example: 24 | 25 | ``` 26 | Python 3.10.8 27 | ``` 28 | 29 | If you end up having **Python version 2** instead of version 3, then try this command instead. 30 | 31 | ```bash 32 | python3 --version 33 | ``` 34 | 35 | We will need Python 3.6 or above. If it doesn't work, then go back to the installation step and make sure you have at least version 3.6. 36 | 37 | ## Python Interpreter 38 | 39 | Before we write some code, let's run the Python interpreter. Type in this command and press Enter. 40 | 41 | ```bash 42 | python 43 | ``` 44 | 45 | Now in the terminal, you can use the Python interpreter. Here's some basic things to try (type and press `Enter` after each line): 46 | 47 | ```python 48 | print("Hello World!") 49 | x = 5 50 | y = 10 51 | z = x + y 52 | print(z) 53 | ``` 54 | 55 | When you're done, type `exit()` and press Enter to exit the Python interpreter. You can also press Ctrl+D to exit. 56 | 57 | ## How does Python work? 58 | 59 | What you've just run is called the "Python interpreter". The Python interpreter is a program that reads and executes the Python code that you write. 60 | 61 | Python is an interpreted language, which means that the code you write isn't actually translated into a language that the computer understands and runs directly. Instead, it's read and executed by the Python interpreter, line by line. 62 | 63 | When you write a Python program, you write it in a text file, and save it with the extension `.py`. This file is called a Python script. 64 | 65 | What we'll be learning today is writing these Python scripts that the Python interpreter (the thing you just installed), will run for us. 66 | 67 | ## Code Editor 68 | 69 | Now let's write our first Python script. For this you can use any text editor. But I recommend [VSCode](https://code.visualstudio.com). 70 | 71 | It's simple and it's free, but it's also powerful enough to use professionally. For the rest of this tutorial, I'll assume you have VSCode. 72 | 73 | But if VSCode isn't for you, here are three other suitable IDEs or code editors for Python: 74 | 75 | - [PyCharm](https://www.jetbrains.com/pycharm/) 76 | - [Sublime Text](https://www.sublimetext.com) 77 | 78 | And if you don't have access to install a code editor, you can also use an online editor (called REPL) such as: 79 | 80 | - [Repl.it](https://repl.it/languages/python3) 81 | - [PythonAnywhere](https://www.pythonanywhere.com) 82 | 83 | ## Write Your First Python Script 84 | 85 | Create a folder somewhere on your computer for this tutorial. And then open up that folder in VSCode (or your editor of choice). 86 | 87 | Once you have that folder, create a new file, and name it `hello.py`. 88 | 89 | And in that file write this line: 90 | 91 | ```python 92 | print("Hello World! 👋") 93 | ``` 94 | 95 | ## Run Your Python Script 96 | 97 | If you're using VSCode, it's really easy. You should just be able to press `F5` (or `Ctrl+F5` on Windows) to run your script. 98 | 99 | If you're using a different editor, you'll need to open up a terminal, and then run your script with the `python` command. 100 | 101 | ```bash 102 | python hello.py 103 | ``` 104 | 105 | Once you've done that, you should see the output `Hello World!` 👋 in your terminal. 106 | 107 | --- 108 | 109 | 🎉 Congratulations, you've written and run your first Python script! Keep going! 👉 -------------------------------------------------------------------------------- /01 - Install and Run Python/hello.py: -------------------------------------------------------------------------------- 1 | print("Hello World! 👋") -------------------------------------------------------------------------------- /02 - Variables and Datatypes/README.md: -------------------------------------------------------------------------------- 1 | # 02: Variables and Data Types 2 | 3 | Variables in Python are how we can store data and how we can refer to it later on, or whenever we need it to do something with. 4 | 5 | Here's some examples of how to define a variable, then print it out to our console: 6 | 7 | ```python 8 | x = 9 9 | print(x) # 9 10 | ``` 11 | 12 | ```python 13 | y = "hello" 14 | print(y) # "hello" 15 | ``` 16 | 17 | Variables must be defined before you can use them. If you haven't defined it first, and try to use it, you will get an error: 18 | 19 | ```python 20 | print(z) 21 | # NameError: name 'z' is not defined 22 | ``` 23 | 24 | ## Naming Variables 25 | 26 | You can name your variables in Python _almost_ anything you want. 27 | 28 | For instance, both `user_name` and `userName` are valid Python variable names, but one is "better" than the other because it uses "snake case" (lower case with underscores), which aligns with Python naming conventions. 29 | 30 | Here are the rules and guidance for naming variables in Python: 31 | 32 | - Can contain letters, numbers, and underscores 33 | - Cannot start with a number 34 | - Should not contain spaces or special characters 35 | - Cannot be one of Python's [reserved keywords](https://docs.python.org/3/reference/lexical_analysis.html#keywords) 36 | - Generally follows [PEP8 naming conventions](https://peps.python.org/pep-0008/#descriptive-naming-styles) 37 | - Should be descriptive and easy to remember 38 | 39 | ### ✅ Good Variable Names 40 | 41 | Good variable names should be short but descriptive. 42 | 43 | ```python 44 | x 45 | account_id 46 | student_name 47 | tax_rate 48 | temperature_fahrenheit 49 | is_valid 50 | ``` 51 | 52 | ### 👉 Example Values 53 | 54 | A variable is associated with a value, which can be things like a **number**, a **word**, or more complex things that we'll learn about later on. 55 | 56 | Here's how we'd create variables and assign them a value: 57 | 58 | ```python 59 | x = 6 60 | account_id = "08293310" 61 | student_name = "jack" 62 | tax_rate = 0.21 63 | temp_fahrenheit = 70 64 | is_valid = True 65 | ``` 66 | 67 | If we want to create variable but don't know what it's value is going to be, we can also assign it `None`, which is a special reserved word in Python: 68 | 69 | ```python 70 | account_id = None 71 | ``` 72 | 73 | ### 🔥 Bad Variable Names 74 | 75 | Here are some examples of variable names that are either bad, or outright invalid. 76 | 77 | ```python 78 | min # Reserved word 79 | 1_var # Cannot start with number! 80 | var_1 # Not descriptive 81 | StudentName # Incorrect casing 82 | proc_ctr # Too abbreviated 83 | distance # Should have unit (km, m, etc) 84 | ``` 85 | 86 | ### Naming is hard! 87 | 88 | Don't worry if you struggle to come up with good variable names. It's a skill that takes time to develop. 89 | 90 | > There are only two hard things in Computer Science: cache invalidation and naming things. — Phil Karlton 91 | 92 | --- 93 | 94 | ## Example Usage of Variables 95 | 96 | And here's some examples of how we can use variables in our programs (and what problems they solve in different scenarios). 97 | 98 | ### 🕹️ Games 99 | 100 | In a game, you might want to store information about a character, like their hitpoints, power, and move speed. 101 | 102 | ```python 103 | hp = 120 104 | power = 10 105 | move_speed = 3.4 106 | ``` 107 | 108 | ### 🍔 Food Ordering App 109 | 110 | In an online food ordering app, you might want to store information about a specific item, like its name, price, and whether it's vegetarian or not. 111 | 112 | ```python 113 | item_name = "burger" 114 | price_usd = 8.99 115 | is_vegetarian = False 116 | ``` 117 | 118 | ### 📈 Finance App 119 | 120 | In a finance app, you might want to store information about a stock order, like its symbol, quantity, and order type. 121 | 122 | ```python 123 | symbol = "AAPL" 124 | quantity = 8 125 | order_type = "BUY" 126 | ``` 127 | 128 | ## Primitive Data Types 129 | 130 | Now let's take a closer look at these 'values.' They are data, or basically - ways of representing information about the world. 131 | 132 | There are four primitive data types in Python. And these are the building blocks to more complex data structures that we'll see later. 133 | 134 | ```python 135 | int # Integer (whole numbers) 136 | float # Float (numbers with decimals) 137 | str # String (literals, words, text) 138 | bool # Boolean (either True or False) 139 | ``` 140 | 141 | And a couple of extra things to be aware of with data types: Strings are case-sensitive. And boolean values (`True` and `False`) need to be capitalised. Here's some examples: 142 | 143 | ```python 144 | x = 8 # int 145 | y = 2.32 # float 146 | name = "Jack" # str 147 | likes_coffee = True # bool 148 | ``` 149 | 150 | The reason for having these four distinct types of data is to ensure that the data is stored in the most efficient, and accurate, way possible. 151 | 152 | Each data type requires a different amount of memory for storage, and having the wrong data type could potentially lead to inaccuracies. 153 | 154 | But more importantly, the data type also gives us a clue about what we can DO with the data. For example, I can add two integers together. 155 | 156 | ```python 157 | # Adding integers 158 | print(2 + 2) # 4 159 | ``` 160 | 161 | I can also add an integer with a float. 162 | 163 | ```python 164 | # Add int with float 165 | print(2 + 3.5) # 5.5 166 | ``` 167 | 168 | I can also add two strings, but that would behave differently. 169 | 170 | ```python 171 | # Adding two strings 172 | print("2" + "2") # "22" 173 | ``` 174 | 175 | Strings are characters, and have no numerical value. So when I add them, it treats them as if we've just cut letters out of the newspaper and pasted them together like you see in movie ransom notes. 176 | 177 | ## Printing in Python 178 | 179 | There's also several ways to "print" information in Python. Printing is how we make some information appear on the screen 180 | 181 | We can use the `print()` function to print information: 182 | 183 | ```python 184 | print("Hello World!") # Hello World! 185 | ``` 186 | 187 | The `print()` function can also be used to "print" the value of variables: 188 | 189 | ```python 190 | x = 12 191 | print(x) # 12 192 | ``` 193 | 194 | We can also print multiple values at once: 195 | 196 | ```python 197 | x = 12 198 | y = 24 199 | print(x, y) # 12, 24 200 | ``` 201 | 202 | We can also combine text and variables in our print statements: 203 | 204 | ```python 205 | x = 12 206 | print("Hello", x) # Hello, 12 207 | ``` 208 | 209 | We can also use a formatting string (f-string) to print out variables. This is a popular approach because it allows us to easily combine text and variables: 210 | 211 | ```python 212 | x = 12 213 | print(f"The value of x is {x}") 214 | # The value of x is 12 215 | ``` 216 | 217 | --- 218 | 219 | # Coding Exercise 02: Variables 220 | 221 | Write a program that prints out the details of an item in a shop. 222 | You need to model the following information: 223 | 224 | - ✏️ Item Name (e.g. "apple") 225 | - ✏️ Quantity (e.g. 4) 226 | - ✏️ Price (e.g. $0.50) 227 | - ✏️ In Stock? (e.g. Yes/No) 228 | 229 | Write a short Python script to store the information in variables, and then print it out in a nice format. 230 | 231 | When you've had a go, see the [solution here!](./solution_02.py). 232 | -------------------------------------------------------------------------------- /02 - Variables and Datatypes/solution_02.py: -------------------------------------------------------------------------------- 1 | """ 2 | Write a short Python script to store the information in variables, and then print it out in a nice format (for an e-commerce website, for example): 3 | 4 | - Item Name (e.g. "apple") 5 | - Quantity (e.g. 4) 6 | - Price (e.g. $0.50) 7 | - In Stock? (e.g. Yes/No) 8 | 9 | """ 10 | 11 | # The variable names should be short, descriptive, and in snake_case. 12 | item_name = "apple" 13 | quantity = 4 14 | price_usd = 0.50 15 | in_stock = True 16 | 17 | # Print out the information in a nice format using f-strings. 18 | print(f"Item Name : {item_name}") 19 | print(f"Quantity : {quantity}") 20 | print(f"Price : ${price_usd}") 21 | print(f"In Stock? : {in_stock}") 22 | -------------------------------------------------------------------------------- /03 - Operators/README.md: -------------------------------------------------------------------------------- 1 | # 03: Operators 2 | 3 | Operators are special symbols that perform specific actions on our data and our variables. Here's an example of us adding two numbers together, where `+` is the operator: 4 | 5 | ```python 6 | result = 5 + 2 7 | print(result) # 7 8 | ``` 9 | 10 | And in the context of this line of code, we would refer to these two numbers (5 and 2) as "operands." The concept of "operators" applies to all programming languages, — not just Python. 11 | 12 | ## Arithmetic Operators 13 | 14 | Arithmetic operators are used to perform basic mathematical operations on numbers. Here are the most common arithmetic operators in Python: 15 | 16 | ```python 17 | x + y # Addition 18 | x - y # Subtraction 19 | x * y # Multiplication 20 | x / y # Division 21 | x // y # Floor division 22 | x % y # Modulus 23 | x ** y # Exponentiation 24 | ``` 25 | 26 | Generally, if you use these operators with integers (whole numbers), the result will also be an integer. If either operand is a float, the result will be a float. For example: 27 | 28 | ```python 29 | print(5 + 3) # 8 30 | print(5 - 3) # 2 31 | print(5 * 3) # 15 32 | ``` 33 | 34 | The exception here is division, which will always result in a float, even if the operands are both integers. 35 | 36 | ```python 37 | print(5 / 3) 38 | # 1.6666666666666667 39 | ``` 40 | 41 | ### Integer Division 42 | 43 | If you want an integer result from division, you need to use the `//` operator. This is an integer division, and will round the result down to the nearest whole number. 44 | 45 | ```python 46 | print(5 / 3) 47 | # 1 48 | ``` 49 | 50 | ### Exponent and Modulo Operators 51 | 52 | The less commonly used ones are the exponent operator (a double \* , which is when you want to raise something to the power of 2 (or any number). 53 | 54 | ```python 55 | 10 ** 2 # == 100 56 | ``` 57 | 58 | And there's the modulo operator, which is a percent symbol. And it lets you find the remainder of division. 59 | 60 | ```python 61 | 10 % 3 # == 1 62 | 10 % 5 # == 0 63 | ``` 64 | 65 | Modulo operations are useful when you want to check if a number is divisible by another number, or you have a "wrap around" situation you want to solve (e.g. converting 24h clock into 12h time). 66 | 67 | ## Order of precedence 68 | 69 | Arithmetic operators also have an order of precedence. So in a case like this, the multiplication will be executed before the addition (even though it comes after, if you read it from left to right): 70 | 71 | ```python 72 | 1 + 2 * 3 # == 7 73 | ``` 74 | 75 | The order of operator precedence in Python is: 76 | 77 | 1. Parentheses 78 | 2. Exponentiation 79 | 3. Multiplication and Division 80 | 4. Addition and Subtraction 81 | 82 | But instead of trying to learn and memorise the order of precedence, you could just also just use parentheses (the round brackets) if you want to control the order of execution. 83 | 84 | ```python 85 | (1 + 2) * 3 # == 9 86 | ``` 87 | 88 | ## Assignment Operators 89 | 90 | Assignment Operators are how set set values to a variable. So this is both how we can create new variables, or update the value of an existing variable. 91 | 92 | ```python 93 | x = 2 94 | # x is now 2 95 | 96 | x = 4 97 | # x is now 4 98 | ``` 99 | 100 | You can also combine the `=` assignment operator with arithmetic operator to [insert] 101 | 102 | ```python 103 | x = 2 104 | x += 2 105 | # x is now 4 106 | 107 | x *= 2 108 | # x is now 8 109 | ``` 110 | 111 | ## Comparison Operators 112 | 113 | Comparison operators are how we check the relationship between two or more values. Let's imagine we want to build a food ordering app in Python. 114 | 115 | We'll need comparison operators to do things like: 116 | 117 | - Check if the user's order is above the minimum order amount 118 | - See if the item they ordered is available in their location 119 | - See if the user's order matches the restaurant's inventory 120 | 121 | We can use comparison operators to compare values and determine if they match or not. Here's a list of common comparison operators in Python: 122 | 123 | ```python 124 | == # Equals 125 | != # Not Equals 126 | < # Less Than 127 | > # Greater Than 128 | <= # Less Than or Equal To 129 | >= # Greater Than or Equal To 130 | ``` 131 | 132 | Here's an example of using the equality comparison operator. 133 | 134 | ```python 135 | x = 3 136 | y = 5 137 | print(x == y) # False 138 | ``` 139 | 140 | ## Expression 141 | 142 | The technical term for a line like this in Python, is an "expression" 143 | 144 | ```python 145 | x == y 146 | ``` 147 | 148 | An expression is a combination of values, variables, operators, and/or functions that results in a single value. In this case, the expression is a comparison operator that checks if the value of x is equal to the value of y. 149 | 150 | You won't know the result of this expression just by looking at this line on its own, unless you know the values of x and y at the exact time that this line is _evaluated_ by Python. 151 | 152 | When you run this program and you get to this line, Python evaluates this expression with the information it has. 153 | 154 | ## Evaluation 155 | 156 | ```python 157 | result = x == y 158 | print(result) # True 159 | ``` 160 | 161 | An "evaluation" in Python is the process of interpreting an expression and determining its value. In this example, the expression is `x == y` and the result of the evaluation is either `True` or `False`. 162 | 163 | So here in this example, we can say "the expression evaluates to `True`". 164 | 165 | ## Logical Operators 166 | 167 | Logical operators are used to compare the relationship between two or more Boolean expressions and return a Boolean value of True or False. 168 | 169 | ```python 170 | x and y # AND 171 | x or y # OR 172 | not x # NOT 173 | ``` 174 | 175 | This is to ultimately help our program make decisions. For example, should I make coffee right now? 176 | 177 | ```python 178 | # Should I make coffee right now? 179 | is_cup_empty = False 180 | is_daytime = True 181 | should_make_coffee = is_cup_empty and is_daytime 182 | ``` 183 | 184 | Here's another example. Let's say we're building a travel planning app, and we want it to recommend whether we should walk or drive to a target location. 185 | 186 | ```python 187 | # Should I walk or drive to my destination? 188 | is_raining = False 189 | is_far = True 190 | should_drive = is_raining or is_far 191 | ``` 192 | 193 | Now you can combine these operators and these expressions to create really complex rules systems. So like in this previous example, how can I well whether something is far away? 194 | 195 | ```python 196 | is_raining = False 197 | is_far = distance_meters > 1000 198 | should_drive = is_raining or is_far 199 | 200 | # Same as inlining it. 201 | should_drive = is_raining or (distance_meters > 1000) 202 | ``` 203 | 204 | --- 205 | 206 | # Coding Exercise 03: Operators 207 | 208 | Can you write a program that calculates [compound interest](https://g.co/kgs/4PYrPD)? If we deposit $240,000 into a bank account with an annual interest rate of 5%, how much will we have after 2 years? Assume that the interest is compounded annually. 209 | 210 | ```python 211 | principal = 240_000 212 | annual_rate = 0.05 213 | years = 2 214 | 215 | new_value = 0 # TODO: Calculate the compound interest 216 | ``` 217 | 218 | The challenge of this exercise is to find the formula for compound interest, and translate it into Python using the correct operators and the correct order of precedence. 219 | 220 | You should get something close to `264600`. Once you've had a go, you can check out the [solution here](./solution_03.py). 221 | -------------------------------------------------------------------------------- /03 - Operators/solution_03.py: -------------------------------------------------------------------------------- 1 | principal = 240_000 2 | annual_rate = 0.05 3 | years = 2 4 | 5 | # Using the compound interest formula (https://g.co/kgs/Fa1yyX) with 6 | # a compound period of 1 year: 7 | 8 | new_value = principal * (1 + annual_rate) ** years 9 | print(f"Compounded balance: {new_value}") 10 | -------------------------------------------------------------------------------- /04 - Conditions/README.md: -------------------------------------------------------------------------------- 1 | # 04: Conditions 2 | 3 | Conditions in Python are boolean expressions used to control the flow of the program. These expressions usually have a value of either `True` or `False`. 4 | 5 | For example, we could have a variable `is_morning` that is `True` if it is morning and `False` if it is not morning. We could then use this variable in a condition to determine what we should drink: 6 | 7 | ```python 8 | if is_morning: 9 | print("Let's go for a ☕️") 10 | else: 11 | print("Let's go for a 🍺!") 12 | ``` 13 | 14 | We can use them together with logical operators and booleans. These are `if/else` statements. 15 | 16 | ### `if` 17 | 18 | Here's an example of an `if` statement. 19 | 20 | ```python 21 | is_raining = True 22 | if is_raining: 23 | print("It's raining! 💧") 24 | ``` 25 | 26 | If this expression evaluates to `True`, the code in this scope will be executed. 27 | 28 | ### Condition Scope in Python 29 | 30 | The scope is whatever is indented (4 spaces) under this `if` condition. If the condition is NOT true, this code path will not be executed. And it'll just carry on to the next thing in its scope. 31 | 32 | To demonstrate that, we can add another print statement here, unindented. This is no longer in the scope of the `if` condition, so if will print, even when the condition in `False`. 33 | 34 | ```python 35 | is_raining = False 36 | if is_raining: 37 | print("It's raining! 💧") # This will not be reached 38 | 39 | print("Have a nice day! 😀") # This will be reached! 40 | ``` 41 | 42 | And now the code path that gets executed will depend on whether it's raining or not. Notice how we still reach the line at the end no matter which code path gets executed. 43 | 44 | ### `else` 45 | 46 | And if we want something something else to happen when the condition in False, we can use the `else` keyword, like this. So let's take our previous example but now imagine we're building a trip planner, like in Google maps. 47 | 48 | And if it's raining we're going to drive. Otherwise we'll take the bicycle. 49 | 50 | ```python 51 | # Route Planner 52 | is_raining = False 53 | if is_raining: 54 | print("Drive to Destination 🚗") 55 | else: 56 | print("Cycle to Destination 🚲") 57 | 58 | ``` 59 | 60 | ### `elif` 61 | 62 | There is also an "else if" statement, which Python shortens to `elif`. This will execute if the preceding conditions are `False`. 63 | 64 | For example, let's imagine we want to write some code to decide how to get to a destination. 65 | 66 | We have variables for whether it's raining and whether it's far away: 67 | 68 | ```python 69 | is_raining = False 70 | is_far = False 71 | ``` 72 | 73 | We want to decide whether to drive, walk or cycle to the destination: 74 | 75 | ```python 76 | if is_raining or is_far: 77 | print("🚗 Drive to Destination") 78 | else: 79 | print("🚶 Walk to Destination") 80 | ``` 81 | 82 | In this case, the `else` statement will execute, because neither `is_raining` nor `is_far` are `True`. 83 | 84 | But what if we want to also include cycling as an option? We can add an `elif` statement: 85 | 86 | ```python 87 | is_raining = False 88 | is_far = True 89 | 90 | if is_raining or is_far: 91 | print("🚗 Drive to Destination") 92 | elif not is_far: 93 | print("🚶 Walk to Destination") 94 | else: 95 | print("🚲 Cycle to Destination") 96 | ``` 97 | 98 | You can also include conditions with numerical values (and use them together with comparison operators). For example, if we want to include cycling as an option only if the distance is between 1 and 10 km, we can add an `elif` statement like this: 99 | 100 | ```python 101 | is_raining = False 102 | distance_kms = 5.0 103 | 104 | if is_raining or distance_kms > 10: 105 | print("🚗 Drive to Destination") 106 | elif distance_kms < 1: 107 | print("🚶 Walk to Destination") 108 | else: 109 | print("🚲 Cycle to Destination") 110 | ``` 111 | 112 | --- 113 | 114 | # Coding Exercise 04: Conditions 115 | 116 | Writing a program for a ticketing system. The program should check the age and student status of customers and display the appropriate message based on these rules: 117 | 118 | - If customer is under 18 - entry is free. 119 | - If customer is a **student _or_ over 65** - entry is 50% discounted. 120 | - Everyone else pays 100%. 121 | 122 | Fill in the code (with if/else conditions) to make the program print the correct message for any given value of `customer_age` and `is_student`. 123 | 124 | ```python 125 | customer_age = 24 126 | is_student = True 127 | 128 | # If customer is under 18 - entry is free. 129 | print("🎟️ Entry is FREE!") 130 | 131 | # If customer is a student or over 65 - entry is 50% discounted. 132 | print("🎟️ Entry is 50% off!") 133 | 134 | # Everyone else pays 100%. 135 | print("🎟️ Entry is full-price!") 136 | ``` 137 | 138 | Once you are done, compare your answer to the [solution here](./solution_04.py). 139 | -------------------------------------------------------------------------------- /04 - Conditions/solution_04.py: -------------------------------------------------------------------------------- 1 | customer_age = 70 2 | is_student = False 3 | 4 | if customer_age < 18: 5 | print("🎟️ Entry is FREE!") 6 | elif is_student or customer_age > 65: 7 | print("🎟️ Entry is 50% off!") 8 | else: 9 | print("🎟️ Entry is full-price!") 10 | -------------------------------------------------------------------------------- /05 - Lists/README.md: -------------------------------------------------------------------------------- 1 | # 05: Lists 2 | 3 | Lists in Python are used to store a collection of items in a single variable. You can store numbers, strings, and even other lists. 4 | 5 | To create a list, you use square brackets and separate each item you want to add to the list with a comma. 6 | 7 | ```python 8 | # How to create a list. 9 | fruit_basket = ["apple", "banana", "carrot"] 10 | ``` 11 | 12 | And because a list is sequential, the order we put things into a list matters. 13 | 14 | And you can access any of the items in a list, by using the square brackets as well. So type variable of the list, square brackets, then number of the item you want to access. 15 | 16 | ```python 17 | # How to create a list. 18 | fruit_basket = ["apple", "banana", "carrot"] 19 | 20 | # Accessing the items in a list. 21 | print(fruit_basket[0]) # "apple" 22 | print(fruit_basket[1]) # "banana" 23 | ``` 24 | 25 | This number is called the "index". It corresponds to the position of an item in the list (except that it starts counting at 0). 26 | 27 | So `0` here corresponds to the first item, which is an "apple". And `1` corresponds to the second item, which is a "banana". 28 | 29 | ## List Operations 30 | 31 | When creating a list, we don't always know ahead of time what all of its contents are going to be. 32 | 33 | So it's quite common to create an empty list, and then be able to add or remove items as our program is running. 34 | 35 | Here's how to add items to an existing list: 36 | 37 | ```python 38 | # Create an empty list 39 | fruit_basket = [] 40 | 41 | # Add items to a list. 42 | fruit_basket.append("apple") 43 | fruit_basket.append("banana") 44 | fruit_basket.append("carrot") 45 | 46 | print(fruit_basket) # ['apple', 'banana', 'carrot'] 47 | ``` 48 | 49 | We can also find out the current length of a list (how many items are in it). 50 | 51 | ```python 52 | # How many items are in the list? 53 | print(len(fruit_basket)) # 3 54 | ``` 55 | 56 | And you can remove items from a list as well. There's two ways to do this. 57 | 58 | ### `remove()` 59 | 60 | ```python 61 | # Remove an item by value. 62 | fruit_basket.remove("banana") 63 | ``` 64 | 65 | This will go through each item in the list and remove the first one it sees with this value. But this is not efficient, precisely because it _has to go through every item_ first to find the one. 66 | 67 | ### `pop()` 68 | 69 | ```python 70 | # Remove the list item from a list. 71 | fruit_basket.pop() 72 | ``` 73 | 74 | This removes the _last_ item from the list. 75 | 76 | This is the most efficient way to remove an item from the list (if you want to know why, then you'll learn to learn about **data structures and algorithms**, which is out of our scope for this tutorial). 77 | 78 | If there is a specific index of an item you want to remove, you can do that too. For example, if I wanted to remove the first item in a list, I can pop the zero-index. 79 | 80 | ```python 81 | fruit_basket.pop(0) 82 | ``` 83 | 84 | And in addition to removing the item from the list, the pop function also returns the item to you, making it available to use. 85 | 86 | ```python 87 | fruit_basket = ["apple", "banana", "carrot"] 88 | fruit = fruit_basket.pop() 89 | print(fruit) # carrot 90 | ``` 91 | 92 | ## Built-in List Functions and Syntax Sugar 🍭 93 | 94 | So that covers the _basics_ of using a list in Python. There's a lot of in-built functions and shortcuts (syntax sugar) to make working with lists more productive. 95 | 96 | > 👉 These "shortcuts" are usually called "syntax sugar" in Python, because they make things easier to write and read, but don't actually change what the code can do. 97 | 98 | ### Using a Negative Index 99 | 100 | You can use a negative number as an index in a list. This starts counting from the end of the list, so the last item has an index of -1, the second to last item has an index of -2 and so on. 101 | 102 | ```python 103 | my_list = ["apple", "banana", "blueberry"] 104 | print(my_list[-1]) # blueberry 105 | ``` 106 | 107 | ### Reversing a List 108 | 109 | You can reverse a list with the `.reverse()` method. This won't give you a new list — it will actually modify your existing list in-place. 110 | 111 | ```python 112 | my_list = [2, 4, 6, 0, 1] 113 | my_list.reverse() 114 | print(my_list) # [1, 0, 6, 4, 2] 115 | ``` 116 | 117 | ### Sorting a List 118 | 119 | You can also sort a list (in-place) using the `.sort()` method. This will sort it in ascending order (if your list is numerical) or alphabetical order (if your list are strings). 120 | 121 | ```python 122 | my_list = [2, 4, 6, 0, 1] 123 | my_list.sort() 124 | print(my_list) # [0, 2, 1, 4, 6] 125 | ``` 126 | 127 | ### Slicing a List ✂️ 128 | 129 | You can also **_slice_** a list using the square bracket notation. This will give you a new list with the items in the specified range. 130 | 131 | ```python 132 | my_list = [2, 4, 6, 0, 1] 133 | sliced_list = my_list[0:3] 134 | print(sliced_list) # [2, 4, 6] 135 | ``` 136 | 137 | ```python 138 | # You can also use this without the index. 139 | # This will just silence from the beginning of the list to index 3. 140 | my_list[:3] 141 | 142 | # Or from index 3 to the end of the list. 143 | my_list[3:] 144 | ``` 145 | 146 | ```python 147 | # Slice (clone) the entire list. 148 | cloned_list = my_list[:] 149 | ``` 150 | 151 | --- 152 | 153 | # Coding Exercise 05: Lists 154 | 155 | Here's a list of your shopping expenses for the last month. We want to find the amount of the **top 3** biggest expenses (as a new list). 156 | 157 | ```python 158 | shopping_expenses = [24, 60, 8, 92, 160, 80, 250, 20, 10] 159 | 160 | top_3_expenses = [] # TODO: Get the top 3 shopping expenses. 161 | # The result should be [250, 160, 92] 162 | ``` 163 | 164 | Once you've given it a go, check out the [solution here](./solution_05.py). 165 | -------------------------------------------------------------------------------- /05 - Lists/solution_05.py: -------------------------------------------------------------------------------- 1 | fruits = {"banana": 2, "apple": 3, "kiwi": 4} 2 | print(fruits.items()) 3 | -------------------------------------------------------------------------------- /06 - Loops/README.md: -------------------------------------------------------------------------------- 1 | # 06: Loops 2 | 3 | Loops in Python are used to repeat a block of code until a _certain condition_ is met. 4 | 5 | This can be useful for when you want to This can be useful for when you want to repeat an action a certain number of times (e.g. do something 100 times), or when you want to repeat an action until a user input is received. 6 | 7 | - Repeat for X number of times 8 | - Repeat for all items in a collection 9 | - Repeat while a condition is "True" 10 | 11 | ## Using Loops with Lists 12 | 13 | Now that you know how to work with lists and individual items in a list, let's first look at how a loop would be useful there. 14 | 15 | What if we wanted to do something with all of the items in a list, one by one? 16 | 17 | Imagine we developing a poker game, and wanted to calculate our odds of winning based on the cards in our hand. 18 | 19 | Or if we wanted to work out the overall account balance from a list of transactions. 20 | 21 | We need a way to go through items in a list one-by-one, so that we can do _something_ with each of the items — whether there's just summing up a transaction amount, or calculating the odds of a winning poker hand. 22 | 23 | In programming, the concept going through something step-by-step is called _iteration_. And we can use a "loop" to iterate through the items in a list. 24 | 25 | ## For Loops 26 | 27 | This is how we can write a loop in Python. And this is called "for" loop because of the "for" keyword here. 28 | 29 | ```python 30 | fruit_basket = ["apple", "banana", "blueberry"] 31 | for fruit in fruit_basket: 32 | print(f"There is a {fruit} in the basket!") 33 | ``` 34 | 35 | And translating this into human language, it's basically saying "for each item in this collection of items", I want to run this piece of code. 36 | 37 | So this piece of code is going to run three times, since there's three items in our list. 38 | 39 | The first time this runs, the "fruit" will be "apple". In the next iteration, it's equal to "banana" and so on. 40 | 41 | ## Enumerate 42 | 43 | Sometimes, when you do a loop, it's also useful to know the index of the item that we're currently iterating on. 44 | 45 | We can use the built-in `enumerate()` function to do that. Wrap this function around our list variable. And now, we have an additional variable we can use, that will tell us the index of the iteration. 46 | 47 | You can all this variable anything you want, but in programming convention, we tend to use the letter "i" which can stand for "iteration" or "index". 48 | 49 | ```python 50 | fruit_basket = ["apple", "banana", "blueberry"] 51 | for i, fruit in enumerate(fruit_basket): 52 | print(f"There is a {fruit} in the basket at index {i}!") 53 | ``` 54 | 55 | ## For In Range 56 | 57 | What if we don't care about a particular list, and we just want to run a piece of code 10 times? 58 | 59 | We can use a for loop to do that as well. We'll just use this in-built range function. And this will loop 10 times. 60 | 61 | ```python 62 | for i in range(10): 63 | print(f"Running: {i} time") 64 | ``` 65 | 66 | And the "range" function is actually quite flexible, so if you want to see what else it can do, I recommend taking a look at the official Python documentation for this function. 67 | 68 | ## While Loops 69 | 70 | Instead of iterating for a fixed range, the "while" loop iterates based on some condition. And it will continue to iterate as long as that condition is true. 71 | 72 | > For example, you might have heard the term "game loop" in reference to video games. It's the game code that runs in a loop as long as the player is still playing. 73 | 74 | A while loop in Python looks like this. We use the keyword 'while' and then follow it with an expression. 75 | 76 | As long as the expression evaluates to true, this code will keep executing. 77 | 78 | ```python 79 | while True: 80 | print("This will loop forever!") 81 | ``` 82 | 83 | So if I actually run this right now, my program will be stuck running forever. This is called an infinite loop, and it's a bug. 84 | 85 | If you accidentally do this, you can force exit your Python interpreter by pressing `Ctrl/Cmd + C` in your terminal. 86 | 87 | ## Break and Continue 88 | 89 | You can also terminate a loop from within using the `break` keyword. So this will now cause the loop to run once, before we break out of of it. 90 | 91 | ```python 92 | while True: 93 | print("This will loop forever!") 94 | break 95 | ``` 96 | 97 | And we can combine this with the other things we've learnt so far, to make the loop break when certain conditions are met. 98 | 99 | For example, let's keep track of how many times this loop is run, and break on the 5th iteration. 100 | 101 | ```python 102 | count = 0 103 | while True: 104 | count += 1 105 | print(f"This has run {count} times!") 106 | if count == 5: 107 | break 108 | ``` 109 | 110 | Another useful keyword to know is the `continue` keyword. 111 | 112 | So rather than breaking out of the loop entirely, this will just tell Python to go back to the beginning of the loop, and continue with the next iteration. 113 | 114 | ```python 115 | count = 0 116 | while True: 117 | count += 1 118 | 119 | if count == 3: 120 | continue 121 | 122 | print(f"This has run {count} times!") 123 | if count == 5: 124 | break 125 | ``` 126 | 127 | To demonstrate that, I can use the `continue` keyword here, when the count is equal to three. 128 | 129 | When it reaches this keyword, the loop will stop what it's doing - so it won't reach the rest of this code. And it'll go back to the beginning of the loop and start it again from there. 130 | 131 | These two keywords also work with the for-loops we looked at earlier. Here's the exact same code, written as a for loop instead of a while loop. 132 | 133 | ```python 134 | for i in range(10): 135 | if i == 3: 136 | continue 137 | 138 | print(f"This has run {count} times!") 139 | if count == 5: 140 | break 141 | ``` 142 | 143 | And finally, the conditional expression in a while loop - this part here, is evaluated fresh each time the loop is run. 144 | 145 | Which means that instead of writing "True" to make the loop run forever, we can just write the actual condition there instead. 146 | 147 | ```python 148 | count = 0 149 | while count < 5: 150 | count += 1 151 | print(f"This has run {count} times!") 152 | ``` 153 | 154 | ## List Comprehensions 155 | 156 | "List Comprehensions" are a bit of an advanced topic and it's quite unique to Python, so we're not going to go too far into this. 157 | 158 | But I'll just provide a quick summary. This is an example of what list-comprehension looks like, in case you see it in the wild. 159 | 160 | ```python 161 | my_list = [2, 4, 6, 0, 1] 162 | new_list = [2 * x for x in my_list] 163 | print(new_list) 164 | ``` 165 | 166 | It's a shortcut for looping through a list, and creating a new list at the same time. In this example, I'm creating a new list where each element is doubled from the original list. 167 | 168 | Now we don't need to use the fancy syntax to do this. You can achieve the same thing by writing it out normally. 169 | 170 | ```python 171 | my_list = [2, 4, 6, 0, 1] 172 | new_list = [] 173 | for x in my_list: 174 | new_list.append(x * 2) 175 | print(new_list) 176 | ``` 177 | 178 | But the list-comprehension technique just makes it a bit faster to write, and a bit easier to read. 179 | 180 | Python is full of shortcuts like this, and we call that "syntax sugar 🍭." 181 | 182 | # Coding Exercise 06: Loops 183 | 184 | Print the following pattern using a `for` loop: 185 | 186 | ```text 187 | 0 188 | 0 1 189 | 0 1 2 190 | 0 1 2 3 191 | 0 1 2 3 4 192 | ``` 193 | 194 | Once you are done, compare your answer to the [solution here](./solution_06.py). 195 | -------------------------------------------------------------------------------- /06 - Loops/solution_06.py: -------------------------------------------------------------------------------- 1 | """ 2 | Print the following pattern using for loop: 3 | 4 | 0 5 | 0 1 6 | 0 1 2 7 | 0 1 2 3 8 | 0 1 2 3 4 9 | """ 10 | 11 | for i in range(5): 12 | message = "" 13 | for sub_number in range(i + 1): 14 | message += f"{sub_number} " 15 | print(message) 16 | -------------------------------------------------------------------------------- /07 - Dictionaries/README.md: -------------------------------------------------------------------------------- 1 | # 07: Dictionaries 2 | 3 | Dictionaries are a **data structure** that stores key value pairs. It's similar to a [list](../05 - Lists/) in that it lets us store a collection of data, except instead of relying on insertion order or an index to access individual items, we use a an arbitrary key (for example, a string like `"user_id"`). 4 | 5 | A **key** is a unique identifier that we can use to access a value in the dictionary. 6 | 7 | ## Dictionary vs List 8 | 9 | Lists are good at storing sequential data. The index starts at 0, and goes up by 1 for each item. But what if we wanted to store user logins? 10 | 11 | If I try to log in with a user name like "Jack", how would I look that up in a list? I could go through each item, and check if my user ID is there. 12 | 13 | But if this list had 1 million users, I'm doing a lot of extra work to look that up. 14 | 15 | Dictionaries are similar to lists in that they let us store data and access it with square brackets. But instead of an ordinal index (ordinal means integers in a sequence), a dictionary uses a "key", which could be anything—a string, a number, or even more complex things. 16 | 17 | If we know the key, we can look up the value of that key instantly, without having to go through every other entry. 18 | 19 | ## Dictionary Example 20 | 21 | ```python 22 | fruit_prices = { 23 | "apple": 0.99, 24 | "banana": 2.50, 25 | "blueberry": 3.00 26 | } 27 | ``` 28 | 29 | If we have the key for the item that we want to access, we can use the square brackets to access that value instantly. 30 | 31 | ```python 32 | print(fruit_prices["apple"]) 33 | ``` 34 | 35 | And this is useful if our data doesn't have any kind of ordering in it's relationship, but we know something unique and specific about the item we want to access. 36 | 37 | ## Adding Elements to a Dictionary 38 | 39 | And you can't append things to a dictionary like you do with a list, but you can add new entries to it either using the assignment operator like this, or by using the 'update' function. 40 | 41 | ```python 42 | 43 | fruit_prices["watermelon"] = 5.00 44 | fruit_prices.update("cherry", 4.00) 45 | 46 | print(fruit_prices) 47 | # {''watermelon': 5.0, 'cherry': 4.0} 48 | ``` 49 | 50 | ## Accessing keys that don't exist 51 | 52 | If you try to access a key in the dictionary that isn't there, you'll get a **KeyError** and your program will crash. 53 | 54 | You have two options to avoid doing that. You can first check if a key exists inside a dictionary using the `in` keyword. 55 | 56 | ```python 57 | if "kiwi" in fruit_prices: 58 | print(fruit_prices["kiwi"]) 59 | else: 60 | print("Didn't find a kiwi!") 61 | ``` 62 | 63 | Or you can use the in-built `get()` function like this. 64 | 65 | ```python 66 | kiwi_price = fruit_price.get("kiwi") 67 | print(kiwi_price) # None 68 | ``` 69 | 70 | And if the value doesn't exist, it'll just return `None`. Or if you prefer you specify a default value for it to return instead. 71 | 72 | ```python 73 | kiwi_price = fruit_price.get("kiwi", 0) 74 | print(kiwi_price) # 0 75 | ``` 76 | 77 | ## Dictionary Built-In Methods 78 | 79 | Many of the things you can do with a list, you can also do with a dictionary. For instance, you can get the length of a dictionary using the `len()` function, which will tell you how many items are in there. 80 | 81 | ```python 82 | fruits = {"banana": 2, "apple": 3, "kiwi": 4} 83 | print(len(fruit_price)) # 3 84 | ``` 85 | 86 | Dictionaries also have their own methods, which you can use to manipulate the data they contain. 87 | 88 | ```python 89 | fruits.clear() # Removes all items from the dictionary 90 | fruits.get("apple") # Gets the value of a key 91 | fruits.items() # Returns a list of key-value pairs 92 | fruits.keys() # Returns a list of all keys 93 | fruits.pop("apple") # Removes an item from the dictionary 94 | fruits.update({"apple": 3}) # Adds or updates existing elements 95 | fruits.values() # Returns a list of all values 96 | ``` 97 | 98 | ## Looping A Dictionary 99 | 100 | Just like a list, you can also iterate through a dictionary using a for-loop. 101 | 102 | ```python 103 | for key in fruit_prices: 104 | print(key) 105 | print(fruit_prices[key]) 106 | ``` 107 | 108 | This loop might not run in the same order that the items were inserted in. 109 | 110 | Dictionaries are an unordered data-structure, which means there's no concept of a 'first item' or a 'last item' in this collection. Just items. 111 | 112 | > 👉 Another important detail is that, when you write a loop like this, you actually get the "key" of each item in the dictionary. You don't actually get the **_value_** that it stores. To get that, you'll need to use the key to access that item. 113 | 114 | If you do want both the keys and the values available to you each iteration of the loop, you can use this in-built `items()` method. 115 | 116 | ```python 117 | for key, value in fruit_prices.items(): 118 | print(key) 119 | print(value) 120 | ``` 121 | 122 | ## Dictionary As Objects 123 | 124 | The data inside a dictionary doesn't all have to be the same type either. Another way to use it is to store different pieces of data about a single object. 125 | 126 | ```python 127 | nike_shoes = { 128 | "name": "Nike AirMax 2020", 129 | "colors": ["red", "white", "black"], 130 | "price": 129.99 131 | } 132 | ``` 133 | 134 | And then I can access specific pieces of information about it. 135 | 136 | In fact, by this point in the tutorial, you'll have learnt enough about data-types and data-structures to be able to **represent probably 99% of all things in the internet**. Data being sent back and forth on all of these giant tech websites can actually be represented as a dictionary. 137 | 138 | The problem though is that "dictionaries" are a Python concept. So how do we communicate data like this to other systems that don't use Python? 139 | 140 | ## JSON Serialisation 141 | 142 | We do that by turning into a format that almost everybody understands. That process is called "serialisation". 143 | 144 | And with dictionaries, it can serialise nicely into something called **JSON**, which stands for **Javascript Object Notation**. 145 | 146 | And JSON data is everywhere. It's pretty much how a large part of how apps communicate with each other via the internet. 147 | 148 | But don't worry about that just yet. All you need to know at this point is Python dictionaries work quite well as JSON data. And Python has easy ways to convert to and from JSON data. 149 | 150 | ```python 151 | import json 152 | fruit_prices = {"apple": 90} 153 | json_fruit_prices = json.dumps(fruit_prices) 154 | print(json_fruit_prices) 155 | ``` 156 | 157 | # Coding Exercise 07: Dictionaries 158 | 159 | Imagine you are making an online expense tracking app, and you are using a dictionary to store the total expenses for things you buy. 160 | 161 | ```python 162 | total_expenses = { 163 | "food": 40, 164 | "transport": 0, 165 | "shopping": 190 166 | } 167 | 168 | # TODO: Add an "entertainment" expense ($20) 169 | # TODO: Increase "food" expense (by $12) 170 | ``` 171 | 172 | You want to add a new item to the dictionary - an "entertainment" expense of $20. You also spend an addition $12 on food and want to update the dictionary. 173 | 174 | Fill in the code that updates the dictionary. Once you've given it a go, check out the [solution here](./solution_07.py). 175 | -------------------------------------------------------------------------------- /07 - Dictionaries/solution_07.py: -------------------------------------------------------------------------------- 1 | """ 2 | Imagine you are making an online expense tracking app, 3 | and you are using a dictionary to store the total expenses for things you buy. 4 | 5 | You want to add a new item to the dictionary - an "entertainment" expense of $20. 6 | You also spend an addition $12 on food and want to update the dictionary. 7 | """ 8 | 9 | total_expenses = {"food": 40, "transport": 0, "shopping": 190} 10 | 11 | # Add an "entertainment" expense ($20) 12 | total_expenses["entertainment"] = 20 13 | # OR total_expenses.update({"entertainment": 20}) 14 | # OR total_expenses["entertainment"] = total_expenses.get("entertainment", 0) + 20 15 | # What are the advantages of each way? 16 | 17 | # Increase "food" expense (by $12) 18 | total_expenses["food"] += 12 19 | # OR total_expenses["food"] = total_expenses.get("food", 0) + 12 20 | 21 | print(total_expenses) 22 | -------------------------------------------------------------------------------- /08 - Tuples and Sets/README.md: -------------------------------------------------------------------------------- 1 | # 08: Tuples and Sets 2 | 3 | ## Tuples 4 | 5 | Tuples in Python are immutable sequences of elements. This is what the syntax looks like: 6 | 7 | ```python 8 | # Creating a tuple 9 | my_tuple = (1, 2, 3) 10 | print(my_tuple) 11 | ``` 12 | 13 | It is similar to a list that we saw earlier. And we can even access its elements in the same way. 14 | 15 | ```python 16 | # Access an element of tuple 17 | print(my_tuple[0]) 18 | print(my_tuple[1]) 19 | print(my_tuple[2]) 20 | ``` 21 | 22 | The difference is that, after a tuple is created, its **elements cannot be changed**. You can't add, remove, or update elements in tuple. 23 | 24 | So far it just sounds like a worse version of a list. So why should we use it? Well it turns out that there's some advantages. 25 | 26 | ## Tuples vs Lists 27 | 28 | - ✅ More efficient 29 | - ✅ Can be used as keys in a dict 30 | - ✅ Safer due to immutability 31 | 32 | They are faster and use less memory than lists, making them more efficient for large data sets. They **can be used as keys in dictionaries**, while lists cannot. 33 | 34 | ```python 35 | my_dict = {(1,2): "value"} 36 | ``` 37 | 38 | They are safer to use in a **multi-threaded environment**, as they cannot be changed by another thread once created. 39 | 40 | They can be used to define **multiple return values for a function**, making the code more readable and easier to understand. 41 | 42 | And, if we know the size a tuple beforehand (which we usually will), we can also unpack it into separate variables directly, instead of using the index to access it. 43 | 44 | This technique is called "unpacking" or "spreading", and is another example of Python's syntax sugar. 45 | 46 | ```python 47 | # Instead of this... 48 | my_tuple = (1, 2, 3) 49 | x = my_tuple[0] 50 | y = my_tuple[1] 51 | z = my_tuple[2] 52 | 53 | # We can do this! 54 | x, y, z = (1, 2, 3) 55 | print(x, y, z) 56 | ``` 57 | 58 | Because of this, it's actually common for Python functions to return a tuple as a data-type. We've actually be using examples of this already. 59 | 60 | Remember the items function when we are iterating through a dictionary? That was us unpacking a tuple. 61 | 62 | ```python 63 | for key, value in fruit_prices.items(): 64 | print(key) 65 | print(value) 66 | ``` 67 | 68 | ## Sets 69 | 70 | Sets are another very useful data-structure. They are unordered collections of items, and are mutable. Sets are useful for membership testing, removing duplicates, and mathematical operations like union, intersection, difference, and symmetric difference. 71 | 72 | Here is an example. You can create one with the curly braces. Similar to a dictionary—except with just the keys, and no associated value. The key ***is*** the value. 73 | 74 | ```python 75 | # Create a set. 76 | favorite_fruits = {"🍊", "🍒", "🍌", "🫐", "🍉"} 77 | ``` 78 | 79 | ```python 80 | # Create an empty set. 81 | favourite_vegetables = set() 82 | ``` 83 | 84 | Sets are a collection of *unique* elements. They are similar to lists or tuples, but with the major difference that all elements in a set are *unique*. **You can't have two of the same element inside a set.** 85 | 86 | Sets come with in-built functions to add or remove elements. And if you try to add something that's already in the set, then nothing will happen. 87 | 88 | ```python 89 | favorite_fruits = {"🍊", "🍒", "🍌", "🫐", "🍉"} 90 | 91 | favorite_fruits.remove("🍌") # Will be removed 92 | favorite_fruits.add("🥝") # Doesn't exist, will be added 93 | favorite_fruits.add("🍒") # Already exists, will be ignored 94 | 95 | print(favorite_fruits) 96 | # Output: {'🍒', '🍉', '🍊', '🥝', '🫐'} 97 | ``` 98 | 99 | So what are sets useful for? What problem does it solve for us? Well, let's look at some examples. 100 | 101 | ## Set Built-In Methods 102 | 103 | It's really easy to check if something belongs inside a set. It's also easy to check for ***overlaps or intersections*** with other sets. 104 | 105 | For instance, let's say I have a set of my favourite fruits. I can use this expression to check if a blueberry is part of my favourite fruits. And in this case, it is, so this expression will evaluate to True. 106 | 107 | ```python 108 | favorite_fruits = {"🍊", "🍒", "🍌", "🫐", "🍉"} 109 | 110 | print("🫐" in favorite_fruits) 111 | # True 112 | ``` 113 | 114 | Now let's say I have another set, of fruits that are red in color. And I want to find where the fruits there overlap with my favourite fruits. 115 | 116 | Or in other words, my favourite fruits which are also red. This is called an `intersection` of two sets, and I can use it like this: 117 | 118 | ```python 119 | favorite_fruits = {"🍊", "🍒", "🍌", "🫐", "🍉"} 120 | red_fruits = {"🍎", "🍓", "🍒", "🍉"} 121 | favourite_red_fruits = favorite_fruits.intersection(red_fruits) 122 | 123 | print(favourite_red_fruits) 124 | # {'🍒', '🍉'} 125 | ``` 126 | 127 | There's also the `union` method, which combines all elements in both sets. 128 | 129 | ```python 130 | # You can find the union (all members) of two sets. 131 | all_fruits = favorite_fruits.union(red_fruits) 132 | print(all_fruits) 133 | # {'🍒', '🍉', '🍎', '🍓', '🍊', '🍌', '🫐', '🍋'} 134 | ``` 135 | 136 | And finally there's also the difference method, which finds the elements that appear in either of the sets, but not both. 137 | 138 | ```python 139 | # You can find the difference (members in one set but not the other) of two sets. 140 | favorite_fruits_but_not_red = favorite_fruits.difference(red_fruits) 141 | print(favorite_fruits_but_not_red) 142 | # {'🍊', '🍌', '🫐', '🍋'} 143 | ``` 144 | 145 | # Coding Exercise 08: Tuples and Sets 146 | 147 | You are creating an application that helps your user find a restaurant according to their food preferences. 148 | 149 | You have a set of your user's favourite foods, and sets of food being served at each restaurant. **Find the restaurant that best matches your user's food preferences.** 150 | 151 | ```python 152 | food_preference = {"🍔", "🍕", "🍤"} 153 | 154 | restaurants = { 155 | "seafood_cove": {"🍤", "🍣", "🐟", "🦀"}, 156 | "hungry_jacks": {"🍔", "🍟", "🍦", "🍕"}, 157 | "potting_shed": {"🥦", "🥕", "🍞", "🥑"}, 158 | } 159 | 160 | # Result should be hungry_jacks with {'🍔', '🍕'} being matched. 161 | ``` 162 | 163 | Here you'll also need to apply the knowledge of dictionaries and loops that you learn from previous chapters! 164 | 165 | Once you've given it a go, check out the [solution here](./solution_08.py). 166 | -------------------------------------------------------------------------------- /08 - Tuples and Sets/solution_08.py: -------------------------------------------------------------------------------- 1 | """ 2 | You are creating an application that helps your user find a restaurant according to their food preferences. 3 | You have a set of your user's preferred foods, and sets of food being served at each restaurant. 4 | 5 | Find the restaurant that best matches your user's food preferences. 6 | """ 7 | 8 | food_preference = {"🍔", "🍕", "🍤", "🥦", "🥕"} 9 | 10 | restaurants = { 11 | "seafood_cove": {"🍤", "🍣", "🐟", "🦀"}, 12 | "hungry_jacks": {"🍔", "🍟", "🍦", "🍕"}, 13 | "potting_shed": {"🥦", "🥕", "🍞", "🥑"}, 14 | } 15 | 16 | # The best match is the restaurant with the most common foods. 17 | # Start with an empty set of best match foods and no best match restaurant. 18 | best_match_foods = set() 19 | best_match_restaurant = None 20 | 21 | # Loop through each restaurant and find the best match 22 | for restaurant_name, menu in restaurants.items(): 23 | # Find the common foods between the restaurant's menu and the user's food preference 24 | common_foods = food_preference.intersection(menu) 25 | 26 | # If the number of common foods is greater than the current best match, update the best match 27 | if len(common_foods) > len(best_match_foods): 28 | best_match_foods = common_foods 29 | best_match_restaurant = restaurant_name 30 | 31 | print( 32 | f"The best match is {best_match_restaurant} with {best_match_foods} matched foods." 33 | ) 34 | -------------------------------------------------------------------------------- /09 - Functions/README.md: -------------------------------------------------------------------------------- 1 | # 09: Functions 2 | 3 | In Python, a function is a block of code that can be reused multiple times. They can accept input (known as parameters or arguments). They can also "return" output too, which can be any type of data. 4 | 5 | To use a function, you can "call" a function by its name. So remember the `print()` statement we've been using? That is a "function" we've been using. Now let's learn how to create our own. 6 | 7 | ### Example: A Simple Function 8 | 9 | ```python 10 | def say_hello_to(name): 11 | greeting = f"Hello {name}!" 12 | return greeting 13 | 14 | message = say_hello_to("jack") 15 | print(message) 16 | ``` 17 | 18 | Let's break down the syntax. You can define a new function by using `def` keyword. And `say_hello_to` is the *name* of the function. You also have to have a pair of parentheses `()`, and then an argument (input) called `name`. 19 | 20 | ```python 21 | def say_hello_to(name): 22 | # The rest of the code [...] 23 | ``` 24 | 25 | Moving on to the function body — this is the block of code that will run when the function is used or executed, and it's one indent level in from the function definition. 26 | 27 | ## Using (Calling) a Function 28 | 29 | Just type the name of the function, along with the parentheses to tell Python that we want to run it. 30 | 31 | If you don't have the parentheses, it won't run. 32 | 33 | ```python 34 | def say_hello(): 35 | print("Hello Jack!") 36 | 37 | say_hello() # "Hello Jack!" 38 | say_hello() # "Hello Jack!" 39 | say_hello # This won't run. You need the (). 40 | ``` 41 | 42 | ## Passing Inputs (Arguments) to a Function 43 | 44 | What if we want to the function to say "hello" to different people? We can make the function accept an input. This is called a "parameter", or an "argument". 45 | 46 | The syntax for that is to just specify it in these brackets as part of the function definition. This will be the name of the argument, and you can call it whatever you want. I'm calling this one `name`. 47 | 48 | Now in the scope of the function, I can use it just like a normal variable. 49 | 50 | ```python 51 | def say_hello(name): 52 | print(f"Hello {name}!") 53 | ``` 54 | 55 | When I call this function now, I must pass an argument in as well, because the function is expecting it. 56 | 57 | ```python 58 | say_hello("Jack") # "Hello Jack!" 59 | say_hello("Alice") # "Hello Alice!" 60 | say_hello("Bob") # "Hello Bob!" 61 | ``` 62 | 63 | And each time this function runs, it'll receive one of these strings as the "name" argument, which is then accessible within the scope of the function. 64 | 65 | ## Returning Output 66 | 67 | Beyond the *scope* (indent level) of the function, you won't be able to access anything you create within a function — including the arguments. 68 | 69 | Anything new variable is created within the scope of the function will cease to exist beyond its scope. It's just there temporarily while the function executes. 70 | 71 | ```python 72 | def say_hello(name): 73 | print(f"Hello {name}!") 74 | 75 | print(name) # Error! 76 | ``` 77 | 78 | So what happens if I **don't** want to *print* this greeting, but instead, store it in a variable as a string, so I can maybe send it in an email later? 79 | 80 | ```python 81 | def say_hello(name): 82 | message = f"Hello {name}!" 83 | 84 | print(message) # Error! 85 | ``` 86 | 87 | It turns out functions also have a way to return output to you when you use it. You can do this with the `return` keyword like this. 88 | 89 | ```python 90 | def say_hello(name): 91 | message = f"Hello {name}!" 92 | return message 93 | 94 | jack_greeting = say_hello("jack") 95 | print(say_hello("jack")) 96 | ``` 97 | 98 | When the function reaches the return statement, it will immediately exit. It will *short-circuit* the function so if you have any more code after the return statement, it will not be executed. 99 | 100 | It will also "return" whatever value you put here. If you put nothing, it'll return `None`. 101 | 102 | ```python 103 | def say_hello(name): 104 | message = f"Hello {name}!" 105 | return message 106 | 107 | greeting = say_hello("jack") 108 | print(greeting) # None 109 | ``` 110 | 111 | So to summarise, you can use "return" to get an output from a function. This output can be stored in a variable, printed, or used in any way you want. 112 | 113 | This is helpful if you want your function to do some work, and use its output for something else (e.g. summing up a list of numbers). 114 | 115 | ## Positional Arguments 116 | 117 | We can also add more than one parameter to the function. In fact we can add as many as we want. Just put a comma in the brackets and add another argument. These are called positional arguments. 118 | 119 | ```python 120 | def say_hello(name, title): 121 | message = f"Hello {title} {name}!" 122 | print(message) 123 | return message 124 | ``` 125 | 126 | And again, to use it, just pass in the value you want when you call the function. 127 | 128 | ```python 129 | say_hello("Jack", "Mr") 130 | say_hello("Alice", "Ms") 131 | say_hello("Bob", "Dr") 132 | ``` 133 | 134 | The positional ordering of data passed to function matters, because it corresponds to the position of the arguments. 135 | 136 | ## Keyword Arguments 137 | 138 | It seems straightforward, but it cause bugs and confusion if you are working on a large project, and need to remember which positions things have to be in, especially if it's no obvious. 139 | 140 | That's why we can also specify arguments by using keywords. 141 | 142 | ```python 143 | say_hello((name="Jack", title="Mr") 144 | say_hello((name="Alice", title="Ms") 145 | say_hello((name="Bob", title="Dr") 146 | ``` 147 | 148 | The keywords simply correspond to the name of the function arguments that we defined in the function signature. 149 | 150 | We also still have the option to use keyword arguments positionally. 151 | 152 | ```python 153 | say_hello("Jack", "Mr") 154 | ``` 155 | 156 | ## Default and Optional Arguments 157 | 158 | Now, in a function like this, the `name` is an important piece of information to have. But the `title`? Maybe not so much. What if I wanted to make this argument optional? 159 | 160 | That's easy as well. All I have to do is just specify a default value for it in the function definition. 161 | 162 | ```python 163 | def say_hello(name, title=""): 164 | print(f"Hello {title} {name}!") 165 | ``` 166 | 167 | So here, if a title isn't provided to the function, then it's just going to default to an empty string. Now I can use only when I need to. 168 | 169 | ```python 170 | say_hello(name="Jack") 171 | say_hello(name="Alice") 172 | say_hello(name="Bob", title="Dr") 173 | ``` 174 | 175 | > 👉 If you want to use optional parameters, they must come after all the required parameters. So I can't swap these around, and have an optional title first, then a name. I will get an error. All the required arguments must come first. 176 | 177 | ## Variadic Arguments 178 | 179 | Sometimes we have to be able to accept an unknown number of arguments. To do this, we can use the `*` syntax. This will allow us to use the argument like a list. 180 | 181 | ```python 182 | def say_hello(*names): 183 | for name in names: 184 | print(f"Hello {name}!") 185 | 186 | say_hello("Jack") 187 | say_hello("Jack", "Alice") 188 | say_hello("Jack", "Alice", "Bob") 189 | ``` 190 | 191 | Here, we can now pass in as many names as we want, and the function will just loop over them and print out the greeting. 192 | 193 | The `*` syntax is also useful for *unpacking* lists and dictionaries. We can unpack the contents of a list, and then pass them into the function. 194 | 195 | ```python 196 | names = ["Jack", "Alice", "Bob"] 197 | say_hello(*names) 198 | # Same as typing say_hello("Jack", "Alice", "Bob") 199 | ``` 200 | 201 | > ⚠️ Be careful! Variadic arguments are the same as passing an actual list into a function! 202 | 203 | ```python 204 | # These are NOT equivalent! 205 | # This one receives 3 separate arguments. 206 | say_hello("Jack", "Alice", "Bob") 207 | 208 | # This one receives a single argument (which is a list). 209 | say_hello(["Jack", "Alice", "Bob"]) 210 | ``` 211 | 212 | ## Common Mistakes With Functions 213 | 214 | Can you see what's wrong with this piece of code? 215 | 216 | ```python 217 | def shout(message): 218 | message += "!!!!" 219 | 220 | hello = "Hello World" 221 | shout(hello) 222 | print(hello) 223 | ``` 224 | 225 | This function is supposed to add some exclamation mark to this message. Which it does, but when we print it here, it's doesn't have those exclamation marks added. Why not? 226 | 227 | Well, here's a hint. It all has to do with the ***mutability*** of data-types. 228 | 229 | Simply put, some data-types can change, and some cannot. All the primitive data-types (strings, integers, floats and booleans) are ***immutable***. 230 | 231 | So when we're adding the exclamation marks to this message variable here, it's only affected the message variable in the scope of this function. 232 | 233 | ```python 234 | message += "!!!!" 235 | ``` 236 | 237 | It doesn't affect the value of the original message itself. This is basically a new ***copy*** of the original message. That's why when we print the original "hello" message, it hasn't changed. 238 | 239 | To fix this up, we'll need to return the updated message. 240 | 241 | ```python 242 | def shout(message): 243 | message += "!!!!" 244 | return message 245 | 246 | hello = "Hello World" 247 | hello = shout(hello) 248 | print(hello) # Hello World!!!! 249 | ``` 250 | 251 | We can store this in a new variable, or we can re-use the same variable name by overwriting it. 252 | 253 | ## Using Mutable Datatypes with Functions 254 | 255 | Inversely, if you pass in a **mutable** data type into a function, like a list, you can actually change its contents within the function. 256 | 257 | You don't have to return it, because the argument acts as a *reference* to the real instance of that data. 258 | 259 | ```python 260 | def double_list_values(my_list): 261 | for i in range(len(my_list)): 262 | my_list[i] *= 2 263 | ``` 264 | 265 | Consider this example where we have a function to double all of the values in a list. Here we don't need to return the doubled list, we can update its values directly within the scope of the function. 266 | 267 | ```python 268 | original_list = [1, 2, 3] 269 | print(original_list) # [1, 2, 3] 270 | 271 | double_list_values(original_list) 272 | print(original_list) # [2, 4, 6] 273 | ``` 274 | 275 | And when we use a function to modify something outside the scope of the function, this is called a "side-effect". 276 | 277 | ## Abstraction 278 | 279 | The major advantage of functions is that they let us build useful *abstractions* on code that is otherwise not very intuitive to read. 280 | 281 | And if you hear the word "abstraction" in software engineering, it means to "hide away the complexity" of something from a user (and sometimes that user is yourself), and just expose the its key elements. 282 | 283 | Basically, it means to simplify something in a really useful way. To me, this is the core value of a function. 284 | 285 | ### Example of Abstraction 286 | 287 | Take a look at this formula. This represents something that is very common in day to day life. You may have seen it before! Can you tell what it is, or what it does? 288 | 289 | ``` 290 | x * (1 + y) ** z 291 | ``` 292 | 293 | What about if we turn these variable names into something meaningful? 294 | 295 | ```python 296 | principal * (1 + rate) ** years 297 | ``` 298 | 299 | Does this help clarify what the formula does? It's calculating the interest for a given principle amount, interest rate, and time period (like our exercise in Chapter 3). It's commonly used for a bank deposit or a home loan. 300 | 301 | ```python 302 | principal = 1000 303 | rate = 0.05 304 | years = 2 305 | interest = principal * (1 + rate) ** years 306 | ``` 307 | 308 | Now let's turn it into a function. 309 | 310 | ```python 311 | def calc_interest(principal, rate, years): 312 | return principal * (1 + rate) ** years 313 | ``` 314 | 315 | So that now we want to use it, it's just really straightforward. 316 | 317 | ```python 318 | new_value = calc_interest(principal=1000, rate=0.05, years=2) 319 | ``` 320 | 321 | We went from something that was cryptic to something that most humans will understand just by reading it, even if they don't know how to code. 322 | 323 | And I think one of the most important skills for a programmer to have is exactly being able to do this. It's not advanced mathematics or memorising a bunch of algorithms. 324 | 325 | But being able to turn complex ideas into simple abstractions, so that we can build more complex things from simple parts. 326 | 327 | ```python 328 | # Before 329 | x * (1 + y) ** z 330 | 331 | # After 332 | new_value = calc_interest(principal=1000, rate=0.05, years=2) 333 | ``` 334 | 335 | # Coding Exercise 09: Functions 336 | 337 | Write a function `apply_discount()` that takes two arguments: the original price and the discount percentage, and returns the discounted price. 338 | 339 | Additionally, If the discount is more than 100%, the new price should be fixed at 0 (and not negative). 340 | 341 | ```python 342 | # TODO: Implement the "apply_discount" function. 343 | 344 | # Test it like this... 345 | new_price = apply_discount(1000, percent=10) 346 | print(new_price) # Should be 900 347 | ``` 348 | 349 | Once you've given it a go, check out the [solution here](./solution_09.py). 350 | -------------------------------------------------------------------------------- /09 - Functions/solution_09.py: -------------------------------------------------------------------------------- 1 | """ 2 | Write a function `apply_discount()` that takes two arguments: 3 | the original price and the discount percentage, and returns the discounted price. 4 | """ 5 | 6 | 7 | def apply_discount(price, percent): 8 | if percent >= 100: 9 | # If discount is 100% or more, the price is free. 10 | return 0 11 | else: 12 | # Otherwise, calculate the discounted price. 13 | discount = price * percent / 100 14 | return price - discount 15 | 16 | 17 | new_price = apply_discount(1000, percent=10) 18 | print(new_price) # Should be 900 19 | 20 | new_price = apply_discount(900, percent=5) 21 | print(new_price) # Should be 855 22 | 23 | new_price = apply_discount(10, percent=200) 24 | print(new_price) # Should be 0 25 | -------------------------------------------------------------------------------- /10 - User Input/README.md: -------------------------------------------------------------------------------- 1 | # 10: User Input 2 | 3 | Ultimately, we write Python code to do useful things for people. So we need a way to get input data from the outside world somehow—whether that's a direct input they type into the console, a local file (like an Excel or PDF file), or over the internet. 4 | 5 | ### Ways to Get User Input 6 | 7 | - 👉 Direct User Input 8 | - 👉 Local Files 9 | - 👉 Over the Internet 10 | 11 | ## Direct User Input 12 | 13 | In our upcoming final project, we want to build a **personal expense tracking app**. We'll need users to enter the name and the amount of the expense. 14 | 15 | The easiest way to do that is using this in-built Python function: 16 | 17 | ``` 18 | input() 19 | ``` 20 | 21 | If you run this in the terminal, the Python script will stop at this statement and wait for the user to type something with their keyboard and press `Enter`. 22 | 23 | We can make it show a text prompt. by putting a string in there. 24 | 25 | ``` 26 | input("Enter your name: ") 27 | ``` 28 | 29 | To store the value that the user types in, we can use the assignment operator to set that value into a variable. 30 | 31 | ```python 32 | user_name = input("Enter your name: ") 33 | print(f"Your name is {user_name}"") 34 | ``` 35 | 36 | That's because this input statement is a built-in function in Python, and when the user types something into the terminal, the function will return (or evaluate to) the thing that the user typed. 37 | 38 | ### ⚠️ `input()` always returns a string! 39 | 40 | The result from using input is always going to be a string. Even if you enter a number. And if you try to use it directly as a number, like try to use arithmetic operators on it, it will either fail or give you strange results. 41 | 42 | ```python 43 | x = input("Enter a number: ") 44 | print(f"x is a {type(x)}") 45 | ``` 46 | 47 | But you can convert a string of digits into a number like this. And this is called "casting" - when you convert one type of data into another. 48 | 49 | ```python 50 | x = int("32") 51 | print(x * 100) 52 | ``` 53 | 54 | # Other Ways To Get User Input 55 | 56 | Although it is out of our scope for this tutorial series, let's take a quick look at various other ways we can get user input from Python. 57 | 58 | ## CLI Arguments 59 | 60 | It's possible to pass in arguments to the Python program when you run it from the command line. This is called "command line arguments". 61 | 62 | The arguments are stored in the `sys.argv` list and can be used in the program. 63 | 64 | ```python 65 | python main.py --verbose 66 | ``` 67 | 68 | ```python 69 | import sys 70 | 71 | if "--verbose" in sys.argv: 72 | print("Verbose mode is enabled!") 73 | ``` 74 | 75 | But if you were serious about adding arguments like this, I suggest using the built-in the [argparse](https://docs.python.org/3/library/argparse.html) library to do the heavy lifting. 76 | 77 | ## File IO 78 | 79 | It is possible to read input from a file as well, whether that's a PDF file, image file, CSV spreadsheet, or just a plain text file. 80 | 81 | ```python 82 | with open("input.txt", "r") as f: 83 | input_data = f.read() 84 | ``` 85 | 86 | We have to use this `with` keyword because when you "open" a file, it will stay open until you explicitly close it. And if you forget to close it, it can cause all kinds of problems later on. 87 | 88 | Using the `with` keyword creates something called a "context manager" for this, and it will automatically close the file for us when we are done using it (and our code exits its scope). 89 | 90 | To write to a file we can use the `open` function again with the `w` mode. 91 | 92 | ```python 93 | with open("output.txt", "w") as f: 94 | f.write("Hello World From Python!") 95 | ``` 96 | 97 | ## REST APIs 98 | 99 | Probably (by far) the most useful way to communicate with the world from Python is by having it talk to other apps over the internet. 100 | 101 | There's a lot of ways for this communication to happen, but the most common way is with REST APIs. This is by sending a structured message to an `http` endpoint and getting response back. 102 | 103 | Here's an example using GitHub's user API to get info about my user account. You can change it to your username if you have a GitHub profile. 104 | 105 | ```python 106 | import requests 107 | 108 | ENDPOINT = "https://api.github.com/users/pixegami" 109 | response = requests.get(ENDPOINT) 110 | data = response.json() 111 | 112 | print(data["name"]) # Jack 113 | print(data["location"]) # Sydney, Australia 114 | ``` 115 | 116 | --- 117 | 118 | # Coding Exercise 10: Input 119 | 120 | Write a program that prompts the user for their name and age, and save that information into a file called `user_info.txt`. 121 | 122 | ```python 123 | # TODO: Get the user's name and age. 124 | # name = ... 125 | # age = ... 126 | 127 | # TODO: Write them to a file called user_info.txt 128 | ``` 129 | 130 | Once you've given it a go, check out the [solution here](./solution_10.py). 131 | -------------------------------------------------------------------------------- /10 - User Input/solution_10.py: -------------------------------------------------------------------------------- 1 | """ 2 | Write a program that prompts the user 3 | for their name and age, and save that information 4 | into a file called `user_info.txt`. 5 | """ 6 | 7 | # Prompt user for input 8 | name = input("What is your name? ") 9 | age = input("What is your age? ") 10 | 11 | # Write to file 12 | with open("user_info.txt", "w") as f: 13 | f.write(f"{name} {age}\n") 14 | -------------------------------------------------------------------------------- /11 - Classes/README.md: -------------------------------------------------------------------------------- 1 | # 11: Classes 2 | 3 | In this chapter we're going to learn about "classes" in Python, and this is the last major concept we need to cover in the beginner series. 4 | 5 | So what are classes? One way to think about it, is that it is a blueprint for objects that we want to create. 6 | 7 | It's also a way of organising code — grouping the variables and functions that we've learnt about previously) into a cohesive unit, so that we all the things we care about in one place, for convenience. 8 | 9 | And it's also a form of abstraction. So if you recall, that means turning something complex and ambiguous into a something that's simple and useful to us. 10 | 11 | Let's look at an example. Say I'm building a food database for a supermarket. And I want to have information about each food product 12 | 13 | ```python 14 | class Product: 15 | 16 | def __init__(self, name, calories, price_per_kg): 17 | self.name = name 18 | self.calories = calories 19 | self.price_per_kg = price_per_kg 20 | 21 | ``` 22 | 23 | Now this class is the blueprint for the product. It's not referring to a *particular* product. It's just a way for us to specify what all products should have. 24 | 25 | Now here's how I can use a class to create a specific copy of a product. 26 | 27 | And a specific, concrete object created from a class is called an 'instance'. 28 | 29 | ### Instances 30 | 31 | ```python 32 | banana = Product("banana", 105, 1.0) 33 | tomato = Product("tomato", 22, 2.1) 34 | potato = Product("potato", 162, 0.9) 35 | ``` 36 | 37 | So here we say that these are instance of a product. And each instance will have it's own set of values. 38 | 39 | ```python 40 | print(banana.calories) # 105 41 | print(banana.tomato) # 22 42 | ``` 43 | 44 | ## Instance Methods 45 | 46 | So we've seen that each instance of a class can have its own set of variables. It can also have it's own *functions*. And when a function is part of a class, it's generally referred to as a "method" (by convention). 47 | 48 | It's no different from a function though—it more or less works the same way. 49 | 50 | Here's how to write one. 51 | 52 | ```python 53 | class Product: 54 | 55 | # ... 56 | 57 | def get_price(self, weight): 58 | return self.price_per_kg * weight 59 | ``` 60 | 61 | It's got to be in the scope of the class. And the first argument is always a "self" argument, just like these variables here. That's how we actually refer back to them and can manipulate them. 62 | 63 | ```python 64 | banana.get_price(0.2) 65 | tomato.get_price(0.5) 66 | ``` 67 | 68 | And when I'm using an instance method, it's receiving that instance as the "self" variable. 69 | 70 | ## Class Attributes 71 | 72 | Now it's also possible for me to have variables or functions that are not associated with any particular instance, but with the entire class itself. 73 | 74 | For example, if you have a class that deals with food nutrition, it might be helpful to store a constant that holds the conversion factor from calories to kilojoules. 75 | 76 | This is a constant that will never change and will be true for all instances of the class, so we could define it like this: 77 | 78 | ```python 79 | class Product: 80 | CALORIES_TO_KILOJOULES = 4.184 81 | # ... 82 | def get_kilojoules(self): 83 | return self.calories * Product.CALORIES_TO_KILOJOULES 84 | ``` 85 | 86 | In Python convention, we capitalise the names of variables that we don't expect to ever change or have new values once it is defined. And we call these variables "constants". 87 | 88 | ## Class Methods 89 | 90 | Now let's extend our food product class with a static method. 91 | 92 | A static method is a type of method that is bound to the class itself, not the instances of the class. 93 | 94 | For example, let's say that we wanted to create a static method on our `Product` class that is used to generate a unique id for new product instances. We would define the static method like this: 95 | 96 | ```python 97 | import uuid 98 | 99 | class Product: 100 | @staticmethod 101 | def generate_unique_id(): 102 | return uuid.uuid4() 103 | ``` 104 | 105 | Now we can call this static method on the class itself, like this: 106 | 107 | ```python 108 | product_id = Product.generate_unique_id() 109 | ``` 110 | 111 | We don't need to create an instance of the `Product` class in order to call this method — we can simply call it directly on the class itself. 112 | 113 | --- 114 | 115 | So you've just seen a specific example of how to use a class in Python. But you can use it in many more ways than this to represent things about the world. 116 | 117 | ## Uses of Classes 118 | 119 | - 👉 Character in a Game 120 | - 👉 Products in a Shop 121 | - 👉 Animals in a Zoo 122 | - 👉 Books in a Library 123 | 124 | ## Object Oriented Programming 125 | 126 | So that's been quick introduction about classes, how to use classes and how to create classes in Python. But it's a subject that goes much deeper than this. 127 | 128 | And if you're keen to learn more about how classes work, then I recommend looking up something called "Object Oriented Programming", which is a programming strategy that revolves heavily around the use of classes. 129 | 130 | However, I think what you've learnt here today is enough to get you going and solving problems. 131 | 132 | I also personally tend to use a functional style of programming myself, which doesn't rely using classes much. So that's why I've kept this chapter relatively lightweight. 133 | 134 | # Coding Exercise 11: Classes 135 | 136 | You're creating a game where the player will have to fight monsters. The game will feature different types of monsters, and each monster will have different stats and abilities. 137 | 138 | Create a class called "Monster" that will keep track of the following information: 139 | 140 | - ✏️ Health Points 141 | - ✏️ Attack Points 142 | - ✏️ Method to "Fight" 143 | 144 | The Monster class should also have a `fight()` method which will allow the monster to attack another monster. 145 | 146 | ```python 147 | class Monster: 148 | def __init__(self): 149 | pass 150 | 151 | def fight(monster): 152 | pass 153 | ``` 154 | 155 | When you've had a go, see the [solution here](./solution_11.py). -------------------------------------------------------------------------------- /11 - Classes/solution_11.py: -------------------------------------------------------------------------------- 1 | """ 2 | Create a class called "Monster" that will keep track 3 | of the following information: 4 | 5 | - Health Points 6 | - Attack Points 7 | - Method to "Fight" 8 | 9 | The Monster class should also have a `fight()` 10 | method which will allow the monster to attack another monster. 11 | """ 12 | 13 | 14 | class Monster: 15 | def __init__(self, name, hp, attack): 16 | self.name = name 17 | self.hp = hp 18 | self.attack = attack 19 | 20 | def fight(self, target): 21 | target.hp -= self.attack 22 | print(f"{self.name} attacked {target.name} for {self.attack} damage!") 23 | print(f"{target.name} has {target.hp} HP left.") 24 | 25 | 26 | # Test the code 27 | centaur = Monster("Centaur", 100, 10) 28 | goblin = Monster("Goblin", 50, 5) 29 | centaur.fight(goblin) 30 | goblin.fight(centaur) 31 | -------------------------------------------------------------------------------- /12 - Final Project/README.md: -------------------------------------------------------------------------------- 1 | # Final Project: Personal Expense Tracking App 2 | 3 | The goal of this project is create a Python app that lets you (as a user) track and categorize your monthly expenses and help you budget. 4 | 5 | The app will let users type their expense category and amount directly into the terminal. It'll then save (append) that expense entry to a file. And finally, read the file to summarise the expense totals for that month. 6 | 7 | It'll also tell the user how much they can spend for the rest of the month to stay in budget (which is a custom value decided by your app, e.g. `$2000`. 8 | 9 | ## 🎯 App Requirements 10 | 11 | 1. Ask the user to add an expense (name, category, amount) 12 | 2. Save expense entries to a `.csv` file. 13 | 3. Read the file to summarise the expense totals for that month 14 | 4. Show the user how much they can spend for the rest of the month (to stay in budget) 15 | 16 | ### ✨ Bonus 17 | 18 | 1. Show expenses by category 19 | 2. Give the user a rough estimate of how much they have left to spend per day 20 | 21 | ## 💡 Recommended Project Structure 22 | 23 | The final project will consist of 2 files: 24 | 25 | 1. `expense.py`: A class for creating and storing expense objects. 26 | 2. `expense_tracker.py`: The main application logic. 27 | 28 | ## ✅ Tasks 29 | 30 | 1. Create the `expense.py` class. 31 | 2. Create the `expense_tracker.py` file and write the main logic for the app. 32 | 3. Run the app to test it. 33 | 34 | ## Example Screenshot 35 | 36 | ![Expense Tracker App](expense_cli.png) 37 | 38 | You should also be able to import your generated CSV to apps like Excel and Google Drive. 39 | 40 | ![Expense Tracker Google Drive](expense_drive.png) 41 | -------------------------------------------------------------------------------- /12 - Final Project/expense.py: -------------------------------------------------------------------------------- 1 | class Expense: 2 | def __init__(self, name, category, amount) -> None: 3 | self.name = name 4 | self.category = category 5 | self.amount = amount 6 | 7 | def __repr__(self): 8 | return f"" 9 | -------------------------------------------------------------------------------- /12 - Final Project/expense_cli.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pixegami/python-for-beginners/29e3317ba73864a0ad6f11ac1d02429dda971d9a/12 - Final Project/expense_cli.png -------------------------------------------------------------------------------- /12 - Final Project/expense_drive.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pixegami/python-for-beginners/29e3317ba73864a0ad6f11ac1d02429dda971d9a/12 - Final Project/expense_drive.png -------------------------------------------------------------------------------- /12 - Final Project/expense_tracker.py: -------------------------------------------------------------------------------- 1 | from expense import Expense 2 | import calendar 3 | import datetime 4 | 5 | 6 | def main(): 7 | print(f"🎯 Running Expense Tracker!") 8 | expense_file_path = "expenses.csv" 9 | budget = 2000 10 | 11 | # Get user input for expense. 12 | expense = get_user_expense() 13 | 14 | # Write their expense to a file. 15 | save_expense_to_file(expense, expense_file_path) 16 | 17 | # Read file and summarize expenses. 18 | summarize_expenses(expense_file_path, budget) 19 | 20 | 21 | def get_user_expense(): 22 | print(f"🎯 Getting User Expense") 23 | expense_name = input("Enter expense name: ") 24 | expense_amount = float(input("Enter expense amount: ")) 25 | expense_categories = [ 26 | "🍔 Food", 27 | "🏠 Home", 28 | "💼 Work", 29 | "🎉 Fun", 30 | "✨ Misc", 31 | ] 32 | 33 | while True: 34 | print("Select a category: ") 35 | for i, category_name in enumerate(expense_categories): 36 | print(f" {i + 1}. {category_name}") 37 | 38 | value_range = f"[1 - {len(expense_categories)}]" 39 | selected_index = int(input(f"Enter a category number {value_range}: ")) - 1 40 | 41 | if selected_index in range(len(expense_categories)): 42 | selected_category = expense_categories[selected_index] 43 | new_expense = Expense( 44 | name=expense_name, category=selected_category, amount=expense_amount 45 | ) 46 | return new_expense 47 | else: 48 | print("Invalid category. Please try again!") 49 | 50 | 51 | def save_expense_to_file(expense: Expense, expense_file_path): 52 | print(f"🎯 Saving User Expense: {expense} to {expense_file_path}") 53 | with open(expense_file_path, "a") as f: 54 | f.write(f"{expense.name},{expense.amount},{expense.category}\n") 55 | 56 | 57 | def summarize_expenses(expense_file_path, budget): 58 | print(f"🎯 Summarizing User Expense") 59 | expenses: list[Expense] = [] 60 | with open(expense_file_path, "r") as f: 61 | lines = f.readlines() 62 | for line in lines: 63 | expense_name, expense_amount, expense_category = line.strip().split(",") 64 | line_expense = Expense( 65 | name=expense_name, 66 | amount=float(expense_amount), 67 | category=expense_category, 68 | ) 69 | expenses.append(line_expense) 70 | 71 | amount_by_category = {} 72 | for expense in expenses: 73 | key = expense.category 74 | if key in amount_by_category: 75 | amount_by_category[key] += expense.amount 76 | else: 77 | amount_by_category[key] = expense.amount 78 | 79 | print("Expenses By Category 📈:") 80 | for key, amount in amount_by_category.items(): 81 | print(f" {key}: ${amount:.2f}") 82 | 83 | total_spent = sum([x.amount for x in expenses]) 84 | print(f"💵 Total Spent: ${total_spent:.2f}") 85 | 86 | remaining_budget = budget - total_spent 87 | print(f"✅ Budget Remaining: ${remaining_budget:.2f}") 88 | 89 | now = datetime.datetime.now() 90 | days_in_month = calendar.monthrange(now.year, now.month)[1] 91 | remaining_days = days_in_month - now.day 92 | 93 | daily_budget = remaining_budget / remaining_days 94 | print(green(f"👉 Budget Per Day: ${daily_budget:.2f}")) 95 | 96 | 97 | def green(text): 98 | return f"\033[92m{text}\033[0m" 99 | 100 | 101 | if __name__ == "__main__": 102 | main() 103 | -------------------------------------------------------------------------------- /12 - Final Project/expenses.csv: -------------------------------------------------------------------------------- 1 | burger,12.0,🍔 Food 2 | sushi,20.0,🍔 Food 3 | electricity,120.0,🏠 Home 4 | gas,70.0,💼 Work 5 | coffee,5.0,✨ Misc 6 | haircut,20.0,✨ Misc 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Python For Beginners 2 | 3 | ![Python For Beginners](/images/learn-python-preview.png) 4 | 5 | A short Python course for complete beginners who want to learn to code in [Python](https://www.python.org/). 6 | This repository contains the code examples for the course. 7 | 8 | ## Table of Contents 9 | 10 | 1. [Installation](./01%20-%20Install%20and%20Run%20Python/) 11 | 2. [Variables and Datatypes](./02%20-%20Variables%20and%20Datatypes/) 12 | 3. [Operators](./03%20-%20Operators/) 13 | 4. [Conditions](./04%20-%20Conditions/) 14 | 5. [Lists](./05%20-%20Lists/) 15 | 6. [Loops](./06%20-%20Loops/) 16 | 7. [Dictionaries](./07%20-%20Dictionaries/) 17 | 8. [Tuples and Sets](./08%20-%20Tuples%20and%20Sets/) 18 | 9. [Functions](./09%20-%20Functions/) 19 | 10. [User Input](./10%20-%20User%20Input/) 20 | 11. [Classes](./11%20-%20Classes/) 21 | 12. [Final Project](./12%20-%20Final%20Project/) 22 | 23 | ## Related Videos 24 | 25 | - 🎯 [Why You Should Learn Python](https://youtu.be/o_GBFsquZuI) 26 | - 🎯 [Python Advanced Learning Road Map](https://youtu.be/c1tgZX2IGqw) 27 | 28 | ## Need Help? 29 | 30 | If you have any questions or need help with anything, please feel free to create an "Issue" on this page or reach me via my [YouTube](https://www.youtube.com/c/pixegami) or Twitter [@pixegami](https://twitter.com/pixegami). 31 | 32 | --- 33 | 34 | by Jack (Pixegami) | [YouTube](https://www.youtube.com/c/pixegami) 35 | -------------------------------------------------------------------------------- /images/learn-python-preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pixegami/python-for-beginners/29e3317ba73864a0ad6f11ac1d02429dda971d9a/images/learn-python-preview.png --------------------------------------------------------------------------------