Advanced Python

Abdikhafar - Oct 10 - - Dev Community

Basics Part2

Helloooo there! Welcome back!!

Wait, are you new here? Don't worry, I got you covered. Here, we are breaking the flow. Have you checked "Python from the word ...Go" Basics Part1? It's an awesome resource to first check out if you are not familiar with Python's variables and data types which comprise a few in-built Python data structures.
They are really gonna come in handy for this section.

Set? Lets' go!!

In the previous module, we learnt about the fundamental Python data types and also covered some of the terms used when talking about code like variables, statements, expressions, functions, methods ...etc.
Most importantly, we covered how to perform actions on the data types (Both functions and methods for each data type).
Up until now, we were still scratching the surface. Every time we write code, we wrote it line by line and hence our interpreter would go line by line running each code up to the last line.

>>> #do something
>>> #do something
>>> #do something
>>> #do something
Enter fullscreen mode Exit fullscreen mode

We are now going to incorporate the idea of running multiple lines over and over to discover the true power of programming for machines, haha!
Hence, in this section, we gonna talk about the idea of conditions and conditional logic. We gonna discuss more on looping and loops where we can perform actions multiple times over and over.
We are now going to break into a new world where instead of going from line by line in order, we gonna loop over till a condition is met.

Conditional Logic
We previously covered Boolean variables (True or False). When we come to conditional logic, Booleans are super important.
Example:
A person(a) wants to purchase a car from a company with specific conditions:

The car must be new.
The car must have a license.
Hence for person(a) to purchase the car:

is_new = True

is_licensed = True

In conditional logic, we use the 'if' keyword.
"If the car is new and licensed, then person(a) can purchase it".
Then, if any of the conditions in purchasing the car is not met, person(a) cannot purchase the car.

Example2:
Let's assume that for one to get into a specific event, the person has to be old(35 years and above). Create a program to only allow old people to the event.
If is_old = True, "allowed to get into the event."
For the syntax:

>>> is_old = True
>>> if is_old:
>>>   print("You are allowed to get in")
You are allowed to get in

>>> is_old = False
>>> if is_old:
>>>   print("You are allowed to get in")
#nothing will be printed out.
Enter fullscreen mode Exit fullscreen mode

Note: Any code that comes after the colon in the condition is automatically indented hence run if the condition is True whereas any code that ain't indented after the condition is not under the condition and hence run separately.
Example:

>>> is_old = True
>>> if is_old:
>>>   print("You are allowed to get in")
>>> print("Hello there")
You are allowed to get in
Hello there

>>> is_old = False
>>> if is_old:
>>>   print("You are allowed to get in")
>>> print("Hello there")
Hello there
Enter fullscreen mode Exit fullscreen mode

What if when a condition is not met(False), we want to perform another condition?

Here is an example:
I leave my house;
If it's cloudy, I bring an umbrella;
otherwise, I bring sunglasses.

We use the keyword 'else'.
Using our example of letting people in for the event, we can add:
If is_old = True, "allowed to get into the event.", otherwise if is_old = False, "not allowed to get in",

>>> is_old = True
>>> if is_old:
>>>   print("You are allowed to get in")
>>> else:
>>>   print("Not allowed in")
You are allowed to get in

>>> is_old = False
>>> if is_old:
>>>   print("You are allowed to get in")
>>> else:
>>>   print("Not allowed in")
Not allowed in
Enter fullscreen mode Exit fullscreen mode

What if by chance you have more than one condition?

Example:
I'm in a restaurant;
If I want meat, I order steak;
otherwise, If I want Pasta, I order spaghetti and meatballs;
otherwise, I order salad.

For such cases, we use the 'elif' keyword (else if).
Using a different example.

A person(b) wants to order chicken pizza. If there is no chicken pizza, the person(b) can order beef pizza; otherwise, if there is none, the person(b) can order rice.

>>> chicken_pizza = True
>>> beef_pizza = True

>>> if chicken_pizza:
>>>   print("Serve me chicken pizza.")
>>> elif beef_pizza:
>>>   print("Serve me beef pizza.")
>>> else:
>>>   print("Serve me rice.")
Serve me chicken pizza.

Enter fullscreen mode Exit fullscreen mode

>>> chicken_pizza = False
>>> beef_pizza = True

>>> if chicken_pizza:
>>>   print("Serve me chicken pizza.")
>>> elif beef_pizza:
>>>   print("Serve me beef pizza.")
>>> else:
>>>   print("Serve me rice.")
Serve me beef pizza.

Enter fullscreen mode Exit fullscreen mode

>>> chicken_pizza = False
>>> beef_pizza = False

>>> if chicken_pizza:
>>>   print("Serve me chicken pizza.")
>>> elif beef_pizza:
>>>   print("Serve me beef pizza.")
>>> else:
>>>   print("Serve me rice.")
Serve me rice.
Enter fullscreen mode Exit fullscreen mode

"For a person to be legalized to drive a car in public, one must have a national identification card and a driving license."
These are two conditions that one must have to avoid being fined by the cops.
To develop such a program, we must have both conditions True hence we can use the keyword 'and' to check whether both conditions are True.
When using 'and' both conditions must be True to execute the condition, if any of them is False, the program will run the 'elif' and 'else' part.

>>> has_id = True
>>> has_license = True

