8000 Move Advanced Python lectures and add missing code in higher order fu… · coding794/complete-python-course@446e06a · GitHub
[go: up one dir, main page]

Skip to content

Commit 446e06a

Browse files
committed
Move Advanced Python lectures and add missing code in higher order functions.
1 parent d5a1ef1 commit 446e06a

File tree

13 files changed

+1076
-0
lines changed

13 files changed

+1076
-0
lines changed
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
"""
2+
What are mutable and immutable objects?
3+
4+
An important concept in programming, and particularly in Python, is the concept of mutability. Some things in Python are mutable and some things are immutable.
5+
6+
“What is mutable?” I hear you say…
7+
8+
A mutable datum is one that you can change after it has been created. An immutable datum is one you cannot change.
9+
10+
Here’s an example of a mutable datum:
11+
"""
12+
13+
friends_last_seen = {
14+
'Rolf': 31,
15+
'Jen': 1,
16+
'Anne': 7
17+
}
18+
19+
"""
20+
How do you know it’s mutable?
21+
22+
Whenever you create a new dictionary, it is actually stored physically in your computer’s RAM—a type of memory that only lives while the computer is on and your app running.
23+
24+
Python can give you the `id` of the object, which is the address of the object in memory. That’s precisely the first cell of the group of cells in the RAM in which your object is stored.
25+
"""
26+
27+
print(id(friends_last_seen))
28+
29+
friends_last_seen = {
30+
'Rolf': 31,
31+
'Jen': 1,
32+
'Anne': 7
33+
}
34+
35+
print(id(friends_last_seen))
36+
37+
"""
38+
Notice how even though the dictionaries have the same values, their `id` values are different. New dictionaries were created each time, and hence their memory addresses are different.
39+
40+
The memory address depends on what memory was available at the time of creation, so it won’t be the same for any two objects which exist at the same time.
41+
42+
However, if we do this:
43+
"""
44+
45+
friends_last_seen = {
46+
'Rolf': 31,
47+
'Jen': 1,
48+
'Anne': 7
49+
}
50+
51+
print(id(friends_last_seen))
52+
53+
friends_last_seen['Anne'] = 20
54+
55+
print(id(friends_last_seen))
56+
57+
"""
58+
We can see that the `id` did not change, even though we changed a value in that dictionary.
59+
60+
The dictionary was mutated. 🎉
61+
62+
There are only a few immutable things in Python:
63+
64+
* integers
65+
* floats
66+
* strings
67+
* tuples
68+
69+
That’s pretty much it. All the other types are mutable.
70+
71+
Here’s why numbers are immutable:
72+
"""
73+
74+
age = 20
75+
print(id(age))
76+
77+
"""
78+
You cannot change the 20. 20 is always 20.
79+
80+
You can change what `age` points to:
81+
"""
82+
83+
age = 30
84+
print(id(age))
85+
86+
"""
87+
But then the `id` also changes—it is now an entirely new integer.
88+
89+
It is the same with strings:
90+
"""
91+
92+
greeting = 'hello'
93+
print(id(greeting))
94+
95+
greeting = 'byebye'
96+
print(id(greeting))
97+
98+
greeting = greeting + 'a'
99+
print(id(greeting))
100+
101+
"""
102+
Tuples are also immutable because you cannot change the tuple itself:
103+
"""
104+
105+
my_tuple = (3, 5, 10)
106+
print(id(my_tuple))
107+
108+
my_tuple = (3, 5)
109+
print(id(my_tuple))
110+
111+
"""
112+
Lists however, are mutable. Once you have a list, you can add or remove elements from it. That does not create a new list object, it modifies it.
113+
"""
114+
115+
friends = ['Rolf', 'Jose']
116+
print(id(friends))
117+
118+
friends.append('Jen')
119+
print(id(friends))
120+
121+
"""
122+
Generally you can look at it like this: when you use `=`, you are likely creating a new thing—with a new `id`.
123+
124+
When you call a function, such as `append` or `__setitem__` (that’s the function that gets called when you do `my_dict[‘Rolf’]`), you’re likely mutating the object.
125+
"""
Lines changed: 114 additions & 0 deletions
F42D
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
"""
2+
Passing by value, reference, and assignment.
3+
4+
Let’s look at a couple examples of calling functions in Python.
5+
"""
6+
7+
friends_last_seen = {
8+
'Rolf': 20,
9+
'Jose': 1,
10+
'Charlie': 9
11+
}
12+
13+
def see_friend(friends, name):
14+
friends[name] = 0
15+
16+
id(friends_last_seen)
17+
id(friends_last_seen['Rolf'])
18+
see_friend(friends_last_seen, 'Rolf')
19+
20+
id(friends_last_seen['Rolf'])
21+
id(friends_last_seen)
22+
print(friends_last_seen)
23+
24+
"""
25+
As you can see if you run that code, `friends` has the value of `friends_last_seen` when we run it—it’s the same thing exactly.
26+
27+
Same dictionary, same `id`.
28+
29+
`friends_last_seen` does not change before and after running the function, but `friends_last_seen['Rolf']` does change.
30+
31+
We used an `=` sign, and also `friends_last_seen['Rolf']` is an integer which is an immutable type.
32+
33+
If we pass an immutable type to the function though, we cannot change it. Let’s try:
34+
"""
35+
36+
age = 20
37+
38+
def increase_age(current_age):
39+
current_age = current_age + 1
40+
41+
id(age)
42+
increase_age(age)
43+
id(age)
44+
45+
"""
46+
I know, I know. We used an equal sign. Can we not use some `.increment()` function or something?
47+
48+
No…
49+
50+
Integers are objects—but they don’t have such a method.
51+
52+
So what about this?
53+
"""
54+
55+
primes = [2, 3, 5]
56+
57+
id(primes)
58+
59+
primes += [7, 11] # We know += is 'like' primes = primes + [7, 11]...
60+
61+
id(primes)
62+
63+
"""
64+
You get the same `id` back!!!
65+
66+
That’s because `+=` is not `=`.
67+
68+
When you do `=`, this happens:
69+
"""
70+
71+
primes = primes.__add__([7, 11])
72+
73+
"""
74+
When you do `+=`, this happens:
75+
"""
76+
77+
primes = primes.__iadd__([7, 11])
78+
79+
"""
80+
Thus, it’s all up to whether `__add__` and `__iadd__` create new objects or modify the existing objects. It’s easy to check their implementations by doing this:
81+
82+
83+
>>> primes = [2, 3, 5]
84+
>>> primes.__add__([7, 11])
85+
[2, 3, 5, 7, 11]
86+
87+
>>> id(primes)
88+
4366591880
89+
>>> primes
90+
[2, 3, 5]
91+
92+
>>> id(primes.__add__([7, 11]))
93+
4366592456
94+
>>> primes
95+
[2, 3, 5]
96+
>>> id(primes.__iadd__([7, 11]))
97+
4366591880
98+
>>> primes
99+
[2, 3, 5, 7, 11]
100+
101+
102+
You can see that when we did `primes.__add__`, it did not change `primes`.
103+
104+
But when we did `primes.__iadd__`, it did change `primes`. Interesting stuff!
105+
106+
---
107+
108+
Enough of this.
109+
110+
Just remember, when you pass something to a function you can mutate that thing—then the value outside the function will have changed too.
111+
112+
113+
Unless it’s immutable, then it won’t have changed.
114+
"""
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
"""
2+
Whenever we call a function that has parameters, we are expect to give it an equal number of arguments. We already know this:
3+
"""
4+
5+
accounts = {
6+
'checking': 1958.00,
7+
'savings': 3695.50
8+
}
9+
10+
def add_balance(amount: float, name: str) -> float:
11+
"""Function to update the balance of an account and return the new balance."""
12+
accounts[name] += amount
13+
return accounts[name]
14+
15+
add_balance(500.00, 'savings')
16+
17+
print(accounts['savings']) # remember, this has changed because the function mutated the dictionary!
18+
19+
"""
20+
However, what you didn’t know is that you can also have default values for arguments. For example, if you wanted the account name to always be `'checking'` unless otherwise specified, you could do so.
21+
"""
22+
23+
def add_balance(amount: float, name: str = 'checking') -> float:
24+
"""Function to update the balance of an account and return the new balance."""
25+
accounts[name] += amount
26+
return accounts[name]
27+
28+
"""
29+
Then you could call the function only with an amount:
30+
"""
31+
32+
add_balance(500.00) # goes into `'checking'` by default
33+
34+
"""
35+
*Important*: arguments with default values must go after arguments without default values. This would be incorrect in Python (type hinting removed to make the point):
36+
"""
37+
38+
# def add_balance(name = 'checking', amount)
39+
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
"""
2+
Here’s a common pitfall in Python that you definitely want to avoid: mutable default arguments.
3+
4+
Take this function:
5+
"""
6+
7+
def create_account(name: str, holder: str, account_holders: list = []):
8+
account_holders.append(holder)
9+
10+
return {
11+
'name': name,
12+
'main_account_holder': holder,
13+
'account_holders': account_holders,
14+
}
15+
16+
17+
a1 = create_account('checking', 'Rolf')
18+
a2 = create_account('savings', 'Anne')
19+
20+
print(a2)
21+
22+
"""
23+
What’s happened? Both Rolf and Anne are named account holders in the savings account!?
24+
25+
The default parameter for the `create_account` function gets evaluated when the function is defined, *not when the function is called*.
26+
27+
Because we’re modifying the parameter inside the function, the next time we call it the parameter is still the list that has been appended to.
28+
29+
There are two ways to solve this problem.
30+
31+
1. Don’t have `account_holders` as a default argument; or
32+
2. Have it as a default argument, but not a mutable one.
33+
"""
34+
35+
## 1: no default argument
36+
"""
37+
In this option we always must pass in an (empty or otherwise) list to the function call. It makes things explicit, but it means we may require a lot of empty lists being passed in there.
38+
"""
39+
40+
def create_account(name: str, holder: str, account_holders: list):
41+
account_holders.append(holder)
42+
43+
return {
44+
'name': name,
45+
'main_account_holder': holder,
46+
'account_holders': account_holders,
47+
}
48+
49+
a1 = create_account('checking', 'Rolf', [])
50+
a2 = create_account('savings', 'Anne', [])
51+
52+
print(a2)
53+
54+
## 2: non-mutable default argument
55+
"""
56+
In this option we have a default value of `None`, so that we don’t have to pass a list of account holders.
57+
58+
If it is `None`, we initialise a new list.
59+
"""
60+
61+
def create_account(name: str, holder: str, account_holders = None):
62+
if not account_holders:
63+
account_holders = []
64+
account_holders.append(holder)
65+
66+
return {
67+
'name': name,
68+
'main_account_holder': holder,
69+
'account_holders': account_holders,
70+
}
71+
72+
a1 = create_account('checking', 'Rolf')
73+
a2 = create_account('savings', 'Anne')
74+
75+
print(a2)

0 commit comments

Comments
 (0)
0