How to Program, Part VI
Object oriented programming
with Python. Here is the way we
define and instantiate a simple object:
object1.py |
1 | class MyObject(object): |
2 | "Documentation string for MyObject" |
3 | pass |
4 | |
5 | my = MyObject() |
6 | help(my) |
$ python ./object1.py
Help on MyObject in module __main__ object:
class MyObject(__builtin__.object)
| Documentation string for MyObject
|
| Data descriptors defined here:
|
| __dict__
| dictionary for instance variables (if defined)
|
| __weakref__
| list of weak references to the object (if defined)
|
Because we wish to be good coders, we
provided a documentation string. You can
print the documentation for the class using
the "help()" function.
Note also the "(object)" in parenthesis after
the class name.
While most of the code in this class will work without putting that
there, we will always
do so in this class. It marks the object as
a "new style" Python object.
If you wish to provide any special initialization
for your object, you do it with a constructor (the __init__
method). Python has many special methods tha
begin and end with double underscore (they are called
"magic names").
object2.py |
1 | class MyObject(object): |
2 | """ |
3 | This is a multi-line |
4 | documentation string. |
5 | """ |
6 | def __init__(self): |
7 | pass |
8 | |
9 | my = MyObject() |
10 | help(my) |
$ python ./object2.py
Help on MyObject in module __main__ object:
class MyObject(__builtin__.object)
| This is a multi-line
| documentation string.
|
| Methods defined here:
|
| __init__(self)
|
| ----------------------------------------------------------------------
| Data descriptors defined here:
|
| __dict__
| dictionary for instance variables (if defined)
|
| __weakref__
| list of weak references to the object (if defined)
|
All object methods must have
at least one argument which, by convention,
is called "self." If you're a C++ or Java
person, you might feel more comfortable with
"this" instead.
object3.py |
1 | class MyObject(object): |
2 | def __init__(self,a,b): |
3 | self.a = a |
4 | self.b = b |
5 | def __str__(self): |
6 | return "("+str(self.a)+","+str(self.b)+")" |
7 | |
8 | my = MyObject(3,4) |
9 | print my |
$ python ./object3.py
(3,4)
|
The definition of our MyObject is called the class. It's like
a blueprint for making objects of type MyObject. On line 8 above
we create an instance of that class. A specific example.
When we use self to set a field on our class, we are setting
it on the instance. We can set it on the class as well. In this
next example, the field "c" is set on the class instead of the instance.
That means that all instances of MyObject see the same value for "c".
object3-1.py |
1 | class MyObject(object): |
2 | c = 5 |
3 | def __init__(self,a,b): |
4 | self.a = a |
5 | self.b = b |
6 | def __str__(self): |
7 | return "("+str(self.a)+","+str(self.b)+")" |
8 | |
9 | print MyObject.c |
10 | my = MyObject(3,4) |
11 | print my |
$ python ./object3-1.py
5
(3,4)
|
In some cases, you may wish to hide a variable from users of your
class. To do this in python, simply prepend two underscores to the
front of your name.
Private Data:
private.py |
1 | class MyClass(object): |
2 | def __init__(self): |
3 | self.__priv = 7 |
4 | def __str__(self): |
5 | return "("+str(self.__priv)+")" |
6 | m = MyClass() |
7 | print m |
8 | print m.__priv # error happens here |
$ python ./private.py
(7)
Traceback (most recent call last):
File "./private.py", line 8, in <module>
print m.__priv # error happens here
AttributeError: 'MyClass' object has no attribute '__priv'
|
Unlike other languages, the enforcement of private data in Python
is weak. You can easily get around it by prepending the class name
and an underscore.
private2.py |
1 | class MyClass(object): |
2 | def __init__(self): |
3 | self.__priv = 7 |
4 | def __str__(self): |
5 | return "("+str(self.__priv)+")" |
6 | m = MyClass() |
7 | print m |
8 | print m._MyClass__priv |
$ python ./private2.py
(7)
7
|
Destructors: Python offers you the ability to automatically clean
up your objects (in some cases). You can do this with a destructor.
dtors.py |
1 | class MyClass(object): |
2 | def __init__(self,a): |
3 | self.a = a |
4 | print "instantiating "+str(self.a) |
5 | def __str__(self): |
6 | return "("+str(self.a)+")" |
7 | def __del__(self): |
8 | print "destructing "+str(self.a) |
9 | m1 = MyClass(1) |
10 | m2 = MyClass(2) |
11 | m3 = MyClass(3) |
12 | m2.f = m3 |
13 | m3.f = m2 # create a cycle of references |
14 | print m2.f,m2.f.f,m2.f.f.f |
$ python ./dtors.py
instantiating 1
instantiating 2
instantiating 3
(3) (2) (3)
destructing 1
|
Static and class methods: Just like Java or C++, Python
allows you to declare static methods--these are methods
that don't require any instance of the class to exist.
You can also declare a "class method." These are
similar to static methods, but take the class as an
argument.
static.py |
1 | class MyClass(object): |
2 | f = 5 |
3 | @staticmethod |
4 | def do_it(): |
5 | print "Do something" |
6 | @classmethod |
7 | def do_it2(cl): |
8 | print "f="+str(cl.f) |
9 | |
10 | MyClass.do_it() |
11 | MyClass.do_it2() |
$ python ./static.py
Do something
f=5
|
Introspection: Python detects when object m1 goes out of scope and calls the
destructor. Objects m2 and m3 are part of a cycle, and Python is
not able to figure out when to call a destructor.
There are several functions you can use to learn about an object (or
"introspect" it), or change it. The "dir()" function returns the names of all the
attributes (member variables and field) of something. The "getattr()" function
lets you look up the value of an attribute on an object by name. Don't
worry what all of the output means right now. Note, however, the
"__class__" attribute. This refers to the special object that contains
the data for the class (or blueprint).
object3-2.py |
1 | class MyObject(object): |
2 | c = 5 |
3 | def __init__(self,a,b): |
4 | self.a = a |
5 | self.b = b |
6 | def __str__(self): |
7 | return "("+str(self.a)+","+str(self.b)+")" |
8 | |
9 | print MyObject.c |
10 | my = MyObject(3,4) |
11 | for attr in dir(my): |
12 | print attr,"=",getattr(my,attr) |
$ python ./object3-2.py
5
__class__ = <class '__main__.MyObject'>
__delattr__ = <method-wrapper '__delattr__' of MyObject object at 0x7f88052a0fd0>
__dict__ = {'a': 3, 'b': 4}
__doc__ = None
__format__ = <built-in method __format__ of MyObject object at 0x7f88052a0fd0>
__getattribute__ = <method-wrapper '__getattribute__' of MyObject object at 0x7f88052a0fd0>
__hash__ = <method-wrapper '__hash__' of MyObject object at 0x7f88052a0fd0>
__init__ = <bound method MyObject.__init__ of <__main__.MyObject object at 0x7f88052a0fd0>>
__module__ = __main__
__new__ = <built-in method __new__ of type object at 0x3a8119adc0>
__reduce__ = <built-in method __reduce__ of MyObject object at 0x7f88052a0fd0>
__reduce_ex__ = <built-in method __reduce_ex__ of MyObject object at 0x7f88052a0fd0>
__repr__ = <method-wrapper '__repr__' of MyObject object at 0x7f88052a0fd0>
__setattr__ = <method-wrapper '__setattr__' of MyObject object at 0x7f88052a0fd0>
__sizeof__ = <built-in method __sizeof__ of MyObject object at 0x7f88052a0fd0>
__str__ = <bound method MyObject.__str__ of <__main__.MyObject object at 0x7f88052a0fd0>>
__subclasshook__ = <built-in method __subclasshook__ of type object at 0x1b5b7f0>
__weakref__ = None
a = 3
b = 4
c = 5
|
There is also the "hasattr()" function, to test whether an object has
an attribute, "setattr()" to change the attribute, and "delattr()" to remove
it.
object3-3.py |
1 | class MyObject(object): |
2 | c = 5 |
3 | def __init__(self,a,b): |
4 | self.a = a |
5 | self.b = b |
6 | def __str__(self): |
7 | return "("+str(self.a)+","+str(self.b)+")" |
8 | |
9 | my = MyObject(3,4) |
10 | print hasattr(my,"a"),hasattr(my,"d") |
11 | print getattr(my,"a") |
12 | delattr(my,"a") |
13 | print hasattr(my,"a") |
performing [python ./object3-3.py]
$ python ./object3-3.py
0
0.5
0.5
0.5
Traceback (most recent call last):
File "/usr/lib/python2.7/site.py", line 68, in <module>
import os
File "/usr/lib/python2.7/os.py", line 400, in <module>
import UserDict
File "/usr/lib/python2.7/UserDict.py", line 116, in <module>
import _abcoll
File "/usr/lib/python2.7/_abcoll.py", line 70, in <module>
Iterable.register(str)
File "/usr/lib/python2.7/abc.py", line 107, in register
if not isinstance(subclass, (type, types.ClassType)):
AttributeError: 'module' object has no attribute 'ClassType'
|
This code works for the instance variable "a", but for
the class variable "c" we sometimes need to use the class
definition.
object3-3.py |
1 | |
performing [python ./object3-3.py]
$ python ./object3-3.py
0
0.5
0.5
0.5
Traceback (most recent call last):
File "/usr/lib/python2.7/site.py", line 68, in <module>
import os
File "/usr/lib/python2.7/os.py", line 400, in <module>
import UserDict
File "/usr/lib/python2.7/UserDict.py", line 116, in <module>
import _abcoll
File "/usr/lib/python2.7/_abcoll.py", line 70, in <module>
Iterable.register(str)
File "/usr/lib/python2.7/abc.py", line 107, in register
if not isinstance(subclass, (type, types.ClassType)):
AttributeError: 'module' object has no attribute 'ClassType'
|
Now for a powerful insight about the nature of Python:
Everything is an object. What does that mean?
everything.py |
1 | class MyObject(object): |
2 | pass |
3 | my = MyObject() |
4 | print hasattr(my,'__class__'),getattr(my,'__class__') |
5 | print hasattr(3,'__class__'),getattr(3,'__class__') |
6 | print hasattr("x",'__class__'),getattr("x",'__class__') |
$ python ./everything.py
True <class '__main__.MyObject'>
True <type 'int'>
True <type 'str'>
|
There are a number of special methods that
you can define.
We've already seen __str__, __init__, and __del__, but one
can also define operators such as +, -, and **.
object4.py |
1 | class MyObject: |
2 | def __init__(self,a,b): |
3 | self.a = a |
4 | self.b = b |
5 | def __add__(self,m): |
6 | return MyObject(self.a+m.a,self.b+m.b) |
7 | def __str__(self): |
8 | return "("+str(self.a)+","+str(self.b)+")" |
9 | |
10 | my1 = MyObject(1,2) |
11 | my2 = MyObject(3,4) |
12 | my3 = my1 + my2 |
13 | print "%s + %s = %s" % (my1,my2,my3) |
14 | my4 = MyObject.__add__(my1,my2) # another way to call it |
15 | print "%s + %s = %s" % (my1,my2,my4) |
$ python ./object4.py
(1,2) + (3,4) = (4,6)
(1,2) + (3,4) = (4,6)
|
You can find more operators here.
Inheritance: In this next example, we show how one class can inherit from another.
Object Obj2 is just like Obj1, except some things are added. If you
define a new constructor, you should
call the constructor of the class you inherit from to make sure the
base object is properly initialized.
object4-1.py |
1 | class Obj1(object): |
2 | def __init__(self,a): |
3 | self.a = a |
4 | class Obj2(Obj1): |
5 | def __init__(self,a,b): |
6 | Obj1.__init__(self,a) # construct superclass |
7 | self.b = b |
8 | class Obj3(Obj1): |
9 | def __init__(self,a,b): # another way to |
10 | super(Obj3,self).__init__(a) # construct superclass |
11 | self.b = b |
12 | |
13 | x = Obj2(1,2) |
14 | print x.a,x.b |
15 | y = Obj3(8,9) |
16 | print y.a,y.b |
$ python ./object4-1.py
1 2
8 9
|
If you don't provide an __init__ method in your base class,
the method from the superclass will be inherited.
object4-2.py |
1 | class Obj1(object): |
2 | def __init__(self,a): |
3 | self.a = a |
4 | class Obj2(Obj1): |
5 | pass |
6 | |
7 | x = Obj2(14) |
8 | print x.a |
$ python ./object4-2.py
14
|
You can inherit from more than one class.
object4-3.py |
1 | class Obj1(object): |
2 | def __init__(self,a): |
3 | self.a = a |
4 | class Obj2(object): |
5 | def __init__(self,b): |
6 | self.b = b |
7 | class Obj3(Obj1,Obj2): |
8 | def __init__(self,a,b): |
9 | Obj1.__init__(self,a) |
10 | Obj2.__init__(self,b) |
11 | o = Obj3(9,10) |
12 | print o.a,o.b |
$ python ./object4-3.py
9 10
|
Factoid: You can distinguish types in Python by calling the type()
function.
object4-4.py |
1 | class MyClass(object): |
2 | pass |
3 | print type(1),type({}),type([]),type("x"),type(MyClass()) |
$ python ./object4-4.py
<type 'int'> <type 'dict'> <type 'list'> <type 'str'> <class '__main__.MyClass'>
|
In addition, you can use the "isinstance()" method to see
if a class is an instance of a given class. In the example,
MyOtherClass inherits from MyClass, so it is an instance of
MyClass.
Fun and tricks: One of the neat features of Python is
the decorator. These are words beginning with the @ symbol,
and are used modify the functionality of a class or function.
object4-5.py |
1 | |
performing [python ./object4-5.py]
$ python ./object4-5.py
0
0.5
0.5
0.5
Traceback (most recent call last):
File "/usr/lib/python2.7/site.py", line 68, in <module>
import os
File "/usr/lib/python2.7/os.py", line 400, in <module>
import UserDict
File "/usr/lib/python2.7/UserDict.py", line 116, in <module>
import _abcoll
File "/usr/lib/python2.7/_abcoll.py", line 70, in <module>
Iterable.register(str)
File "/usr/lib/python2.7/abc.py", line 107, in register
if not isinstance(subclass, (type, types.ClassType)):
AttributeError: 'module' object has no attribute 'ClassType'
|
object4-5.py |
1 | |
performing [python ./object4-5.py]
$ python ./object4-5.py
0
0.5
0.5
0.5
Traceback (most recent call last):
File "/usr/lib/python2.7/site.py", line 68, in <module>
import os
File "/usr/lib/python2.7/os.py", line 400, in <module>
import UserDict
File "/usr/lib/python2.7/UserDict.py", line 116, in <module>
import _abcoll
File "/usr/lib/python2.7/_abcoll.py", line 70, in <module>
Iterable.register(str)
File "/usr/lib/python2.7/abc.py", line 107, in register
if not isinstance(subclass, (type, types.ClassType)):
AttributeError: 'module' object has no attribute 'ClassType'
|
In the above example, Python saw the definition for <, and ==, and
created methods for all the other comparison operators.
We didn't define the greater >= operator, but it's there.
All we had to do was use the total_ordering decorator.
For your interest, here's how to create a decorator
for an object. In this example, our decorator provides the method "pr".
object5.py |
1 | # Pass a class in |
2 | def add_pr(clazz): |
3 | # Create a new class from the one passed in |
4 | class newclazz(clazz): |
5 | # Add a method to this class |
6 | def pr(self): |
7 | print "this is pr" |
8 | # Return the new class, or modified blueprint |
9 | return newclazz |
10 | |
11 | @add_pr |
12 | class MyClass(object): |
13 | pass |
14 | |
15 | m = MyClass() |
16 | m.pr() |
$ python ./object5.py
this is pr
|
Here's how to create a total ordering with only a < function.
object6.py |
1 | def ordering(clazz): |
2 | class newclazz(clazz): |
3 | def __ge__(self,m): |
4 | return m < self; |
5 | def __ne__(self,m): |
6 | return (m < self) or (self < m) |
7 | def __eq__(self,m): |
8 | return not (self != m) |
9 | def __le__(self,m): |
10 | return self < m or self == m |
11 | def __gt__(self,m): |
12 | return not (self <= m) |
13 | return newclazz |
14 | |
15 | @ordering |
16 | class Num(object): |
17 | def __init__(self,n): |
18 | self.n = n |
19 | def __str__(self): |
20 | return str(self.n) |
21 | def __lt__(self,num): |
22 | return self.n < num.n |
23 | |
24 | n1 = Num(1) |
25 | for i in range(3): |
26 | n2 = Num(i) |
27 | print "%s < %s = %s" % (n1,n2,n1 < n2) |
28 | print "%s <= %s = %s" % (n1,n2,n1 <= n2) |
29 | print "%s > %s = %s" % (n1,n2,n1 > n2) |
30 | print "%s >= %s = %s" % (n1,n2,n1 >= n2) |
31 | print "%s == %s = %s" % (n1,n2,n1 == n2) |
32 | print "%s != %s = %s" % (n1,n2,n1 != n2) |
$ python ./object6.py
1 < 0 = False
1 <= 0 = False
1 > 0 = True
1 >= 0 = True
1 == 0 = False
1 != 0 = True
1 < 1 = False
1 <= 1 = True
1 > 1 = False
1 >= 1 = False
1 == 1 = True
1 != 1 = False
1 < 2 = True
1 <= 2 = True
1 > 2 = False
1 >= 2 = False
1 == 2 = False
1 != 2 = True
|
OK, that was a bit of a digression into a slightly obscure bit of python, but
I hope you think it was fun! What should this code do?
object7.py |
1 | class Num(object): |
2 | def __init__(self,n): |
3 | self.n = n |
4 | def __lt__(self,num): |
5 | return self.n < num.n |
6 | def __add__(self,m): |
7 | return Num(self.n + m.n) |
8 | def __str__(self): |
9 | return str(self.n) |
10 | def __mul__(self,n): |
11 | if type(n) == int: |
12 | if n == 0: |
13 | return 0 |
14 | sum = self |
15 | n -= 1 |
16 | while n > 0: |
17 | sum += self |
18 | n -= 1 |
19 | return sum |
20 | elif isinstance(n,Num): |
21 | return self * n.n |
22 | |
23 | n1 = Num(1) |
24 | n2 = Num(2) |
25 | print n1*30*n2 |
Properties are another cool trick. You can turn accesses of
an object's member fields into function calls.
Props.py |
1 | class Scramjet(object): |
2 | def __init__(self): |
3 | self.__engine = "Acme" |
4 | |
5 | @property |
6 | def engine(self): |
7 | print "Getting the engine: "+self.__engine |
8 | return self.__engine |
9 | |
10 | @engine.setter |
11 | def engine(self,value): |
12 | print "Setting the engine: "+value |
13 | self.__engine = value |
14 | return value |
15 | s = Scramjet() |
16 | e = s.engine |
17 | print "Engine is",e |
18 | e = s.engine = "Boeing" |
19 | print "Engine is",e |
$ python ./Props.py
Getting the engine: Acme
Engine is Acme
Setting the engine: Boeing
Engine is Boeing
|
|