>>> if has_id and has_license:
>>>   print("Allowed to drive")
>>> else:
>>>   print("Not allowed to drive")
Allowed to drive

Enter fullscreen mode Exit fullscreen mode

>>> has_id = False
>>> has_license = True

>>> if has_id and has_license:
>>>   print("Allowed to drive")
>>> else:
>>>   print("Not allowed to drive")
Not allowed to drive

Enter fullscreen mode Exit fullscreen mode

>>> has_id = True
>>> has_license = False

>>> if has_id and has_license:
>>>   print("Allowed to drive")
>>> else:
>>>   print("Not allowed to drive")
Enter fullscreen mode Exit fullscreen mode

Not allowed to drive
Python Indentation
The interpreter in python finds meaning in the spacing hence indentation(tabs) and white spaces in python is essential.

Truthy Vs Falsy
In python, whenever there's a value, the interpreter recognizes that as True. Otherwise, when there's a zero(0) or no value, python interpreter recognizes that as False.
These are referred to as Truthy and Falsy values.

>>> print(bool('hello'))
>>> print(bool(5))
>>> print(bool(''))
>>> print(bool(0))
>>> print(bool(None))
True
True
False
False
False
Enter fullscreen mode Exit fullscreen mode

All values are considered "truthy" except the following; which are considered "falsy":

None

False

0

0.0

0j

Decimal(0)

Fraction(0, 1)

[] - an empty list

{} - an empty dict

() - an empty tuple

'' - an empty str

b'' - an empty bytes

set() - an empty set

range(0) - an empty range

Objects for which:

obj.bool() returns False
obj.len() returns 0
Note: A "truthy" value will satisfy the check performed by if or while statements. We use "truthy" and "falsy" to differentiate from the bool values True and False.
Example:

>>> has_id = 'hello'
>>> has_license = 5

>>> if has_id and has_license:
>>>   print("Allowed to drive")
>>> else:
>>>   print("Not allowed to drive")
Allowed to drive

Enter fullscreen mode Exit fullscreen mode

>>> has_id = 'hello'
>>> has_license = 0

>>> if has_id and has_license:
>>>   print("Allowed to drive")
>>> else:
>>>   print("Not allowed to drive")
Not allowed to drive

Enter fullscreen mode Exit fullscreen mode

>>> has_id = None
>>> has_license = True

>>> if has_id and has_license:
>>>   print("Allowed to drive")
>>> else:
>>>   print("Not allowed to drive")
Not allowed to drive

Enter fullscreen mode Exit fullscreen mode

>>> has_id = True
>>> has_license = ''

>>> if has_id and has_license:
>>>   print("Allowed to drive")
>>> else:
>>>   print("Not allowed to drive")
Not allowed to driv
Enter fullscreen mode Exit fullscreen mode

e
A good though not perfect example on the use of "truthy" and "falsy" application is in forms or in keying in log in credentials.

Image description

When a field is set to as 'a required field', the system expects the field to be "truthy" hence should not be left blank as this will lead to it being assigned "falsy".

Ternary Operator (Conditional Expression)
This is another way to do conditional logic. This works the same as 'if statements' but can in a way be referred to as a 'shortcut' so can only be used in certain conditional logic.
In this mode, we start with the condition then incorporate the "if statement".
"condition_if_true" if condition else "condition_if_false"

Example:
Let's use an example to determine if a user is your friend (eg. on Facebook whether the user can message you).

>>> is_friend = True
>>> can_message = "Message allowed" if is_friend else "not allowed to message"
>>> print(can_message)
Message allowed

>>> is_friend = True
>>> can_message = "Message allowed" if is_friend else "not allowed to message"
>>> print(can_message)
not allowed to message

Enter fullscreen mode Exit fullscreen mode

Short Circuiting
Previously we saw how to use the 'and' keyword to validate whether both statements are True:

>>> is_friend = True
>>> is_user = True
>>> print(is_friend and is_user)
True

>>> is_friend = True
>>> is_user = False
>>> print(is_friend and is_user)
False
Enter fullscreen mode Exit fullscreen mode

In short circuiting, the interpreter ignores one part of the condition(despite it being false) and returns either True or False.
Example, using the 'or' keyword, if the first part of the condition is True the interpreter returns True without checking the second part.
When we use the 'and' keyword, when the the first part of the statement is False, the interpreter ignores(short circuits) the second part and returns False.
An example using the 'or' keyword:

>>> is_friend = True
>>> is_user = False
>>> print(is_friend or is_user)
True

>>> is_friend = True
>>> is_user = True
>>> print(is_friend or is_user)
True

>>> is_friend = False
>>> is_user = True
>>> if is_friend or is_user:
>>>   print("Best friends forever")
Best friends forever

>>> is_friend = False
>>> is_user = True
>>> if False or is_user:
>>>   print("Best friends forever")
>>> else:
>>>   print("Never friends")
Best friends forever

>>> is_friend = True
>>> is_user = False
>>> if False or is_user:
>>>   print("Best friends forever")
>>> else:
>>>   print("Never friends")
Never friends

>>> if True or True:
>>>   print("Best friends forever")
>>> else:
>>>   print("Never friends")
Best friends forever
Enter fullscreen mode Exit fullscreen mode

Logical Operators
We have looked at a few logical operators previously 'and' and 'or'. A logical operator allows us to perform logic between two things.
Other logical operators include:

