{ "cells": [ { "cell_type": "markdown", "id": "06f201f8-faa2-42fc-bde5-eb7531269584", "metadata": { "tags": [] }, "source": [ "# How do you create an object? (continued)" ] }, { "cell_type": "markdown", "id": "6de44188-0743-417c-961c-82ae3edc85eb", "metadata": { "tags": [] }, "source": [ "## Special Class Methods: *The Python Data Model*\n", "\n", "When we were introducing and exploring the `dir` built-in function, we noticed that a *lot* of objects have attributes with \"special\" names, in particular names that start and end with a double-underscore (e.g., `__init__`). \n", "\n", "*These attributes are special!* And typically Python reserves special names for these attributes to give the objects special functionality. \n", "\n", "We'll touch on a few common *special methods* below, but you can take a look at a more complete list of *all* of the special methods in the [Python Data Model in the Python Documentation](https://docs.python.org/3/reference/datamodel.html?highlight=data%20model#special-method-names)." ] }, { "cell_type": "markdown", "id": "8c82d108-627b-432a-b590-8c08aadaaa42", "metadata": {}, "source": [ "### Example: Initializing instance data during construction\n", "\n", "The common way to initialize instance data at construction time is with the `__init__` method. For example, if you have a class with instance data `y`, you can initialize the value of `y` at construction time like so:" ] }, { "cell_type": "code", "execution_count": null, "id": "c516ba63-9561-4497-8b60-6bc1d025cf93", "metadata": {}, "outputs": [], "source": [ "class MyClass1:\n", " \n", " def __init__(self, y):\n", " self.y = y" ] }, { "cell_type": "markdown", "id": "1aa9321d-1b1f-4df7-935c-42048e551ec7", "metadata": {}, "source": [ "And you can initialize the value of `y` when you create each instance:" ] }, { "cell_type": "code", "execution_count": null, "id": "23e188c1-f30e-4862-95c3-04571b188cf4", "metadata": {}, "outputs": [], "source": [ "mc1 = MyClass1(2)" ] }, { "cell_type": "code", "execution_count": null, "id": "919d9032-124f-4aa3-af5e-70eeda7b0295", "metadata": {}, "outputs": [], "source": [ "mc1.y" ] }, { "cell_type": "markdown", "id": "84b885df-b70f-49f0-9f3d-6404bbbafa32", "metadata": {}, "source": [ "### Example: String representation of an object\n", "\n", "When an object is \"printed\" or converted to a string, the `__str__` special method is called. There is a default behavior for this for any object, but you can customize it with this method." ] }, { "cell_type": "code", "execution_count": null, "id": "c0d3a16e-93c1-495a-be67-813d9f1db1fb", "metadata": {}, "outputs": [], "source": [ "str(mc1)" ] }, { "cell_type": "code", "execution_count": null, "id": "ee6374cf-c7d9-4d41-8947-7062a16c0e45", "metadata": {}, "outputs": [], "source": [ "class MyClass2:\n", " \n", " def __str__(self):\n", " return ''" ] }, { "cell_type": "code", "execution_count": null, "id": "16d0e916-c67f-48a0-ab74-7c9f016e91ae", "metadata": {}, "outputs": [], "source": [ "mc2 = MyClass2()" ] }, { "cell_type": "code", "execution_count": null, "id": "cd485c94-5de6-40c9-a453-55dc614e00bd", "metadata": {}, "outputs": [], "source": [ "str(mc2)" ] }, { "cell_type": "markdown", "id": "296d8ec3-d9f0-46d1-99f2-e0a048fc63a6", "metadata": {}, "source": [ "### Example: Rich comparison\n", "\n", "You can define \"less than\", \"greator than\", \"equal to\", \"not equal to\", \"greator than or equal to\", and \"less than or equal to\" meaning to your custom objects with the `__lt__`, `__gt__`, `__eq__`, `__ne__`, `__ge__`, and `__le__` methods." ] }, { "cell_type": "code", "execution_count": null, "id": "296cc604-2ae2-48e1-832e-4a1a87413645", "metadata": {}, "outputs": [], "source": [ "class MyClass3:\n", " \n", " def __init__(self, x=2):\n", " self.x = x\n", " \n", " def __lt__(self, other):\n", " return self.x < other.x\n", " \n", " def __gt__(self, other):\n", " return self.x > other.x" ] }, { "cell_type": "code", "execution_count": null, "id": "f17c9978-cdaa-47c1-a103-5aea5a65c1b7", "metadata": {}, "outputs": [], "source": [ "mc3a = MyClass3(4)\n", "mc3b = MyClass3(6)" ] }, { "cell_type": "code", "execution_count": null, "id": "86f74517-f9cf-4274-9795-86e2a93f4282", "metadata": {}, "outputs": [], "source": [ "mc3a < mc3b" ] }, { "cell_type": "code", "execution_count": null, "id": "b29007ec-f9bb-4ea1-81d7-69fc49b00026", "metadata": {}, "outputs": [], "source": [ "mc3b > mc3a" ] }, { "cell_type": "markdown", "id": "caf8ad63-1a15-4869-a034-d7e27c85cc30", "metadata": {}, "source": [ "### Example: Container with a defined length\n", "\n", "Sometimes you want to create an object that \"contains\" a variable number of other things. Lists are like this, as are dictionaries. You can implement the `__len__` object to allow the `len()` built-in function to return the \"length\" (number of items contained) of the object." ] }, { "cell_type": "code", "execution_count": null, "id": "5dc195a4-9c4e-409e-9724-1c165ad9a8de", "metadata": {}, "outputs": [], "source": [ "class MyClass4:\n", " \n", " def __init__(self, *args):\n", " self.data = args\n", " \n", " def __len__(self):\n", " return len(self.data)" ] }, { "cell_type": "code", "execution_count": null, "id": "ce0a0174-87c0-464b-a274-ca100942d2c4", "metadata": {}, "outputs": [], "source": [ "mc4 = MyClass4(1,2,3,4)" ] }, { "cell_type": "code", "execution_count": null, "id": "0330a6a6-4600-43a0-bde2-51bec058c1ca", "metadata": {}, "outputs": [], "source": [ "mc4.data" ] }, { "cell_type": "code", "execution_count": null, "id": "a6b89259-297b-4de1-8068-de70da6a83fc", "metadata": {}, "outputs": [], "source": [ "len(mc4)" ] }, { "cell_type": "markdown", "id": "2400db5a-9ff8-4bf1-a8d2-81c71a15806f", "metadata": {}, "source": [ "### Example: Check if something is \"in\" a container\n", "\n", "When you create objects that \"contain\" other things, you sometimes want an easy way of seeing if something is \"contained\" by the object. This is handled with the `in` keyword and you can implement how it works with the `__contains__` special method." ] }, { "cell_type": "code", "execution_count": null, "id": "d448aea0-105e-4823-a52e-c3b9b254a76e", "metadata": {}, "outputs": [], "source": [ "class MyClass5:\n", " \n", " def __init__(self, *args):\n", " self.data = args\n", " \n", " def __contains__(self, item):\n", " return item in self.data" ] }, { "cell_type": "code", "execution_count": null, "id": "0fe653a4-6a72-40d0-9675-644e26da2eb6", "metadata": {}, "outputs": [], "source": [ "mc5 = MyClass5(1,2,3)" ] }, { "cell_type": "code", "execution_count": null, "id": "6dbe8789-92c7-4b63-910e-1a005782089c", "metadata": {}, "outputs": [], "source": [ "2 in mc5" ] }, { "cell_type": "code", "execution_count": null, "id": "21f3152b-d5e0-4979-8945-2a6d95cbf2a6", "metadata": {}, "outputs": [], "source": [ "4 in mc5" ] }, { "cell_type": "markdown", "id": "387f9acf-904b-457b-9d21-1b5a61b21e07", "metadata": {}, "source": [ "### Example: Key-value access for a container\n", "\n", "Dictionary-like key-value access to an object (i.e., `object[key] = value`) can be provided with the `__getitem__` and `__setitem__` special methods." ] }, { "cell_type": "code", "execution_count": null, "id": "7801e29b-d5e3-44ef-bbef-6759aa5ebb5e", "metadata": {}, "outputs": [], "source": [ "class MyClass6:\n", " \n", " def __init__(self, **kwargs):\n", " self.data = kwargs\n", " \n", " def __getitem__(self, key):\n", " return self.data[key]\n", " \n", " def __setitem__(self, key, value):\n", " self.data[key] = value" ] }, { "cell_type": "code", "execution_count": null, "id": "b178ed36-2200-4eb5-bcdd-0f3e22c679eb", "metadata": {}, "outputs": [], "source": [ "mc6 = MyClass6(a=2, b=3, c=6)" ] }, { "cell_type": "code", "execution_count": null, "id": "156b7a02-77dc-4272-a232-c9c4b0a17c97", "metadata": {}, "outputs": [], "source": [ "mc6['a']" ] }, { "cell_type": "code", "execution_count": null, "id": "c02c88dd-f49d-4b29-a07a-1c4781702891", "metadata": {}, "outputs": [], "source": [ "mc6['d'] = 8" ] }, { "cell_type": "code", "execution_count": null, "id": "ecdb5b83-8e8a-4129-ba67-58e51a85f33c", "metadata": {}, "outputs": [], "source": [ "mc6.data" ] }, { "cell_type": "markdown", "id": "ed938b57-e19d-4376-a39d-f10f8dbc1405", "metadata": {}, "source": [ "### Example: Numeric types\n", "\n", "There are actually a *lot* of special methods that you can implement to create numeric data-like operations, such as addition (`__add__`, `__radd__`, and `__iadd__`), subtraction (`__sub__`, `__rsub__`, and `__isub__`), multiplication (`__mul__`, `__rmul__`, and `__imul__`), division (`__truediv__`, `__floordiv__`, ...), and others.\n", "\n", "I won't go into all of this here, but I recommend you look at the [Python documentation](https://docs.python.org/3/reference/datamodel.html?highlight=data%20model#emulating-numeric-types) if you want more details." ] }, { "cell_type": "code", "execution_count": null, "id": "56706a8d-79f1-470d-94c8-c2feae44775c", "metadata": {}, "outputs": [], "source": [ "class MyClass7:\n", " \n", " def __init__(self, x):\n", " self.x = x\n", " \n", " def __add__(self, other):\n", " if isinstance(other, MyClass7):\n", " new_x = self.x + other.x\n", " else:\n", " new_x = self.x + other\n", " return MyClass7(new_x)\n", " \n", " def __sub__(self, other):\n", " if isinstance(other, MyClass7):\n", " new_x = self.x - other.x\n", " else:\n", " new_x = self.x - other\n", " return MyClass7(new_x)" ] }, { "cell_type": "markdown", "id": "2a18884d-537f-4c53-ad35-29fa04f63e8e", "metadata": {}, "source": [ "
\n", " Note:\n", "

Numeric special methods can get complicated because you don't really know if other is an instance of a MyClass7 object, or not! You have to handle special cases manually.

\n", "
" ] }, { "cell_type": "code", "execution_count": null, "id": "750a0338-cb15-440e-97bb-7264395e45a0", "metadata": {}, "outputs": [], "source": [ "mc7a = MyClass7(3)\n", "mc7b = MyClass7(7)" ] }, { "cell_type": "code", "execution_count": null, "id": "5a702af0-3ac5-47d6-b3b5-a000540396cd", "metadata": {}, "outputs": [], "source": [ "mc7c = mc7a + mc7b\n", "mc7c.x" ] }, { "cell_type": "code", "execution_count": null, "id": "d39a7f59-3651-444b-a958-3264635d3158", "metadata": {}, "outputs": [], "source": [ "mc7d = mc7a - mc7b\n", "mc7d.x" ] }, { "cell_type": "code", "execution_count": null, "id": "95028514-4ff6-4a92-afd9-3e9a857cd312", "metadata": {}, "outputs": [], "source": [ "mc7e = mc7a + 2\n", "mc7e.x" ] }, { "cell_type": "markdown", "id": "362ff1f4-6d1d-4a16-a0de-cbc31f89fe17", "metadata": {}, "source": [ "
\n", " Note:\n", "

If you want to deal with cases like 2 + mc7a, then you have to implement the __radd__ special method!

\n", "
" ] }, { "cell_type": "markdown", "id": "18179fdb-9dae-4080-b3ac-89ae0c47de61", "metadata": {}, "source": [ "***There's so much you can do with Python special methods, and this just scratches the surface...***" ] }, { "cell_type": "markdown", "id": "01ba2148-9d4a-4173-b29f-6d655b4db982", "metadata": {}, "source": [ "| | | |\n", "| :- | -- | -: |\n", "| [[Home]](../index.ipynb) | | [« Previous](04.ipynb) \\| [Next »](06.ipynb) |" ] } ], "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.9.7" } }, "nbformat": 4, "nbformat_minor": 5 }