From daa59d0f01cb3a48d1cd4be14a4147ed88f03159 Mon Sep 17 00:00:00 2001
From: jrasero
Date: Tue, 8 Oct 2024 10:06:34 -0400
Subject: [PATCH] Update documentation
---
.../chapters/module-2/In-class_100324.ipynb | 95 +++
.../module-2/In-class_100324_sols.ipynb | 247 ++++++
.../module-3/031-errors_and_exceptions.ipynb | 8 +-
_sources/chapters/module-3/032-classes.ipynb | 722 ++++++++++++++----
chapters/module-2/In-class_100324.html | 482 ++++++++++++
chapters/module-2/In-class_100324_sols.html | 600 +++++++++++++++
.../module-3/031-errors_and_exceptions.html | 19 +-
chapters/module-3/032-classes.html | 488 +++++++++---
genindex.html | 1 +
index.html | 1 +
objects.inv | Bin 1059 -> 1080 bytes
reports/chapters/module-3/032-classes.err.log | 28 +
search.html | 1 +
searchindex.js | 2 +-
14 files changed, 2440 insertions(+), 254 deletions(-)
create mode 100644 _sources/chapters/module-2/In-class_100324.ipynb
create mode 100644 _sources/chapters/module-2/In-class_100324_sols.ipynb
create mode 100644 chapters/module-2/In-class_100324.html
create mode 100644 chapters/module-2/In-class_100324_sols.html
create mode 100644 reports/chapters/module-3/032-classes.err.log
diff --git a/_sources/chapters/module-2/In-class_100324.ipynb b/_sources/chapters/module-2/In-class_100324.ipynb
new file mode 100644
index 0000000..be4804d
--- /dev/null
+++ b/_sources/chapters/module-2/In-class_100324.ipynb
@@ -0,0 +1,95 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "id": "6b64190e-5dce-4574-ad0d-ddb5a5a2cc00",
+ "metadata": {},
+ "source": [
+ "## Instructions \n",
+ "* Complete Jupyter Notebook for tasks. Clearly show relevant work.\n",
+ "\n",
+ "* Before beginning to fill in the notebook, make sure you have written down your name in the first cell, as well as of any collaborators in case you have worked in groups.\n",
+ "\n",
+ "* Before you turn this problem in, make sure everything runs as expected. First, **restart the kernel** (in the menubar, select Kernel $\\rightarrow$ Restart) and then **run all cells** (in the menubar, select Cell $\\rightarrow$ Run All).\n",
+ "\n",
+ "* Make sure your changes have been changed and when ready, submit the jupyter notebook through Canvas. "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 29,
+ "id": "91980179-e320-4362-8941-3ef911ce7671",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "NAME = \"\"\n",
+ "COLLABORATORS = \"\""
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "332218e0-c6bc-4c48-9fc6-7bf2294a273b",
+ "metadata": {},
+ "source": [
+ "---"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "f08804f8-a3cb-4f48-82b2-068bb2c347ea",
+ "metadata": {},
+ "source": [
+ "**1. The following function aims at returning whether a given list of numbers contains at least one element divisible by 7, but it fails due to a bug. You can test this failing behaviour by passing, for example, the list: [10, 7, 13].**\n",
+ "\n",
+ "**Redefine the function correcting the bug. Test that it behaves correctly with several examples. In addition, in a separate markdown cell, explain why the function was failing. (3 points)**"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "id": "ee7b6179-eca2-4fae-89b6-c2bc5db5863e",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def has_atleast_one_seven(nums):\n",
+ " \"\"\"Return whether the given list of numbers is lucky. A lucky list contains\n",
+ " at least one number divisible by 7.\n",
+ " \"\"\"\n",
+ " for num in nums:\n",
+ " if num % 7 == 0:\n",
+ " return True\n",
+ " else:\n",
+ " return False"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "70fed6c8-c03a-4b5d-ba84-20f33a4c9870",
+ "metadata": {},
+ "source": [
+ "**2. Using a for loop and and if statements, print all the numbers between 10 and 1000 (including both sides) that are divisible by 7 and the sum of their digits is greater than 10, but only if the number itself is also odd. (2 points)**"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3 (ipykernel)",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.11.9"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/_sources/chapters/module-2/In-class_100324_sols.ipynb b/_sources/chapters/module-2/In-class_100324_sols.ipynb
new file mode 100644
index 0000000..8555626
--- /dev/null
+++ b/_sources/chapters/module-2/In-class_100324_sols.ipynb
@@ -0,0 +1,247 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "id": "6b64190e-5dce-4574-ad0d-ddb5a5a2cc00",
+ "metadata": {},
+ "source": [
+ "## Instructions \n",
+ "* Complete Jupyter Notebook for tasks. Clearly show relevant work.\n",
+ "\n",
+ "* Before beginning to fill in the notebook, make sure you have written down your name in the first cell, as well as of any collaborators in case you have worked in groups.\n",
+ "\n",
+ "* Before you turn this problem in, make sure everything runs as expected. First, **restart the kernel** (in the menubar, select Kernel $\\rightarrow$ Restart) and then **run all cells** (in the menubar, select Cell $\\rightarrow$ Run All).\n",
+ "\n",
+ "* Make sure your changes have been changed and when ready, submit the jupyter notebook through Canvas. "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 29,
+ "id": "91980179-e320-4362-8941-3ef911ce7671",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "NAME = \"\"\n",
+ "COLLABORATORS = \"\""
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "332218e0-c6bc-4c48-9fc6-7bf2294a273b",
+ "metadata": {},
+ "source": [
+ "---"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "f08804f8-a3cb-4f48-82b2-068bb2c347ea",
+ "metadata": {},
+ "source": [
+ "**1. The following function aims at returning whether a given list of numbers contains at least one element divisible by 7, but it fails due to a bug. You can test this failing behaviour by passing, for example, the list: [10, 7, 13].**\n",
+ "\n",
+ "**Redefine the function correcting the bug. Test that it behaves correctly with several examples. In addition, in a separate markdown cell, explain why the function was failing. (3 points)**"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "id": "ee7b6179-eca2-4fae-89b6-c2bc5db5863e",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def has_atleast_one_seven(nums):\n",
+ " \"\"\"Return whether the given list of numbers is lucky. A lucky list contains\n",
+ " at least one number divisible by 7.\n",
+ " \"\"\"\n",
+ " for num in nums:\n",
+ " if num % 7 == 0:\n",
+ " return True\n",
+ " else:\n",
+ " return False"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "id": "e9c98609-e356-4f40-b1e3-b81488101022",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Solution\n",
+ "def has_atleast_one_seven(nums):\n",
+ " \"\"\"Return whether the given list of numbers is lucky. A lucky list contains\n",
+ " at least one number divisible by 7.\n",
+ " \"\"\"\n",
+ " for num in nums:\n",
+ " if num % 7 == 0:\n",
+ " return True\n",
+ "\n",
+ " return False"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 6,
+ "id": "df21433c-eaec-4448-93b4-bf3fd6ce4719",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "True"
+ ]
+ },
+ "execution_count": 6,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "has_atleast_one_seven([10,1,13,1,1,1,1,6,7])"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 5,
+ "id": "2cf67aef-1518-49c4-97d2-dc888289945a",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "False"
+ ]
+ },
+ "execution_count": 5,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "has_atleast_one_seven([10,8,13,1,4,5,6,81,6])"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "15b49768-ee34-4350-9342-73a27876f36c",
+ "metadata": {},
+ "source": [
+ "The bug was that in the original function, only one iteration was performed, because of both return in the if/else, which made you exit the function after the first iteration. We only need to exit the function early if we happen to hit an element divisible by 7. If there is not, we should carry on looping the list until the end. If that happens, that's becaus we don't have any element divisible by 7, and the function should return a False."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "70fed6c8-c03a-4b5d-ba84-20f33a4c9870",
+ "metadata": {},
+ "source": [
+ "**2. Using a for loop and and if statements, print all the numbers between 10 and 1000 (including both sides) that are divisible by 7 and the sum of their digits is greater than 10, but only if the number itself is also odd. (2 points)**"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 7,
+ "id": "4480d428-ee6e-4629-b298-e0001548828e",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "49\n",
+ "77\n",
+ "119\n",
+ "147\n",
+ "175\n",
+ "189\n",
+ "245\n",
+ "259\n",
+ "273\n",
+ "287\n",
+ "329\n",
+ "357\n",
+ "371\n",
+ "385\n",
+ "399\n",
+ "427\n",
+ "455\n",
+ "469\n",
+ "483\n",
+ "497\n",
+ "525\n",
+ "539\n",
+ "553\n",
+ "567\n",
+ "581\n",
+ "595\n",
+ "609\n",
+ "623\n",
+ "637\n",
+ "651\n",
+ "665\n",
+ "679\n",
+ "693\n",
+ "707\n",
+ "735\n",
+ "749\n",
+ "763\n",
+ "777\n",
+ "791\n",
+ "805\n",
+ "819\n",
+ "833\n",
+ "847\n",
+ "861\n",
+ "875\n",
+ "889\n",
+ "903\n",
+ "917\n",
+ "931\n",
+ "945\n",
+ "959\n",
+ "973\n",
+ "987\n"
+ ]
+ }
+ ],
+ "source": [
+ "numbers = range(10, 1001) # You have to use 1001 here to include 1000\n",
+ "\n",
+ "for num in numbers:\n",
+ " if num % 7 == 0: # This condition checks if the number is divisible by 7\n",
+ " num_as_str = str(num) # convert the number to string and store this info in a new variable\n",
+ "\n",
+ " # This block now calculates the sum of the digits of num as a string \n",
+ " sum_digits = 0\n",
+ " for s in num_as_str: # This loop through its individual digit of num and update the sumation adding the iterated digit\n",
+ " sum_digits += int(s) # we have to convert the character back to an integer. \n",
+ "\n",
+ " # Now add a condition to print num, only if the sum of digits is greater than 10 and num is odd, i.e, not even.\n",
+ " if (sum_digits > 10) and (num % 2 != 0):\n",
+ " print(num)"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3 (ipykernel)",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.11.9"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/_sources/chapters/module-3/031-errors_and_exceptions.ipynb b/_sources/chapters/module-3/031-errors_and_exceptions.ipynb
index a4b1819..d1d6751 100644
--- a/_sources/chapters/module-3/031-errors_and_exceptions.ipynb
+++ b/_sources/chapters/module-3/031-errors_and_exceptions.ipynb
@@ -730,10 +730,10 @@
"\n",
"Write a function check_positive_number(num) that:\n",
"\n",
- "1 - Tries to check if a number is positive.\n",
- "2 - If the number is negative, raise a ValueError with the message 'Number must be positive'.\n",
- "3 - If no exception occurs, print 'The number is positive' inside the else block.\n",
- "4 - Ensure that, regardless of whether an exception occurs, a final message 'Check complete' is printed using a finally block\n",
+ "1 - Tries to check if a number is positive. \n",
+ "2 - If the number is negative, raise a ValueError with the message 'Number must be positive'. \n",
+ "3 - If no exception occurs, print 'The number is positive' inside the else block. \n",
+ "4 - Ensure that, regardless of whether an exception occurs, a final message 'Check complete' is printed using a finally block.\n",
"\n",
"```"
]
diff --git a/_sources/chapters/module-3/032-classes.ipynb b/_sources/chapters/module-3/032-classes.ipynb
index 81798cd..300da3e 100644
--- a/_sources/chapters/module-3/032-classes.ipynb
+++ b/_sources/chapters/module-3/032-classes.ipynb
@@ -5,17 +5,33 @@
"id": "1d579d43",
"metadata": {},
"source": [
- "# Object-Oriented Programming (OOP)\n",
+ "# Introduction to object-oriented programming (OOP)\n",
+ "\n",
+ "What you will learn in this lesson:\n",
+ "\n",
+ "- Classes and objects\n",
+ "- Attributes and methods\n",
+ "- Inheritance"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "b19c96a2-603f-4fef-992d-cf5105e65cba",
+ "metadata": {},
+ "source": [
+ "## Introduction \n",
"\n",
"Python is an **object-oriented** programming (OOP) language. The object-oriented programming is kind of paradigm that helps you group state (attributes) and behavior (methods) together in handy packets of functionality. It also allows for useful specialized mechanisms such as inheritance.\n",
"\n",
"The fact that python is an OOP language does not mean you are force to follow this paradigm (unlike some other OOP languages). You can still do procedural programming as we have learned so far, with modules and functions. In Python, you can select the paradigm that fits best for your your purposes, or even mix paradigms.\n",
"\n",
- "- Classes and objects\n",
- "- Attributes and methods\n",
- "- Inheritance\n",
- "- Polymorphism\n",
- "- Encapsulation"
+ "OOP is about grouping DATA with the FUNCTIONS that manipulate that data and hiding HOW it manipulates it so you can MODIFY the behavior through INHERITANCE.\n",
+ "\n",
+ "Advantages of OOP programming:\n",
+ "\n",
+ "- **MAINTAINABILITY**. Object-oriented programming methods make code more maintainable. Identifying the source of errors is easier because objects are self-contained.\n",
+ "- **REUSABILITY**. Because objects contain both data and methods that act on data, objects can be thought of as self-contained black boxes. This feature makes it easy to reuse code in new systems.Messages provide a predefined interface to an object's data and functionality. With this interface, an object can be used in any context.\n",
+ "- **SCALABILITY**. Object-oriented programs are also scalable. As an object's interface provides a road map for reusing the object in new software, and provides all the information needed to replace the object without affecting other code. This way aging code can be replaced with faster algorithms and newer technology."
]
},
{
@@ -23,125 +39,228 @@
"id": "c61267a7",
"metadata": {},
"source": [
- "## 1. Classes and Objects\n",
+ "## Classes and Objects\n",
"\n",
- "**Classes** help describe/model data structures, or collections of functions centered on a particular set of tasks related to a particular type of data. A class is much like a template or schema. Classes may, but does not have to, take parameters.\n",
+ "
\n",
+ "Everything defined in Python is an object. \n",
+ "
\n",
"\n",
- "- **Attributes** - values/fields specific to each instance of a class.\n",
- "- **Methods** - functions available within and specific to a class. Methods may consume attributes or other parameters.\n",
- "- **Constructors** - the attribute structure/default values of a class.\n",
+ "You've heard this phrase quite a few times throughout the course. But what is an object?\n",
"\n",
- "**Instances** / **Objects** are actual, concrete implementations of a class with specific values.\n",
+ "> An **object** is like a special kind of data structure (like list, dictionaries, etc) that not only stores data (**attributes**) but also has functions (**methods**) that can do things with that data.\n",
"\n",
- "> \"OO is about grouping DATA with the FUNCTIONS that manipulate that data and hiding HOW it manipulates it so you can MODIFY the behavior through INHERITANCE.\"\n",
+ "Objects arise from **classes**. What is a class?\n",
"\n",
- "Advantages of OOP programming:\n",
+ "> **Classes** are much like a template or scheme for creating objects. It defines what data (**attributes**) the objects will store and what actions (**methods**) they can perform. "
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "358dca66-2d7b-423a-872b-fe95f3aa79c6",
+ "metadata": {},
+ "source": [
+ "### Defining a class"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "2f6c3f05-ccf1-44f1-8dad-ab2893df86bb",
+ "metadata": {},
+ "source": [
+ "The syntax to define a class is the following: \n",
"\n",
- "- **MAINTAINABILITY** Object-oriented programming methods make code more maintainable. Identifying the source of errors is easier because objects are self-contained.\n",
- "- **REUSABILITY** Because objects contain both data and methods that act on data, objects can be thought of as self-contained black boxes. This feature makes it easy to reuse code in new systems.Messages provide a predefined interface to an object's data and functionality. With this interface, an object can be used in any context.\n",
- "- **SCALABILITY** Object-oriented programs are also scalable. As an object's interface provides a road map for reusing the object in new software, and provides all the information needed to replace the object without affecting other code. This way aging code can be replaced with faster algorithms and newer technology."
+ "
"
]
},
{
"cell_type": "markdown",
- "id": "3df98fab",
+ "id": "e0ed8573-f700-47b1-b96b-1291bdb775ce",
"metadata": {},
"source": [
- "You can think of classes as a kind of data structure (e.g.), which can store information:"
+ "Let's break this syntax down a bit:\n",
+ "\n",
+ "- We start with the keyword `class` to let Python now we are going to define a class.\n",
+ "- Then we provide a name for the class. (Note: Pythonists like to use CamelCase naming convention for this...)\n",
+ "- A parenthesis specifying the **inheritance** of the class. Here we used `object`, which is the most basic class that all other classes inherit from. If no inheritence is specified, stick with `object`. We will come to inheritance later.\n",
+ "- We end the header of the definition with a `:`, to specify that whatever comes next belongs to the class.\n",
+ "- After that, in an indented block, we include the attributes and methods of the class."
]
},
{
"cell_type": "code",
- "execution_count": 104,
- "id": "131b3ab7",
+ "execution_count": 1,
+ "id": "b19ad091-2d1c-40ca-8bc4-d8c4d8f8c5f5",
"metadata": {},
"outputs": [],
"source": [
+ "# Example\n",
"class Animal(object):\n",
- " age = 10 #years\n",
- " height = 0.8 #cm\n",
- " origin = \"Africa\"\n",
- " sex = \"female\"\n",
- " name = \"Bob\"\n",
- " wild = True"
+ " pass"
]
},
{
"cell_type": "markdown",
- "id": "a045b7ae",
+ "id": "78e36208-a0f8-4018-a7eb-b4e6e4063299",
"metadata": {},
"source": [
- "To be able to use a class, we have to instantiate it, i.e. create an object of it. **Think of a class as the RECIPE to create a particular object.**"
+ "**Instances** / **Objects** are actual, concrete implementations of a class with specific values."
]
},
{
"cell_type": "code",
- "execution_count": 105,
- "id": "e1cb6c1c",
+ "execution_count": 2,
+ "id": "0ea12afa-44e1-42f7-967b-c21d9a0ba715",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "\n"
+ ]
+ }
+ ],
+ "source": [
+ "my_animal = Animal() # Creating a Animal object\n",
+ "print(type(my_animal)) # Show that it's a Animal object"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "7dde44cc-1045-4a58-9275-c247db0467c1",
+ "metadata": {},
+ "source": [
+ "### Attributes and methods"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "1fd07c8f-4e37-44a1-8abd-558339cb8308",
+ "metadata": {},
+ "source": [
+ "What are **attributes**?\n",
+ "\n",
+ "> **Attributes** are the values/fields specific to each instance (or object) of a class."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "id": "fbfa319d-7d66-4d47-9ebb-d7df89601c4c",
"metadata": {},
"outputs": [],
"source": [
- "my_animal = Animal()"
+ "class Animal(object):\n",
+ " age = 11 #years\n",
+ " weight = 5 # kgs\n",
+ " name = \"Kenny\"\n",
+ " wild = False"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "4d15e624-e8db-4f21-b5ce-a1ad6033c37e",
+ "metadata": {},
+ "source": [
+ "Therefore, you may think of classes as a kind of data structure (e.g. Dictionaries) to store information. \n",
+ "\n",
+ "Once you define an object, you can access its attributes using append a `.` and calling its specific attribute:"
]
},
{
"cell_type": "code",
- "execution_count": 106,
- "id": "ef1a56a1",
+ "execution_count": 4,
+ "id": "ac31ea98-47e6-43d8-80a0-333c3eb791fb",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
- "10\n",
- "Africa\n"
+ "11\n",
+ "Kenny\n"
]
}
],
"source": [
+ "my_animal = Animal() # Creating a Animal object\n",
+ "\n",
"print(my_animal.age)\n",
- "print(my_animal.origin)"
+ "print(my_animal.name)"
]
},
{
"cell_type": "markdown",
- "id": "98244111",
+ "id": "3ecd0d7c-964f-41e3-a44b-c5b2fa417ea4",
"metadata": {},
"source": [
- "The difference with data structures is that class can implement methods, which can perform an operation:"
+ "
\n",
+ "Important:. You need to create an object in order to access its attributes (and methods, as explained below). Until then, you only have a general design manual (the class), not a specific object! Think of creating an object as defining a new type of data, like integers, floats, lists or dictionaries.\n",
+ "
"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "bd8d7453-a992-4b7b-855c-d267adf9d18a",
+ "metadata": {},
+ "source": [
+ "And what are **methods**?\n",
+ "\n",
+ "> **Methods** are functions available within and specific to a class. Methods may consume attributes or other parameters."
]
},
{
"cell_type": "code",
- "execution_count": 107,
- "id": "5e3aa41d",
+ "execution_count": 6,
+ "id": "24719571-398a-46ca-9eab-1d7837956a9e",
"metadata": {},
"outputs": [],
"source": [
"class Animal(object):\n",
" \n",
- " age = 10 #years\n",
- " height = 0.8 #cm\n",
- " name = \"Bob\"\n",
- " wild = True\n",
+ " age = 11 #years\n",
+ " weight = 5 # kgs\n",
+ " name = \"Kenny\"\n",
+ " wild = False\n",
" \n",
" def greet(self):\n",
- " #print(f\"Greeting human. I am an animal of {self.age} of age and {self.height} tall.\") \n",
- " print(f\"Greetings human. I am an animal\") "
+ " print(f\"Greetings, human. I am an animal.\") "
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "8b0ab7d9-b83a-4b59-860b-25c99f824282",
+ "metadata": {},
+ "source": [
+ "So the difference with typical data structures is that classes can also implement **methods**, which can perform an specific operation."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "f7f7e1b8-a91d-452e-8760-4ae3213fb0c1",
+ "metadata": {},
+ "source": [
+ "Once you define an object, you can again use one of its methods by appending a `.` to the name of the object and calling its specific method. \n",
+ "\n",
+ "Methods are just functions, so therefore they should follow their syntax to call them:"
]
},
{
"cell_type": "code",
- "execution_count": 108,
- "id": "f157e013",
+ "execution_count": 7,
+ "id": "7c0d5b2b-58b9-4874-ac60-3de582c7f140",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
- "Greetings human. I am an animal\n"
+ "Greetings, human. I am an animal.\n"
]
}
],
@@ -150,22 +269,121 @@
"animal.greet()"
]
},
+ {
+ "cell_type": "markdown",
+ "id": "1bf4b777-596a-44de-bc34-5c74eac933dc",
+ "metadata": {},
+ "source": [
+ "Did we see this kind of behavior of using a `.` to call methods before? \n",
+ "\n",
+ "Yes! For example:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 8,
+ "id": "f879f3bc-febe-4bc5-b8a3-afd9425cf8be",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "'HELLO'"
+ ]
+ },
+ "execution_count": 8,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "my_string = \"hello\"\n",
+ "my_string.upper()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 9,
+ "id": "edb7bb07-4e0e-4903-99ff-e4be3bec3cde",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "dict_values([11, 'Kenny', False])"
+ ]
+ },
+ "execution_count": 9,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "my_dict = {\"age\": 11, \"name\": \"Kenny\", \"wild\": False}\n",
+ "my_dict.values()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "ff03b353-2aa3-4f29-924f-e71191f4c8d5",
+ "metadata": {},
+ "source": [
+ "Reiterating, this is because EVERYTHING DEFINED IN PYTHON IS AN OBJECT."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "ce8305b8-b277-4912-a239-5548606a0207",
+ "metadata": {},
+ "source": [
+ "Finally, **methods can use the information stored in the attributes**:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 10,
+ "id": "e5bc602c-2bda-4941-9a7d-3630e878f82d",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "class Animal(object):\n",
+ " \n",
+ " age = 11 #years\n",
+ " weight = 5 # kgs\n",
+ " name = \"Kenny\"\n",
+ " wild = False\n",
+ " \n",
+ " def greet(self): # Do not worry about the 'self' here\n",
+ " print(f\"Greetings, human. I am an animal. My name is {self.name}. I am {self.age} years old and I weight {self.weight} kgs.\") "
+ ]
+ },
{
"cell_type": "code",
- "execution_count": 109,
- "id": "3b452f8d",
+ "execution_count": 11,
+ "id": "b12fd45b-326c-4263-bc9a-8623b937464a",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
- "This animal is named: Bob, is 10 years old, and 0.8 cm tall\n"
+ "Greetings, human. I am an animal. My name is Kenny. I am 11 years old and I weight 5 kgs.\n"
]
}
],
"source": [
- "print(f\"This animal is named: {animal.name}, is {animal.age} years old, and {animal.height} cm tall\")"
+ "animal = Animal()\n",
+ "animal.greet()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "9a047198-fc61-4d09-b864-3ff4319f8ad0",
+ "metadata": {},
+ "source": [
+ "
\n",
+ "Take-home message: A class is a TEMPLATE to create a particular object. Think of them as design manuals for making machines, for example a terminator. These machines can have certain attributes (e.g. height, manufacturer, a target to terminate), and perform certain operations (e.g. walk, talk, terminate a target). \n",
+ "
"
]
},
{
@@ -173,18 +391,20 @@
"id": "fd1d8864",
"metadata": {},
"source": [
- "## 2. Initialize classes\n",
+ "## Initialize classes\n",
+ "\n",
+ "Ok, so classes can save information in the form of attributes, and also implement methods aimed at performing specific operations.\n",
"\n",
- "Ok, so classess can save information in the form of attributes, and also implement methods aimed at perform specific operations.\n",
+ "We also mentioned that classes are a template for creating specific kinds of objects (e.g. Animals). \n",
"\n",
- "We also mentioned that classes are a recipe to create specific kinds of objects (e.g. Animals). However, in the previous example, our animal was too specific. **What if we want an animal with a different set of attributes (e.g. a different name)?** \n",
+ "However, in our previous example, our animal was too specific. **What if we want an animal with a different set of attributes (e.g. a different name)?** \n",
"\n",
"We could redefine the class with the new attribute values:"
]
},
{
"cell_type": "code",
- "execution_count": 110,
+ "execution_count": 13,
"id": "fafc4301",
"metadata": {},
"outputs": [
@@ -192,25 +412,23 @@
"name": "stdout",
"output_type": "stream",
"text": [
- "This animal is named: John, is 10 years old, and 0.8 cm tall\n"
+ "Greetings, human. I am an animal. My name is Firulais. I am 11 years old and I weight 5 kgs.\n"
]
}
],
"source": [
"class Animal(object):\n",
" \n",
- " age = 10 #years\n",
- " height = 0.8 #cm\n",
- " name = \"John\"\n",
- " wild = True\n",
+ " age = 11 #years\n",
+ " weight = 5 # kgs\n",
+ " name = \"Firulais\"\n",
+ " wild = False\n",
" \n",
- " def greet(self):\n",
- " #print(f\"Greeting human. I am an animal of {self.age} of age and {self.height} tall.\") \n",
- " print(f\"Greeting human. I am an animal\") \n",
+ " def greet(self): # Do not worry about the 'self' here\n",
+ " print(f\"Greetings, human. I am an animal. My name is {self.name}. I am {self.age} years old and I weight {self.weight} kgs.\") \n",
" \n",
"new_animal = Animal()\n",
- "\n",
- "print(f\"This animal is named: {new_animal.name}, is {new_animal.age} years old, and {new_animal.height} cm tall\")"
+ "new_animal.greet()"
]
},
{
@@ -218,7 +436,9 @@
"id": "52018f9f",
"metadata": {},
"source": [
- "Obviously, this is pretty inefficient when you want to scalate things, lacking of reusibility, which when you have to use . And this was one of the advantages of classes."
+ "Obviously, this is pretty inefficient when you want to scalate things, lacking of reusability. And reusability was one of the advantages of classes. \n",
+ "\n",
+ "How could we redefine our new attribute values without having to redefine our whole class again?"
]
},
{
@@ -231,7 +451,7 @@
},
{
"cell_type": "code",
- "execution_count": 111,
+ "execution_count": 16,
"id": "b5de2ce5",
"metadata": {},
"outputs": [
@@ -239,14 +459,47 @@
"name": "stdout",
"output_type": "stream",
"text": [
- "This animal is named: Bob, is 10 years old, and 0.8 cm tall\n"
+ "Greetings, human. I am an animal. My name is Kenny. I am 11 years old and I weight 5 kgs.\n",
+ "Greetings, human. I am an animal. My name is Firulais. I am 11 years old and I weight 5 kgs.\n"
]
}
],
"source": [
- "new_animal.__setattr__(\"name\", \"Bob\")\n",
+ "animal.greet()\n",
"\n",
- "print(f\"This animal is named: {new_animal.name}, is {new_animal.age} years old, and {new_animal.height} cm tall\")"
+ "animal.__setattr__(\"name\", \"Firulais\")\n",
+ "\n",
+ "animal.greet()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "1069c6b1-6fc9-4a26-90ce-7727ef89ab8d",
+ "metadata": {},
+ "source": [
+ "(**N.B.** This function can be also used to add new attributes)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 17,
+ "id": "19166260-13b9-4441-92d3-e2a5965245a4",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "'blue'"
+ ]
+ },
+ "execution_count": 17,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "new_animal.__setattr__(\"eyes\", \"blue\")\n",
+ "new_animal.eyes"
]
},
{
@@ -254,51 +507,50 @@
"id": "7b3fbb2c",
"metadata": {},
"source": [
- "But this changes the original animal we created with the name \"John\". We should be able to define objects with the attributes that we would like and have them separately. \n",
+ "But this changes the original animal we created. What if we want to have separate animals, each with its own unique attribute values?\n",
"\n",
- "We can do this in the instentation moment. We need to specify which attributes in the classs need to be initialize. This can be done through the method `__init__` in the class definition:"
+ "We can do this when we instantiate the class—that is, when we create our objects. For that, we need to specify which attributes in the class should be initialized. This is done including the `__init__` method in the class definition as follows:"
]
},
{
"cell_type": "code",
- "execution_count": 112,
+ "execution_count": 22,
"id": "f989ce52",
"metadata": {},
"outputs": [],
"source": [
"class Animal(object):\n",
" \n",
- " def __init__(self, \n",
+ " def __init__(self, # Do not worry about the 'self' here\n",
" age,\n",
- " height,\n",
+ " weight,\n",
" name,\n",
" wild\n",
" ):\n",
" \n",
" self.age = age\n",
- " self.height = height\n",
+ " self.weight = weight\n",
" self.name = name\n",
" self.wild = wild\n",
"\n",
- " def greet(self):\n",
- " #print(f\"Greeting human. I am an animal of {self.age} of age and {self.height} tall.\") \n",
- " print(f\"Greeting human. I am an animal\") \n"
+ " def greet(self): # Do not worry about the 'self' here\n",
+ " print(f\"Greetings, human. I am an animal. My name is {self.name}. I am {self.age} years old and I weight {self.weight} kgs.\") \n"
]
},
{
"cell_type": "code",
- "execution_count": 113,
+ "execution_count": 23,
"id": "a4cc342c",
"metadata": {},
"outputs": [],
"source": [
- "animal_bob = Animal(age = 10,height = 0.8, name = \"Bob\", wild = True)\n",
- "animal_john = Animal(age = 10, height = 0.8, name = \"John\", wild = True)"
+ "animal_kenny = Animal(age=11, weight=5, name = \"Kenny\", wild = False)\n",
+ "animal_firulais = Animal(age = 2, weight = 2, name = \"Firulais\", wild = True)"
]
},
{
"cell_type": "code",
- "execution_count": 114,
+ "execution_count": 24,
"id": "063fbdf1",
"metadata": {},
"outputs": [
@@ -306,14 +558,45 @@
"name": "stdout",
"output_type": "stream",
"text": [
- "This animal is named: Bob\n",
- "This animal is named: John\n"
+ "This animal is named: Kenny\n",
+ "Greetings, human. I am an animal. My name is Kenny. I am 11 years old and I weight 5 kgs.\n",
+ "This animal is named: Firulais\n",
+ "Greetings, human. I am an animal. My name is Firulais. I am 2 years old and I weight 2 kgs.\n"
]
}
],
"source": [
- "print(f\"This animal is named: {animal_bob.name}\")\n",
- "print(f\"This animal is named: {animal_john.name}\")"
+ "print(f\"This animal is named: {animal_kenny.name}\")\n",
+ "animal_kenny.greet()\n",
+ "\n",
+ "print(f\"This animal is named: {animal_firulais.name}\")\n",
+ "animal_firulais.greet()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "0ba1caa0-09f5-4b8f-a4e0-a9e01d5ef7d8",
+ "metadata": {},
+ "source": [
+ "As you can see, we can create as many objects of a specific class as we want, each with its own attribute values, because we now have the template (the class) to do this!"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "484eace0-ac51-4b49-92d5-3c82b7ef118b",
+ "metadata": {},
+ "source": [
+ "**Try it yourself!**: Create your own Animal object and assigning values to its attributes when we define it, as we've just done."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 25,
+ "id": "5e0766c7-6d98-49a6-834b-69c12df393ce",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Your answer here"
]
},
{
@@ -321,12 +604,12 @@
"id": "b1e9a385",
"metadata": {},
"source": [
- "**We can also define classess that take initial attributes by default.** We just need to specify these values in the `__init__` function (N.B. `__init__` is a function, so it follows the same rules when it comes to parameter definition and order)."
+ "**We can also define classes that take initial attributes by default.** We just need to specify these values in the `__init__` function (**N.B.** In the end, `__init__` is a function, so it follows the same rules when it comes to parameter definition and order)."
]
},
{
"cell_type": "code",
- "execution_count": 115,
+ "execution_count": 26,
"id": "e673dbf1",
"metadata": {},
"outputs": [],
@@ -334,25 +617,24 @@
"class Animal(object):\n",
" \n",
" def __init__(self, \n",
- " age,\n",
- " height,\n",
+ " age = 1,\n",
+ " weight = 2,\n",
" name = \"Max\",\n",
- " wild = False,\n",
+ " wild = True\n",
" ):\n",
" \n",
" self.age = age\n",
- " self.height = height\n",
+ " self.weight = weight\n",
" self.name = name\n",
" self.wild = wild\n",
"\n",
- " def greet(self):\n",
- " #print(f\"Greeting human. I am an animal of {self.age} of age and {self.height} tall.\") \n",
- " print(f\"Greeting human. I am an animal\") \n"
+ " def greet(self): # Do not worry about the 'self' here\n",
+ " print(f\"Greetings, human. I am an animal. My name is {self.name}. I am {self.age} years old and I weight {self.weight} kgs.\") \n"
]
},
{
"cell_type": "code",
- "execution_count": 116,
+ "execution_count": 27,
"id": "92dfbc9e",
"metadata": {},
"outputs": [
@@ -360,14 +642,51 @@
"name": "stdout",
"output_type": "stream",
"text": [
- "This animal is named: Max, is 12 years old, and 1.2 cm tall\n"
+ "Greetings, human. I am an animal. My name is Max. I am 12 years old and I weight 2 kgs.\n"
]
}
],
"source": [
- "animal_3 = Animal(age = 12,height = 1.2)\n",
+ "another_animal = Animal(age = 12)\n",
"\n",
- "print(f\"This animal is named: {animal_3.name}, is {animal_3.age} years old, and {animal_3.height} cm tall\")"
+ "another_animal.greet()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "71aa97dd-2c0d-4c63-ade9-640232e5ad3a",
+ "metadata": {},
+ "source": [
+ "### What is `self`?"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "322b7db0-1308-45ee-be86-1f153eb2c708",
+ "metadata": {},
+ "source": [
+ "`self` is a way for an object to refer to itself. When you create an object from a class, that object needs a way to access its own data (attributes) and perform its own actions (methods).\n",
+ "\n",
+ "Think of it like this: when you're talking about yourself, you use the word 'I' or 'me'. In Python, self works the same way for objects—it’s how the object refers to itself.\n",
+ "\n",
+ "In the `Animal` example, when we write self.name, it means \"the name of this specific animal\". So if we create an animal like `buddy = Animal(name=\"Buddy\")`, the `self.name` for that object is \"Buddy\"."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "6f0e3a41-6ff2-4742-a0b3-c4a87f906577",
+ "metadata": {},
+ "source": [
+ "
\n",
+ "Warning: \n",
+ " \n",
+ " self is only needed inside the class definition to refer to the specific object you're working with. When you're defining methods inside the class, self is how the object refers to its own attributes and methods.\n",
+ " \n",
+ " \n",
+ "When you're using the object outside of the class, you don’t need to include self. The object already knows how to refer to itself. \n",
+ "buddy = Animal(name=\"Buddy\") \n",
+ "buddy.greet() # You don’t need to use 'self' here\n",
+ "
"
]
},
{
@@ -375,16 +694,28 @@
"id": "031f6478",
"metadata": {},
"source": [
- "## 3. Inheritance\n",
+ "## Inheritance\n",
"\n",
- "What if we now have a new recipe (class), which is just an extension of a previous recipe to, for example, make it more specific? Do we need to redifine this recipe again? **NO!** This is where classes become really handy, because you can make them inherit from other classes.\n",
+ "What if we want a new template (class) that is just an extension of a previous one, to make it more specific, for example?\n",
"\n",
- "When a class inherits from another class, it inherits **all** its method and attributes, **unless it overrides them**."
+ "Do we need to redefine everything from scratch? **NO!** This is where classes become really handy. You can make them **inherit** from other (parent) classes. This allows us to create a new (child) class that takes on the properties (attributes) and behaviors (methods) of an existing (parent) class, while also allowing us to add new features or modify existing ones.\n",
+ "\n",
+ "
\n",
+ "IMPORTANT: When a class inherits from another class, it inherits all its method and attributes, unless it overrides them.\n",
+ "
\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "c0baa004-9420-4990-96ce-77ad6235232f",
+ "metadata": {},
+ "source": [
+ "To specify inheritance in class, we write the parent class name in parentheses after the new (child) class name in the class definition:"
]
},
{
"cell_type": "code",
- "execution_count": 117,
+ "execution_count": 28,
"id": "64fbdf34",
"metadata": {},
"outputs": [],
@@ -394,9 +725,17 @@
" pass"
]
},
+ {
+ "cell_type": "markdown",
+ "id": "b956758c-b4c7-45b5-94d3-9cd267565d64",
+ "metadata": {},
+ "source": [
+ "Remember when we included `object` in our class definition? That's because all classes in Python, by default, inherit from object. While it's not strictly necessary to specify this inheritance, it’s considered good practice to include it."
+ ]
+ },
{
"cell_type": "code",
- "execution_count": 118,
+ "execution_count": 29,
"id": "fd650a2a",
"metadata": {},
"outputs": [
@@ -404,15 +743,12 @@
"name": "stdout",
"output_type": "stream",
"text": [
- "This a god named: Pretzels, is 5 years old, and 10 cm tall\n",
- "Greeting human. I am an animal\n"
+ "Greetings, human. I am an animal. My name is Mordisquitos. I am 5 years old and I weight 10 kgs.\n"
]
}
],
"source": [
- "my_dog = Dog(age = 5, height=10, name=\"Pretzels\")\n",
- "\n",
- "print(f\"This a god named: {my_dog.name}, is {my_dog.age} years old, and {my_dog.height} cm tall\")\n",
+ "my_dog = Dog(age = 5, weight=10, name=\"Mordisquitos\")\n",
"my_dog.greet()"
]
},
@@ -421,25 +757,25 @@
"id": "d3d0dcea",
"metadata": {},
"source": [
- "Mmm, but here, when the dog salutes us, it says it is an animal, which is true, but we may want it to be more specific. We can do this by redefining its greet method:"
+ "Mmm, but here, when the dog salutes us, it says it is an animal, which is true, but we may want it to be more specific. We can do this by **redefining** its `greet` method:"
]
},
{
"cell_type": "code",
- "execution_count": 119,
+ "execution_count": 30,
"id": "5b27ebfe",
"metadata": {},
"outputs": [],
"source": [
- "# Here Dog will inherit from Animal\n",
+ "# Here Dog will inherit from Animal, and redefine its greet method\n",
"class Dog(Animal):\n",
" def greet(self):\n",
- " print(f\"Greetings human. I am a dog!\") "
+ " print(f\"Greetings, human. I am a dog. My name is {self.name}. I am {self.age} years old and I weight {self.weight} kgs.\") "
]
},
{
"cell_type": "code",
- "execution_count": 120,
+ "execution_count": 31,
"id": "d09e5740",
"metadata": {},
"outputs": [
@@ -447,14 +783,12 @@
"name": "stdout",
"output_type": "stream",
"text": [
- "This a god named: Pretzels, is 5 years old, and 10 cm tall\n",
- "Greetings human. I am a dog!\n"
+ "Greetings, human. I am a dog. My name is Pretzels. I am 5 years old and I weight 10 kgs.\n"
]
}
],
"source": [
- "my_dog = Dog(age = 5, height=10, name=\"Pretzels\")\n",
- "print(f\"This a god named: {my_dog.name}, is {my_dog.age} years old, and {my_dog.height} cm tall\")\n",
+ "my_dog = Dog(age = 5, weight=10, name=\"Pretzels\")\n",
"my_dog.greet()"
]
},
@@ -463,12 +797,12 @@
"id": "27b3bda1",
"metadata": {},
"source": [
- "Note that this has **only** the `greet` method in the Dog class. The Animal class still has the original `greet` method"
+ "Note that this has **only** affected the `greet` method in the Dog class. The Animal class still has the original `greet` method"
]
},
{
"cell_type": "code",
- "execution_count": 121,
+ "execution_count": 33,
"id": "c10bf301",
"metadata": {},
"outputs": [
@@ -476,23 +810,151 @@
"name": "stdout",
"output_type": "stream",
"text": [
- "Greeting human. I am an animal\n"
+ "Greetings, human. I am an animal. My name is Pretzels. I am 5 years old and I weight 10 kgs.\n"
]
}
],
"source": [
- "# Note that this has on\n",
- "my_animal = Animal(age = 5, height=10, name=\"Pretzels\")\n",
+ "my_animal = Animal(age = 5, weight=10, name=\"Pretzels\")\n",
"my_animal.greet()"
]
},
+ {
+ "cell_type": "markdown",
+ "id": "08aa5447-dd6a-4282-b9b1-dc1226ee1de8",
+ "metadata": {},
+ "source": [
+ "Finally, our \"child\" class may have their own specific methods too, in addition to the ones they inherit from its parent(s):"
+ ]
+ },
{
"cell_type": "code",
- "execution_count": null,
- "id": "cc1de1e2",
+ "execution_count": 34,
+ "id": "cdc24719-2b1b-4a4b-b6eb-0a2264dbf545",
"metadata": {},
"outputs": [],
- "source": []
+ "source": [
+ "# Here Dog will inherit from Animal, redefine its greet method and add a new method call \"bark\"\n",
+ "class Dog(Animal):\n",
+ " def greet(self):\n",
+ " print(f\"Greetings, human. I am a dog. My name is {self.name}. I am {self.age} years old and I weight {self.weight} kgs.\") \n",
+ " \n",
+ " # This is a new method we add to this class\n",
+ " def bark(self): \n",
+ " print(\"wow! woof! bow-wow!\") "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 35,
+ "id": "eeb617dd-f75e-42c0-bcc9-16d7af49ccf6",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "wow! woof! bow-wow!\n"
+ ]
+ }
+ ],
+ "source": [
+ "my_dog = Dog(age = 5, weight=10, name=\"Pretzels\")\n",
+ "my_dog.bark()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "ee995e7c-3c9e-41e0-b11e-b661059d19ea",
+ "metadata": {},
+ "source": [
+ "This method belongs ONLY to the Dog class. Therefore, the following will give an error:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 36,
+ "id": "30f8a980-234e-451f-bd60-5dc8ba21519f",
+ "metadata": {},
+ "outputs": [
+ {
+ "ename": "AttributeError",
+ "evalue": "'Animal' object has no attribute 'bark'",
+ "output_type": "error",
+ "traceback": [
+ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
+ "\u001b[0;31mAttributeError\u001b[0m Traceback (most recent call last)",
+ "Cell \u001b[0;32mIn[36], line 2\u001b[0m\n\u001b[1;32m 1\u001b[0m my_dog \u001b[38;5;241m=\u001b[39m Animal(age \u001b[38;5;241m=\u001b[39m \u001b[38;5;241m5\u001b[39m, weight\u001b[38;5;241m=\u001b[39m\u001b[38;5;241m10\u001b[39m, name\u001b[38;5;241m=\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mPretzels\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n\u001b[0;32m----> 2\u001b[0m my_dog\u001b[38;5;241m.\u001b[39mbark()\n",
+ "\u001b[0;31mAttributeError\u001b[0m: 'Animal' object has no attribute 'bark'"
+ ]
+ }
+ ],
+ "source": [
+ "my_dog = Animal(age = 5, weight=10, name=\"Pretzels\")\n",
+ "my_dog.bark()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "453e9410-3cfe-4be8-a86f-cbc3ff4fe361",
+ "metadata": {},
+ "source": [
+ "## Practice excersises"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "ea67a91e-a6b3-4de6-8f74-58d366c7d248",
+ "metadata": {},
+ "source": [
+ "```{exercise}\n",
+ ":label: classes1\n",
+ "\n",
+ "Let's create our recipe for a terminator!\n",
+ "\n",
+ "1- Define a class that can be initilize to a given terminator name (e.g. T-800). As a second attribute, define the name of the person this terminator is supposed to terminate (e.g. John Connor).\n",
+ "\n",
+ "2- Now, this class should have a method `ask_and_decide`, which, given an input name of person, it decides whether to terminate that person. Show this decision as print message (Hint: You can use the second attribute and `if/else` for this). \n",
+ "\n",
+ "3-Test the class by creating an object with a specific terminator and target, and then call `ask_and_decide` with different names to see how the terminator behaves.\n",
+ "\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 37,
+ "id": "681113e0-993c-48b7-8d6e-1ffb44410869",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Your answers from here"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "deb44e1b-5b5a-41b1-8616-26d260e052a5",
+ "metadata": {},
+ "source": [
+ "```{exercise}\n",
+ ":label: classes2\n",
+ "\n",
+ "As a Cyberdyne Systems engineer, you've been tasked with developing a new, more advanced terminator. This new terminator should extend the functionalities of the one you created in the previous exercise by adding a new ability. This could be anything you like—be creative! You can also add new attributes if you'd like.\n",
+ "\n",
+ "Remember to use inheritance for this exercise to build on the existing terminator class.\n",
+ "\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 38,
+ "id": "fb85f526-e117-4653-aa4f-2f8ecb08129f",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Your answers from here"
+ ]
}
],
"metadata": {
diff --git a/chapters/module-2/In-class_100324.html b/chapters/module-2/In-class_100324.html
new file mode 100644
index 0000000..b24bb26
--- /dev/null
+++ b/chapters/module-2/In-class_100324.html
@@ -0,0 +1,482 @@
+
+
+
+
+
+
+
+
+
+
+ Instructions — DS-1002 Programming for Data Science
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Skip to main content
+
+
+
+
+
+
+
+
+
+
+
+
+
Complete Jupyter Notebook for tasks. Clearly show relevant work.
+
Before beginning to fill in the notebook, make sure you have written down your name in the first cell, as well as of any collaborators in case you have worked in groups.
+
Before you turn this problem in, make sure everything runs as expected. First, restart the kernel (in the menubar, select Kernel \(\rightarrow\) Restart) and then run all cells (in the menubar, select Cell \(\rightarrow\) Run All).
+
Make sure your changes have been changed and when ready, submit the jupyter notebook through Canvas.
+
+
+
+
NAME=""
+COLLABORATORS=""
+
+
+
+
+
+
1. The following function aims at returning whether a given list of numbers contains at least one element divisible by 7, but it fails due to a bug. You can test this failing behaviour by passing, for example, the list: [10, 7, 13].
+
Redefine the function correcting the bug. Test that it behaves correctly with several examples. In addition, in a separate markdown cell, explain why the function was failing. (3 points)
+
+
+
defhas_atleast_one_seven(nums):
+"""Return whether the given list of numbers is lucky. A lucky list contains
+ at least one number divisible by 7.
+ """
+ fornuminnums:
+ ifnum%7==0:
+ returnTrue
+ else:
+ returnFalse
+
+
+
+
+
2. Using a for loop and and if statements, print all the numbers between 10 and 1000 (including both sides) that are divisible by 7 and the sum of their digits is greater than 10, but only if the number itself is also odd. (2 points)
Complete Jupyter Notebook for tasks. Clearly show relevant work.
+
Before beginning to fill in the notebook, make sure you have written down your name in the first cell, as well as of any collaborators in case you have worked in groups.
+
Before you turn this problem in, make sure everything runs as expected. First, restart the kernel (in the menubar, select Kernel \(\rightarrow\) Restart) and then run all cells (in the menubar, select Cell \(\rightarrow\) Run All).
+
Make sure your changes have been changed and when ready, submit the jupyter notebook through Canvas.
+
+
+
+
NAME=""
+COLLABORATORS=""
+
+
+
+
+
+
1. The following function aims at returning whether a given list of numbers contains at least one element divisible by 7, but it fails due to a bug. You can test this failing behaviour by passing, for example, the list: [10, 7, 13].
+
Redefine the function correcting the bug. Test that it behaves correctly with several examples. In addition, in a separate markdown cell, explain why the function was failing. (3 points)
+
+
+
defhas_atleast_one_seven(nums):
+"""Return whether the given list of numbers is lucky. A lucky list contains
+ at least one number divisible by 7.
+ """
+ fornuminnums:
+ ifnum%7==0:
+ returnTrue
+ else:
+ returnFalse
+
+
+
+
+
+
+
# Solution
+defhas_atleast_one_seven(nums):
+"""Return whether the given list of numbers is lucky. A lucky list contains
+ at least one number divisible by 7.
+ """
+ fornuminnums:
+ ifnum%7==0:
+ returnTrue
+
+ returnFalse
+
+
+
+
+
+
+
has_atleast_one_seven([10,1,13,1,1,1,1,6,7])
+
+
+
+
+
True
+
+
+
+
+
+
+
has_atleast_one_seven([10,8,13,1,4,5,6,81,6])
+
+
+
+
+
False
+
+
+
+
+
The bug was that in the original function, only one iteration was performed, because of both return in the if/else, which made you exit the function after the first iteration. We only need to exit the function early if we happen to hit an element divisible by 7. If there is not, we should carry on looping the list until the end. If that happens, that’s becaus we don’t have any element divisible by 7, and the function should return a False.
+
2. Using a for loop and and if statements, print all the numbers between 10 and 1000 (including both sides) that are divisible by 7 and the sum of their digits is greater than 10, but only if the number itself is also odd. (2 points)
+
+
+
numbers=range(10,1001)# You have to use 1001 here to include 1000
+
+fornuminnumbers:
+ ifnum%7==0:# This condition checks if the number is divisible by 7
+ num_as_str=str(num)# convert the number to string and store this info in a new variable
+
+ # This block now calculates the sum of the digits of num as a string
+ sum_digits=0
+ forsinnum_as_str:# This loop through its individual digit of num and update the sumation adding the iterated digit
+ sum_digits+=int(s)# we have to convert the character back to an integer.
+
+ # Now add a condition to print num, only if the sum of digits is greater than 10 and num is odd, i.e, not even.
+ if(sum_digits>10)and(num%2!=0):
+ print(num)
+
1 - Tries to check if a number is positive.
-2 - If the number is negative, raise a ValueError with the message ‘Number must be positive’.
-3 - If no exception occurs, print ‘The number is positive’ inside the else block.
-4 - Ensure that, regardless of whether an exception occurs, a final message ‘Check complete’ is printed using a finally block
+
1 - Tries to check if a number is positive.
+2 - If the number is negative, raise a ValueError with the message ‘Number must be positive’.
+3 - If no exception occurs, print ‘The number is positive’ inside the else block.
+4 - Ensure that, regardless of whether an exception occurs, a final message ‘Check complete’ is printed using a finally block.
Python is an object-oriented programming (OOP) language. The object-oriented programming is kind of paradigm that helps you group state (attributes) and behavior (methods) together in handy packets of functionality. It also allows for useful specialized mechanisms such as inheritance.
-
The fact that python is an OOP language does not mean you are force to follow this paradigm (unlike some other OOP languages). You can still do procedural programming as we have learned so far, with modules and functions. In Python, you can select the paradigm that fits best for your your purposes, or even mix paradigms.
+
+
Introduction to object-oriented programming (OOP)#
Classes help describe/model data structures, or collections of functions centered on a particular set of tasks related to a particular type of data. A class is much like a template or schema. Classes may, but does not have to, take parameters.
Python is an object-oriented programming (OOP) language. The object-oriented programming is kind of paradigm that helps you group state (attributes) and behavior (methods) together in handy packets of functionality. It also allows for useful specialized mechanisms such as inheritance.
+
The fact that python is an OOP language does not mean you are force to follow this paradigm (unlike some other OOP languages). You can still do procedural programming as we have learned so far, with modules and functions. In Python, you can select the paradigm that fits best for your your purposes, or even mix paradigms.
+
OOP is about grouping DATA with the FUNCTIONS that manipulate that data and hiding HOW it manipulates it so you can MODIFY the behavior through INHERITANCE.
+
Advantages of OOP programming:
-
Attributes - values/fields specific to each instance of a class.
-
Methods - functions available within and specific to a class. Methods may consume attributes or other parameters.
-
Constructors - the attribute structure/default values of a class.
+
MAINTAINABILITY. Object-oriented programming methods make code more maintainable. Identifying the source of errors is easier because objects are self-contained.
+
REUSABILITY. Because objects contain both data and methods that act on data, objects can be thought of as self-contained black boxes. This feature makes it easy to reuse code in new systems.Messages provide a predefined interface to an object’s data and functionality. With this interface, an object can be used in any context.
+
SCALABILITY. Object-oriented programs are also scalable. As an object’s interface provides a road map for reusing the object in new software, and provides all the information needed to replace the object without affecting other code. This way aging code can be replaced with faster algorithms and newer technology.
-
Instances / Objects are actual, concrete implementations of a class with specific values.
You’ve heard this phrase quite a few times throughout the course. But what is an object?
-
“OO is about grouping DATA with the FUNCTIONS that manipulate that data and hiding HOW it manipulates it so you can MODIFY the behavior through INHERITANCE.”
+
An object is like a special kind of data structure (like list, dictionaries, etc) that not only stores data (attributes) but also has functions (methods) that can do things with that data.
-
Advantages of OOP programming:
+
Objects arise from classes. What is a class?
+
+
Classes are much like a template or scheme for creating objects. It defines what data (attributes) the objects will store and what actions (methods) they can perform.
MAINTAINABILITY Object-oriented programming methods make code more maintainable. Identifying the source of errors is easier because objects are self-contained.
-
REUSABILITY Because objects contain both data and methods that act on data, objects can be thought of as self-contained black boxes. This feature makes it easy to reuse code in new systems.Messages provide a predefined interface to an object’s data and functionality. With this interface, an object can be used in any context.
-
SCALABILITY Object-oriented programs are also scalable. As an object’s interface provides a road map for reusing the object in new software, and provides all the information needed to replace the object without affecting other code. This way aging code can be replaced with faster algorithms and newer technology.
+
We start with the keyword class to let Python now we are going to define a class.
+
Then we provide a name for the class. (Note: Pythonists like to use CamelCase naming convention for this…)
+
A parenthesis specifying the inheritance of the class. Here we used object, which is the most basic class that all other classes inherit from. If no inheritence is specified, stick with object. We will come to inheritance later.
+
We end the header of the definition with a :, to specify that whatever comes next belongs to the class.
+
After that, in an indented block, we include the attributes and methods of the class.
-
You can think of classes as a kind of data structure (e.g.), which can store information:
Therefore, you may think of classes as a kind of data structure (e.g. Dictionaries) to store information.
+
Once you define an object, you can access its attributes using append a . and calling its specific attribute:
+
+
+
my_animal=Animal()# Creating a Animal object
+
+print(my_animal.age)
+print(my_animal.name)
-
10
-Africa
+
11
+Kenny
-
The difference with data structures is that class can implement methods, which can perform an operation:
+
+Important:. You need to create an object in order to access its attributes (and methods, as explained below). Until then, you only have a general design manual (the class), not a specific object! Think of creating an object as defining a new type of data, like integers, floats, lists or dictionaries.
+
And what are methods?
+
+
Methods are functions available within and specific to a class. Methods may consume attributes or other parameters.
+
classAnimal(object):
- age=10#years
- height=0.8#cm
- name="Bob"
- wild=True
+ age=11#years
+ weight=5# kgs
+ name="Kenny"
+ wild=Falsedefgreet(self):
- #print(f"Greeting human. I am an animal of {self.age} of age and {self.height} tall.")
- print(f"Greetings human. I am an animal")
+ print(f"Greetings, human. I am an animal.")
+
So the difference with typical data structures is that classes can also implement methods, which can perform an specific operation.
+
Once you define an object, you can again use one of its methods by appending a . to the name of the object and calling its specific method.
+
Methods are just functions, so therefore they should follow their syntax to call them:
animal=Animal()
@@ -447,160 +512,257 @@
1. Classes and Objects
-
Greetings human. I am an animal
+
Greetings, human. I am an animal.
+
+
+
+
+
Did we see this kind of behavior of using a . to call methods before?
Reiterating, this is because EVERYTHING DEFINED IN PYTHON IS AN OBJECT.
+
Finally, methods can use the information stored in the attributes:
-
print(f"This animal is named: {animal.name}, is {animal.age} years old, and {animal.height} cm tall")
+
classAnimal(object):
+
+ age=11#years
+ weight=5# kgs
+ name="Kenny"
+ wild=False
+
+ defgreet(self):# Do not worry about the 'self' here
+ print(f"Greetings, human. I am an animal. My name is {self.name}. I am {self.age} years old and I weight {self.weight} kgs.")
+
+
+
+
+
+
+
animal=Animal()
+animal.greet()
-
This animal is named: Bob, is 10 years old, and 0.8 cm tall
+
Greetings, human. I am an animal. My name is Kenny. I am 11 years old and I weight 5 kgs.
+
+Take-home message: A class is a TEMPLATE to create a particular object. Think of them as design manuals for making machines, for example a terminator. These machines can have certain attributes (e.g. height, manufacturer, a target to terminate), and perform certain operations (e.g. walk, talk, terminate a target).
+
Ok, so classess can save information in the form of attributes, and also implement methods aimed at perform specific operations.
-
We also mentioned that classes are a recipe to create specific kinds of objects (e.g. Animals). However, in the previous example, our animal was too specific. What if we want an animal with a different set of attributes (e.g. a different name)?
Ok, so classes can save information in the form of attributes, and also implement methods aimed at performing specific operations.
+
We also mentioned that classes are a template for creating specific kinds of objects (e.g. Animals).
+
However, in our previous example, our animal was too specific. What if we want an animal with a different set of attributes (e.g. a different name)?
We could redefine the class with the new attribute values:
classAnimal(object):
- age=10#years
- height=0.8#cm
- name="John"
- wild=True
+ age=11#years
+ weight=5# kgs
+ name="Firulais"
+ wild=False
- defgreet(self):
- #print(f"Greeting human. I am an animal of {self.age} of age and {self.height} tall.")
- print(f"Greeting human. I am an animal")
+ defgreet(self):# Do not worry about the 'self' here
+ print(f"Greetings, human. I am an animal. My name is {self.name}. I am {self.age} years old and I weight {self.weight} kgs.")new_animal=Animal()
-
-print(f"This animal is named: {new_animal.name}, is {new_animal.age} years old, and {new_animal.height} cm tall")
+new_animal.greet()
-
This animal is named: John, is 10 years old, and 0.8 cm tall
+
Greetings, human. I am an animal. My name is Firulais. I am 11 years old and I weight 5 kgs.
-
Obviously, this is pretty inefficient when you want to scalate things, lacking of reusibility, which when you have to use . And this was one of the advantages of classes.
+
Obviously, this is pretty inefficient when you want to scalate things, lacking of reusability. And reusability was one of the advantages of classes.
+
How could we redefine our new attribute values without having to redefine our whole class again?
We could use the built-in method __setattr__, which all classes in Python have, to set a new attribute value.
-
new_animal.__setattr__("name","Bob")
+
animal.greet()
-print(f"This animal is named: {new_animal.name}, is {new_animal.age} years old, and {new_animal.height} cm tall")
+animal.__setattr__("name","Firulais")
+
+animal.greet()
-
This animal is named: Bob, is 10 years old, and 0.8 cm tall
+
Greetings, human. I am an animal. My name is Kenny. I am 11 years old and I weight 5 kgs.
+Greetings, human. I am an animal. My name is Firulais. I am 11 years old and I weight 5 kgs.
-
But this changes the original animal we created with the name “John”. We should be able to define objects with the attributes that we would like and have them separately.
-
We can do this in the instentation moment. We need to specify which attributes in the classs need to be initialize. This can be done through the method __init__ in the class definition:
+
(N.B. This function can be also used to add new attributes)
But this changes the original animal we created. What if we want to have separate animals, each with its own unique attribute values?
+
We can do this when we instantiate the class—that is, when we create our objects. For that, we need to specify which attributes in the class should be initialized. This is done including the __init__ method in the class definition as follows:
classAnimal(object):
- def__init__(self,
+ def__init__(self,# Do not worry about the 'self' hereage,
- height,
+ weight,name,wild):self.age=age
- self.height=height
+ self.weight=weightself.name=nameself.wild=wild
- defgreet(self):
- #print(f"Greeting human. I am an animal of {self.age} of age and {self.height} tall.")
- print(f"Greeting human. I am an animal")
+ defgreet(self):# Do not worry about the 'self' here
+ print(f"Greetings, human. I am an animal. My name is {self.name}. I am {self.age} years old and I weight {self.weight} kgs.")
print(f"This animal is named: {animal_bob.name}")
-print(f"This animal is named: {animal_john.name}")
+
print(f"This animal is named: {animal_kenny.name}")
+animal_kenny.greet()
+
+print(f"This animal is named: {animal_firulais.name}")
+animal_firulais.greet()
-
This animal is named: Bob
-This animal is named: John
+
This animal is named: Kenny
+Greetings, human. I am an animal. My name is Kenny. I am 11 years old and I weight 5 kgs.
+This animal is named: Firulais
+Greetings, human. I am an animal. My name is Firulais. I am 2 years old and I weight 2 kgs.
-
We can also define classess that take initial attributes by default. We just need to specify these values in the __init__ function (N.B. __init__ is a function, so it follows the same rules when it comes to parameter definition and order).
+
As you can see, we can create as many objects of a specific class as we want, each with its own attribute values, because we now have the template (the class) to do this!
+
Try it yourself!: Create your own Animal object and assigning values to its attributes when we define it, as we’ve just done.
+
+
+
# Your answer here
+
+
+
+
+
We can also define classes that take initial attributes by default. We just need to specify these values in the __init__ function (N.B. In the end, __init__ is a function, so it follows the same rules when it comes to parameter definition and order).
classAnimal(object):def__init__(self,
- age,
- height,
+ age=1,
+ weight=2,name="Max",
- wild=False,
+ wild=True):self.age=age
- self.height=height
+ self.weight=weightself.name=nameself.wild=wild
- defgreet(self):
- #print(f"Greeting human. I am an animal of {self.age} of age and {self.height} tall.")
- print(f"Greeting human. I am an animal")
+ defgreet(self):# Do not worry about the 'self' here
+ print(f"Greetings, human. I am an animal. My name is {self.name}. I am {self.age} years old and I weight {self.weight} kgs.")
-
animal_3=Animal(age=12,height=1.2)
+
another_animal=Animal(age=12)
-print(f"This animal is named: {animal_3.name}, is {animal_3.age} years old, and {animal_3.height} cm tall")
+another_animal.greet()
-
This animal is named: Max, is 12 years old, and 1.2 cm tall
+
Greetings, human. I am an animal. My name is Max. I am 12 years old and I weight 2 kgs.
self is a way for an object to refer to itself. When you create an object from a class, that object needs a way to access its own data (attributes) and perform its own actions (methods).
+
Think of it like this: when you’re talking about yourself, you use the word ‘I’ or ‘me’. In Python, self works the same way for objects—it’s how the object refers to itself.
+
In the Animal example, when we write self.name, it means “the name of this specific animal”. So if we create an animal like buddy=Animal(name="Buddy"), the self.name for that object is “Buddy”.
+
+Warning:
+
+ self is only needed inside the class definition to refer to the specific object you're working with. When you're defining methods inside the class, self is how the object refers to its own attributes and methods.
+
+
+When you're using the object outside of the class, you don’t need to include self. The object already knows how to refer to itself.
+buddy = Animal(name="Buddy")
+buddy.greet() # You don’t need to use 'self' here
+
What if we now have a new recipe (class), which is just an extension of a previous recipe to, for example, make it more specific? Do we need to redifine this recipe again? NO! This is where classes become really handy, because you can make them inherit from other classes.
-
When a class inherits from another class, it inherits all its method and attributes, unless it overrides them.
What if we want a new template (class) that is just an extension of a previous one, to make it more specific, for example?
+
Do we need to redefine everything from scratch? NO! This is where classes become really handy. You can make them inherit from other (parent) classes. This allows us to create a new (child) class that takes on the properties (attributes) and behaviors (methods) of an existing (parent) class, while also allowing us to add new features or modify existing ones.
+
+IMPORTANT: When a class inherits from another class, it inherits all its method and attributes, unless it overrides them.
+
+
To specify inheritance in class, we write the parent class name in parentheses after the new (child) class name in the class definition:
# Here Dog will inherit from Animal
@@ -610,59 +772,137 @@
1- Define a class that can be initilize to a given terminator name (e.g. T-800). As a second attribute, define the name of the person this terminator is supposed to terminate (e.g. John Connor).
+
2- Now, this class should have a method ask_and_decide, which, given an input name of person, it decides whether to terminate that person. Show this decision as print message (Hint: You can use the second attribute and if/else for this).
+
3-Test the class by creating an object with a specific terminator and target, and then call ask_and_decide with different names to see how the terminator behaves.
+
+
+
+
+
# Your answers from here
+
+
+
+
+
+
+
Exercise 26
+
+
As a Cyberdyne Systems engineer, you’ve been tasked with developing a new, more advanced terminator. This new terminator should extend the functionalities of the one you created in the previous exercise by adding a new ability. This could be anything you like—be creative! You can also add new attributes if you’d like.
+
Remember to use inheritance for this exercise to build on the existing terminator class.