Greater than >

Less than <

Equal to ==

Greater than or equal to >=

Less than or equal to <=

Not equal to != (Opposite of equal to)

not keyword / Function - It negates the statement. Can also be written with bracket: not().


They return either True or False:
>>> print(4 > 5)
False

>>> print(4 < 5)
True

>>> print(4 == 5)
False

>>> print(1 >= 0)
True

>>> print(1 <= 0)
False

>>> print(0 >= 0)
True

>>> print(0 != 0)
False

>>> print(not(True))
False

>>> print(not True)
False

>>> print(not False)
True

>>> print(not(1 == 1))
False

>>> print(not(1 != 1))
True
Enter fullscreen mode Exit fullscreen mode

Note: A single equal sign = symbol is used in assigning values to variables hence to use the "equal to" operator for comparison, we use a double == symbol.

>>> print('a' > 'b')
False

>>> print('a' > 'A')
True
Enter fullscreen mode Exit fullscreen mode

But why/how is 'a' > 'b' False; and 'a' > 'A' True?

In the case of strings, Python compares the ASCII values of the characters. Hence, 'a' ASCII value is 97, 'b' is 98 and 'A' ASCII value is 65 that's why 'a' is greater than 'A' and 'b' is greater than 'a'.

*optional
In the case of, print('abc' < 'bac'), the result will be True. (Though this is a bit beyond the scope of the course).
This kind of comparison uses lexicographical ordering: first the first two items are compared, and if they differ this determines the outcome of the comparison; if they are equal, the next two items are compared, and so on, until either sequence is exhausted.

Lexicographical ordering for strings uses the Unicode code point number to order individual characters.

>>> print(1 < 2 < 3 < 4)
True

>>> print(1 < 2 > 3 < 4)
False
Enter fullscreen mode Exit fullscreen mode

Exercise1 (Done below)
You have been hired by a gaming company to create a program for a game where the character has magic powers and is an expert.
If the character has magic and is an expert, the output should be "You are a master magician". Otherwise, if the character has magic but is not an expert, the output should be "At least you're getting there". Else, if the character has no magic, the output should be "You need magic powers".

>>> print("Enter '1'(Yes) or '0'(No) for each question.")
>>> has_magic = bool(int(input("Does the character has magic? ")))
>>> is_expert = bool(int(input("Is the character an expert? ")))

>>> if has_magic and is_expert:
>>>   print("You are a master magician.")
>>> elif has_magic and not is_expert:
>>>   print("At least you're getting there.")
>>> elif not has_magic:
>>>  print("You need magic powers.")
Enter '1'(Yes) or '0'(No) for each question.
Does the character has magic? _1_
Is the character an expert? _1_
You are a master magician.
Enter fullscreen mode Exit fullscreen mode

(Re-run the program)
Enter '1'(Yes) or '0'(No) for each question.
Does the character has magic? 0
Is the character an expert? 1
You need magic powers.

(Re-run the program)
Enter '1'(Yes) or '0'(No) for each question.
Does the character has magic? 1
Is the character an expert? 0
At least you're getting there.
'is' keyword
Unlike the double equal sign ==, which compares the equality in values, is is a keyword that checks if the location in memory where one value is stored is the same as the other's.

Example:
>>> print(True == 1)
>>> print('' == 1)
>>> print('1' == 1)
>>> print([] == 0)
>>> print(10 == 10.0)
>>> print([] == [])
>>> print([1, 2, 3] == [1, 2, 3])
True
False
False
False
True
True
True

>>> print(True is 1)
>>> print('' is 1)
>>> print('1' is 1)
>>> print([] is 0)
>>> print(10 is 10.0)
>>> print([] is [])
>>> print([1, 2, 3] is [1, 2, 3])
False
False
False
False
False
False
False

>>> print(True is True)
True

>>> print('1' is '1')
True

>>> print(10 is 10)
True
Enter fullscreen mode Exit fullscreen mode

Once a list is created, it is stored in different memory space hence print([] is []) or print([1, 2, 3] is [1, 2, 3]) will always evaluate to False.

All Data Structures in Python are stored in different memory locations.

For Loops
Loops are one of the most powerful features of a programming languages. The concept of looping allows us to run lines of code over and over till we accomplish a specific task.
In creating a for loop, we use the keyword 'for'.
Example: for i in 'name':
'i' in the loop is a variable for each element in the loop and can be any different name: for item in 'name':, for teddy in 'name': and is created for each item in 'name'(iterable).
An iterable is something that can be looped over.

>>> for item in 'name':
>>>    print(item)
n
a
m
e

>>> for item in [1, 2, 3, 4]:
>>>    print(item)
1
2
3
4

>>> name = 'Mark'
>>> for i in name:
>>>    print(i)
M
a
r
k

>>> for item in {1, 2, 3, 4}:
>>>    print(item)
1
2
3
4

>>> for item in (1, 2, 3, 4):
>>>    print(item)
1
2
3
4

>>> for item in (1, 2, 3, 4):
>>>    print(item)
>>>    print(item)
>>>    print(item)
>>> print("Hello")

1
1
1
2
2
2
3
3
3
4
4
4
Hello

>>> for item in (1, 2, 3, 4):
>>>    print(item)
>>>    print(item)
>>>    print(item)
>>> print(item)
1
1
1
2
2
2
3
3
3
4
4
4
4
Nested for loops
>>> for item in (1, 2, 3, 4, 5):
>>>     for x in ['a', 'b', 'c']:
>>>         print(item, x)
1 a
1 b
1 c
2 a
2 b
2 c
3 a
3 b
3 c
4 a
4 b
4 c
5 a
5 b
5 c
Enter fullscreen mode Exit fullscreen mode

Iterables
An iterable is an object or a collection that can be iterated over (looped over).
An iterable can be a list, tuple, dictionary, set and string. This means that one can go one by one checking each item in the collection.

Iterating over a dictionary

>>> user = {
>>>     'name' : 'Mark',
>>>     'age' : 30,
>>>     'can_swim' : False
>>>   }

>>> for item in user:
>>>     print(item)
name
age
can_swim
Enter fullscreen mode Exit fullscreen mode

When we iterate over a dictionary we only get the keys but can use the dictionary methods to loop over the dictionary items which includes its values.

One is 'x.items()' where we get the key-value pairs in tuples form.

>>> user = {
>>>     'name' : 'Mark',
>>>     'age' : 30,
>>>     'can_swim' : False
>>>   }

>>> for item in user.items():
>>>     print(item)
('name', 'Mark')
('age', 30)
('can_swim', False)
Enter fullscreen mode Exit fullscreen mode

Second is 'x.values()' where we get only the values in the

dictionary.
>>> user = {
>>>     'name' : 'Mark',
>>>     'age' : 30,
>>>     'can_swim' : False
>>>   }

>>> for item in user.values():
>>>     print(item)
Mark
30
False
Enter fullscreen mode Exit fullscreen mode

Third is 'x.keys()' where we get only the keys in the dictionary. Works the same as iterating the dictionary without including a method.

>>> user = {
>>>     'name' : 'Mark',
>>>     'age' : 30,
>>>     'can_swim' : False
>>>   }

>>> for item in user.keys():
>>>     print(item)
name
age
can_swim
Enter fullscreen mode Exit fullscreen mode

What if you want to print the items (key and values) in the dictionary separately? We can use tuple unpacking.

>>> user = {
>>>     'name' : 'Mark',
>>>     'age' : 30,
>>>     'can_swim' : False
>>>   }

>>> for item in user.items():
>>>     key, value = item
>>>     print(key, value)
name Mark
age 30
can_swim False
Enter fullscreen mode Exit fullscreen mode

(second way of unpacking)


>>> user = {
>>>     'name' : 'Mark',
>>>     'age' : 30,
>>>     'can_swim' : False
>>>   }

>>> for key, value in user.items():
>>>     print(key, value)
name Mark
age 30
can_swim False
Enter fullscreen mode Exit fullscreen mode

Exercise2 (Done below)
Building a simple 'counter' to loop over a list and sum up the items in the list. The list is provided below.
my_list = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

>>> my_list = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
>>> sum = 0
>>> for i in my_list:
>>>     sum += i
>>> print (sum)
55
Enter fullscreen mode Exit fullscreen mode

range() in loops
It returns an object that produces a sequence of integers from the start (which is inclusive) to stop (exclusive).

>>> print(range(100))
range(0, 100)

>>> print(range(0, 100))
range(0, 100)
Enter fullscreen mode Exit fullscreen mode

We can iterate a range of numbers.

>>> for num in range(20)
>>>    print(num)
0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

>>> for i in range(2, 15)
>>>    print(i)
2
3
4
5
6
7
8
9
10
11
12
13
14

>>> for i in range(10):
>>>    print('my name is')
my name is
my name is
my name is
my name is
my name is
my name is
my name is
my name is
my name is
my name is
Enter fullscreen mode Exit fullscreen mode

When one 'does not want to use' a variable name in the loop, the person can use an underscore _:

>>> for _ in range(20)
>>>    print(_)
0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

>>> for _ in range(10):
>>>    print('my name is')
my name is
my name is
my name is
my name is
my name is
my name is
my name is
my name is
my name is
my name is
(start: stop: stepover) in range()
>>> for _ in range(0, 10, 2)
>>>    print(_)
0
2
4
6
8

>>> for _ in range(0, 10, -1)
>>>    print(_)
#nothing will be printed out

>>> for _ in range(10, 0, -1)
>>>    print(_)
10
9
8
7
6
5
4
3
2
1

>>> for _ in range(10, 0, -2)
>>>    print(_)
10
8
6
4
2

>>> for _ in range(2)
>>>    print(list(range(10)))
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Enter fullscreen mode Exit fullscreen mode

enumerate()
It returns each item in the iterable with its index in tuple form.

>>> for i in enumerate('mark'):
>>>    print(i)
(0, 'm')
(1, 'a')
(2, 'r')
(3, 'k')

>>> for i, j in enumerate('mark'):
>>>    print(i, j)
0 m
1 a
2 r
3 k

>>> for i, char in enumerate(list(range(100))):
>>>     if char == 50:
>>>         print(f"The index of 50 is: {i}")
The index of 50 is: 50

>>> for i, j in enumerate('Mark'):
>>>     if j == 'r':
>>>         print(f"The index of r is: {i}")
The index of r is: 2
Enter fullscreen mode Exit fullscreen mode

