{ "cells": [ { "cell_type": "markdown", "metadata": { "lines_to_next_cell": 0, "toc": true }, "source": [ "

Table of Contents

\n", "
" ] }, { "cell_type": "markdown", "metadata": { "lines_to_next_cell": 0 }, "source": [ "# Creating user-defined types\n", "\n", "This notebook shows how to use classes to compartmentalize information. The basic\n", "idea is to produce objects that can contain complex data (called *state*) but\n", "present a few simple, easily documented methods to access that data.\n", "\n", "## Learning objectives\n", "\n", "* Be able to define a simple class that contains class variables, instance variables and instance methods and use it to pass parameters into and out of a function." ] }, { "cell_type": "markdown", "metadata": { "lines_to_next_cell": 0 }, "source": [ "## Types and classes\n", "\n", "In python, a type and a class are essentially synonyms. Consider the following code example:" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "\n", "\n", "class Problem_Def:\n", " \"\"\"\n", " this class holds the specifcation for the domain,\n", " including the value of the porosity\n", " \"\"\"\n", "\n", " def __init__(self, nx, ny, poro, wx, wy):\n", " self.nx = nx\n", " self.ny = ny\n", " self.poro = poro\n", " self.wx = wx\n", " self.wy = wy\n", "\n", "\n", "n_x = 20\n", "n_y = 30\n", "poro = 0.4\n", "width_x = 10\n", "width_y = 15\n", "the_problem = Problem_Def(n_x, n_y, poro, width_x, width_y)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In words, we say that `the_problem` is an **instance** of type `Problem_Def`. The instance is **constructed** by calling the special\n", "**instance method** `__init__` which is referred to as the **class constructor**." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Class variables\n", "\n", "Suppose we have some constants that we want to share among every\n", "instance of a class. We guarantee that every instance has\n", "the same set of constants by making them **class variables**" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "class Problem_Def:\n", " \"\"\"\n", " this class holds the specifcation for the domain,\n", " including the value of the porosity\n", " \"\"\"\n", "\n", " speed_of_light = 3.0e8 # m/s\n", "\n", " def __init__(self, nx, ny, poro, wx, wy):\n", " self.nx = nx\n", " self.ny = ny\n", " self.poro = poro\n", " self.wx = wx\n", " self.wy = wy" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Note the difference between prob1.wx and prob2.wx:" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "200.0 300000000.0 300 300000000.0\n" ] } ], "source": [ "width_x = 200.0\n", "prob1 = Problem_Def(n_x, n_y, poro, width_x, width_y)\n", "width_x = 300\n", "prob2 = Problem_Def(n_x, n_y, poro, width_x, width_y)\n", "print(prob1.wx, prob1.speed_of_light, prob2.wx, prob2.speed_of_light)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Instance variables\n", "\n", "The whole point of constructing instances is to have them\n", "carry unique information. Note that `__init__` has no return\n", "line. This is because it doesn't return a value, instead it\n", "modifies the memory of the specific instance being constructed\n", "(called `self` ) by setting the instance variables to the values\n", "specified by the class constructor. The `__init__` constructor is\n", "a regular python function, so it can take default values like this:" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "10\n" ] } ], "source": [ "class Problem_Def:\n", " \"\"\"\n", " this class holds the specifcation for the domain,\n", " including the value of the porosity\n", " \"\"\"\n", "\n", " speed_of_light = 3.0e8 # m/s\n", "\n", " def __init__(self, nx, ny, poro, wx=10, wy=10):\n", " self.nx = nx\n", " self.ny = ny\n", " self.poro = poro\n", " self.wx = wx\n", " self.wy = wy\n", "\n", "\n", "prob3 = Problem_Def(n_x, n_y, poro)\n", "print(prob3.wx)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Instance methods\n", "\n", "A class can define functions (called `methods` when used in a class) in\n", "addition to its constructor. This essentially solves the problem\n", "of passing many arguments to a function. Consider this new\n", "method:" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "inside critical_coef\n", "note that I only need self\n", "to use all the information inside\n", "the instance\n", "\n", "\n", "crit_condition=500.0\n" ] } ], "source": [ "class Problem_Def:\n", " \"\"\"\n", " this class holds the specifcation for the domain,\n", " including the value of the porosity\n", " \"\"\"\n", "\n", " speed_of_light = 3.0e8 # m/s\n", "\n", " def __init__(self, nx, ny, poro, wx=10, wy=10):\n", " self.nx = nx\n", " self.ny = ny\n", " self.poro = poro\n", " self.wx = wx\n", " self.wy = wy\n", "\n", " def critical_coef(self):\n", " print(\n", " (\n", " \"inside critical_coef\\n\"\n", " \"note that I only need self\\n\"\n", " \"to use all the information inside\\n\"\n", " \"the instance\\n\\n\"\n", " )\n", " )\n", " crit_num = self.nx * self.wx / self.poro\n", " return crit_num\n", "\n", "\n", "prob4 = Problem_Def(n_x, n_y, poro)\n", "crit_condition = prob4.critical_coef()\n", "print(f\"crit_condition={crit_condition}\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Passing self to instance methods\n", "\n", "Python made the design decision to require that all instance\n", "methods take self as their first parameter, even though it\n", "isn't needed when you actually call the method. A language like C++ made\n", "a different decision -- its version of `self`, called `this`,\n", "isn't needed in c++ method signatures. This rationale for this difference is an example of\n", "this line from the *Zen of Python*:\n", "\n", "**Explicit is better than implicit**\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Your turn\n", "\n", "In the cell below add two new instance methods to the\n", "`Problem_Def` class:\n", "\n", "* `to_dict`: a method that returns a dictionary containing the\n", " 5 data members of the instance\n", "\n", "* `from_dict`: a method that takes a dictonary with the five\n", " data members and returns an new instance of type `Problem_Def`\n", "\n", "* Test that you can **round trip** an instance of your class by\n", " saving it as a dict, then using that dict to construct a new\n", " class instance\n", "\n", "* Note that `from_dict` doesn't need any information from the instance\n", " in order to create a new object. This is outside the scope of\n", " this course, but if/when you learn more about object-oriented\n", " programming you'll see that `from_dict` is an example of a function that should\n", " be written as a class method, not an instance method." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Solution" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Summary\n", "\n", "* Python uses user-defined types, called classes, to solve the\n", " problem of *information hiding*, i.e. organizing information so that\n", " it can be compartmentalized, reducing the complexity of\n", " code.\n", "\n", "* We don't need to use inheritence in this course, but it is a big\n", " part of what makes objects useful. It allows us to extend other\n", " peoples classes without touching their original source code.\n", " This is called the \"open/closed principal\", i.e. objects should\n", " be open to extension while being closed to modification." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Appendix\n", "\n", "### Further reading\n", "\n", "Again, not needed for this course, but useful if you want to understand\n", "the main parts of python or other object-oriented languages\n", "\n", "* [Chapter15 of How to think like a computer scientist](http://openbookproject.net/thinkcs/python/english3e/classes_and_objects_I.html)\n", "\n", "* [Module 4 of Python like you mean it](https://www.pythonlikeyoumeanit.com/module_4.html)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Internet sources for the definitions used in this notebook:\n", "\n", "From https://docs.python.org/3/tutorial/classes.html\n", "\n", "Classes provide a means of bundling data and functionality together. Creating a new class creates a new type of object, allowing new instances of that type to be made. Each class instance can have attributes attached to it for maintaining its state. Class instances can also have methods (defined by its class) for modifying its state.\n", "\n", "From: https://www.tutorialspoint.com/python/python_classes_objects.htm\n", "\n", "Class − A user-defined prototype for an object that defines a set of attributes that characterize any object of the class. The attributes are data members (class variables and instance variables) and methods, accessed via dot notation.\n", "\n", "Instance − An individual object of a certain class. An object obj that belongs to a class Circle, for example, is an instance of the class Circle.\n", "\n", "Instantiation − The creation of an instance of a class.\n", "\n", "Class variable − A variable that is shared by all instances of a class. Class variables are defined within a class but outside any of the class's methods. Class variables are not used as frequently as instance variables are.\n", "\n", "Data member − A class variable or instance variable that holds data associated with a class and its objects.\n", "\n", "Function overloading − The assignment of more than one behavior to a particular function. The operation performed varies by the types of objects or arguments involved.\n", "\n", "Instance variable − A variable that is defined inside a method and belongs only to the current instance of a class.\n", "\n", "Inheritance − The transfer of the characteristics of a class to other classes that are derived from it.\n", "\n", "Method − A special kind of function that is defined in a class definition.\n", "\n", "Object − A unique instance of a data structure that's defined by its class. An object comprises both data members (class variables and instance variables) and methods.\n", "\n", "Operator overloading − The assignment of more than one function to a particular operator.\n", "\n", "Creating Classes\n", "The class statement creates a new class definition. The name of the class immediately\n", "follows the keyword class followed by a colon as follows:\n", "\n", " class ClassName:\n", " \"Optional class documentation string\"\n", " class body\n", "\n", "The class has a documentation string, which can be accessed via ClassName.__doc__.\n", "\n", "The class body consists of all the component statements defining class members, data attributes and functions." ] } ], "metadata": { "jupytext": { "cell_metadata_filter": "all", "notebook_metadata_filter": "all", "text_representation": { "extension": ".py", "format_name": "percent", "format_version": "1.2", "jupytext_version": "1.0.2" } }, "kernelspec": { "display_name": "Python 3", "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.6.7" }, "nbsphinx": { "execute": "never" }, "toc": { "base_numbering": 1, "nav_menu": {}, "number_sections": true, "sideBar": true, "skip_h1_title": true, "title_cell": "Table of Contents", "title_sidebar": "Contents", "toc_cell": true, "toc_position": { "height": "calc(100% - 180px)", "left": "10px", "top": "150px", "width": "198.225px" }, "toc_section_display": true, "toc_window_display": true } }, "nbformat": 4, "nbformat_minor": 2 }