5. Classes¶
In python everything is an object. In other words everything is an instance of a class. We cover in this section different aspects of classes and object oriented approach available in Python. Before, let us summarize the characteristics of objects. Objects
- can store data
- are instances of python classes
- are created by an expression
- can be referenced by one or more variable
- have a unique identifier returned by id()
- are mutable or immutable depending on the type (e.g., list vs tuple)
- can be converted to string using str()
- can be printed
- are deleted automatically when no longer needed
5.1. Simplest class example¶
The following example defines a class Simplest that contains nothing. Then, we create an instance s.
class Simplest(object):
"""simple class prototype"""
pass
Note that all classes since version 2.2 should inherit from object(), which is the most base type. In eqrlier version, the syntas was:
class OldStyle:
"""simple class prototype"""
pass
You may still use this syntax or see it in code here and there. However, we will not use it. The way to create an instance is simply:
s = Simplest()
you add attributes dynamically:
s.attribute = 1
This very simple class has no interest except to show the syntax of instanciation. The way we added attribute here above is dynamic. If you need it in all instances, it would be better to add it directly in the class definition as shown later on.
You can already introspect the instance since it contains hidden special fields: __doc__ and __module__. The first contains what we provided in our code as a docstring: “simple class prototype”. The second one contains the string “__main__”. See Importing Modules for more details about the __module__ field.
You can check that s is indeed an instance of simplest:
>>> isinstance(s, Simplest)
True
and that Simplest is a sub class of object:
>>> issubclass(Simplest, object)
True
There is also a hidden attribute called __dict__ that gives you all the attributes of a class defined so far in the form of a dictionary:
>>> s.__dict__
{'attribute': 1}
as well as the name of the class:
>>> s.__name__
"Simplest"
There are quite a few other special attributes that we will discover later in this section.
Todo
s.__format__ s.__module__ s.__subclasshook__ s.__delattr__ s.__getattribute__ s.__new__ s.__setattr__ s.__weakref__ s.__hash__ s.__reduce__ s.__sizeof__ s.__reduce_ex__
5.2. Constructor¶
Constructor are optional. However, most of the time you would require it and is defined using the special method __init__ .
Here is a simple Range class that behaves like the built-in range() function but with a constant step of 1. We could have inherited from range but we will see inheritance later on. For now, let us design the class to show how to use a constructor with the special method __init__:
class Range(object):
def __init__(self, start=0, end=10):
self.counter = start
self.end = end
def next(self):
if self.counter < self.end:
res = self.counter
self.counter += 1
return res
else:
return None # optional
As you can see the constructor has 3 arguments. The first one is used by Python. It is like this keyword in C++. It refers to the class itself and is compulsary. self is not a keyword, it is just a parameter. You could call it this and the syntax would be perfectly correct. However, you should name it self since it is the custom followed by everybody writting Python code.
The 2 other arguments are the user arguments provided during an instanciation. Like functions, arguments can be optional or not (see Functions):
c = Range(5, 10)
c = Range(end=10)
5.3. Class and instance variables¶
Here is a simple class in Python that illustrate the difference between a class variable and an instance variable:
class MyClass(object):
counter = 0
def __init__(self, arg1)
self.arg1 = arg1
counter = counter + 1
def __str__(self):
return 'There are %d instances of MyClass' % self.counter
counter is a class variable shared by all instances:
>>> c1 = MyClass(1)
>>> c2 = MyClass(2)
>>> print c1
'There are 2 instances of MyClass'
>>> c2.counter
2
All class members (including the data members) are public and all the methods are virtual in Python.
Inside a class definition, all names beginning with two leading underscores are translated by adding a single underscore and the class name to the beginning:
>>> MyClass._MyClass__privateVariable
Although the double underscores seems like a standard private method, it is not since, we can still access to it using the _MyClass prefix. So, if you know how this works behind the scene, it is still possible to access private methods outside the class, even though you’re not supposed to.
Note
If you don’t want the name-mangling effect, but you still want to send a signal for other objects to stay away, you can use a single initial underscore.
5.4. Inheritance¶
In order to inherit fro, an existing class, use the following syntax:
class Animal(object):
def __init__(self, name="unknown"):
self.name = name
def __str__(self):
print("I'm a {0}".format(self.name))
def Cat(Animal):
def __init__(self):
super().__init__(name='CAT')
Here, the parent’s class Animal is initialised inside the child class using the super keyword. The Cat inherits from Animal and therefore has the method print already available.
5.5. Multiple inheritance¶
if Python cannot find a variable or method in the local namespace, it will perform a depth first search of the super classes in the same order in which the superclasses are specified in the class definition.
That’s all you need to know !! Let us study an example:
class Vehicle(object):
def __init__(self, name, color, wheel):
self.name = name
self.wheels = None
self.color = None
def set_wheels(self, n):
self.wheels = 2
def __str__(self):
txt = str(self.name) + ":" + registration
return txt
class TwoWheeler(object):
def __init__(self, name, color):
self.name = name
self.set_wheels(2)
self.color = None
def __str__(self):
return self.wheels
class MotorVehicle(object):
def __init__(self, name, color, power):
self.name = name
self.set_wheels(2)
self.color = None
self.power
class Bicycle(TwoWheeler, gear):
def __init__(self, name, color, gear):
self.name = name
self.set_wheels(2)
self.color = None
5.6. Diamond inheritance¶
Example before but a TwoWheeler and MotorVehicle both inherit from the vehicle. In Python, looking for a method or attribute from vehicle follows the rule introiduced in section xx so Python first look inside Twohwheeler then in Vehicle. So, the method os MotorVehcile is ignored if found before.
5.7. The iterator method¶
class Reverse(object):
"""Iterator for looping over a sequence backwards."""
def __init__(self, data):
self.data = data
self.index = len(data)
def __iter__(self):
return self
def next(self):
if self.index == 0:
raise StopIteration
self.index = self.index - 1
return self.data[self.index]
It can be used as follow:
>>> rev = Reverse([1,2,3,4])
>>> for x in rev: print x
4
3
2
1
5.7.1. Overloading standard behaviour¶
Overlaoding is the ability to have several functions with the same name but different set of parameters. This is possible on functions and metohods in languages such as C++.
This is not possible for functions or methods in Python. However, polymorphism is possible.
5.8. example: special method __str__ to create astring representation of an instance¶
when calling str(x) on a list, Python invokes the special methoods __str__, which can be overloed. Let us create a new list class:
class MyList(list):
def __init__(self, x):
list.__init__(self,x)
def __str__(self):
if len(self)>10:
txt = "This list contains %s elements" % str(len(self))+"\n"
this = []
this.extend(self[0:5])
this.extend(["..."])
this.extend(self[-5:])
txt += this.__str__()
else:
txt = list.__str__(self)
return txt
>>> m = MyList(range(0,100))
>>> print(m)
This list contains 100 elements
[0, 1, 2, 3, 4, '...', 95, 96, 97, 98, 99]
>>> isinstance(m, list)
True
>>> isinstance(m, MyList)
True
__str__: informal representation of an instance. __repr__: formal representation of an instance.: returns a valid python expression that can be evaluated ti re-cerate an object with the same value.
5.9. Destructor¶
The __del__ method is rarely used in practice and there is a good reason for this: there’s no guarantee that __del__ will be called in a specific time period. So it is no recommended to rely on this method. If you awnt to perform some cleanup, it is better to make your own cleanup function.
Note that del() does not call __del__ . del decreases the reference counter and __del__ is called when the coutner is zero.
The way Python periodically reclaims block of memory that are no lpnger in use is termed garbage collection. The garbage collector is performed during program execution and triggered when an object’s reference coutn reaches zero. You do not see when an instance is destroyed but the special method __del__ (desctrutor) is called at that moment. It is invoked only onece per instance.
You can control the garbage collector with the mod:gc module.
Reference countin fails when there are cycles:
x = [1, 2, 3]
y = x
x = y
None of the refence counter will be wero s oneither will be collected . To cope with this situation, Python perdiocally runs a cycle-detection routine? The garbage collector does not collect cyclic instnces for which __del__ is defined. A good reason to not use that method.
. discuss about the gc moule and garbage collector
5.10. setting the truth value of an instance¶
You can ask whether an instance is true or false. by default an instance truth value is true but you can use __nonzero__ to return 0 or 1
If you do not define __nonzero__, Python invokes __len__(). if none are defined, te instaqnce is considered True.
5.11. comparing instances¶
There are 6 specials methods related to comparison that can be overlaoded
method | operator |
---|---|
__lt__(self, other) | self < other |
__le__(self, other) | self <= other |
__gt__(self, other) | self > other |
__ge__(self, other) | self >= other |
__eq__(self, other) | self == other |
__ne__(self, other) | self != other |
Here is an example with that redefine the __eq__:
>>> class MyGraph():
def __init__(self, nodes, edges):
self.nodes = nodes[:]
self.edges = edges[:]
def __eq__(self, g):
if sorted(self.nodes) != sorted(g.nodes):
return False
if sorted(self.edges) != sorted(g.edges):
return False
return True
>>> g1 = MyGraph(['A','B','C'], edges=[('A','B'), ('B','C')])
>>> g2 = MyGraph(['A','C','B'], edges=[('B','C'), ('A','B')])
>>> g1 == g2
True
These methods can return any object not just 1/0 or True/False.
In older version of Python, the __cmp__ method could be used. It should return a negative value if self<other, a zero if self==other and a positive value if self>other. If you define __cmp__, you should also define __hash__ to calculate an integer hash key to use in dictionary operations.
5.12. Accessing instance attributes¶
__getattr__is called only is an instance cannot be found in the __dict__ method that is it is neither an attribute nor a method. It should return an attribute value or raise an AttributeError exception.
__setattr__ invoket when you attempt to assign an attribute. if defined, it is invoked in place of the normal behaviour (storing the value in the instance dictionary). If you want to set an attribute, use self.__dict__[name] = value. self.name causes recursion error.
See also the property hereafter that provide a nice mechanism to make attribute read-only.
__delattr__
Example:
class Simple(object):
def __init__(self,x, y):
self.x = x
self.y = y
class Tuned(object):
def __init__(self):
self._x = None
self._y = None
def __getattr__(self, name):
if name =="x":
return self._x
if name =="y":
return self._y
def __setattr__(self, name, value):
if value>=0:
self.__dict__[name] = value
else:
raise ValueError("value must be positive.")
t = Tuned()
t.x = 1
t.y = -1
differnce with getattr, setattr, delattr functions:
method | operator |
---|---|
__lt__(self, other) | self < other |
__le__(self, other) | self <= other |
__gt__(self, other) | self > other |
__ge__(self, other) | self >= other |
__eq__(self, other) | self == other |
__ne__(self, other) | self != other |
5.13. property¶
Todo
5.14. Treating an instance like a list or dictionary¶
You can use special methods to make your class behave like sequences and dictionary with slicing, indexing and key loopkup.
__len__ should return an integer greater than or equal to zero. If __nonzero__ is not defined, the value of __len__ is used to tell if the instance if True or False.
__getitem__, __setitem__, and __delitem__ are used to define the behaviour or the brackets operator:
a[0]
a[0] = 1
del a[0]
__delitem__ is called whenever you use the del keyword.
__getslice__, __setslice__, __delslice__ are used to define the following operators:
a[0:10]
a[0:10] = range(0,10)
del a[0:10]
of course the setslice and delslice cannot be used for immutable sequences
__getslice__ should return the same type of sequence that it is passed.
__contains__ is called when you use the in keyword.
Here is a summary
method | Description |
---|---|
__len__(self) | returns len(self) |
__getitem__(self, i) | return self[i] |
__setitem__(self, i, value) | self <= other |
__delitem__(self, i) | self > other |
__getslice__(self, i, j) | self >= other |
__setslice__(self, i, j) | self == other |
__delslice__(self, i, j) | self != other |
__contains__(self, item) | self != other |
dictionary and list should also implement concatenation (addition) and repetition (multiplication) as well as mathematical methods. It is done by redefining the __add__, __radd__ ,__iadd__, __mul__, __rmul__, __imul__ methods.
5.15. Mathematical opertions on instance¶
The mathematical methods that can be implemented are provided in this section. Each mathematical operations has its method. For instance the binary operator methods take the folloing form:
x + y
that is defined within your class as:
x.__add__(y)
where x is the self of the method and y is a parameter.
The binary operator methods are summarized here below:
methods | Effets |
---|---|
__add__(self, other) | self + other |
__sub__(self, other) | self - other |
__mul__(self, other) | self * other |
__div__(self, other) | self / other |
__mod__(self, other) | self % other |
__divmod__(self, other) | divmod(self, other) |
__pow__(self, other) | self ** other |
__lshift__(self, other) | self << other |
__rshift__(self, other) | self >> oter |
__and__(self, other) | self & other |
__or__(self, other) | self | other |
__xor__(self, other) | self ^ other |
Python also provides binary operator methods with reversed operands. There are used if the left operand does not support the operation. For instance in the operation:
x + y
If x does not support the + operator, Python tries:
y + x
by invoking:
y.__radd__(x)
The list of reversed operator methods is the same as above with a r character added to its left.
methods | Effets |
---|---|
__radd__(self, other) | other + self |
__rsub__(self, other) | other - self |
__rmul__(self, other) | other * self |
__rdiv__(self, other) | other / self |
__rmod__(self, other) | other % self |
__rdivmod__(self, other) | divmod(self, other) |
__rpow__(self, other) | self ** other |
__rlshift__(self, other) | other << self |
__rrshift__(self, other) | other >> self |
__rand__(self, other) | other & self |
__ror__(self, other) | other | self |
__rxor__(self, other) | other & self |
The in_place (or augmented) operator methods performs in place modifications of self and return the result. In
x += y
Python tries to find
x.__iadd__(y)
If that method is not defined, then Python searches for x.__add__(y) and y.__radd__(x) as if x+y was invoked.
methods | Effets |
---|---|
__iadd__(self, other) | self += other |
__isub__(self, other) | self -= other |
__imul__(self, other) | self *= other |
__idiv__(self, other) | self /= other |
__imod__(self, other) | self %= other |
__ipow__(self, other) | self **= other |
__ilshift__(self, other) | self <<= other |
__irshift__(self, other) | self >>= other |
__iand__(self, other) | self &= other |
__ior__(self, other) | self |= other |
__ixor__(self, other) | self ^= other |
Unary operator methods are also available
methods | Effets |
---|---|
__neg__(self) | -self |
__pos__(self) | +self |
__abs__(self) | abs(self) |
__invert__(self) | ~self |
Finally, conversion methods are the following ones. Note that oct and hex should return oct and hexadecimal strings. int, long; float, complex should convert values to the appropriate built-in type. __coerce__ should follow the rules given in the Python Reference Manual.
methods | Effets |
---|---|
__int__(self) | int(self) |
__long__(self) | long(self) |
__float__(self) | float(self) |
__complex__(self) | complex(self) |
__oct__(self) | oct(self) |
__hex__(self) | hex(self) |
__coerce__(self) | coerce(self) |
Todo
more examples ?
5.16. calling an instance¶
if __call__ is provided, you can call your instance:
class Mode(object):
def __init__(self, x):
self.x = x
self.mode = {}
def __call__(self, mode):
if mode == 1:
return
elif mode == 2:
return
else:
raise NotImplementedError
mode = Mode()
mean = mode(mode=1)
variance = mode(mode=2)
Todo
check this example