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.
Note on terminology:
The terms type and class are roughly synonymous in Python. Everything in Python is a object. Every object has a type. class
statements create new user-defined types which are often called a class.
However, the type of an object, sometimes loosely called an object, is not the same as an instance of an object. There is only one type object of a type (e.g. only one int
type), but there can be many objects of the same type (many int
s), each of which is called an instance of that object (more properly these should be called instances of a type).
This proliferation of names is partly due to different names being used for the same concepts in different object-oriented programming languages (today these are primarily Python, Java and C++).
# create a new type of object with nothing in it:
class mytype():
pass
v=mytype()
type(v)
__main__.mytype
Objects should define an __init__
function that is called when an object is created and initializes the object (a "constructor").
Within the class we define functions that are the methods for objects of this type.
We can also define other "dunder" (for "double underscore") methods that perform standard operations, including those performed by operators. For example, __add__()
is the function that implements the +
operator, __eq__()
is the function that implements the ==
operators, etc..
All method definitions take a first argument (called 'self' by convention) that refers to the instance of the object being created or being acted on. Each method can also have other arguments.
class mytype():
'''
This would be the documentation for this type. Hello, world!
'''
# constructor:
def __init__(self,a=0):
self.x=a
# some methods without arguments
def str(self):
return str(self.x)
def __repr__(self):
return "mytype("+str(self.x)+")"
def negated(self):
return -self.x
# "dunder" methods:
def __eq__(self,a):
'''
This is documentation for a method. Hello, again!
'''
# self.x == a
return self.x == a
v=mytype(3)
print(v.__eq__(3), v.__eq__(5), v == 5, v, v.negated())
help(v)
v.str()
True False False mytype(3) -3 Help on mytype in module __main__ object: class mytype(builtins.object) | mytype(a=0) | | This would be the documentation for this type. Hello, world! | | Methods defined here: | | __eq__(self, a) | This is documentation for a method. Hello, again! | | __init__(self, a=0) | Initialize self. See help(type(self)) for accurate signature. | | __repr__(self) | Return repr(self). | | negated(self) | | str(self) | # some methods without arguments | | ---------------------------------------------------------------------- | 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
'3'
Unlike some other OO languages, members are not private. You can modify and inspect objects dynamically.
v=mytype(3)
print(v)
v.x=5
print(v.x)
# add a method to an existing object:
v=mytype(5)
mytype.doubleit = lambda self: 2*self.x
print(v.doubleit())
mytype(3) 5 10
But only objects of built-in types (e.g. ints, lists, dictionaries) can be created using syntax instead of constructor functions. User-defined objects must be created using a constructor and binding it to a variable using the assignment operator.
v = mytype(1)
# 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))
v=mytype(1)
print(v,type(v))
mytype(1) <class '__main__.mytype'> 5 <class 'int'> mytype(1) <class '__main__.mytype'>
But our type is incomplete, and lacks much functionality. For example, we can't add an integer to it:
v=mytype(3)
print(v+1)
--------------------------------------------------------------------------- TypeError Traceback (most recent call last) Cell In[6], line 2 1 v=mytype(3) ----> 2 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"). It "inherits" all the methods of the parent type.
Here's an example that subclasses the int
type but reverses the meanings of the +
and -
operators (not a practical example). To do this we define the __sub__
and __add__
methods using the existing __sub__
and __add__
methods in the int
type (to avoid recursion).
# 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:
# 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['abc']='abc'
print(v)
{'abc': 'ABC'}