Skip to content

Commit 0b6e8da

Browse files
committed
Add section on mutability to lesson 4
1 parent 709272d commit 0b6e8da

File tree

1 file changed

+190
-90
lines changed

1 file changed

+190
-90
lines changed

lessons/04-function-definitions/function_definitions.ipynb

+190-90
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@
148148
},
149149
{
150150
"cell_type": "code",
151-
"execution_count": 2,
151+
"execution_count": null,
152152
"id": "8e60bf90",
153153
"metadata": {},
154154
"outputs": [],
@@ -173,7 +173,7 @@
173173
},
174174
{
175175
"cell_type": "code",
176-
"execution_count": 3,
176+
"execution_count": null,
177177
"id": "eee490d3",
178178
"metadata": {},
179179
"outputs": [],
@@ -530,163 +530,263 @@
530530
},
531531
{
532532
"cell_type": "markdown",
533-
"id": "b6700428",
533+
"id": "5a6efcb7",
534534
"metadata": {},
535535
"source": [
536-
"## Iterating over dictionaries\n",
536+
"# Mutability\n",
537537
"\n",
538-
"We can iterate over dictionaries by calling the dictionary's `.items()` method. It returns an iterable of tuples, with each tuple containing a key and its value:"
538+
"**Mutability** refers to the concept of what values in a program can change. An object in Python is **mutable** if it can be changed, and **immutable** if it cannot. We've already discussed the fact that tuples are immutable. Strings and numbers are also immutable. If you assign a number to a variable, it's always the same number. If you later assign a different number to the variable, the variable has changed, but the number hasn't."
539539
]
540540
},
541541
{
542542
"cell_type": "code",
543543
"execution_count": null,
544-
"id": "a9e25ce5",
544+
"id": "b4448fc8",
545545
"metadata": {},
546546
"outputs": [],
547547
"source": [
548-
"prices = {\n",
549-
" 'apple': 0.99,\n",
550-
" 'orange': 1.29,\n",
551-
" 'watermelon': {'one': 1.79, 'two': 2.99},\n",
552-
"}\n",
548+
"x = 42\n",
549+
"print(x)\n",
553550
"\n",
554-
"for key, value in prices.items():\n",
555-
" print(f'Key is {key}, value is {value}')"
551+
"# x.add(1) # This isn't possible, we can't change the number\n",
552+
"\n",
553+
"# We still have a variable named x, it's the same variable, but now\n",
554+
"# it refers to a different value.\n",
555+
"x = 50\n",
556+
"print(x)\n",
557+
"\n",
558+
"# Now we've added 10 to x, which creates a \"new\" number, that\n",
559+
"# is assigned to x.\n",
560+
"x = x + 10\n",
561+
"print(x)"
556562
]
557563
},
558564
{
559565
"cell_type": "markdown",
560-
"id": "31ebc9af",
566+
"id": "125fd6f2",
561567
"metadata": {},
562568
"source": [
563-
"## Adding default arguments\n",
564-
"\n",
565-
"A popular pattern when writing Python code is to use keyword arguments to introduce new features to a function without having to update all of the existing places where it is called."
569+
"42, 50 and 60 are all different numbers. The variable `x` holds each of them in turn."
570+
]
571+
},
572+
{
573+
"cell_type": "markdown",
574+
"id": "a78eb419",
575+
"metadata": {},
576+
"source": [
577+
"Strings may \"appear\" to be mutable, but methods on strings that seem to modify the string actually return a new string."
566578
]
567579
},
568580
{
569581
"cell_type": "code",
570582
"execution_count": null,
571-
"id": "1ea57c5f",
583+
"id": "dd1bbd4e",
572584
"metadata": {},
573585
"outputs": [],
574586
"source": [
575-
"def find_job(database, cpu):\n",
576-
" workers = []\n",
577-
" for name, cycles in database.items():\n",
578-
" if cycles >= cpu:\n",
579-
" workers.append(name)\n",
580-
" return workers\n",
581-
" \n",
582-
"def find_increasing_jobs(database):\n",
583-
" candidates = {}\n",
584-
" for i in range(0, 100, 10):\n",
585-
" candidates[i] = find_job(database, i)\n",
586-
" return candidates\n",
587-
" \n",
588-
"db = {\n",
589-
" 'alpha': 45,\n",
590-
" 'beta': 55,\n",
591-
" 'gamma': 91,\n",
592-
" 'phi': 27,\n",
593-
"}\n",
594-
"\n",
595-
"data = find_increasing_jobs(db)\n",
596-
"print(data)"
587+
"p = 'pineapple'\n",
588+
"p.capitalize()\n",
589+
"print(p)"
597590
]
598591
},
599592
{
600593
"cell_type": "markdown",
601-
"id": "7801a176",
594+
"id": "8c5f58d8",
602595
"metadata": {},
603596
"source": [
604-
"We can add an argument for only returning the first job that meets our criteria. The main thing here to consider is that the default value of the argument should match the behavior before we modified the code. Here we introduce the `first_only` keyword argument, and set it to `False` because the old version of the function behaved as if this value was `False`."
597+
"The code `p.capitalize()` returns a new version of the string with it capitalized. We have to assign it to a variable or otherwise use it in an expression for it to do anything."
605598
]
606599
},
607600
{
608601
"cell_type": "code",
609602
"execution_count": null,
610-
"id": "229bc790",
603+
"id": "31933868",
611604
"metadata": {},
612605
"outputs": [],
613606
"source": [
614-
"def find_job(database, cpu, first_only=False):\n",
615-
" workers = []\n",
616-
" for name, cycles in database.items():\n",
617-
" if cycles >= cpu:\n",
618-
" workers.append(name)\n",
619-
" if first_only:\n",
620-
" break\n",
621-
" return workers\n",
622-
"\n",
623-
"def find_first_increasing_jobs(database):\n",
624-
" candidates = {}\n",
625-
" for i in range(0, 100, 10):\n",
626-
" candidates[i] = find_job(database, i, first_only=True)\n",
627-
" return candidates\n",
628-
"\n",
629-
"data = find_increasing_jobs(db)\n",
630-
"print(data)\n",
631-
"\n",
632-
"print('===')\n",
633-
"\n",
634-
"data2 = find_first_increasing_jobs(db)\n",
635-
"print(data2)"
607+
"p = 'pineapple'\n",
608+
"q = p.capitalize()\n",
609+
"print(q)"
636610
]
637611
},
638612
{
639613
"cell_type": "markdown",
640-
"id": "bf69868e",
614+
"id": "0ac4580f",
641615
"metadata": {},
642616
"source": [
643-
"Given the following function definition:"
617+
"Of course, we could have also \"overwritten\" the value of `p` with the newly capitilized string."
644618
]
645619
},
646620
{
647621
"cell_type": "code",
648622
"execution_count": null,
649-
"id": "b7c3a4b1",
623+
"id": "5ac8d008",
650624
"metadata": {},
651625
"outputs": [],
652626
"source": [
653-
"def find_grocery_deals(groceries, price_point):\n",
654-
" ans = []\n",
655-
" for item, price in groceries.items():\n",
656-
" if price <= price_point:\n",
657-
" ans.append(item)\n",
658-
" return ans\n",
627+
"p = 'pineapple'\n",
628+
"p = p.capitalize()\n",
629+
"print(p)"
630+
]
631+
},
632+
{
633+
"cell_type": "markdown",
634+
"id": "36faae94",
635+
"metadata": {},
636+
"source": [
637+
"Meanwhile, data structures like lists and dictionaries are mutable. We can modify them with methods like `append()`."
638+
]
639+
},
640+
{
641+
"cell_type": "code",
642+
"execution_count": 4,
643+
"id": "1dc8e9c3",
644+
"metadata": {},
645+
"outputs": [
646+
{
647+
"name": "stdout",
648+
"output_type": "stream",
649+
"text": [
650+
"['apple', 'lime', 'pear', 'kiwi']\n"
651+
]
652+
}
653+
],
654+
"source": [
655+
"fruits = ['apple', 'lime', 'pear']\n",
656+
"fruits.append('kiwi')\n",
657+
"print(fruits)"
658+
]
659+
},
660+
{
661+
"cell_type": "markdown",
662+
"id": "0c2ede6f",
663+
"metadata": {},
664+
"source": [
665+
"If two variables refer to the same list, changes to the list will be reflected in both."
666+
]
667+
},
668+
{
669+
"cell_type": "code",
670+
"execution_count": 5,
671+
"id": "a3535ed3",
672+
"metadata": {},
673+
"outputs": [
674+
{
675+
"name": "stdout",
676+
"output_type": "stream",
677+
"text": [
678+
"['apple', 'lime', 'pear', 'kiwi']\n"
679+
]
680+
}
681+
],
682+
"source": [
683+
"fruits = ['apple', 'lime', 'pear']\n",
684+
"other_fruits = fruits\n",
685+
"fruits.append('kiwi')\n",
686+
"print(other_fruits)"
687+
]
688+
},
689+
{
690+
"cell_type": "markdown",
691+
"id": "65f2f7dc",
692+
"metadata": {},
693+
"source": [
694+
"This means that if we pass a list to a funciton, and the function modifies the list, we will see those changes in the place where we called the function."
695+
]
696+
},
697+
{
698+
"cell_type": "code",
699+
"execution_count": 7,
700+
"id": "3069413b",
701+
"metadata": {},
702+
"outputs": [
703+
{
704+
"name": "stdout",
705+
"output_type": "stream",
706+
"text": [
707+
"['apple', 'lime', 'pear']\n",
708+
"['apple', 'lime', 'pear', 'kiwi']\n"
709+
]
710+
}
711+
],
712+
"source": [
713+
"def add_kiwi(fruits):\n",
714+
" fruits.append('kiwi')\n",
659715
"\n",
660-
"safeway = {\n",
661-
" 'apples': 1.29,\n",
662-
" 'limes': 0.79,\n",
663-
" 'toilet paper': 2.99,\n",
664-
" 'chicken': 4.99,\n",
665-
" 'beans': 1.79,\n",
666-
"}\n",
716+
"fruit_list = ['apple', 'lime', 'pear']\n",
717+
"print(fruit_list)\n",
718+
"add_kiwi(fruit_list)\n",
719+
"print(fruit_list)"
720+
]
721+
},
722+
{
723+
"cell_type": "markdown",
724+
"id": "73447721",
725+
"metadata": {},
726+
"source": [
727+
"Not so for numbers."
728+
]
729+
},
730+
{
731+
"cell_type": "code",
732+
"execution_count": 6,
733+
"id": "e5fa2b9a",
734+
"metadata": {},
735+
"outputs": [
736+
{
737+
"name": "stdout",
738+
"output_type": "stream",
739+
"text": [
740+
"42\n",
741+
"42\n"
742+
]
743+
}
744+
],
745+
"source": [
746+
"def add_ten(x):\n",
747+
" # We only reassigned the value of the variable x, we did not\n",
748+
" # modify the number `42`, because the number is immutable.\n",
749+
" x = x + 10\n",
667750
"\n",
668-
"result = find_grocery_deals(safeway, 2.00)\n",
669-
"print(result)"
751+
"y = 42\n",
752+
"print(y)\n",
753+
"add_ten(y)\n",
754+
"print(y)\n"
670755
]
671756
},
672757
{
673758
"cell_type": "markdown",
674-
"id": "9fd18c5d",
759+
"id": "b48eb1a1",
675760
"metadata": {},
676761
"source": [
677-
"Try to extend the `find_grocery_deals` function in a *backwards compatible way*, so that it can skip any items that are in the fruit list:"
762+
"It may seem that you have to memorize that numbers just kind of behave differently than lists. But that's not the case! The real difference between those two blocks of code is that `fruits.append('kiwi')` is calling the `.append()` method on the list directly, which modifies the list. The code `x = x + 10` only modifies _which value x is refering to_ (52 instead of 42), it does not modify the number `42`. The actual objects and data \"in memory\" are different than the variables that refer to them."
763+
]
764+
},
765+
{
766+
"cell_type": "markdown",
767+
"id": "b6700428",
768+
"metadata": {},
769+
"source": [
770+
"## Iterating over dictionaries\n",
771+
"\n",
772+
"We can iterate over dictionaries by calling the dictionary's `.items()` method. It returns an iterable of tuples, with each tuple containing a key and its value:"
678773
]
679774
},
680775
{
681776
"cell_type": "code",
682777
"execution_count": null,
683-
"id": "9b1584de",
778+
"id": "a9e25ce5",
684779
"metadata": {},
685780
"outputs": [],
686781
"source": [
687-
"fruits = ['apples', 'limes']\n",
782+
"prices = {\n",
783+
" 'apple': 0.99,\n",
784+
" 'orange': 1.29,\n",
785+
" 'watermelon': {'one': 1.79, 'two': 2.99},\n",
786+
"}\n",
688787
"\n",
689-
"def find_grocery_deals(groceries, price_point, ???)"
788+
"for key, value in prices.items():\n",
789+
" print(f'Key is {key}, value is {value}')"
690790
]
691791
},
692792
{
@@ -1089,7 +1189,7 @@
10891189
],
10901190
"metadata": {
10911191
"kernelspec": {
1092-
"display_name": "Python 3 (ipykernel)",
1192+
"display_name": "PythonClass-OhLRe-cl",
10931193
"language": "python",
10941194
"name": "python3"
10951195
},
@@ -1103,7 +1203,7 @@
11031203
"name": "python",
11041204
"nbconvert_exporter": "python",
11051205
"pygments_lexer": "ipython3",
1106-
"version": "3.12.0"
1206+
"version": "3.11.9"
11071207
}
11081208
},
11091209
"nbformat": 4,

0 commit comments

Comments
 (0)