Classes

We can create new types of objects with the class statement. Within the class we can define member functions that operate on objects of this class.

In [39]:
# create a new type of object with nothing in it:
class mytype():
    pass
    
v=mytype()
print(dir(v))
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__']

Usually we define an __init__ function to initialize the object (a constructor). We can define other "dunder" functions that perform well-known operations, including those performed by operators (+, ==, <, etc). And we can define our own functions.

In [2]:
# constructor:
class mytype():
    '''
    This is displayed by help().
    '''
    def __init__(self,a=0):
        self.x=a
    def __eq__(self,a):
        return self.x == a
    def __repr__(self):
        return str(self.x)
    def negated(self):
        return -self.x
        
v=mytype(3)
# help(mytype)
print(v.x, v == 3, v == 5, v, v.negated())
help(mytype)
3 True False 3 -3
Help on class mytype in module __main__:

class mytype(builtins.object)
 |  This is displayed by help().
 |  
 |  Methods defined here:
 |  
 |  __eq__(self, a)
 |      Return self==value.
 |  
 |  __init__(self, a=0)
 |      Initialize self.  See help(type(self)) for accurate signature.
 |  
 |  __repr__(self)
 |      Return repr(self).
 |  
 |  negated(self)
 |  
 |  ----------------------------------------------------------------------
 |  Data descriptors defined here:
 |  
 |  __dict__
 |      dictionary for instance variables (if defined)
 |  
 |  __weakref__
 |      list of weak references to the object (if defined)
 |  
 |  ----------------------------------------------------------------------
 |  Data and other attributes defined here:
 |  
 |  __hash__ = None

Unlike some other OO languages, members are not private. You can also modify and inspect objects dynamically.

In [4]:
class myclass():
    x=1
    
# members (attributes) are not private:
v=myclass()
print(v.x)

v.y=3
print(v.y)

print(dir(v))
1
3
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'x', 'y']

But objects are not variables. There is a difference between creating an object and binding it to a variable using the assignment operator. Only objects of built-in types (e.g. ints, lists, dictionaries) can be created using syntax instead of constructor functions.

In [3]:
# and variables are not objects:
print(v,type(v))
# if we assign to v, it now points to an int:
v = 5
print(v,type(v))
3 <class '__main__.mytype'>
5 <class 'int'>
In [5]:
# but this type is incomplete, so not very useful :
v=mytype(3)
print(v+1)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-5-911295e6356a> in <module>()
      1 # but this type is incomplete, so not very useful :
      2 v=mytype(3)
----> 3 print(v+1)

TypeError: unsupported operand type(s) for +: 'mytype' and 'int'

In many cases it's worth building on the features provided by an existing type ("subclassing"). Here's an example that subclasses the int type. Here we access the existing __sub__ and __add__ functions in the int type to avoid recursion.

In [16]:
# so let's subclass an existing type
# and redefine some operators
class myint(int):
    def __add__(self,x):
        return int.__sub__(self,x)
    def __sub__(self,x):
        return int.__add__(self,x)

# new type is just like int but +/- are reversed:
x=myint(1)
print(x, x+1, x-1, x+x)
1 0 2 0

We can also define the function call and indexing (__setitem__) operators. In this example we define a new type of dictionary that converts stored values to upper-case. As before, we re-use dict's__setitem__ method:

In [40]:
# another example of subclassing an existing type
# changing the behaviour of the [] operator for a dict:
class updict(dict):
    def __setitem__(self,k,v):
        dict.__setitem__(self,k,str.upper(v))
        
v=updict()
v[0]='x'
print(v)
{0: 'X'}