While Loops
In While loop, a command is run when a specific condition is met till the condition becomes false after constant looping.

eg:
>>> i = 0
>>> while 0 < 50:
>>>     print(i)
0
0
0
0
0
Enter fullscreen mode Exit fullscreen mode

will run infinitely for 0 will always be less than 50.


>>> i = 0
>>> while i < 50:
>>>     i += 5
>>>     print(i)
5
10
15
20
25
30
35
40
45
50
Enter fullscreen mode Exit fullscreen mode

break command in while loop
When a break keyword is used in while loop, it breaks the loop after the first run.

>>> i = 0
>>> while 0 < 50:
>>>     print(i)
>>>     break
0
Enter fullscreen mode Exit fullscreen mode

using else in while loop

>>> i = 0
>>> while i < 50:
>>>     i += 5
>>>     print(i)
>>> else:
>>>     print("Done with work")
5
10
15
20
25
30
35
40
45
50
Done with work

>>> i = 0
>>> while i > 50:
>>>     i += 5
>>>     print(i)
>>> else:
>>>     print("Done with work")
Done with work
Enter fullscreen mode Exit fullscreen mode

The else block in while loop will only execute when there is no break statement in the while loop.

>>> i = 0
>>> while i < 50:
>>>     i += 5
>>>     print(i)
>>>     break
>>> else:
>>>     print("Done with work")
0
Enter fullscreen mode Exit fullscreen mode

How for loops and while loops relates

>>> my_list = [1, 2, 3]
>>> for item in my_list:
>>>     print(item)
1
2
3
Enter fullscreen mode Exit fullscreen mode
------------------------------------
>>> my_list = [1, 2, 3]
>>> i = 0
>>> while i < len(my_list):
>>>     print(my_list[i])
>>>     i += 1 
1
2
3
Enter fullscreen mode Exit fullscreen mode

While loops are more flexible for we have a conditional statement but for loops are simpler. With the while loop, we have to remember to hope the loop in the course or use a break statement, to avoid getting an infinite loop.

>>> while True:
>>>     input("Say something: ")
>>>     break
Say something: _hi_

>>> while True:
>>>     response = input("Say something: ")
>>>     if (response == "bye"):
>>>         break
Say something: _hi_
Say something: _hi_
Say something: _bye_
Enter fullscreen mode Exit fullscreen mode

break, continue, pass
The break keyword breaks out of the loop. The continue keyword continues the loop till the condition is met without running the indented line(s) below it. The pass keyword is used to pass to the next line. It is mainly used as a placeholder.

>>> my_list = [1, 2, 3]
>>> for item in my_list:
>>>    continue
>>>    print(item)
#nothing will be printed

>>> i = 0
>>> while i < len(my_list):
>>>    i += 1
>>>    continue
>>>    print(my_list[i])
#nothing will be printed

>>> my_list = [1, 2, 3]
>>> for item in my_list:
>>>    pass

>>> i = 0
>>> while i < len(my_list):
>>>    print(my_list[i])
>>>    i += 1
>>>    pass
1
2
3
Enter fullscreen mode Exit fullscreen mode

Functions
Up until now, we've worked with python functions like print, list, input function and many more that allowed us to perform actions on our data types.
We can also create our own functions and use them on our program.
When creating functions in Python we use the def - 'define' keyword. We then give our function a name (defining the function) as we do with variables then we add the brackets () and a colon at the end.
For one to use a function one has to 'call it'.

>>> def say_hello(): #defining the function
>>>    print("Hellooo")
>>> say_hello() #calling the function
Enter fullscreen mode Exit fullscreen mode

Hellooo
Functions are super useful because the work under the principle of 'DRY - Don't Repeat Yourself' because instead of the programmer re-typing the code each and every time, the programmer can just call the function as many times as possible to run a specific block of code.

Example: Using our Christmas tree example above, we can use the function to output it multiple of times.

>>> picture = [
       [0, 0, 0, 1, 0, 0, 0],
       [0, 0, 1, 1, 1, 0, 0],
       [0, 1, 1, 1, 1, 1, 0],
       [1, 1, 1, 1, 1, 1, 1],
       [0, 0, 0, 1, 0, 0, 0],
       [0, 0, 0, 1, 0, 0, 0]
    ]

>>> def show_tree():
>>>    for row in picture:
>>>        for pixel in row:
>>>            if pixel == 0:
>>>                print(" ", end = ' ')
>>>            else:
>>>                print("*", end = ' ')
>>>        print(" ")

>>> show_tree()
>>> show_tree()
>>> show_tree()

      *        
    * * *      
  * * * * *
* * * * * * *
      *
      *
      *
    * * *
  * * * * *
* * * * * * *
      *
      *
      *
    * * *
  * * * * *
* * * * * * *
      *
      *
Enter fullscreen mode Exit fullscreen mode

The function is stored in a specific place in memory once created.

>>> def say_hello(): 
>>>    print("Hellooo")
>>> print(say_hello)
<function say_hello at 0x000002332BB33E20>

Enter fullscreen mode Exit fullscreen mode

The characters '0x000002332BB33E20' show the memory location where the function has been stored.

Arguments Vs Parameters(in functions)
The power of functions beyond it being able to be called multiple times, is the ability of the programmer to make it dynamic. In its brackets, one can pass parameters.
The values which are defined at the time of the function prototype or definition of the function are called as parameters.
When a function is 'called', the actual values that are passed during the 'call' are called as arguments.

