{ "cells": [ { "cell_type": "markdown", "id": "0", "metadata": {}, "source": [ "# Object-oriented Programming" ] }, { "cell_type": "markdown", "id": "1", "metadata": {}, "source": [ "# Table of Contents\n", " - [References](#References)\n", " - [A `class` as a blueprint of objects](#A-class-as-a-blueprint-of-objects)\n", " - [Example 1](#Example-1)\n", " - [Properties and methods](#Properties-and-methods)\n", " - [Example 2](#Example-2)\n", " - [Python's special methods](#Python's-special-methods)\n", " - [`__str__` and `__repr__`](#__str__-and-__repr__)\n", " - [Example 3](#Example-3)\n", " - [Comparison methods](#Comparison-methods)\n", " - [Example 4](#Example-4)\n", " - [More comparison methods](#More-comparison-methods)\n", " - [The `@property` keyword](#The-@property-keyword)\n", " - [Quick glossary](#Quick-glossary)\n", " - [Quiz](#Quiz)\n", " - [Exercises](#Exercises)\n", " - [Exercise 1: Ice cream scoop](#Exercise-1:-Ice-cream-scoop)\n", " - [Exercise 2: Ice cream bowl](#Exercise-2:-Ice-cream-bowl)\n", " - [Exercise 3: Ice cream shop](#Exercise-3:-Ice-cream-shop)\n", " - [Exercise 4: Intcode computer ð¶ï¸](#Exercise-4:-Intcode-computer-ð¶ï¸)" ] }, { "cell_type": "markdown", "id": "2", "metadata": {}, "source": [ "# References" ] }, { "cell_type": "markdown", "id": "3", "metadata": {}, "source": [ "- [Python classes (read)](https://www.py4e.com/html3/14-objects)\n", "- [Python classes (video lecture)](https://www.py4e.com/lessons/Objects#)\n", "- [Python Data Model](https://docs.python.org/3/reference/datamodel.html)" ] }, { "cell_type": "markdown", "id": "4", "metadata": { "tags": [] }, "source": [ "# A `class` as a blueprint of objects" ] }, { "cell_type": "markdown", "id": "5", "metadata": {}, "source": [ "Object-oriented programming (OOP) is probably the most well-known approach to programming. Almost every programming language in some way or another supports this paradigm.\n", "\n", "The idea behind OOP is simple: instead of defining our functions in one part of the code, and the data on which those functions operate in a separate part of the code, we define them together.\n", "\n", "The concept of **object** is the heart of OOP, and the fundamental building block is the **class**. We have already seen many types of objects, each belonging to different classes:" ] }, { "cell_type": "code", "execution_count": null, "id": "6", "metadata": { "tags": [] }, "outputs": [], "source": [ "data = \"Python\", 2023, True, 3.14159, (1, 2.0, \"3.0\"), {\"a\": 10, \"b\": 11}\n", "\n", "for d in data:\n", " print(type(d))" ] }, { "cell_type": "markdown", "id": "7", "metadata": {}, "source": [ "In Python, to create a custom class we use the `class` keyword, and we can initialize class attributes in the special method `__init__`.\n", "\n", "For example, let's create a class that represents a rectangle as a geometrical entity:" ] }, { "cell_type": "code", "execution_count": null, "id": "8", "metadata": { "tags": [] }, "outputs": [], "source": [ "class Rectangle:\n", " def __init__(self, width, height):\n", " self.width = width\n", " self.height = height" ] }, { "cell_type": "markdown", "id": "9", "metadata": {}, "source": [ "The first argument (`self`) is automatically filled in by Python and contains **the object being created**. It's the way a class can modify itself, in some sense.\n", "\n", "
self is just a convention (although a good one, and you should use it to make your code more understandable by others), you could really call it whatever (valid) name you want\n",
"class called Person which should have two attributes called first_name and last_name. Create an instance of this class, representing a person, which is being initialized by using the arguments passed in the solution function. Lastly, return the instance.\n",
"Person will now have a method called full_name() that returns the string: My name is {first_name} {last_name}. Create an instance of this class, representing a person, which is being initialized by using the arguments passed in the solution function. Lastly, return the instance.\n",
"self. notation when you wish to access the class attributes.\n",
"Person will now have a __str__() method instead of full_name(), with the same functionality. Then, add a __repr__() method that returns the string: Person({first_name}, {last_name}). Create an instance of this class, representing a person, which is being initialized by using the arguments passed in the solution function. Lastly, return the instance.\n",
"Person will now have an additional attribute called age. Then, add a __eq__() comparison method that makes sure that two persons are the same when they have the same first name, last name and age. Create an instance of this class, representing a person, which is being initialized by using the arguments passed in the solution function. Lastly, return the instance.\n",
"Rectangle class, the meaning of the greater/less than operations is not formally defined. We chose to compare the rectangles areas but that's completely arbitrary.\n",
"area as one of Rectangle's properties? The only difference with the other ones, it that this property requires some additional logic in the background, in order for its value to be calculated."
]
},
{
"cell_type": "markdown",
"id": "94",
"metadata": {},
"source": [
"Python provides you the special keyword `@property`. We are not going into the details of what this keyword does, but here's how you can use it in your classes to make them more \"user-friendly\":"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "95",
"metadata": {
"tags": []
},
"outputs": [],
"source": [
"class Rectangle:\n",
" def __init__(self, width, height):\n",
" self.width = width\n",
" self.height = height\n",
" \n",
" @property\n",
" def area(self):\n",
" return self.width * self.height\n",
" \n",
" @property\n",
" def perimeter(self):\n",
" return 2 * (self.width + self.height)\n",
" \n",
" def __str__(self):\n",
" return f'Rectangle (width={self.width}, height={self.height})'\n",
" \n",
" def __repr__(self):\n",
" return self.__str__()"
]
},
{
"cell_type": "markdown",
"id": "96",
"metadata": {},
"source": [
"You can simply add `@property` **just above** the line that defines your property. For example, the `def area()` or `def perimeter()` functions above."
]
},
{
"cell_type": "markdown",
"id": "97",
"metadata": {},
"source": [
"By doing so, you can now access the values of area and perimeter in the same way that you would for any other property of the class:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "98",
"metadata": {
"tags": []
},
"outputs": [],
"source": [
"r1 = Rectangle(1, 10)\n",
"r2 = Rectangle(10, 20)\n",
"\n",
"print(f\"Perimeters: r1={r1.perimeter}, r2={r2.perimeter}\")\n",
"print(f\"Areas: r1={r1.area}, r2={r2.area}\")"
]
},
{
"cell_type": "markdown",
"id": "99",
"metadata": {},
"source": [
"The only difference is that in this way we are unable to directly set the value of these properties. Their values are always internally calculated based on their defined logic, and we are only able to access them."
]
},
{
"cell_type": "markdown",
"id": "100",
"metadata": {},
"source": [
"See what happens when you try to execute the following line:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "101",
"metadata": {},
"outputs": [],
"source": [
"r1.area = 5"
]
},
{
"cell_type": "markdown",
"id": "102",
"metadata": {},
"source": [
"If you want to learn more about the `@property` keyword, you can check out [this link](https://docs.python.org/3/library/functions.html#property). **Beware**, it's rather advanced stuff! If it's your first time with Python, just skip it for the time being."
]
},
{
"cell_type": "markdown",
"id": "103",
"metadata": {
"tags": []
},
"source": [
"# Quick glossary\n"
]
},