{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "<small><small><i>\n", "All the IPython Notebooks in **Python Advanced Topics** lecture series by Dr. Milaan Parmar are available @ **[GitHub](https://github.com/milaan9/07_Python_Advanced_Topics)**\n", "</i></small></small>" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Python `@property` decorator\n", "\n", "In this class, you will learn about Python **`@property`** decorator; a pythonic way to use getters and setters in object-oriented programming.\n", "\n", "Python programming provides us with a built-in **`@property`** decorator which makes usage of getter and setters much easier in Object-Oriented Programming.\n", "\n", "Before going into details on what **`@property`** decorator is, let us first build an intuition on why it would be needed in the first place." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Class Without Getters and Setters\n", "\n", "Let us assume that we decide to make a **[class](https://github.com/milaan9/06_Python_Object_Class/blob/main/002_Python_Classes_and_Objects.ipynb)** that stores the temperature in degrees Celsius. It would also implement a method to convert the temperature into degrees Fahrenheit. One way of doing this is as follows:" ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "ExecuteTime": { "end_time": "2021-06-22T06:24:07.534892Z", "start_time": "2021-06-22T06:24:07.529034Z" } }, "outputs": [], "source": [ "class Celsius:\n", " def __init__(self, temperature = 0):\n", " self.temperature = temperature\n", "\n", " def to_fahrenheit(self):\n", " return (self.temperature * 1.8) + 32" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can make objects out of this class and manipulate the **`temperature`** attribute as we wish:" ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "ExecuteTime": { "end_time": "2021-06-22T06:24:08.889417Z", "start_time": "2021-06-22T06:24:08.873792Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "37\n", "98.60000000000001\n" ] } ], "source": [ "# Basic method of setting and getting attributes in Python\n", "\n", "class Celsius:\n", " def __init__(self, temperature=0):\n", " self.temperature = temperature\n", "\n", " def to_fahrenheit(self):\n", " return (self.temperature * 1.8) + 32\n", "\n", "\n", "# Create a new object\n", "human = Celsius()\n", "\n", "# Set the temperature\n", "human.temperature = 37\n", "\n", "# Get the temperature attribute\n", "print(human.temperature)\n", "\n", "# Get the to_fahrenheit method\n", "print(human.to_fahrenheit())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The extra decimal places when converting into Fahrenheit is due to the floating point arithmetic error. To learn more, visit **[Python Floating Point Arithmetic Error](https://github.com/milaan9/02_Python_Datatypes/blob/main/001_Python_Numbers.ipynb)**.\n", "\n", "Whenever we assign or retrieve any object attribute like **`temperature`** as shown above, Python searches it in the object's built-in **`__dict__`** dictionary attribute." ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "ExecuteTime": { "end_time": "2021-06-22T06:24:10.170211Z", "start_time": "2021-06-22T06:24:10.144824Z" }, "scrolled": true }, "outputs": [ { "data": { "text/plain": [ "{'temperature': 37}" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "human.__dict__" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Therefore, **`man.temperature`** internally becomes **`man.__dict__['temperature']`**." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Using Getters and Setters\n", "\n", "Suppose we want to extend the usability of the **`Celsius`** class defined above. We know that the temperature of any object cannot reach below -273.15 degrees Celsius (Absolute Zero in Thermodynamics)\n", "\n", "Let's update our code to implement this value constraint.\n", "\n", "An obvious solution to the above restriction will be to hide the attribute **`temperature`** (make it private) and define new getter and setter methods to manipulate it. This can be done as follows:" ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "ExecuteTime": { "end_time": "2021-06-22T06:24:11.473954Z", "start_time": "2021-06-22T06:24:11.465168Z" } }, "outputs": [], "source": [ "# Making Getters and Setter methods\n", "class Celsius:\n", " def __init__(self, temperature=0):\n", " self.set_temperature(temperature)\n", "\n", " def to_fahrenheit(self):\n", " return (self.get_temperature() * 1.8) + 32\n", "\n", " # getter method\n", " def get_temperature(self):\n", " return self._temperature\n", "\n", " # setter method\n", " def set_temperature(self, value):\n", " if value < -273.15:\n", " raise ValueError(\"Temperature below -273.15 is not possible.\")\n", " self._temperature = value" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "As we can see, the above method introduces two new **`get_temperature()`** and **`set_temperature()`** methods.\n", "\n", "Furthermore, **`temperature`** was replaced with **`_temperature`**. An underscore **`_`** at the beginning is used to denote private variables in Python." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now, let's use this implementation:" ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "ExecuteTime": { "end_time": "2021-06-22T06:24:13.263058Z", "start_time": "2021-06-22T06:24:12.928094Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "37\n", "98.60000000000001\n" ] }, { "ename": "ValueError", "evalue": "Temperature below -273.15 is not possible.", "output_type": "error", "traceback": [ "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[1;31mValueError\u001b[0m Traceback (most recent call last)", "\u001b[1;32m<ipython-input-5-f7d40ce0c112>\u001b[0m in \u001b[0;36m<module>\u001b[1;34m\u001b[0m\n\u001b[0;32m 28\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 29\u001b[0m \u001b[1;31m# new constraint implementation\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m---> 30\u001b[1;33m \u001b[0mhuman\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mset_temperature\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m-\u001b[0m\u001b[1;36m300\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 31\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 32\u001b[0m \u001b[1;31m# Get the to_fahreheit method\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", "\u001b[1;32m<ipython-input-5-f7d40ce0c112>\u001b[0m in \u001b[0;36mset_temperature\u001b[1;34m(self, value)\u001b[0m\n\u001b[0;32m 14\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0mset_temperature\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mvalue\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 15\u001b[0m \u001b[1;32mif\u001b[0m \u001b[0mvalue\u001b[0m \u001b[1;33m<\u001b[0m \u001b[1;33m-\u001b[0m\u001b[1;36m273.15\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m---> 16\u001b[1;33m \u001b[1;32mraise\u001b[0m \u001b[0mValueError\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m\"Temperature below -273.15 is not possible.\"\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 17\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m_temperature\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mvalue\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 18\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n", "\u001b[1;31mValueError\u001b[0m: Temperature below -273.15 is not possible." ] } ], "source": [ "# Making Getters and Setter methods\n", "class Celsius:\n", " def __init__(self, temperature=0):\n", " self.set_temperature(temperature)\n", "\n", " def to_fahrenheit(self):\n", " return (self.get_temperature() * 1.8) + 32\n", "\n", " # getter method\n", " def get_temperature(self):\n", " return self._temperature\n", "\n", " # setter method\n", " def set_temperature(self, value):\n", " if value < -273.15:\n", " raise ValueError(\"Temperature below -273.15 is not possible.\")\n", " self._temperature = value\n", "\n", "\n", "# Create a new object, set_temperature() internally called by __init__\n", "human = Celsius(37)\n", "\n", "# Get the temperature attribute via a getter\n", "print(human.get_temperature())\n", "\n", "# Get the to_fahrenheit method, get_temperature() called by the method itself\n", "print(human.to_fahrenheit())\n", "\n", "# new constraint implementation\n", "human.set_temperature(-300)\n", "\n", "# Get the to_fahreheit method\n", "print(human.to_fahrenheit())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This update successfully implemented the new restriction. We are no longer allowed to set the temperature below -273.15 degrees Celsius.\n", "\n", ">**Note**: The private variables don't actually exist in Python. There are simply norms to be followed. The language itself doesn't apply any restrictions.\n", "```python\n", ">>> human._temperature = -300\n", ">>> human.get_temperature()\n", "-300\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "However, the bigger problem with the above update is that all the programs that implemented our previous class have to modify their code from **`obj.temperature`** to **`obj.get_temperature()`** and all expressions like **`obj.temperature = val`** to **`obj.set_temperature(val)`**.\n", "\n", "This refactoring can cause problems while dealing with hundreds of thousands of lines of codes.\n", "\n", "All in all, our new update was not backwards compatible. This is where **`@property`** comes to rescue." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## The property Class\n", "\n", "A pythonic way to deal with the above problem is to use the property class. Here is how we can update our code:" ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "ExecuteTime": { "end_time": "2021-06-22T06:24:15.200609Z", "start_time": "2021-06-22T06:24:15.184983Z" } }, "outputs": [], "source": [ "# using property class\n", "class Celsius:\n", " def __init__(self, temperature=0):\n", " self.temperature = temperature\n", "\n", " def to_fahrenheit(self):\n", " return (self.temperature * 1.8) + 32\n", "\n", " # getter\n", " def get_temperature(self):\n", " print(\"Getting value...\")\n", " return self._temperature\n", "\n", " # setter\n", " def set_temperature(self, value):\n", " print(\"Setting value...\")\n", " if value < -273.15:\n", " raise ValueError(\"Temperature below -273.15 is not possible\")\n", " self._temperature = value\n", "\n", " # creating a property object\n", " temperature = property(get_temperature, set_temperature)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We added a **`print()`** function inside **`get_temperature()`** and **`set_temperature()`** to clearly observe that they are being executed.\n", "\n", "The last line of the code makes a property object `temperature`. Simply put, property attaches some code (**`get_temperature`** and **`set_temperature`**) to the member attribute accesses (**`temperature`**)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's use this update code:" ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "ExecuteTime": { "end_time": "2021-06-22T06:24:17.212379Z", "start_time": "2021-06-22T06:24:17.186011Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Setting value...\n", "Getting value...\n", "37\n", "Getting value...\n", "98.60000000000001\n", "Setting value...\n" ] }, { "ename": "ValueError", "evalue": "Temperature below -273.15 is not possible", "output_type": "error", "traceback": [ "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[1;31mValueError\u001b[0m Traceback (most recent call last)", "\u001b[1;32m<ipython-input-7-dd6f749658a4>\u001b[0m in \u001b[0;36m<module>\u001b[1;34m\u001b[0m\n\u001b[0;32m 29\u001b[0m \u001b[0mprint\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mhuman\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mto_fahrenheit\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 30\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m---> 31\u001b[1;33m \u001b[0mhuman\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mtemperature\u001b[0m \u001b[1;33m=\u001b[0m \u001b[1;33m-\u001b[0m\u001b[1;36m300\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[1;32m<ipython-input-7-dd6f749658a4>\u001b[0m in \u001b[0;36mset_temperature\u001b[1;34m(self, value)\u001b[0m\n\u001b[0;32m 16\u001b[0m \u001b[0mprint\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m\"Setting value...\"\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 17\u001b[0m \u001b[1;32mif\u001b[0m \u001b[0mvalue\u001b[0m \u001b[1;33m<\u001b[0m \u001b[1;33m-\u001b[0m\u001b[1;36m273.15\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m---> 18\u001b[1;33m \u001b[1;32mraise\u001b[0m \u001b[0mValueError\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m\"Temperature below -273.15 is not possible\"\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 19\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m_temperature\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mvalue\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 20\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n", "\u001b[1;31mValueError\u001b[0m: Temperature below -273.15 is not possible" ] } ], "source": [ "# using property class\n", "class Celsius:\n", " def __init__(self, temperature=0):\n", " self.temperature = temperature\n", "\n", " def to_fahrenheit(self):\n", " return (self.temperature * 1.8) + 32\n", "\n", " # getter\n", " def get_temperature(self):\n", " print(\"Getting value...\")\n", " return self._temperature\n", "\n", " # setter\n", " def set_temperature(self, value):\n", " print(\"Setting value...\")\n", " if value < -273.15:\n", " raise ValueError(\"Temperature below -273.15 is not possible\")\n", " self._temperature = value\n", "\n", " # creating a property object\n", " temperature = property(get_temperature, set_temperature)\n", "\n", "\n", "human = Celsius(37)\n", "\n", "print(human.temperature)\n", "\n", "print(human.to_fahrenheit())\n", "\n", "human.temperature = -300" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "As we can see, any code that retrieves the value of **`temperature`** will automatically call **`get_temperature()`** instead of a dictionary (**`__dict__`**) look-up. Similarly, any code that assigns a value to **`temperature`** will automatically call **`set_temperature()`**.\n", "\n", "We can even see above that **`set_temperature()`** was called even when we created an object." ] }, { "cell_type": "code", "execution_count": 8, "metadata": { "ExecuteTime": { "end_time": "2021-06-22T06:24:18.774427Z", "start_time": "2021-06-22T06:24:18.768568Z" }, "scrolled": true }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Setting value...\n" ] } ], "source": [ "human = Celsius(37)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Can you guess why?**\n", "\n", "The reason is that when an object is created, the **`__init__()`** method gets called. This method has the line **`self.temperature = temperature`**. This expression automatically calls **`set_temperature()`**.\n", "\n", "Similarly, any access like **`c.temperature`** automatically calls **`get_temperature()`**. This is what property does. Here are a few more examples." ] }, { "cell_type": "code", "execution_count": 9, "metadata": { "ExecuteTime": { "end_time": "2021-06-22T06:24:19.924845Z", "start_time": "2021-06-22T06:24:19.904340Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Getting value...\n" ] }, { "data": { "text/plain": [ "37" ] }, "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ "human.temperature" ] }, { "cell_type": "code", "execution_count": 10, "metadata": { "ExecuteTime": { "end_time": "2021-06-22T06:24:21.069408Z", "start_time": "2021-06-22T06:24:21.056712Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Setting value...\n" ] } ], "source": [ "human.temperature = 37" ] }, { "cell_type": "code", "execution_count": 11, "metadata": { "ExecuteTime": { "end_time": "2021-06-22T06:24:21.877044Z", "start_time": "2021-06-22T06:24:21.856538Z" } }, "outputs": [ { "ename": "NameError", "evalue": "name 'c' is not defined", "output_type": "error", "traceback": [ "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[1;31mNameError\u001b[0m Traceback (most recent call last)", "\u001b[1;32m<ipython-input-11-2087a9c41308>\u001b[0m in \u001b[0;36m<module>\u001b[1;34m\u001b[0m\n\u001b[1;32m----> 1\u001b[1;33m \u001b[0mc\u001b[0m\u001b[1;33m.\u001b[0m \u001b[0mto_fahrenheit\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[1;31mNameError\u001b[0m: name 'c' is not defined" ] } ], "source": [ "c. to_fahrenheit()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "By using property, we can see that no modification is required in the implementation of the value constraint. Thus, our implementation is backward compatible.\n", "\n", ">**Note**: The actual temperature value is stored in the private **`_temperature`** variable. The **`temperature`** attribute is a property object which provides an interface to this private variable." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## The `@property` Decorator\n", "\n", "In Python, **`property()`** is a built-in function that creates and returns a **`property`** object. The syntax of this function is:\n", "\n", "```python\n", "property(fget=None, fset=None, fdel=None, doc=None)\n", "```\n", "\n", "where,\n", "\n", "* **`fget`** is function to get value of the attribute\n", "* **`fset`** is function to set value of the attribute\n", "* **`fdel`** is function to delete the attribute\n", "* **`doc`** is a string (like a comment)\n", "\n", "As seen from the implementation, these function arguments are optional. So, a property object can simply be created as follows." ] }, { "cell_type": "code", "execution_count": 12, "metadata": { "ExecuteTime": { "end_time": "2021-06-22T06:24:24.203272Z", "start_time": "2021-06-22T06:24:24.192533Z" } }, "outputs": [ { "data": { "text/plain": [ "<property at 0x170b84f24a0>" ] }, "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "source": [ "property()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "A property object has three methods, `getter()`, `setter()`, and `deleter()` to specify `fget`, `fset` and `fdel` at a later point. This means, the line:\n", "\n", "```python\n", "temperature = property(get_temperature,set_temperature)\n", "```\n", "\n", "can be broken down as:\n", "\n", "```python\n", "# make empty property\n", "temperature = property()\n", "# assign fget\n", "temperature = temperature.getter(get_temperature)\n", "# assign fset\n", "temperature = temperature.setter(set_temperature)\n", "```\n", "\n", "These two pieces of codes are equivalent.\n", "\n", "Programmers familiar with **[Python Decorators](https://github.com/milaan9/07_Python_Advanced_Topics/blob/main/004_Python_Decorators.ipynb)** can recognize that the above construct can be implemented as decorators.\n", "\n", "We can even not define the names **`get_temperature`** and **`set_temperature`** as they are unnecessary and pollute the class namespace." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "For this, we reuse the **`temperature`** name while defining our getter and setter functions. Let's look at how to implement this as a decorator:" ] }, { "cell_type": "code", "execution_count": 13, "metadata": { "ExecuteTime": { "end_time": "2021-06-22T06:24:26.264846Z", "start_time": "2021-06-22T06:24:26.225786Z" }, "scrolled": true }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Setting value...\n", "Getting value...\n", "37\n", "Getting value...\n", "98.60000000000001\n", "Setting value...\n" ] }, { "ename": "ValueError", "evalue": "Temperature below -273 is not possible", "output_type": "error", "traceback": [ "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[1;31mValueError\u001b[0m Traceback (most recent call last)", "\u001b[1;32m<ipython-input-13-57953b0b580c>\u001b[0m in \u001b[0;36m<module>\u001b[1;34m\u001b[0m\n\u001b[0;32m 28\u001b[0m \u001b[0mprint\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mhuman\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mto_fahrenheit\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 29\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m---> 30\u001b[1;33m \u001b[0mcoldest_thing\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mCelsius\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m-\u001b[0m\u001b[1;36m300\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[1;32m<ipython-input-13-57953b0b580c>\u001b[0m in \u001b[0;36m__init__\u001b[1;34m(self, temperature)\u001b[0m\n\u001b[0;32m 3\u001b[0m \u001b[1;32mclass\u001b[0m \u001b[0mCelsius\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 4\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0m__init__\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mtemperature\u001b[0m\u001b[1;33m=\u001b[0m\u001b[1;36m0\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 5\u001b[1;33m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mtemperature\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mtemperature\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 6\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 7\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0mto_fahrenheit\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", "\u001b[1;32m<ipython-input-13-57953b0b580c>\u001b[0m in \u001b[0;36mtemperature\u001b[1;34m(self, value)\u001b[0m\n\u001b[0;32m 17\u001b[0m \u001b[0mprint\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m\"Setting value...\"\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 18\u001b[0m \u001b[1;32mif\u001b[0m \u001b[0mvalue\u001b[0m \u001b[1;33m<\u001b[0m \u001b[1;33m-\u001b[0m\u001b[1;36m273.15\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m---> 19\u001b[1;33m \u001b[1;32mraise\u001b[0m \u001b[0mValueError\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m\"Temperature below -273 is not possible\"\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 20\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m_temperature\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mvalue\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 21\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n", "\u001b[1;31mValueError\u001b[0m: Temperature below -273 is not possible" ] } ], "source": [ "# Using @property decorator\n", "\n", "class Celsius:\n", " def __init__(self, temperature=0):\n", " self.temperature = temperature\n", "\n", " def to_fahrenheit(self):\n", " return (self.temperature * 1.8) + 32\n", "\n", " @property\n", " def temperature(self):\n", " print(\"Getting value...\")\n", " return self._temperature\n", "\n", " @temperature.setter\n", " def temperature(self, value):\n", " print(\"Setting value...\")\n", " if value < -273.15:\n", " raise ValueError(\"Temperature below -273 is not possible\")\n", " self._temperature = value\n", "\n", "\n", "# create an object\n", "human = Celsius(37)\n", "\n", "print(human.temperature)\n", "\n", "print(human.to_fahrenheit())\n", "\n", "coldest_thing = Celsius(-300)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The above implementation is simple and efficient. It is the recommended way to use **`property`**." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "hide_input": false, "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.8.8" }, "toc": { "base_numbering": 1, "nav_menu": {}, "number_sections": true, "sideBar": true, "skip_h1_title": false, "title_cell": "Table of Contents", "title_sidebar": "Contents", "toc_cell": false, "toc_position": {}, "toc_section_display": true, "toc_window_display": false }, "varInspector": { "cols": { "lenName": 16, "lenType": 16, "lenVar": 40 }, "kernels_config": { "python": { "delete_cmd_postfix": "", "delete_cmd_prefix": "del ", "library": "var_list.py", "varRefreshCmd": "print(var_dic_list())" }, "r": { "delete_cmd_postfix": ") ", "delete_cmd_prefix": "rm(", "library": "var_list.r", "varRefreshCmd": "cat(var_dic_list()) " } }, "types_to_exclude": [ "module", "function", "builtin_function_or_method", "instance", "_Feature" ], "window_display": false } }, "nbformat": 4, "nbformat_minor": 4 }