.. DO NOT EDIT. .. THIS FILE WAS AUTOMATICALLY GENERATED BY SPHINX-GALLERY. .. TO MAKE CHANGES, EDIT THE SOURCE PYTHON FILE: .. "auto_examples/oop/magical_methods.py" .. LINE NUMBERS ARE GIVEN BELOW. .. only:: html .. note:: :class: sphx-glr-download-link-note Click :ref:`here ` to download the full example code or to run this example in your browser via Binder .. rst-class:: sphx-glr-example-title .. _sphx_glr_auto_examples_oop_magical_methods.py: ===================== 3.16 magic methods ===================== This file describes the so called magic methods in python. Magic methods are those methods which start with double underscore ``__`` sign. We have already seen some of the magic methods such as ``__init__`` in :ref:`sphx_glr_auto_examples_oop_init.py` , ``__call__`` in :ref:`sphx_glr_auto_examples_oop_call.py` and about ``__repr__`` and ``__str__`` in :ref:`sphx_glr_auto_examples_oop_str_repr.py` lessons. Here we will cover some more. .. GENERATED FROM PYTHON SOURCE LINES 15-23 ``__add__`` ------------- This method determines the behavior when addition is performed on the instance of its class. Thus, using ``__add__`` method of a class, we can define how the addition on the instance of this class will work. For example, in class `NonSenseInteger` below, we are defining ``__add__`` method, so any instance of `NonSenseInteger` class will behave the way we are defining in ``__add__`` method. .. GENERATED FROM PYTHON SOURCE LINES 23-39 .. code-block:: default import os class NonSenseInteger(int): def __init__(self, value): self.value = value def __add__(self, other): return self.value - other ns_int = NonSenseInteger(10) print(ns_int + 5) .. rst-class:: sphx-glr-script-out .. code-block:: none 5 .. GENERATED FROM PYTHON SOURCE LINES 40-41 Above we have defined that addition will work as subtraction. .. GENERATED FROM PYTHON SOURCE LINES 41-44 .. code-block:: default print(5 + ns_int) .. rst-class:: sphx-glr-script-out .. code-block:: none 15 .. GENERATED FROM PYTHON SOURCE LINES 45-47 However, above, when the instance of class is on right side of addition operation, the addition did not happened the way we defined in ``__add__`` method. .. GENERATED FROM PYTHON SOURCE LINES 49-56 ``__radd__`` ------------- The ``__add__`` method does not determines the addition behavior of a class when the instance of the class is on right side of ``+`` operator. In order to overwrite this behavior i.e., the working of addition operation when the instance of class is on right side of ``+``, we have to write ``__radd__`` method. .. GENERATED FROM PYTHON SOURCE LINES 56-75 .. code-block:: default class NonSenseInteger(int): def __init__(self, value): self.value = value def __add__(self, other): return self.value - other def __radd__(self, other): return self.value * other ns_int = NonSenseInteger(10) print(ns_int + 5) print(5 + ns_int) .. rst-class:: sphx-glr-script-out .. code-block:: none 5 50 .. GENERATED FROM PYTHON SOURCE LINES 76-79 Above we see that when `ns_int` was on left side, subtraction was performed as we defined in ``__add__`` method and when `ns_int` was on right side, multiplication was performed as we defined inside ``__radd__`` method. .. GENERATED FROM PYTHON SOURCE LINES 81-85 ``__mul__`` ------------ This method determines the behavior when multiplication is performed on the instance of its class. .. GENERATED FROM PYTHON SOURCE LINES 85-100 .. code-block:: default class NonSenseInteger(int): def __init__(self, value): self.value = value def __mul__(self, other): return self.value + other ns_int = NonSenseInteger(10) print(ns_int * 5) .. rst-class:: sphx-glr-script-out .. code-block:: none 15 .. GENERATED FROM PYTHON SOURCE LINES 101-103 Although 10 * 5 is 50, but we got 15, because we modified the multiplication behavior of our `NoneSenseInteger` class. .. GENERATED FROM PYTHON SOURCE LINES 103-106 .. code-block:: default print(5 * ns_int) .. rst-class:: sphx-glr-script-out .. code-block:: none 50 .. GENERATED FROM PYTHON SOURCE LINES 107-109 ``__rmul__`` ------------- .. GENERATED FROM PYTHON SOURCE LINES 109-127 .. code-block:: default class NonSenseInteger(int): def __init__(self, value): self.value = value def __mul__(self, other): return self.value + other def __rmul__(self, other): return self.value - other ns_int = NonSenseInteger(10) print(ns_int * 5) print(5 * ns_int) .. rst-class:: sphx-glr-script-out .. code-block:: none 15 5 .. GENERATED FROM PYTHON SOURCE LINES 128-132 ``__sub__`` ------------ This method determines the behavior when subtraction operation is performed on the instance of its class. .. GENERATED FROM PYTHON SOURCE LINES 132-146 .. code-block:: default class NonSenseInteger(int): def __init__(self, value): self.value = value def __sub__(self, other): return self.value + other ns_int = NonSenseInteger(10) print(ns_int - 5) .. rst-class:: sphx-glr-script-out .. code-block:: none 15 .. GENERATED FROM PYTHON SOURCE LINES 147-150 .. code-block:: default print(5 - ns_int) .. rst-class:: sphx-glr-script-out .. code-block:: none -5 .. GENERATED FROM PYTHON SOURCE LINES 151-153 ``__rsub__`` ------------- .. GENERATED FROM PYTHON SOURCE LINES 153-172 .. code-block:: default class NonSenseInteger(int): def __init__(self, value): self.value = value def __sub__(self, other): return self.value + other def __rsub__(self, other): return self.value * other ns_int = NonSenseInteger(10) print(ns_int - 5) print(5 - ns_int) .. rst-class:: sphx-glr-script-out .. code-block:: none 15 50 .. GENERATED FROM PYTHON SOURCE LINES 173-177 ``__truediv__`` ---------------- This method determines the behavior when division operation is performed on the instance of the class. .. GENERATED FROM PYTHON SOURCE LINES 177-190 .. code-block:: default class NonSenseInteger(int): def __init__(self, value): self.value = value def __truediv__(self, other): return self.value * other ns_int = NonSenseInteger(10) print(ns_int / 5) .. rst-class:: sphx-glr-script-out .. code-block:: none 50 .. GENERATED FROM PYTHON SOURCE LINES 191-194 .. code-block:: default print(5 / ns_int) .. rst-class:: sphx-glr-script-out .. code-block:: none 0.5 .. GENERATED FROM PYTHON SOURCE LINES 195-197 ``__rtruediv__`` ----------------- .. GENERATED FROM PYTHON SOURCE LINES 197-216 .. code-block:: default class NonSenseInteger(int): def __init__(self, value): self.value = value def __truediv__(self, other): return self.value * other def __rtruediv__(self, other): return self.value + other ns_int = NonSenseInteger(10) print(ns_int / 5) print(5 / ns_int) .. rst-class:: sphx-glr-script-out .. code-block:: none 50 15 .. GENERATED FROM PYTHON SOURCE LINES 217-222 ``__enter__`` and ``__exit__`` ------------------------------ These methods are used by the context manager i.e. ``with``. They are executed/called when we 'enter' and 'exit' the context manager. .. GENERATED FROM PYTHON SOURCE LINES 222-242 .. code-block:: default class Insan: def __init__(self, name, year, age): self.name = name self.year = year self.age = age def __enter__(self): print(f"{self.name} was born in year {self.year}") return self def __exit__(self, exc_type, exc_val, exc_tb): print(f"{self.name} lived until {self.year + self.age} year") return def married(self, spouse_name:str): print(f"{self.name} married with {spouse_name}") return .. GENERATED FROM PYTHON SOURCE LINES 243-247 .. code-block:: default with Insan('Ali', 600, 63) as person: print("entered") .. rst-class:: sphx-glr-script-out .. code-block:: none Ali was born in year 600 entered Ali lived until 663 year .. GENERATED FROM PYTHON SOURCE LINES 248-253 If you note the print order of strings, you will find out that ``__enter__`` method was executed before ``print()`` function was called. Similarly, ``__exit__`` method was executed after ``print()`` function was called i.e. at the time of exiting the context manager. .. GENERATED FROM PYTHON SOURCE LINES 256-257 what if we implment only ``__exit__`` and not ``__enter__``? .. GENERATED FROM PYTHON SOURCE LINES 257-272 .. code-block:: default class Insan: def __init__(self, name, year, age): self.name = name self.year = year self.age = age def __exit__(self, exc_type, exc_val, exc_tb): print(f"{self.name} lived until {self.year + self.age} year") return def married(self, spouse_name:str): print(f"{self.name} married with {spouse_name}") return .. GENERATED FROM PYTHON SOURCE LINES 273-277 .. code-block:: default ali = Insan('Ali', 600, 63) print(type(ali)) .. rst-class:: sphx-glr-script-out .. code-block:: none .. GENERATED FROM PYTHON SOURCE LINES 278-284 .. code-block:: default # uncomment following three lines # with Insan('Ali', 600, 63) as person: # print("entered") # person.married('Falima') # -> AttributeError: __enter__ .. GENERATED FROM PYTHON SOURCE LINES 285-291 The error message shows that it is not possible to use `Insan` class with context manager without implementing ``__enter__`` method for this class. This is because when we say `with Insan('Ali', 600, 63) as person:`, the ``__enter__`` method of `Insan` class is called implicitly. When this method does not exist, we get the error as shown above. .. GENERATED FROM PYTHON SOURCE LINES 293-294 Same is true if we implement only ``__enter__`` method and not ``__exit__`` method. .. GENERATED FROM PYTHON SOURCE LINES 294-309 .. code-block:: default class Insan: def __init__(self, name, year, age): self.name = name self.year = year self.age = age def __enter__(self): print(f"{self.name} was born in year {self.year}") return self def married(self, spouse_name:str): print(f"{self.name} married with {spouse_name}") return .. GENERATED FROM PYTHON SOURCE LINES 310-316 .. code-block:: default # uncomment following three lines # with Insan('Ali', 600, 63) as person: # print("entered") # person.married('Falima') # -> AttributeError: __enter__ .. GENERATED FROM PYTHON SOURCE LINES 317-318 Other than that, we can still use this class as normal python class. .. GENERATED FROM PYTHON SOURCE LINES 318-322 .. code-block:: default ali = Insan('Ali', 600, 63) print(ali.age) .. rst-class:: sphx-glr-script-out .. code-block:: none 63 .. GENERATED FROM PYTHON SOURCE LINES 323-327 ``__iter__`` and ``__next__`` ------------------------------ The ``__next__`` magic method determines what will happen when we call ``next`` function on the instance of the class. .. GENERATED FROM PYTHON SOURCE LINES 327-344 .. code-block:: default class Insan: def __init__(self, num_child): self.children = [f"child_{i}" for i in range(num_child)] self.index = 0 def __next__(self): item = self.children[self.index] self.index += 1 return item ali = Insan(2) print(ali.children) .. rst-class:: sphx-glr-script-out .. code-block:: none ['child_0', 'child_1'] .. GENERATED FROM PYTHON SOURCE LINES 345-347 .. code-block:: default next(ali) .. rst-class:: sphx-glr-script-out .. code-block:: none 'child_0' .. GENERATED FROM PYTHON SOURCE LINES 348-351 .. code-block:: default next(ali) .. rst-class:: sphx-glr-script-out .. code-block:: none 'child_1' .. GENERATED FROM PYTHON SOURCE LINES 352-354 If we call the ``next`` function again on `ali`, we will get IndexError due to what is happening inside ``__next__`` method above. .. GENERATED FROM PYTHON SOURCE LINES 354-358 .. code-block:: default # uncomment following line # next(ali) .. GENERATED FROM PYTHON SOURCE LINES 359-361 Although, we can apply ``next`` function on `ali` but we can still not use it in a ``for`` loop. .. GENERATED FROM PYTHON SOURCE LINES 361-368 .. code-block:: default ali = Insan(2) # uncomment following two lines # for child in ali: # print .. GENERATED FROM PYTHON SOURCE LINES 369-370 This is because `ali` is not an ``iterable`` and we can verify it as below .. GENERATED FROM PYTHON SOURCE LINES 370-375 .. code-block:: default import collections isinstance(ali, collections.abc.Iterable) .. rst-class:: sphx-glr-script-out .. code-block:: none False .. GENERATED FROM PYTHON SOURCE LINES 376-381 Since `ali` is not an "iterable", therefore we can not use it in ``for`` loop. The reason is that the ``for`` loop requests an iterator from the iterable object, and then calls ``__next__`` on that iterable until it hits the ``StopIteration`` exception. This happens under the surface which is also the reason why we would want iterators to implement the ``__iter__`` as well. .. GENERATED FROM PYTHON SOURCE LINES 383-385 **Question:** why the code ``isinstance(ali, collections.abc.Iterator)`` returns False? .. GENERATED FROM PYTHON SOURCE LINES 387-405 .. code-block:: default class Insan: def __init__(self, num_child): self.children = [f"child_{i}" for i in range(num_child)] self.index = 0 def __next__(self): item = self.children[self.index] self.index += 1 return item def __iter__(self): return self ali = Insan(2) .. GENERATED FROM PYTHON SOURCE LINES 406-408 .. code-block:: default isinstance(ali, collections.abc.Iterator) .. rst-class:: sphx-glr-script-out .. code-block:: none True .. GENERATED FROM PYTHON SOURCE LINES 409-411 .. code-block:: default isinstance(ali, collections.abc.Iterable) .. rst-class:: sphx-glr-script-out .. code-block:: none True .. GENERATED FROM PYTHON SOURCE LINES 412-413 Now we can use `ali` in a for loop but, .. GENERATED FROM PYTHON SOURCE LINES 413-419 .. code-block:: default # uncomment following two lines # ali = Insan(2) # for child in ali: # print(child) .. GENERATED FROM PYTHON SOURCE LINES 420-422 but after the last iteration, we will get ``IndexError`` because of the way we have implemented the ``__next__`` method above. .. GENERATED FROM PYTHON SOURCE LINES 424-426 **Question:** Elaborate the above mentioned reasoning? .. GENERATED FROM PYTHON SOURCE LINES 426-448 .. code-block:: default class Insan: def __init__(self, num_child): self.children = [f"child_{i}" for i in range(num_child)] self.index = 0 def __next__(self): try: item = self.children[self.index] except IndexError: raise StopIteration self.index += 1 return item def __iter__(self): return self ali = Insan(2) .. GENERATED FROM PYTHON SOURCE LINES 449-453 Above we have implemented the ``__next__`` method in a way to raise ``StopIteration`` error instead of ``IndexError``. Since the ``for`` loop under the hood runs until ``StopIteration`` and then the for loop just bypasses the ``StopIteration``, we can now use the `ali` in ``for`` loop safely. .. GENERATED FROM PYTHON SOURCE LINES 453-457 .. code-block:: default for child in ali: print(child) .. rst-class:: sphx-glr-script-out .. code-block:: none child_0 child_1 .. GENERATED FROM PYTHON SOURCE LINES 458-460 However, there is a problem in the above code, if we run the above for loop again, we don't get any output as shown below, .. GENERATED FROM PYTHON SOURCE LINES 460-464 .. code-block:: default for child in ali: print(child) .. GENERATED FROM PYTHON SOURCE LINES 465-467 This is because we are not not resetting ``self.index`` to 0 after raising ``StopIteration`` exception. .. GENERATED FROM PYTHON SOURCE LINES 467-490 .. code-block:: default class Insan: def __init__(self, num_child): self.children = [f"child_{i}" for i in range(num_child)] self.index = 0 def __next__(self): try: item = self.children[self.index] except IndexError: self.index = 0 raise StopIteration self.index += 1 return item def __iter__(self): return self ali = Insan(2) for child in ali: print(child) .. rst-class:: sphx-glr-script-out .. code-block:: none child_0 child_1 .. GENERATED FROM PYTHON SOURCE LINES 491-495 .. code-block:: default for child in ali: print(child) .. rst-class:: sphx-glr-script-out .. code-block:: none child_0 child_1 .. GENERATED FROM PYTHON SOURCE LINES 496-500 ``__len__`` ------------ This method determines the output of ``len`` function, when applied on the instance of a class. .. GENERATED FROM PYTHON SOURCE LINES 500-514 .. code-block:: default class Family: def __init__(self, num_children): self.num_children = num_children def __len__(self): return 1 + 1 + self.num_children fam = Family(3) len(fam) .. rst-class:: sphx-glr-script-out .. code-block:: none 5 .. GENERATED FROM PYTHON SOURCE LINES 515-520 Since `fam` is instance of ``Family`` class, the answer to ``len`` function was same as we determined in ``__len__`` method. Had we not defined the ``__len__`` method for `Family` class, we would have got ``TypeError`` if we had applied ``len`` function on it. .. GENERATED FROM PYTHON SOURCE LINES 520-532 .. code-block:: default class Family: def __init__(self, num_children): self.num_children = num_children fam = Family(3) # uncomment the following line # len(fam) # -> TypeError: object of type 'Family' has no len() .. GENERATED FROM PYTHON SOURCE LINES 533-537 ``__getitem__`` and ``__setitem__`` ------------------------------------ If we define these methods for a class, then we can index the instance of the class using the slice operator i.e., ``[]``. .. GENERATED FROM PYTHON SOURCE LINES 537-547 .. code-block:: default class Data: def __init__(self, values): self.values = values def __getitem__(self, item): return self.values[item] data = Data([1, 2, 3, 4]) .. GENERATED FROM PYTHON SOURCE LINES 548-550 .. code-block:: default print(data[0]) .. rst-class:: sphx-glr-script-out .. code-block:: none 1 .. GENERATED FROM PYTHON SOURCE LINES 551-553 .. code-block:: default print(data[1]) .. rst-class:: sphx-glr-script-out .. code-block:: none 2 .. GENERATED FROM PYTHON SOURCE LINES 554-559 .. code-block:: default # uncomment following two lines # for idx in range(5): # print(data[idx]) # -> IndexError: list index out of range .. GENERATED FROM PYTHON SOURCE LINES 560-562 The above example was too simple. Following example shows a more useful case for employment of ``__getitem__`` method where we would like to index two arrays simultaneously. .. GENERATED FROM PYTHON SOURCE LINES 562-573 .. code-block:: default class Data: def __init__(self, x, y): self.x = x self.y = y def __getitem__(self, item): return self.x[item], self.y[item] data = Data([1,2,3], [11, 12, 13]) print(data[0]) .. rst-class:: sphx-glr-script-out .. code-block:: none (1, 11) .. GENERATED FROM PYTHON SOURCE LINES 574-581 .. code-block:: default data = Data([1, 2, 3, 4], [11, 12, 13]) _x, _y = data[0] print(_x, _y) .. rst-class:: sphx-glr-script-out .. code-block:: none 1 11 .. GENERATED FROM PYTHON SOURCE LINES 582-586 Even the lengths of `x` and `y` are not equal in above case, we were still able to slice them. We should have constructed the `Data` class in such a way to raise the error when the lengths are not equal. Without this, the error message becomes more confusing when the item is present in `x` but not in `y`. .. GENERATED FROM PYTHON SOURCE LINES 586-590 .. code-block:: default # uncomment following line # print(data[3]) # -> IndexError: list index out of range .. GENERATED FROM PYTHON SOURCE LINES 591-599 .. code-block:: default class Data: def __init__(self, x, y): assert len(x) == len(y), 'length of x and y should be equal' self.x = x self.y = y def __getitem__(self, item): return self.x[item], self.y[item] .. GENERATED FROM PYTHON SOURCE LINES 600-601 Now,if the lengths of `x` and `y` are not equal, we will get more useful error message. .. GENERATED FROM PYTHON SOURCE LINES 601-605 .. code-block:: default # uncomment following line # data = Data([1, 2, 3, 4], [11, 12, 13]) # -> AssertionError: length of x and y should be equal .. GENERATED FROM PYTHON SOURCE LINES 606-613 .. code-block:: default data = Data([1, 2, 3], [11, 12, 13]) _x, _y = data[0] print(_x, _y) .. rst-class:: sphx-glr-script-out .. code-block:: none 1 11 .. GENERATED FROM PYTHON SOURCE LINES 614-618 ``__del__`` ------------ This method determines what will happen to an object (instance of a class) when ``del object`` is executed. .. GENERATED FROM PYTHON SOURCE LINES 618-631 .. code-block:: default class File: def __init__(self, name): self.path = os.path.join(os.getcwd(), name) with open(self.path, 'w'): pass def __del__(self): print(f"deleting file {self.path}") os.remove(self.path) return f = File("test.txt") .. GENERATED FROM PYTHON SOURCE LINES 632-635 .. code-block:: default os.path.exists(f.path) .. rst-class:: sphx-glr-script-out .. code-block:: none True .. GENERATED FROM PYTHON SOURCE LINES 636-640 .. code-block:: default del f .. rst-class:: sphx-glr-script-out .. code-block:: none deleting file /home/docs/checkouts/readthedocs.org/user_builds/python-seekho/checkouts/dev/scripts/oop/test.txt .. GENERATED FROM PYTHON SOURCE LINES 641-644 ``__contains__`` ----------------- This method determines what will happen when we use the instance of a class after ``in`` keyword. .. GENERATED FROM PYTHON SOURCE LINES 644-651 .. code-block:: default class Country: def __init__(self, provinces:list): self.provinces = provinces def __contains__(self, item): return item in self.provinces .. GENERATED FROM PYTHON SOURCE LINES 652-653 `Country` is a class which can have `provinces`. .. GENERATED FROM PYTHON SOURCE LINES 653-658 .. code-block:: default pak = Country(['balochistan', 'kpk', 'sind', 'punjab', 'gb']) print('sind' in pak) .. rst-class:: sphx-glr-script-out .. code-block:: none True .. GENERATED FROM PYTHON SOURCE LINES 659-663 .. code-block:: default print('sindh' in pak) .. rst-class:: sphx-glr-script-out .. code-block:: none False .. GENERATED FROM PYTHON SOURCE LINES 664-664 For a more comprehensive documentation on magical methods see `this `_ .. rst-class:: sphx-glr-timing **Total running time of the script:** ( 0 minutes 0.020 seconds) .. _sphx_glr_download_auto_examples_oop_magical_methods.py: .. only:: html .. container:: sphx-glr-footer sphx-glr-footer-example .. container:: binder-badge .. image:: images/binder_badge_logo.svg :target: https://mybinder.org/v2/gh/AtrCheema/python-seekho/master?urlpath=lab/tree/notebooks/auto_examples/oop/magical_methods.ipynb :alt: Launch binder :width: 150 px .. container:: sphx-glr-download sphx-glr-download-python :download:`Download Python source code: magical_methods.py ` .. container:: sphx-glr-download sphx-glr-download-jupyter :download:`Download Jupyter notebook: magical_methods.ipynb ` .. only:: html .. rst-class:: sphx-glr-signature `Gallery generated by Sphinx-Gallery `_