
{
"cell_type": "markdown",
"id": "104",
"metadata": {},
"source": [
"\n",
"| Term | Definition |\n",
"| --- | --- |\n",
"| Class | A blueprint for creating objects that define their attributes and methods. |\n",
"| Instance | An individual occurrence of a class, created from the blueprint of the class. |\n",
"| Method | A function defined within a class that performs some action on an instance of that class or the class itself. |\n",
"| Attribute | A variable that is bound to a class or instance and holds some value or reference. |\n",
"| `__init__` | A special method that is automatically called when an instance of a class is created, used for initializing the instance's attributes. |\n",
"| `__str__/__repr__` | Special methods used for defining a string representation of a class or instance, used for debugging or displaying information about the object. `__str__` is used for human-readable output, while `__repr__` is used for machine-readable output. |\n",
"| `@property` | A special keyword used to define a method that can be accessed like an attribute, when additional logic or validation is required. |"
]
},
{
"cell_type": "markdown",
"id": "105",
"metadata": {
"tags": []
},
"source": [
"---"
]
},
{
"cell_type": "markdown",
"id": "106",
"metadata": {},
"source": [
"# Quiz\n",
"\n",
"Run the following cell to test your knowledge with a small quiz."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "107",
"metadata": {},
"outputs": [],
"source": [
"from tutorial.quiz import object_oriented_programming as oop\n",
"\n",
"oop.OopQuiz()"
]
},
{
"cell_type": "markdown",
"id": "108",
"metadata": {},
"source": [
"# Exercises"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "109",
"metadata": {},
"outputs": [],
"source": [
"%reload_ext tutorial.tests.testsuite"
]
},
{
"cell_type": "markdown",
"id": "110",
"metadata": {},
"source": [
"## Exercise 1: Ice cream scoop"
]
},
{
"cell_type": "markdown",
"id": "111",
"metadata": {},
"source": [
"Define a class `Scoop` that represents a single scoop of ice cream. Each scoop should have a **single** attribute, `flavor`, a string that you can initialize when you create the instance of `Scoop`.\n",
"\n",
"Define also a `__str__` method to return a string reprensentation of a scoop. The output should be `Ice cream scoop with flavor 'Scoop class for every flavor contained in the function parameter flavors. This function should return a list that collects the Scoop instances of the ice cream flavors.\n",
""
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "112",
"metadata": {},
"outputs": [],
"source": [
"%%ipytest\n",
"\n",
"def solution_ice_cream_scoop(flavors: tuple[str]) -> list:\n",
" \"\"\"A function that contains the definition of a class Scoop, and returns a list of Scoop instances.\n",
"\n",
" Scoop is a class with one attribute called 'flavor'.\n",
" Scoop implements the __str__() method which should return the string 'Ice cream scoop with flavor '{flavor}'\n",
"\n",
" Args:\n",
" flavors: all available ice cream flavors\n",
" Returns:\n",
" - a list containing one Scoop instance per flavor\n",
" \"\"\"\n",
"\n",
" # Write your solution here\n",
" return"
]
},
{
"cell_type": "markdown",
"id": "113",
"metadata": {},
"source": [
"## Exercise 2: Ice cream bowl\n",
"\n",
"Create a class `Bowl` that can hold many ice cream scoops, as many as you like. You *should use* the custom class `Scoop` that you created in the previous exercise.\n",
"\n",
"The `Bowl` class should have a method called `add_scoops()` that accepts **variable number** of scoops.\n",
"\n",
"Define also the __str__ method to return the reprensentation of a bowl, which should report the content of the bowl you just created. For example: Ice cream bowl with chocolate, vanilla, stracciatella scoops.
__init__ method of the Bowl class, you should define an attribute called scoops, that acts as a container to hold the scoops you might want to add.\n",
"flavors. The output of this function should be the instance of the bowl you just created.\n",
"__init__ method of the Shop class, you should define an attribute called flavors, that acts as a container to hold the available flavours.__eq__ and __lt__ to define __le__.flavors. The output of this function should be the instance of the shop you just created.\n",
"0 after the program stops?\n",
"Computer with a method called run()__init__() method should process your input string and assign the attribute program, which should be a list of integers