#!/usr/bin/env python # coding: utf-8 # # How do you create an object? (continued) # ## Special Class Methods: *The Python Data Model* # # 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__`). # # *These attributes are special!* And typically Python reserves special names for these attributes to give the objects special functionality. # # 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). # ### Example: Initializing instance data during construction # # 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: # In[ ]: class MyClass1: def __init__(self, y): self.y = y # And you can initialize the value of `y` when you create each instance: # In[ ]: mc1 = MyClass1(2) # In[ ]: mc1.y # ### Example: String representation of an object # # 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. # In[ ]: str(mc1) # In[ ]: class MyClass2: def __str__(self): return '' # In[ ]: mc2 = MyClass2() # In[ ]: str(mc2) # ### Example: Rich comparison # # 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. # In[ ]: class MyClass3: def __init__(self, x=2): self.x = x def __lt__(self, other): return self.x < other.x def __gt__(self, other): return self.x > other.x # In[ ]: mc3a = MyClass3(4) mc3b = MyClass3(6) # In[ ]: mc3a < mc3b # In[ ]: mc3b > mc3a # ### Example: Container with a defined length # # 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. # In[ ]: class MyClass4: def __init__(self, *args): self.data = args def __len__(self): return len(self.data) # In[ ]: mc4 = MyClass4(1,2,3,4) # In[ ]: mc4.data # In[ ]: len(mc4) # ### Example: Check if something is "in" a container # # 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. # In[ ]: class MyClass5: def __init__(self, *args): self.data = args def __contains__(self, item): return item in self.data # In[ ]: mc5 = MyClass5(1,2,3) # In[ ]: 2 in mc5 # In[ ]: 4 in mc5 # ### Example: Key-value access for a container # # Dictionary-like key-value access to an object (i.e., `object[key] = value`) can be provided with the `__getitem__` and `__setitem__` special methods. # In[ ]: class MyClass6: def __init__(self, **kwargs): self.data = kwargs def __getitem__(self, key): return self.data[key] def __setitem__(self, key, value): self.data[key] = value # In[ ]: mc6 = MyClass6(a=2, b=3, c=6) # In[ ]: mc6['a'] # In[ ]: mc6['d'] = 8 # In[ ]: mc6.data # ### Example: Numeric types # # 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. # # 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. # In[ ]: class MyClass7: def __init__(self, x): self.x = x def __add__(self, other): if isinstance(other, MyClass7): new_x = self.x + other.x else: new_x = self.x + other return MyClass7(new_x) def __sub__(self, other): if isinstance(other, MyClass7): new_x = self.x - other.x else: new_x = self.x - other return MyClass7(new_x) #
# Note: #

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.

#
# In[ ]: mc7a = MyClass7(3) mc7b = MyClass7(7) # In[ ]: mc7c = mc7a + mc7b mc7c.x # In[ ]: mc7d = mc7a - mc7b mc7d.x # In[ ]: mc7e = mc7a + 2 mc7e.x #
# Note: #

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

#
# ***There's so much you can do with Python special methods, and this just scratches the surface...*** # | | | | # | :- | -- | -: | # | [[Home]](../index.ipynb) | | [« Previous](04.ipynb) \| [Next »](06.ipynb) |