>>> def say_hello(name, age): #name and age are parameters.
>>>    print(f"Hello {name}, You're {age} yrs")
>>> say_hello("Abdikhafar", 20) #"Mark" and 20 are arguments.
Hello Abdikhafar, You're 20 yrs

>>> def say_hello(name, age):
>>>    print(f"Hello {name}, You're {age} yrs")
>>> say_hello("Mark", 20)
>>> say_hello("Emily", 19)
>>> say_hello("Dan", 17)
Hello Mark, You're 20 yrs
Hello Emily, You're 19 yrs
Hello Dan, You're 17 yrs
Enter fullscreen mode Exit fullscreen mode

The above arguments are referred to as positional arguments because they are required to be in the proper position.

>>> def say_hello(name, age):
>>>    print(f"Hello {name}, You're {age} yrs")
>>> say_hello(20, "Abdikhafar")
Hello 20, You're Abdikhafar yrs
Enter fullscreen mode Exit fullscreen mode

Default Parameters and Keyword Arguments
Keyword arguments, as opposed to positional arguments, allow us to not worry about the position hence the arguments can be in any position.
However this makes the code more complicated and not a proper practice way.

>>> def say_hello(name, age):
>>>    print(f"Hello {name}, You're {age} yrs")
>>> say_hello(age = 20, name = "Abdikhafar")
Hello Abdikhafar, You're 20 yrs
Enter fullscreen mode Exit fullscreen mode

Default parameters allow us to give constant values as we define the function. Default parameters only work when no values have been passed as arguments to the function.

>>> def say_hello(name = "Emily", age = 17):
>>>    print(f"Hello {name}, You're {age} yrs")
>>> say_hello()
Hello Emily, You're 17 yrs
Enter fullscreen mode Exit fullscreen mode
>>> def say_hello(name = "Emily", age = 17):
>>>    print(f"Hello {name}, You're {age} yrs")
>>> say_hello("Dan", 23)
>>> say_hello()
Hello Dan, You're 23 yrs
Hello Emily, You're 17 yrs

>>> def say_hello(name = "Emily", age = 17):
>>>    print(f"Hello {name}, You're {age} yrs")
>>> say_hello("Irene")
Hello Irene, You're 17 yrs
Enter fullscreen mode Exit fullscreen mode

Return Statement
This is a keyword in python mostly used together with functions.
Functions always have to return something and when there is no return statement, the function will always return None.

>>> def sum(num1, num2):
>>>    num1 + num2
>>> print(sum(4, 5))
None
Enter fullscreen mode Exit fullscreen mode

When the return statement is used;

>>> def sum(num1, num2):
>>>    return num1 + num2
>>> print(sum(4, 5))
9

Enter fullscreen mode Exit fullscreen mode

A function should do one thing really well and/or should return something. This however doesn't mean that the code only has to be one line.

>>> def sum(num1, num2):
>>>    return num1 + num2
>>> total = sum(10, 5)
>>> print(sum(10, total))
25

>>> def sum(num1, num2):
>>>    return num1 + num2
>>> print(sum(10, sum(10, 5)))
25

>>> def sum(num1, num2):
>>>    def another_func(num1, num2):
>>>       return num1 + num2
>>> total = sum(10, 20)
>>> print(total)
None

def sum(num1, num2):
   def another_func(num1, num2):
      return num1 + num2
   return another_func
total = sum(10, 20)
print(total)
<function sum.<locals>.another_func at 0x000002387BF49B40>

>>> def sum(num1, num2):
>>>    def another_func(num1, num2):
>>>       return num1 + num2
>>>    return another_func
>>> total = sum(10, 20)
>>> print(total(10, 20))
30

>>> def sum(num1, num2):
>>>    def another_func(num1, num2):
>>>       return num1 + num2
>>>    return another_func(num1, num2)
>>> total = sum(10, 20)
>>> print(total)
30
Enter fullscreen mode Exit fullscreen mode

To avoid confusion when working with more than one function (function in a function), it is advisable to use different names for the parameter.

>>> def sum(num1, num2):
>>>    def another_func(n1, n2):
>>>       return num1 + num2
>>>    return another_func(num1, num2)
>>> total = sum(10, 20)
>>> print(total)
30
Note: A return keyword automatically exits the function in that any code (to output) below the return statement is never run.
>>> def sum(num1, num2):
>>>    def another_func(n1, n2):
>>>       return num1 + num2
>>>    return another_func(num1, num2)
>>>    return 5
>>>    print("Hello")
>>> total = sum(10, 20)
>>> print(total)
30
Enter fullscreen mode Exit fullscreen mode

Methods Vs Functions
Examples of inbuilt functions in python include : list(), print(), max(), min(), input(). We've also found out that we can use the keyword def to define our own functions.

When using methods, we use the dot(.) notation. Methods are owned by 'whatever is to the left of the dot(.)' be it strings, tuples, integers etc. Methods of the fundamental data types have been covered in the previous module where all the data types have been covered too.
Both Functions and Methods allow us to take actions on the data types.
None: Similar to functions, we can also build our own methods which will be explained in details in the next module as we discuss on 'classes and objects'.

