Key takeaways 1. A function is a block of code that performs a specific task when the function is called (invoked). You can use functions to make your code reusable, better organized, and more readable. Functions can have parameters and return values. 2. There are at least four basic types of functions in Python: built-in functions which are an integral part of Python (such as the print() function). You can see a complete list of Python built-in functions at https://docs.python.org/3/library/functions.html. the ones that come from pre-installed modules (you'll learn about them in the Python Essentials 2 course) user-defined functions which are written by users for users - you can write your own functions and use them freely in your code, the lambda functions (you'll learn about them in the Python Essentials 2 course.) 3. You can define your own function using the def keyword and the following syntax: def your_function(optional parameters): # the body of the function You can define a function which doesn't take any arguments, e.g.: def message(): # defining a function print("Hello") # body of the function message() # calling the function You can define a function which takes arguments, too, just like the one-parameter function below: def hello(name): # defining a function print("Hello,", name) # body of the function name = input("Enter your name: ") hello(name) # calling the function We'll tell you more about parametrized functions in the next section. Don't worry. Key takeaways 1. You can pass information to functions by using parameters. Your functions can have as many parameters as you need. An example of a one-parameter function: def hi(name): print("Hi,", name) hi("Greg") An example of a two-parameter function: def hi_all(name_1, name_2): print("Hi,", name_2) print("Hi,", name_1) hi_all("Sebastian", "Konrad") An example of a three-parameter function: def address(street, city, postal_code): print("Your address is:", street, "St.,", city, postal_code) s = input("Street: ") p_c = input("Postal Code: ") c = input("City: ") address(s, c, p_c) 2. You can pass arguments to a function using the following techniques: positional argument passing in which the order of arguments passed matters (Ex. 1), keyword (named) argument passing in which the order of arguments passed doesn't matter (Ex. 2), a mix of positional and keyword argument passing (Ex. 3). Ex. 1 def subtra(a, b): print(a - b) subtra(5, 2) # outputs: 3 subtra(2, 5) # outputs: -3 Ex. 2 def subtra(a, b): print(a - b) subtra(a=5, b=2) # outputs: 3 subtra(b=2, a=5) # outputs: 3 Ex. 3 def subtra(a, b): print(a - b) subtra(5, b=2) # outputs: 3 subtra(5, 2) # outputs: 3 It's important to remember that positional arguments mustn't follow keyword arguments. That's why if you try to run the following snippet: def subtra(a, b): print(a - b) subtra(5, b=2) # outputs: 3 subtra(a=5, 2) # Syntax Error Python will not let you do it by signalling a SyntaxError. Days of Year month_days = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] def is_year_leap(year): if (year % 4) == 0: if (year % 100) == 0: if (year % 400) == 0: return True # Divisible by 400, it's a leap year else: return False # Divisible by 100 but not 400, not a leap year else: return True # Divisible by 4 but not 100, it's a leap year else: return False # Not divisible by 4, not a leap year def days_in_month(year, month): # Handle leap year for February if is_year_leap(year) and month == 2: return 29 else: return month_days[month - 1] # Adjusting for 0-based index def day_of_year(year, month, day): total_days = 0 # Add the days of all the previous months for i in range(month - 1): total_days += days_in_month(year, i + 1) # Add the days of the current month total_days += day return total_days print(day_of_year(2000, 12, 31)) Prime Number: def is_prime(num): for i in range(2, int(num ** 0.5) + 1): if num % i == 0: return False # Not prime if divisible return True # Prime if no divisors found for i in range(1, 20): if is_prime(i + 1): print(i + 1, end=" ") print() Key takeaways 1. You can use the return keyword to tell a function to return some value. The return statement exits the function, e.g.: def multiply(a, b): return a * b print(multiply(3, 4)) # outputs: 12 def multiply(a, b): return print(multiply(3, 4)) # outputs: None 2. The result of a function can be easily assigned to a variable, e.g.: def wishes(): return "Happy Birthday!" w = wishes() print(w) # outputs: Happy Birthday! Look at the difference in output in the following two examples: # Example 1 def wishes(): print("My Wishes") return "Happy Birthday" wishes() # outputs: My Wishes # Example 2 def wishes(): print("My Wishes") return "Happy Birthday" print(wishes()) # outputs: My Wishes # Happy Birthday 3. You can use a list as a function's argument, e.g.: def hi_everybody(my_list): for name in my_list: print("Hi,", name) hi_everybody(["Adam", "John", "Lucy"]) 4. A list can be a function result, too, e.g.: def create_list(n): my_list = [] for i in range(n): my_list.append(i) return my_list print(create_list(5)) Key takeaways 1. A variable that exists outside a function has a scope inside the function body (Example 1) unless the function defines a variable of the same name (Example 2, and Example 3), e.g.: Example 1: var = 2 def mult_by_var(x): return x * var print(mult_by_var(7)) # outputs: 14 Example 2: def mult(x): var = 5 return x * var print(mult(7)) # outputs: 35 Example 3: def mult(x): var = 7 return x * var var = 3 print(mult(7)) # outputs: 49 2. A variable that exists inside a function has a scope inside the function body (Example 4), e.g.: Example 4: def adding(x): var = 7 return x + var print(adding(4)) # outputs: 11 print(var) # NameError 3. You can use the global keyword followed by a variable name to make the variable's scope global, e.g.: var = 2 print(var) # outputs: 2 def return_var(): global var var = 5 return var print(return_var()) # outputs: 5 print(var) # outputs: 5 def fib(n): if n < 1: return None if n < 3: return 1 elem_1 = elem_2 = 1 the_sum = 0 for i in range(3, n + 1): the_sum = elem_1 + elem_2 elem_1, elem_2 = elem_2, the_sum return the_sum for n in range(1, 10): # testing print(n, "->", fib(n)) List of contents Welcome to PE1 Back Welcome to PE1 About the course Back About the course About PE1 Syllabus | Why learn Python? Applications for Python Earn PCEP certification 0.0.0.5 Buy the Python Essentials 1 course in book format Module 1: Introduction to Python and computer programming Back Module 1: Introduction to Python and computer programming 1.1. Introduction to programming with Python Back 1.1. Introduction to programming with Python Python Essentials 1 - Module 1 1.1.1.1 Programming - absolute basics 1.1.1.2 Programming - absolute basics 1.1.1.3 Programming - absolute basics 1.1.1.4 Programming - absolute basics | Compilation vs. interpretation 1.1.1.5 Programming - absolute basics | Compilation vs. interpretation 1.1.1.6 Programming - absolute basics | Compilation vs. interpretation 1.1.2.1 Python - a tool, not a reptile 1.1.2.2 Python - a tool, not a reptile 1.1.2.3 Python - a tool, not a reptile | Why Python? 1.1.2.4 Python - a tool, not a reptile | Why Python, why not? 1.1.3.1 Python 2 vs. Python 3 1.1.3.2 There is more than one Python: CPython and Cython 1.1.3.3 There is more than one Python: Jython, PyPy, and RPython 1.2. Downloading and installing Python Back 1.2. Downloading and installing Python 1.2.1.1 Begin your Python journey 1.2.1.2 Begin your Python journey | Downloading and installing Python 1.2.1.3 Begin your Python journey 1.2.1.4 Begin your Python journey 1.2.1.5 Begin your Python journey 1.2.1.6 Begin your Python journey 1.2.1.7 Begin your Python journey 1.2.1.8 Begin your Python journey 1.2.1.9 Module Completion 1.3.1 PE1 -- Module 1 Quiz Quiz Back 1.3.1 PE1 -- Module 1 Quiz PE1 -- Module 1 Quiz Quiz 1.4.1 PE1 -- Module 1 Test Test Back 1.4.1 PE1 -- Module 1 Test PE1 -- Module 1 Test Test Module 2: Data types, variables, basic I/O operations, and basic operators Back Module 2: Data types, variables, basic I/O operations, and basic operators 2.1. Hello, world! Back 2.1. Hello, world! Python Essentials 1: Module 2 2.1.1.1 Your very first program 2.1.1.2 Your first program 2.1.1.3 Your first program 2.1.1.4 Your first program 2.1.1.5 Your first program 2.1.1.6 LAB: The print() function Lab 2.1.1.7 Your first program 2.1.1.8 Your very first program 2.1.1.9 Your very first program 2.1.1.10 Your very first program 2.1.1.11 Your very first program 2.1.1.12 Your very first program 2.1.1.13 Your very first program 2.1.1.14 Your very first program 2.1.1.15 Your very first program 2.1.1.16 Your very first program 2.1.1.17 Your very first program 2.1.1.18 LAB: The print() function Lab 2.1.1.19 LAB: Formatting the output Lab 2.1.1.20 SECTION SUMMARY 2.2. Python literals Back 2.2. Python literals 2.2.1.1 Python literals 2.2.1.2 Python literals 2.2.1.3 Python literals 2.2.1.4 Python literals 2.2.1.5 Python literals 2.2.1.6 Python literals 2.2.1.7 Python literals 2.2.1.8 Python literals 2.2.1.9 Python literals 2.2.1.10 Python literals 2.2.1.11 LAB: Python literals - strings Lab 2.2.1.12 SECTION SUMMARY 2.3. Arithmetic operators and the hierarchy of priorities Back 2.3. Arithmetic operators and the hierarchy of priorities 2.3.1.1 Operators - data manipulation tools 2.3.1.2 Operators - data manipulation tools 2.3.1.3 Operators - data manipulation tools 2.3.1.4 Operators - data manipulation tools 2.3.1.5 Operators - data manipulation tools 2.3.1.6 Operators - data manipulation tools 2.3.1.7 Operators - data manipulation tools 2.3.1.8 Operators - data manipulation tools 2.3.1.9 Operators ‒ data manipulation tools 2.3.1.10 SECTION SUMMARY 2.4. Variables Back 2.4. Variables 2.4.1.1 Variables - data-shaped boxes 2.4.1.2 Variables - data-shaped boxes 2.4.1.3 Variables - data-shaped boxes 2.4.1.4 Variables - data-shaped boxes 2.4.1.5 Variables - data-shaped boxes 2.4.1.6 Variables - data-shaped boxes 2.4.1.7 LAB: Variables Lab 2.4.1.8 Variables - data-shaped boxes 2.4.1.9 LAB: Variables: a simple converter Lab 2.4.1.10 LAB: Operators and expressions Lab 2.4.1.11 SECTION SUMMARY 2.5. Comments Back 2.5. Comments 2.5.1.1 A comment on comments 2.5.1.2 LAB: Comments Lab 2.5.1.3 SECTION SUMMARY 2.6. The input() function and string operators Back 2.6. The input() function and string operators 2.6.1.1 How to talk to a computer 2.6.1.2 How to talk to a computer 2.6.1.3 How to talk to a computer 2.6.1.4 How to talk to a computer 2.6.1.5 How to talk to a computer 2.6.1.6 How to talk to a computer: string operators 2.6.1.7 How to talk to a computer: string operators 2.6.1.8 How to talk to a computer: string operators 2.6.1.9 LAB: Simple input and output Lab 2.6.1.10 LAB: Operators and expressions Lab 2.6.1.11 LAB: Operators and expressions Lab 2.6.1.12 SECTION SUMMARY 2.6.1.13 Module Completion 2.7.1 PE1 -- Module 2 Quiz Quiz Back 2.7.1 PE1 -- Module 2 Quiz PE1 -- Module 2 Quiz Quiz 2.8.1 PE1 -- Module 2 Test Test Back 2.8.1 PE1 -- Module 2 Test PE1 -- Module 2 Test Test Module 3: Boolean values, conditional execution, loops, lists and list processing, logical and bitwise operations Back Module 3: Boolean values, conditional execution, loops, lists and list processing, logical and bitwise operations 3.1. Comparison operators and conditional execution Back 3.1. Comparison operators and conditional execution Python Essentials 1: Module 3 3.1.1.1 Making decisions in Python 3.1.1.2 Making decisions in Python 3.1.1.3 Making decisions in Python 3.1.1.4 LAB: Questions and answers Lab 3.1.1.5 Making decisions in Python 3.1.1.6 Making decisions in Python 3.1.1.7 Making decisions in Python 3.1.1.8 Making decisions in Python 3.1.1.9 Making decisions in Python 3.1.1.10 LAB: Comparison operators and conditional execution Lab 3.1.1.11 LAB: Essentials of the if-else statement Lab 3.1.1.12 LAB: Essentials of the if-elif-else statement Lab 3.1.1.13 SECTION SUMMARY (1/2) 3.1.1.14 SECTION SUMMARY (2/2) 3.2. Loops Back 3.2. Loops 3.2.1.1 Loops in Python | while 3.2.1.2 Loops in Python | while 3.2.1.3 LAB: Essentials of the while loop - Guess the secret number Lab 3.2.1.4 Loops in Python | for 3.2.1.5 Loops in Python | for 3.2.1.6 LAB: Essentials of the for loop – counting mississippily Lab 3.2.1.7 Loop control in Python | break and continue 3.2.1.8 Loop control in Python | break and continue 3.2.1.9 LAB: The break statement - Stuck in a loop Lab 3.2.1.10 LAB: The continue statement - the Ugly Vowel Eater Lab 3.2.1.11 LAB: The continue statement - the Pretty Vowel Eater Lab 3.2.1.12 Python loops | else 3.2.1.13 Python loops | else 3.2.1.14 LAB: Essentials of the while loop Lab 3.2.1.15 LAB: Collatz's hypothesis Lab 3.2.1.16 SECTION SUMMARY (1/2) 3.2.1.17 SECTION SUMMARY (2/2) 3.3. Logic and bit operations in Python Back 3.3. Logic and bit operations in Python 3.3.1.1 Logic and bit operations in Python | and, or, not 3.3.1.2 Logic and bit operations in Python | and, or, not 3.3.1.3 Logic and bit operations in Python 3.3.1.4 Logic and bit operations in Python | Bitwise operators 3.3.1.5 Logic and bit operations in Python | Bit shifting 3.3.1.6 SECTION SUMMARY 3.4. Lists Back 3.4. Lists 3.4.1.1 Lists - collections of data 3.4.1.2 Lists - collections of data | Indexing 3.4.1.3 Lists - collections of data | Indexing 3.4.1.4 Lists - collections of data | Operations on lists 3.4.1.5 Lists - collections of data | Operations on lists 3.4.1.6 LAB: The basics of lists Lab 3.4.1.7 Lists - collections of data | Functions and methods 3.4.1.8 Lists - collections of data | list methods 3.4.1.9 Lists - collections of data | list methods 3.4.1.10 Lists - collections of data | lists and loops 3.4.1.11 Lists - collections of data | lists and loops 3.4.1.12 Lists ‒ collections of data | lists and loops 3.4.1.13 LAB: The basics of lists - the Beatles Lab 3.4.1.14 SECTION SUMMARY 3.5. Sorting simple lists Back 3.5. Sorting simple lists 3.5.1.1 Sorting simple lists - the bubble sort algorithm 3.5.1.2 Sorting simple lists - the bubble sort algorithm 3.5.1.3 Sorting simple lists - the bubble sort algorithm 3.5.1.4 SECTION SUMMARY 3.6. List processing Back 3.6. List processing 3.6.1.1 Operations on lists 3.6.1.2 Operations on lists | slices 3.6.1.3 Operations on lists | slices 3.6.1.4 Operations on lists | slices 3.6.1.5 Operations on lists | slices, del 3.6.1.6 Operations on lists | in, not in 3.6.1.7 Lists - more details 3.6.1.8 Lists - more details 3.6.1.9 LAB: Operating with lists - basics Lab 3.6.1.10 SECTION SUMMARY 3.7. Multidimensional arrays Back 3.7. Multidimensional arrays 3.7.1.1 Lists in advanced applications 3.7.1.2 Lists in advanced applications | Arrays 3.7.1.3 Lists in advanced applications | Arrays 3.7.1.4 Lists in advanced applications | Arrays 3.7.1.5 Lists in advanced applications | Arrays 3.7.1.6 SECTION SUMMARY 3.7.1.7 Module Completion 3.8.1 PE1 -- Module 3 Quiz Quiz Back 3.8.1 PE1 -- Module 3 Quiz PE1 -- Module 3 Quiz Quiz 3.9.1 PE1 -- Module 3 Test Test Back 3.9.1 PE1 -- Module 3 Test PE1 -- Module 3 Test Test Module 4: Functions, tuples, dictionaries, data processing, and exceptions Now Back Module 4: Functions, tuples, dictionaries, data processing, and exceptions 4.1. Functions Back 4.1. Functions Python Essentials 1: Module 4 4.1.1.1 Functions 4.1.1.2 Functions 4.1.1.3 Writing functions 4.1.1.4 Writing functions 4.1.1.5 Functions 4.1.1.6 SECTION SUMMARY 4.2. Function parameters and argument passing Back 4.2. Function parameters and argument passing 4.2.1.1 How functions communicate with their environment 4.2.1.2 How functions communicate with their environment 4.2.1.3 How functions communicate with their environment 4.2.1.4 How functions communicate with their environment 4.2.1.5 How functions communicate with their environment 4.2.1.6 How functions communicate with their environment 4.2.1.7 How functions communicate with their environment 4.2.1.8 SECTION SUMMARY 4.3. Returning results from functions Back 4.3. Returning results from functions 4.3.1.1 Returning a result from a function 4.3.1.2 Returning a result from a function 4.3.1.3 Returning a result from a function 4.3.1.4 Returning a result from a function 4.3.1.5 Returning a result from a function 4.3.1.6 LAB: A leap year: writing your own functions Lab 4.3.1.7 LAB: How many days: writing and using your own functions Lab 4.3.1.8 LAB: Day of the year: writing and using your own functions Lab 4.3.1.9 LAB: Prime numbers - how to find them Lab 4.3.1.10 LAB: Converting fuel consumption Lab 4.3.1.11 SECTION SUMMARY 4.4. Functions and scopes Back 4.4. Functions and scopes 4.4.1.1 Scopes in Python 4.4.1.2 Scopes in Python 4.4.1.3 Scopes in Python | global 4.4.1.4 Scopes in Python 4.4.1.5 SECTION SUMMARY 4.5. Creating simple functions Now Back 4.5. Creating simple functions 4.5.1.1 Creating functions | two-parameter functions 4.5.1.2 Creating functions | two-parameter functions 4.5.1.3 Creating functions | three-parameter functions 4.5.1.4 Creating functions | testing triangles 4.5.1.5 Creating functions | right-angle triangles 4.5.1.6 Creating functions: factorials 4.5.1.7 Creating functions | Fibonacci numbers 4.5.1.8 Creating functions | recursion Now 4.5.1.9 SECTION SUMMARY 4.6. Tuples and dictionaries Back 4.6. Tuples and dictionaries 4.6.1.1 Tuples and dictionaries 4.6.1.2 Tuples and dictionaries 4.6.1.3 Tuples and dictionaries 4.6.1.4 Tuples and dictionaries 4.6.1.5 Tuples and dictionaries 4.6.1.6 Tuples and dictionaries | methods 4.6.1.7 Tuples and dictionaries | methods 4.6.1.8 Tuples and dictionaries 4.6.1.9 Tuples and dictionaries working together 4.6.1.10 SECTION SUMMARY (1/3) 4.6.1.11 SECTION SUMMARY (2/3) 4.6.1.12 SECTION SUMMARY (3/3) 4.7.1 Exceptions Back 4.7.1 Exceptions 4.7.1.1 Exceptions 4.7.1.2 Exceptions 4.7.1.3 Exceptions (try-except) 4.7.1.4 Exceptions 4.7.1.5 Exceptions (two exceptions) 4.7.1.6 Exceptions | Deafult exceptions 4.7.1.7 Exceptions 4.7.1.8 Testing and debugging 4.7.1.9 Testing and debugging 4.7.1.10 Testing: bug vs. debug 4.7.1.11 print debugging 4.7.1.12 Testing and debugging – tips 4.7.1.13 SECTION SUMMARY 4.7.2.1 PROJECT: Tic-Tac-Toe Lab 4.7.2.2 Module Completion 4.8.1 PE1 -- Module 4 Quiz Quiz Back 4.8.1 PE1 -- Module 4 Quiz PE1 -- Module 4 Quiz Quiz 4.9.1 PE1 -- Module 4 Test Test Back 4.9.1 PE1 -- Module 4 Test PE1 -- Module 4 Test Test Course completion Back Course completion Python Essentials 1 Completed Back Python Essentials 1 Completed Python Essentials 1 Completed PE1 -- Summary Test Test Earn PCEP certification Earn PCEP certification Congratulations Some simple functions: recursion There's one more thing we want to show you to make everything complete - it's recursion. This term may describe many different concepts, but one of them is especially interesting - the one referring to computer programming. In this field, recursion is a technique where a function invokes itself. These two cases seem to be the best to illustrate the phenomenon - factorials and Fibonacci numbers. Especially the latter. The Fibonacci numbers definition is a clear example of recursion. We already told you that: Fibi = Fibi-1 + Fibi-2 The definition of the ith number refers to the i-1 number, and so on, till you reach the first two. Can it be used in the code? Yes, it can. It can also make the code shorter and clearer. The second version of our fib() function makes direct use of this definition: def fib(n): if n < 1: return None if n < 3: return 1 return fib(n - 1) + fib(n - 2) The code is much clearer now. But is it really safe? Does it entail any risk? Yes, there is a little risk indeed. If you forget to consider the conditions which can stop the chain of recursive invocations, the program may enter an infinite loop. You have to be careful. The factorial has a second, recursive side too. Look: n! = 1 × 2 × 3 × ... × n-1 × n It's obvious that: 1 × 2 × 3 × ... × n-1 = (n-1)! So, finally, the result is: n! = (n-1)! × n This is in fact a ready recipe for our new solution. Here it is: def factorial_function(n): if n < 0: return None if n < 2: return 1 return n * factorial_function(n - 1) Does it work? Yes, it does. Try it for yourself. Our short functional journey is almost over. The next section will take care of two curious Python data types: tuples and dictionaries. def fib(n): if n < 1: return None if n < 3: return 1 return fib(n - 1) + fib(n - 2) def factorial_function(n): if n < 0: return None if n < 2: return 1 return n * factorial_function(n - 1) Key takeaways 1. A function can call other functions or even itself. When a function calls itself, this situation is known as recursion, and the function which calls itself and contains a specified termination condition (i.e., the base case - a condition which doesn't tell the function to make any further calls to that function) is called a recursive function. 2. You can use recursive functions in Python to write clean, elegant code, and divide it into smaller, organized chunks. On the other hand, you need to be very careful as it might be easy to make a mistake and create a function which never terminates. You also need to remember that recursive calls consume a lot of memory, and therefore may sometimes be inefficient. When using recursion, you need to take all its advantages and disadvantages into consideration. The factorial function is a classic example of how the concept of recursion can be put in practice: # Recursive implementation of the factorial function. def factorial(n): if n == 1: # The base case (termination condition.) return 1 else: return n * factorial(n - 1) print(factorial(4)) # 4 * 3 * 2 * 1 = 24 dictionary = {"cat": "chat", "dog": "chien", "horse": "cheval"} words = ['cat', 'lion', 'horse'] for word in words: if word in dictionary: print(word, "->", dictionary[word]) else: print(word, "is not in dictionary") How to use a dictionary? If you want to get any of the values, you have to deliver a valid key value: print(dictionary['cat']) print(phone_numbers['Suzy']) Getting a dictionary's value resembles indexing, especially thanks to the brackets surrounding the key's value. Note: if the key is a string, you have to specify it as a string; keys are case-sensitive: 'Suzy' is something different from 'suzy'. The snippet outputs two lines of text: chat 22657854310 output And now the most important news: you mustn't use a non-existent key. Trying something like this: print(phone_numbers['president']) will cause a runtime error. Try to do it. Fortunately, there's a simple way to avoid such a situation. The in operator, together with its companion, not in, can salvage this situation. The following code safely searches for some French words: dictionary = {"cat": "chat", "dog": "chien", "horse": "cheval"} words = ['cat', 'lion', 'horse'] for word in words: if word in dictionary: print(word, "->", dictionary[word]) else: print(word, "is not in dictionary") The code's output looks as follows: cat -> chat lion is not in dictionary horse -> cheval output NOTE When you write a big or lengthy expression, it may be a good idea to keep it vertically aligned. This is how you can make your code more readable and more programmer-friendly, e.g.: # Example 1: dictionary = { "cat": "chat", "dog": "chien", "horse": "cheval" } # Example 2: phone_numbers = {'boss': 5551234567, 'Suzy': 22657854310 } This kind of formatting is called a hanging indent. Key takeaways: tuples 1. Tuples are ordered and unchangeable (immutable) collections of data. They can be thought of as immutable lists. They are written in round brackets: my_tuple = (1, 2, True, "a string", (3, 4), [5, 6], None) print(my_tuple) my_list = [1, 2, True, "a string", (3, 4), [5, 6], None] print(my_list) Each tuple element may be of a different type (i.e., integers, strings, booleans, etc.). What is more, tuples can contain other tuples or lists (and the other way round). 2. You can create an empty tuple like this: empty_tuple = () print(type(empty_tuple)) # outputs: 3. A one-element tuple may be created as follows: one_elem_tuple_1 = ("one", ) # Brackets and a comma. one_elem_tuple_2 = "one", # No brackets, just a comma. If you remove the comma, you will tell Python to create a variable, not a tuple: my_tuple_1 = 1, print(type(my_tuple_1)) # outputs: my_tuple_2 = 1 # This is not a tuple. print(type(my_tuple_2)) # outputs: 4. You can access tuple elements by indexing them: my_tuple = (1, 2.0, "string", [3, 4], (5, ), True) print(my_tuple[3]) # outputs: [3, 4] 5. Tuples are immutable, which means you cannot change their elements (you cannot append tuples, or modify, or remove tuple elements). The following snippet will cause an exception: my_tuple = (1, 2.0, "string", [3, 4], (5, ), True) my_tuple[2] = "guitar" # The TypeError exception will be raised. However, you can delete a tuple as a whole: my_tuple = 1, 2, 3, del my_tuple print(my_tuple) # NameError: name 'my_tuple' is not defined 6. You can loop through a tuple elements (Example 1), check if a specific element is (not)present in a tuple (Example 2), use the len() function to check how many elements there are in a tuple (Example 3), or even join/multiply tuples (Example 4): # Example 1 tuple_1 = (1, 2, 3) for elem in tuple_1: print(elem) # Example 2 tuple_2 = (1, 2, 3, 4) print(5 in tuple_2) print(5 not in tuple_2) # Example 3 tuple_3 = (1, 2, 3, 5) print(len(tuple_3)) # Example 4 tuple_4 = tuple_1 + tuple_2 tuple_5 = tuple_3 * 2 print(tuple_4) print(tuple_5) EXTRA You can also create a tuple using a Python built-in function called tuple(). This is particularly useful when you want to convert a certain iterable (e.g., a list, range, string, etc.) to a tuple: my_tuple = tuple((1, 2, "string")) print(my_tuple) my_list = [2, 4, 6] print(my_list) # outputs: [2, 4, 6] print(type(my_list)) # outputs: tup = tuple(my_list) print(tup) # outputs: (2, 4, 6) print(type(tup)) # outputs: By the same fashion, when you want to convert an iterable to a list, you can use a Python built-in function called list(): tup = 1, 2, 3, my_list = list(tup) print(type(my_list)) # outputs: Key takeaways: dictionaries 1. Dictionaries are unordered*, changeable (mutable), and indexed collections of data. (*In Python 3.6x dictionaries have become ordered by default. Each dictionary is a set of key: value pairs. You can create it by using the following syntax: my_dictionary = { key1: value1, key2: value2, key3: value3, } 2. If you want to access a dictionary item, you can do so by making a reference to its key inside a pair of square brackets (ex. 1) or by using the get() method (ex. 2): pol_eng_dictionary = { "kwiat": "flower", "woda": "water", "gleba": "soil" } item_1 = pol_eng_dictionary["gleba"] # ex. 1 print(item_1) # outputs: soil item_2 = pol_eng_dictionary.get("woda") print(item_2) # outputs: water 3. If you want to change the value associated with a specific key, you can do so by referring to the item's key name in the following way: pol_eng_dictionary = { "zamek": "castle", "woda": "water", "gleba": "soil" } pol_eng_dictionary["zamek"] = "lock" item = pol_eng_dictionary["zamek"] print(item) # outputs: lock 4. To add or remove a key (and the associated value), use the following syntax: phonebook = {} # an empty dictionary phonebook["Adam"] = 3456783958 # create/add a key-value pair print(phonebook) # outputs: {'Adam': 3456783958} del phonebook["Adam"] print(phonebook) # outputs: {} You can also insert an item to a dictionary by using the update() method, and remove the last element by using the popitem() method, e.g.: pol_eng_dictionary = {"kwiat": "flower"} pol_eng_dictionary.update({"gleba": "soil"}) print(pol_eng_dictionary) # outputs: {'kwiat': 'flower', 'gleba': 'soil'} pol_eng_dictionary.popitem() print(pol_eng_dictionary) # outputs: {'kwiat': 'flower'} 5. You can use the for loop to loop through a dictionary, e.g.: pol_eng_dictionary = { "zamek": "castle", "woda": "water", "gleba": "soil" } for item in pol_eng_dictionary: print(item) # outputs: zamek # woda # gleba 6. If you want to loop through a dictionary's keys and values, you can use the items() method, e.g.: pol_eng_dictionary = { "zamek": "castle", "woda": "water", "gleba": "soil" } for key, value in pol_eng_dictionary.items(): print("Pol/Eng ->", key, ":", value) 7. To check if a given key exists in a dictionary, you can use the in keyword: pol_eng_dictionary = { "zamek": "castle", "woda": "water", "gleba": "soil" } if "zamek" in pol_eng_dictionary: print("Yes") else: print("No") 8. You can use the del keyword to remove a specific item, or delete a dictionary. To remove all the dictionary's items, you need to use the clear() method: pol_eng_dictionary = { "zamek": "castle", "woda": "water", "gleba": "soil" } print(len(pol_eng_dictionary)) # outputs: 3 del pol_eng_dictionary["zamek"] # remove an item print(len(pol_eng_dictionary)) # outputs: 2 pol_eng_dictionary.clear() # removes all the items print(len(pol_eng_dictionary)) # outputs: 0 del pol_eng_dictionary # removes the dictionary 9. To copy a dictionary, use the copy() method: pol_eng_dictionary = { "zamek": "castle", "woda": "water", "gleba": "soil" } copy_dictionary = pol_eng_dictionary.copy() Some useful exceptions Let’s discuss in more detail some useful (or rather, the most common) exceptions you may experience. ZeroDivisionError This appears when you try to force Python to perform any operation which provokes division in which the divider is zero, or is indistinguishable from zero. Note that there is more than one Python operator which may cause this exception to raise. Can you guess them all? Yes, they are: /, //, and %. ValueError Expect this exception when you're dealing with values which may be inappropriately used in some context. In general, this exception is raised when a function (like int() or float()) receives an argument of a proper type, but its value is unacceptable. TypeError This exception shows up when you try to apply a data whose type cannot be accepted in the current context. Look at the example: short_list = [1] one_value = short_list[0.5] You're not allowed to use a float value as a list index (the same rule applies to tuples, too). TypeError is an adequate name to describe the problem, and an adequate exception to raise. AttributeError This exception arrives – among other occasions – when you try to activate a method which doesn't exist in an item you're dealing with. For example: short_list = [1] short_list.append(2) short_list.depend(3) The third line of our example attempts to make use of a method which isn’t contained in the lists. This is the place where AttributeError is raised. SyntaxError This exception is raised when the control reaches a line of code which violates Python's grammar. It may sound strange, but some errors of this kind cannot be identified without first running the code. This kind of behavior is typical of interpreted languages – the interpreter always works in a hurry and has no time to scan the whole source code. It is content with checking the code which is currently being run. An example of such a category of issues will be presented very soon. It's a bad idea to handle this exception in your programs. You should produce code that is free of syntax errors, instead of masking the faults you’ve caused. Tests, testing, and testers The answer is simpler than you may expect, and a bit disappointing, too. Python – as you know for sure – is an interpreted language. This means that the source code is parsed and executed at the same time. Consequently, Python may not have time to analyze the code lines which aren't subject to execution. As an old developer's saying states: "it's a feature, not a bug" (please don't use this phrase to justify your code's weird behavior). Do you understand now why passing through all execution paths is so vital and inevitable? Let’s assume that you complete your code and the tests you've made are successful. You deliver your code to the testers and – fortunately! – they found some bugs in it. We’re using the word "fortunately" completely consciously. You need to accept that, firstly, testers are the developer’s best friends – don't treat the bugs they discover as an offense or a malignancy; and, secondly, each bug the testers find is a bug that won't affect the users. Both factors are valuable and worth your attention. You already know that your code contains a bug or bugs (the latter is more likely). How do you locate them and how do you fix your code? Bug vs. debug The basic measure a developer can use against bugs is – unsurprisingly – a debugger, while the process during which bugs are removed from the code is called debugging. According to an old joke, debugging is a complicated mystery game in which you are simultaneously the murderer, the detective, and – the most painful part of the intrigue – the victim. Are you ready to play all these roles? Then you must arm yourself with a debugger. A debugger is a specialized piece of software that can control how your program is executed. Using the debugger, you can execute your code line-by-line, inspect all the variables' states and change their values on demand without modifying the source code, stop program execution when certain conditions are or aren't met, and do lots of other useful tasks. Bug vs. Debug We can say that every IDE is equipped with a more or less advanced debugger. Even IDLE has one, although you may find its handling a bit complicated and troublesome. If you want to make use of IDLE's integrated debugger, you should activate it using the “Debug” entry in the main IDLE window menu bar. It's the start point for all debugger facilities. Click here to see the screenshots that show the IDLE debugger during a simple debugging session. (Thank you, University of Kentucky!) You can see how the debugger visualizes variables and parameter values, and note the call stack which shows the chain of invocations leading from the currently executed function to the interpreter level. If you want to know more about the IDLE debugger, consult the IDLE documentation. Some useful tips Here are some tips which may help you to find and eliminate the bugs. None of them is either ultimate or definitive. Use them flexibly and rely on your intuition. Don't believe yourself – check everything twice. Try to tell someone (for example, your friend or coworker) what your code is expected to do and how it actually behaves. Be concrete and don't omit details. Answer all questions your helper asks. You'll likely realize the cause of the problem while telling your story, as speaking activates these parts of your brain which remain idle during coding. If no human can help you with the problem, use a yellow rubber duck instead. We're not kidding – consult the Wikipedia article to learn more about this commonly used technique: Rubber Duck Debugging. Try to isolate the problem. You can extract the part of your code that is suspected of being responsible for your troubles and run it separately. You can comment out parts of the code that obscure the problem. Assign concrete values to variables instead of reading them from the input. Test your functions by applying predictable argument values. Analyze the code carefully. Read it aloud. If the bug has appeared recently and didn't show up earlier, analyze all the changes you've introduced into your code – one of them may be the reason. Take a break, drink a cup of coffee, take your dog and go for a walk, read a good book for a moment or two, make a phone call to your best friend – you'll be surprised how often it helps. Be optimistic – you'll find the bug eventually; we promise you this. Unit testing – a higher level of coding There is also one important and widely used programming technique that you will have to adopt sooner or later during your developer career – it's called unit testing. The name may a bit confusing, as it's not only about testing the software, but also (and most of all) about how the code is written. To make a long story short – unit testing assumes that tests are inseparable parts of the code and preparing the test data is an inseparable part of coding. This means that when you write a function or a set of cooperating functions, you're also obliged to create a set of data for which your code's behavior is predictable and known. Moreover, you should equip your code with an interface that can be used by an automated testing environment. In this approach, any amendment made to the code (even the least significant) should be followed by the execution of all the unit tests accompanied by your source. To standardize this approach and make it easier to apply, Python provides a dedicated module named unittest. We're not going to discuss it here – it's a broad and complex topic. Therefore, we’ve prepared a separate course and certification path for this subject. It is called “Testing Essentials with Python”, and we invite you to participate in it. See you soon! Key takeaways – Exceptions 1. In Python, there is a distinction between two kinds of errors: syntax errors (parsing errors), which occur when the parser comes across a statement that is incorrect. For example: Trying to execute the following line: print("Hello, World!) will cause a SyntaxError, and result in the following (or similar) message being displayed in the console: File "main.py", line 1 print("Hello, World!) ^ SyntaxError: EOL while scanning string literal output Pay attention to the arrow – it indicates the place where the Python parser has run into trouble. In our case, it's the missing double quote. Did you notice it? exceptions, which occur even when a statement/expression is syntactically correct; these are the errors that are detected during execution when your code results in an error which is not uncoditionally fatal. For example: Trying to execute the following line: print(1/0) will cause a ZeroDivisionError exception, and result in the following (or similar) message being displayed in the console: Traceback (most recent call last): File "main.py", line 1, in print(1/0) ZeroDivisionError: division by zero output Pay attention to the last line of the error message – it actually tells you what happened. There are many different types of exceptions, such as ZeroDivisionError, NameError, TypeError, and many more; and this part of the message informs you of what type of exception has been raised. The preceding lines show you the context in which the exception has occured. 2. You can "catch" and handle exceptions in Python by using the try-except block. So, if you have a suspicion that any particular snippet may raise an exception, you can write the code that will gracefully handle it, and will not interrupt the program. Look at the example: while True: try: number = int(input("Enter an integer number: ")) print(number/2) break except: print("Warning: the value entered is not a valid number. Try again...") The code above asks the user for input until they enter a valid integer number. If the user enters a value that cannot be converted to an int, the program will print Warning: the value entered is not a valid number. Try again..., and ask the user to enter a number again. What happens in such a case? The program enters the while loop. The try block/clause is executed. The user enters a wrong value, for example: hello!. An exception occurs, and the rest of the try clause is skipped. The program jumps to the except block, executes it, and then continues running after the try-except block. If the user enters a correct value and no exception occurs, the subsequent instructions in the try block are executed. 3. You can handle multiple exceptions in your code block. Look at the following examples: while True: try: number = int(input("Enter an int number: ")) print(5/number) break except ValueError: print("Wrong value.") except ZeroDivisionError: print("Sorry. I cannot divide by zero.") except: print("I don't know what to do...") You can use multiple except blocks within one try statement, and specify particular exception names. If one of the except branches is executed, the other branches will be skipped. Remember: you can specify a particular built-in exception only once. Also, don't forget that the default (or generic) exception, that is the one with no name specified, should be placed at the bottom of the branch (use the more specific exceptions first, and the more general last). You can also specify and handle multiple built-in exceptions within a single except clause: while True: try: number = int(input("Enter an int number: ")) print(5/number) break except (ValueError, ZeroDivisionError): print("Wrong value or No division by zero rule broken.") except: print("Sorry, something went wrong...") 4. Some of the most useful Python built-in exceptions are: ZeroDivisionError, ValueError, TypeError, AttributeError, and SyntaxError. One more exception that, in our opinion, deserves your attention is the KeyboardInterrupt exception, which is raised when the user hits the interrupt key (CTRL-C or Delete). Run the code above and hit the key combination to see what happens. To learn more about the Python built-in exceptions, consult the official Python documentation. 5. Last but not least, you should remember about testing and debugging your code. Use such debugging techniques as print debugging; if possible – ask someone to read your code and help you to find bugs in it or to improve it; try to isolate the fragment of code that is problematic and susceptible to errors: test your functions by applying predictable argument values, and try to handle the situations when someone enters wrong values; comment out the parts of the code that obscure the issue. Finally, take breaks and come back to your code after some time with a fresh pair of eyes.