In python all variables (including functions) are objects. We have already seen some concepts of object oriented programming : attributes and methods. Let us look to complex numbers
z = 1 + 2J
print z.real # this is an attribute
zconj = z.conjugate() # this is a method
The type of the object referenced by the variable z is a complex number. It means that somewhere a class defined what a complex number is.
Let us look how to create our own Complex class.
class Complex(object):
def __init__(self, x, y):
self.real = x
self.imag = y
z = Complex(1,2.)
In this example, we have created the class. The init method is called when the object is created and it sets the atributes real and imag of the complex number.
On can write the conjugate methode and add other methods:
class Complex(object):
...
def conjugate(self):
return Complex(self.real, -self.imag)
def modulus(self):
return math.sqrt(self.real**2 + self.imag**2)
z = Complex(1,2.)
conjz = z.conjugate()
The __str__ and __repr__ method are used to create a string for simplified printing (__str__) or full representation (__repr__).
def __str__(self):
if self.imag >0:
return "({0} + {1}J)".format(self.real, self.imag)
else:
return "({0} - {1}J)".format(self.real, -self.imag)
def __repr__(self):
return "Complex({0}, {1})".format(repr(self.real), repr(self.imag))
The __add__, __mul__, __sub__ and __div__ (and __truediv__) methods are used to implement addition, multiplication, substraction and division. Let us implement the __add__ method
def __add__(self, other):
return Complex(self.real+other.real, self.imag+other.imag)
This method will let us add two complex numbers using the ‘+’ sign.
a = Complex(1,2)
b = Complex(1,-1)
print a+b
Example of a laser driver
class Verdi(object):
""" Verdi driver class
example :
laser = Verdi('COM7')
laser.set_power(15)
"""
def __init__(self, port):
""" ... """
self.serial_connection = serial.Serial(port=port, baudrate=19200)
def read_cmd(self, cmd):
self.serial_connection.write('?{0}\r\n'.format(cmd))
return self.serial_connection.readline()
def write_cmd(self, cmd, val):
self.serial_connection.write('{0}:{1}\r\n'.format(cmd, val))
def get_power(self):
return self.read_cmd('P')
def set_power(self, val):
self.write_cmd('P', '{0:6.4f}'.format(val))
laser = Verdi('COM7')
laser.set_power(15)
The web page https://docs.python.org/2/tutorial/classes.html contains an interesting tutorial on classes.
We have already seen some special methods used to customize object. The data model of Python (https://docs.python.org/2/reference/datamodel.html) describes all the special methods. They are used to customize the way object are displayed or compared with another. They are also used to mimic different type like the numeric type, containers type (list, dictionnary, ...), callable (function).
Two builtins functions are usefull when dealing with object : the function isinstance(obj, cls) tests if obj is an instance of cls. The hasattr(obj, attr_name) function test if obj has an attribute called attr_name.
When creating a numeric like object, consider also the implementation of reversed operator. When python see a + b then it call the method __add__ on object a. In the example of Complex number that we have seen, we have implemented the addition of two Complex number. We can also modify the method __add__ to be able to add a number to a Complex number (a is Complex but b is a number). We cannot modify the method __add__ of float (for example) so that we can add a Complex number to a float (a is a float). The way to do it is to implement the __radd__ (reversed add) method. Below is the way to do implement the addition for Complex :
import numbers
class Complex(object):
def __add__(self, other):
if isinstance(other, numbers.Number):
other = Complex(other, 0)
if isinstance(other, Complex):
return Complex(self.real+other.real,
self.imag+other.imag)
raise NotImplemented
def __radd__(self, other):
return self + other # addition is commutative
If the operation is not implemented, you should raise a NotImplemented error. This error will be catched by the interpreter to try the reverse operation.
Classes implements two kinds of attributes : data attributes are used to store variables inside the object. Methods are used to call function that depends on the object and other arguments. Methods are use to perform an actions : modifying the object, performing calculations, ...
Properties is another king of attribute. When a method (without arguments) is used to get information from the object, the value return from this method is similar to an attribute. For example, complex number were described using real and imaginary part. To get the modulus of the complexe number, we have used a method. But if the complex number were represented using a polar representation, then the norm would be an attribute. Using property allows to hide this implementation detail.
class Complex(object):
def __init__(self, x=None, y=0, r=None, theta=None):
if x is not None:
self.real = x
self.imag = y
else:
if r is None or theta is None:
raise ValueError('Complex number should be defined\
using either x and y or r and theta')
self.real = r*math.cos(theta)
self.image = r*math.sin(theta)
@property
def r(self):
return self.modulus()
@property
def theta(self):
return math.atan2(self.imag, self.real)
def __mul__(self, other):
r = self.r*other.r
theta = self.theta + other.theta
return Complex(r=r, theta=theta)
Properties are also usefull when you want to perform an action when an attribute is set. For example, using the Verdi class defined above, we can create the power property with a setter and a getter :
class Verdi(object):
...
def get_power(self):
...
def set_power(self, val):
...
power = property(get_power, set_power, doc="blabla")
Now we can use this object like this :
laser = Verdi('COM7')
laser.power = 15 # Set the laser power to 15 W
print laser.power # Return the actual power of the laser
One of the main feature of object oriented programming is the possibility for classes to heritate from other classes. When a class B heritates from class A, then all attributes of class A are avalailable for class B. Actually, we have already used heritage, as the every object heritates from the class object. For example, by default, every object have a method called __repr__.
In a typical situation, a class heritated from a single class. This situation occurs when one wants to specialize a class. For example, one can create a Imaginary number class. Such a number, will be a complex number, but the __init__ and __str__ method can be changed :
class Imaginary(Complex):
def __init__(self, y=1):
return Complex.__init__(self, x=0, y=y)
def __str__(self):
return "{0}J".format(self.imag)