Besides the predefined low-level built-in types of data, brought by the Python language, any developer can define his own types, called "classes", and create instances (objects) of this new type. Those new objects are generally composed of several lower-level objects, and their new type can provides new functions, just the same as built-in types provide functions and operators.
Let's say we want to work with 3D vectors.
With built-in types, we would create three variables x, y, z for each vector we want to handle:
import math
x1 = 1
y1 = 2
z1 = 3
x2 = 4
y2 = 5
z2 = 6
def fmt(x, y, z):
return "<{},{},{}>".format(x, y, z)
def norm(x, y, z):
return math.sqrt(x*x+y*y+z*z)
print(fmt(x1, y1, z1), "has norm:", norm(x1, y1, z1))
print(fmt(x2, y2, z2), "has norm:", norm(x2, y2, z2))
<1,2,3> has norm: 3.7416573867739413 <4,5,6> has norm: 8.774964387392123
Let's now create a trivial new class Vector
. We declare nothing yet in this class (just pass
). Yet, each time we instanciate a new object of type Vector
, thanks to a call to Vector()
, we use it to store three values x, y, z, and then we simplify the interface of fmt()
:
import math
class Vector(object): # create an empty class Vector
pass
v1 = Vector() # create an object v1 of type Vector
v1.x = 1 # create a new attribute x in the object v1
v1.y = 2 # create a new attribute y in the object v1
v1.z = 3 # create a new attribute z in the object v1
v2 = Vector() # create an object p2 of type Vector
v2.x = 4
v2.y = 5
v2.z = 6
def fmt(v): # fmt() now directly receive the whole Vector
return "<{},{},{}>".format(v.x, v.y, v.z)
def norm(v):
return math.sqrt(v.x*v.x+v.y*v.y+v.z*v.z)
print(fmt(v1), "has norm:", norm(v1))
print(fmt(v2), "has norm:", norm(v2))
<1,2,3> has norm: 3.7416573867739413 <4,5,6> has norm: 8.774964387392123
Let's add a function "set" for setting the initial values of a vector.
import math
class Vector(object):
pass
def set(v, xx, yy, zz):
v.x = xx
v.y = yy
v.z = zz
def fmt(v):
return "<{},{},{}>".format(v.x, v.y, v.z)
def norm(v):
return math.sqrt(v.x*v.x+v.y*v.y+v.z*v.z)
p1 = Vector()
set(v1,1,2,3)
p2 = Vector()
set(v2,4,5,6)
print(fmt(v1), "has norm:", norm(v1))
print(fmt(v2), "has norm:", norm(v2))
<1,2,3> has norm: 3.7416573867739413 <4,5,6> has norm: 8.774964387392123
Let's integrate the functions within the class. By convention, the first argument
is called self
. When calling the function, self
receives the object on the left.
import math
class Vector(object):
def set(self, xx, yy, zz):
self.x = xx
self.y = yy
self.z = zz
def fmt(self):
return "<{},{},{}>".format(self.x, self.y, self.z)
def norm(self):
return math.sqrt(self.x*self.x+self.y*self.y+self.z*self.z)
v1 = Vector()
v1.set(1, 2, 3)
v2 = Vector()
v2.set(4, 5, 6)
print(v1.fmt(), "has norm:", v1.norm())
print(v2.fmt(), "has norm:", v2.norm())
<1,2,3> has norm: 3.7416573867739413 <4,5,6> has norm: 8.774964387392123
Let's use special functions which are automatically called by the interpreter, and simplify again the use of the new class.
class Vector(object):
def __init__(self, xx, yy, zz):
self.x = xx
self.y = yy
self.z = zz
def __str__(self):
return "<{},{},{}>".format(self.x, self.y, self.z)
def norm(self):
return math.sqrt(self.x*self.x+self.y*self.y+self.z*self.z)
v1 = Vector(1, 2, 3) # implicitly call Vector.__init__(v1,1,2,3)
v2 = Vector(4, 5, 6) # implicitly call Vector.__init__(v2,4,5,6)
print(v1, "has norm:", v1.norm()) # implicitly call print(Vector.__str__(v1))
print(v2, "has norm:", v1.norm()) # implicitly call print(Vector.__str__(v2))
<1,2,3> has norm: 3.7416573867739413 <4,5,6> has norm: 3.7416573867739413
With classes, it is possible to define new kinds of error, which you can raise and catch.
class ForbiddenChoice(Exception):
"""This exception report a bad input value."""
def __init__(self, val):
self.val = val
def __str__(self):
return 'Forbidden choice: {}'.format(self.val)
def choice():
n = int(input('Choose a number from 0 to 9: '))
if (n<0) or (n>9): raise ForbiddenChoice(n)
return n
try:
n = choice()
print('Your choice is:', n)
except ForbiddenChoice as error:
print(error)
--------------------------------------------------------------------------- StdinNotImplementedError Traceback (most recent call last) Cell In[6], line 14 11 return n 13 try: ---> 14 n = choice() 15 print('Your choice is:', n) 16 except ForbiddenChoice as error: Cell In[6], line 9, in choice() 8 def choice(): ----> 9 n = int(input('Choose a number from 0 to 9: ')) 10 if (n<0) or (n>9): raise ForbiddenChoice(n) 11 return n File ~/.cache/pypoetry/virtualenvs/pyplot-doc-VOfsvtlq-py3.11/lib/python3.11/site-packages/ipykernel/kernelbase.py:1186, in Kernel.raw_input(self, prompt) 1184 if not self._allow_stdin: 1185 msg = "raw_input was called, but this frontend does not support input requests." -> 1186 raise StdinNotImplementedError(msg) 1187 return self._input_request( 1188 str(prompt), 1189 self._parent_ident["shell"], 1190 self.get_parent("shell"), 1191 password=False, 1192 ) StdinNotImplementedError: raw_input was called, but this frontend does not support input requests.
Some "factory" function can create new classes for you. For example, collections.namedtuple()
can build a new immutable simple class, whose internal values can only be set at initialisation.
import collections
Vector = collections.namedtuple('Vector', ['x', 'y', 'z'])
v = Vector(1, 0, 1)
print(v)
v.y = 2 # forbidden ! the class is immutable.
Vector(x=1, y=0, z=1)
--------------------------------------------------------------------------- AttributeError Traceback (most recent call last) Cell In[7], line 6 4 v = Vector(1, 0, 1) 5 print(v) ----> 6 v.y = 2 # forbidden ! the class is immutable. AttributeError: can't set attribute
The special function __call__
, when added to class, make all its objects "callable", i.e. you can use those objects as if they were functions. We call them "object-functions"...
class Saxpy(object):
"""This object-function computes ax+y."""
def __init__(self, a):
self.a = a
def __call__(self, x, y):
return (self.a*x + y)
s1 = Saxpy(2) # implicitly call Saxpy.__init__(s1,2)
print(s1(3, 4)) # implicitly call Saxpy.__str__(s1, 3, 4)
print(s1(10, 2)) # ...
s2 = Saxpy(3)
print(s2(3, 4))
print(s2(10, 2))
10 22 13 32