Docstrings
This is using triple quotes ('''...''') to 'comment' multiple lines. It can also be used in functions to give more info about a function.

Image description

It works the same as the more info about a function provided by the developer environments when typing the inbuilt functions.

Image description

Help function
Used to give more info about a function. When a docstring is passed in a function, the help function returns the docstring.

>>> help(print)

print(...)
    print(value, ..., sep=' ', end='\n', file=sys.stdout, flush=False)
Enter fullscreen mode Exit fullscreen mode
Prints the values to a stream, or to sys.stdout by default.
Optional keyword arguments:
file:  a file-like object (stream); defaults to the current sys.stdout.
sep:   string inserted between values, default a space.
end:   string appended after the last value, default a newline.
flush: whether to forcibly flush the stream.
Enter fullscreen mode Exit fullscreen mode

>>> def test(a):
       '''
       Info: This is a function that prints the testing data.
       '''
       print(a)
>>> help(test)
test(a)
Enter fullscreen mode Exit fullscreen mode
Info: This is a function that prints the testing data.
Enter fullscreen mode Exit fullscreen mode

We can also use the dunder method (Magic method) 'will get into it later in the course' to get more info on a function.

>>> def test(a):
       '''
       Info: This is a function that prints the testing data.
       '''
       print(a)
>>>print(test.__doc___)
Enter fullscreen mode Exit fullscreen mode

Info: This is a function that prints the testing data.
Docstrings are really useful to add comments and definition to a function to enable other people understand what your function does without searching through your multiple files.

Writing clean code

>>> def is_even(num):
>>>    if num % 2 == 0:
>>>      return True
>>>    elif num % 2 != 0:
>>>      return False
>>> print(is_even(50))
True
Vs
>>> def is_even(num):
>>>    if num % 2 == 0:
>>>      return True
>>>    else:
>>>      return False
>>> print(is_even(50))
True
Vs
>>> def is_even(num):
>>>    if num % 2 == 0:
>>>      return True
>>>    return False
>>> print(is_even(50))
True
Vs
>>> def is_even(num):
>>>    return num % 2 == 0
>>> print(is_even(50))
True
Enter fullscreen mode Exit fullscreen mode

*args and **kwargs
*args - arguments
**kwargs - Keyword arguments

In function we have special characters called *args and **kwargs.

>>> def super_func(num):
>>>    return sum(num)
>>> super_func(1, 2, 3, 4, 5)
Enter fullscreen mode Exit fullscreen mode

returns an error because the function should take just 1 positional argument but we gave 5.

_args (adding an asterisk at the start of the parameter) allows one to pass more than one positional argument.

>>> def super_func(*num):
>>>    print(*num)
>>>    return sum(num)
>>> super_func(1, 2, 3, 4, 5)
1 2 3 4 5

>>> def super_func(*num):
>>>    print(num)
>>>    return sum(num)
>>> super_func(1, 2, 3, 4, 5)
(1 2 3 4 5)

>>> def super_func(*num):
>>>    return sum(num)
>>> print(super_func(1, 2, 3, 4, 5))
15
Enter fullscreen mode Exit fullscreen mode

__kwargs allows us to use keyword arguments. It returns a dictionary of the values.

>>> def super_func(*num, **kwargs):
>>>    print(kwargs)
>>> super_func(1, 2, 3, 4, 5, num1=10, num2=15)
{'num1': 10, 'num2': 15}

>>> def super_func(*num, **kwargs):
>>>    print(kwargs)
>>>    total = 0
>>>     for items in kwargs.values():
>>>        total += items
>>>     return sum(num) + total
>>> print(super_func(1, 2, 3, 4, 5, num1=10, num2=15))
{'num1': 10, 'num2': 15}
40
Enter fullscreen mode Exit fullscreen mode

There's a rule of positioning when one is using parameters, default parameters, *args and **kwargs:

parameters, *args, default parameters, **kwargs

>>> def super_func(name, *num, greet="hi", **kwargs):
>>>    print(kwargs)
>>>    total = 0
>>>     for items in kwargs.values():
>>>        total += items
>>>     return sum(num) + total
>>> print(super_func("Andy", 1, 2, 3, 4, 5, num1=10, num2=15))
{'num1': 10, 'num2': 15}
40
Enter fullscreen mode Exit fullscreen mode


>>> my_list = [10, 2, 3, 4, 8, 11]
>>> even_list = []
>>> def highest_even():
>>>    for i in my_list:
>>>       if i % 2 == 0:
>>>          even_list.append(i)
>>>          even_list.sort()
>>>    print(even_list[-1])
>>> highest_even()
10
We can also use the max keyword to return the maximum even number:
>>> my_list = [10, 2, 3, 4, 8, 11]
>>> def highest_even():
>>>    even_list = []
>>>    for i in my_list:
>>>       if i % 2 == 0:
>>>          even_list.append(i)
>>>    return max(even_list)
>>> print(highest_even())
10

Scope
"What variables do I have access to?"
>>> print(name)
#returns an error; NameError: name 'name' is not defined
Once a variable is not defined, one cannot have access to it.
A variable with a global scope is a variable that can be accessed by anybody in the file and can be used anywhere; in the conditional logic; in the while loop; in a function, etc.

Enter fullscreen mode Exit fullscreen mode

total = 100
print(total)
10


0

#total has a global scope
A variable with a functional scope is a variable that can only be accessed in within the function.

Enter fullscreen mode Exit fullscreen mode

def some_func():
total = 100
print(total)


#returns an error; NameError: name 'total' is not defined

Enter fullscreen mode Exit fullscreen mode

def some_func():
total = 100
print(total)
100


Scope Rules
"What would you expect the output of the code below to be?"

Enter fullscreen mode Exit fullscreen mode

a = 1
def confusion():
a = 5
return a
print(a)
print(confusion())


The output will be 1 and 5. This is because the first print function, print(a), outputs the value stored in the a with the global scope while the second print function, print(confusion()), returns the value stored in the a variable in the function.
The a is not modified after the function because of scope.

Rules the interpreter follow in scope:

1. Local Scope - Scope within the functional scope.

Enter fullscreen mode Exit fullscreen mode

a = 1
def confusion():
a = 5
return a
print(confusion())
print(a)
5
1



Enter fullscreen mode Exit fullscreen mode

a = 1
def confusion():
return a
print(confusion())
print(a)
1
1


2. Parent Local scope - Works where there's a function within a function.

Enter fullscreen mode Exit fullscreen mode

a = 1
def parent():
a = 10
def confusion():
return a
return confusion()
print(parent())
print(a)
10
1


3. Global scope

Enter fullscreen mode Exit fullscreen mode

a = 1
def parent():
def confusion():
return a
return confusion()
print(parent())
print(a)
1
1


4. Built in python functions

Enter fullscreen mode Exit fullscreen mode

a = 1
def parent():
def confusion():
return sum
return confusion()
print(parent())
print(a)

1


Note: Parameters are part of the local scope:

Enter fullscreen mode Exit fullscreen mode

b = 10
def confusion(b):
print(b)
confusion(300)
300

b = 10
def confusion(b):
print(b)
confusion(b)
10


Global keyword
"What if one wants to refer to a global variable while in the function without creating a new variable?"
Example:

Enter fullscreen mode Exit fullscreen mode

total = 0
def count():
total += 1
return total
print(count())


#returns an error; UnboundLocalError: local variable 'total' referenced before assignment
The error occurs because the function count does not recognize total. Hence, we have to add a variable total in the function:

Enter fullscreen mode Exit fullscreen mode

total = 0
def count():
total = 0
total += 1
return total
print(count())
1


To avoid recreating another variable in the function we can use the global keyword to tell the interpreter that we want to use the global scope on the variable in the function.

Enter fullscreen mode Exit fullscreen mode

total = 0
def count():
global total
total += 1
return total
print(count())
1


Enter fullscreen mode Exit fullscreen mode

total = 0
def count():
global total
total += 1
return total
count()
count()
print(count())
3


Dependency injection (A simplified version of global scope)

Enter fullscreen mode Exit fullscreen mode

total = 0
def count(total):
total += 1
return total
print(count(total))
1

total = 0
def count(total):
total += 1
return total
print(count(count(count(total))))
3


Nonlocal Keyword
This is a new keyword(feature) in python 3 and its used to refer to the parent local scope.

Enter fullscreen mode Exit fullscreen mode

def outer():
x = "local"
def inner():
nonlocal x
x = "nonlocal"
print("inner:", x)
inner()
print("outer:", x)
outer()
inner: nonlocal
outer: nonlocal


#Without the nonlocal keyword

Enter fullscreen mode Exit fullscreen mode

def outer():
x = "local"
def inner():
x = "nonlocal"
print("inner:", x)
inner()
print("outer:", x)
outer()
inner: nonlocal
outer: local


Why do we need scope?

"Why not just have every variable with a global scope so that everything has access to everything?"
Machines don't have infinite power/memory hence we need to be cautious of the resources we use. Scope is a good demonstration of this.
Eg. When a function is run, we create one memory space hence when for instance we use nonlocal, we instead of creating another memory space, we use an existing one.

Hurraay!! 🥳
We come to the end of the second module. Hope you've learnt a lot and ready to implement the skills in different fields.
As we wrap up, here is a simple exercise for you to try before checking the answer done below:

Enter fullscreen mode Exit fullscreen mode

age = input("What is your age?: ")
if int(age) < 18:
print("Sorry, you are too young to drive this car. Powering off!")
elif int(age) > 18:
print("Powering On. Enjoy the ride!");
else:
print("Congratulations on your first year of driving. Enjoy the ride!")


Given the code above, perform the actions below on the code:

Wrap the above code in a function called checkDriverAge() that whenever you call this function, you will get prompted for age.

Instead of using the input(), make the checkDriverAge() function accept an argument of age, so that if you enter eg. checkDriverAge(92), it returns "Powering On. Enjoy the ride!"

Make the default age set to 0 if no argument is given.

Answer:

Enter fullscreen mode Exit fullscreen mode

def checkDriverAge(age=0):
if int(age) < 18:
print("Sorry, you are too young to drive this car. Powering off")
elif int(age) > 18:
print("Powering On. Enjoy the ride!");
else:
print("Congratulations on your first year of driving. Enjoy the ride!")
checkDriverAge(10)
Sorry, you are too young to drive this car. Powering off




What next? 🤔
Let's meet in the next module #Advanced Python1.

Till next time; bye bye.

Enter fullscreen mode Exit fullscreen mode
. . . .
Terabox Video Player