programming with python - university of bonnlaotzu.bit.uni-bonn.de/ipec_slides/lecture6.pdf ·...
TRANSCRIPT
Programming with Python
IPEC Winter School 2015B-IT
Dr. Tiansi Dong & Dr. Joachim Köhler
Lecture 6: Designing Python Classes
Constructor and destructor
● constructor : __init__
● destructor : __del__
– __del__ is called automatically when an instance's memoryspace is reclaimed
>>> class Life:def __init__(self, name='nobody'):
print('hello ', name)self.name = name
def __del__(self):print('Goopbye ', self.name)
>>> x = Life('Brian')Hello Brian>>> x = Life('Loretta')hello Lorettagoodbye Brian
Operator Overloading 1
● Subtraction operation: __sub__
– __sub__ specifies the subtraction operation
>>> class Number:def __init__(self, start):
self.data = startdef __sub__(self, other):
return Number(self.data - other)
>>> x = Number(4)>>> y = x -4>>> y<__main__.Number object at 0x0292EA70>>>> y.data0>>> y = 3-xTraceback (most recent call last): File "<pyshell#42>", line 1, in <module> y = 3-xTypeError: unsupported operand type(s) for -: 'int' and 'Number'
Operator Overloading 2
● No polymorphism for operator overloading in Python
>>> class Number:def __init__(self, start):
self.data = startdef __sub__(self, other):
return Number(self.data - other)def __sub__(other, self):
return Number(other - self.data)
>>> x = Number(4)>>> y = x-4Traceback (most recent call last): File "<pyshell#47>", line 1, in <module> y = x-4 File "<pyshell#45>", line 7, in __sub__ return Number(other - self.data)AttributeError: 'int' object has no attribute 'data'>>> y = 4-xTraceback (most recent call last): File "<pyshell#48>", line 1, in <module> y = 4-xTypeError: unsupported operand type(s) for -: 'int' and 'Number'
Operator Overloading 3
● __sub__, __rsub__ and __isub__
– __sub__ does not support the use of instance appearing on theright side of the operator
– __isub__ supports in-place subtraction
class Number:def __init__(self, start):
self.data = startdef __sub__(self, other):
return Number(self.data - other)def __rsub__(self, other):
return Number(other - self.data)def __isub__(self, other):
self.data -=otherreturn self
>>> x = Number(4) >>> x<__main__.Number object at0x029C7B90>>>> y = x-4>>> y.data0>>> y = 5-x>>> y.data1>>> x -= 3>>> x.data1>>>
Operator Overloading 4
● Boolean tests: __bool__and __len__
– Python first tries __bool__ to obtain a direct Boolean value, ifmissing, tries __len__ to determine a truth value from theobject length
– In Python 2.6 __bool__ is not recognized as a special method>>> class Truth:
def __bool__(self):return True
>>> X = Truth()>>> if X: print('yes')yes>>> class Truth0:
def __len__(self):return 0
>>> X = Truth0()>>> if not X: print('no')no
>>> class Truth:def __bool__(self):
return Truedef __len__(self):
return 0
>>> X = Truth()>>> if X: print('yes')yes #Python 3.0 tries __bool__first#Python 2.6 tries __len__ firstPython 2.6 users should use__nonzero__ instead of __bool__
Operator Overloading 5
● Comparisons: __lt__, __gt__ , __le__, __ge__,__eq__, __ne__
– For comparisons of <, >, <= ,>=, ==, !=
– No relations are assumed among these comparison relations
– __cmp__ used in Python 2.6, removed in Python 3.0
>>> class Comp:data = “hallo”def __gt__(self, str):
return self.data > strdef __lt__(self, str):
return self.data < str
>>> X = Comp()>>> print(X > 'hi')False>>> print(X < 'hi')True
Operator Overloading 6
● String representation: __repr__ and __str__
– __str__ is tried first for the print function and the str built-infunction
– __repr__ is used in all other contexts, as well as for the print function and the str function when __str__ is not available.
Operator Overloading 7
>>> class adder:def __init__(self, value=0):
self.data = valuedef __add__(self, other):
self.data += other
>>> class addrepr(adder):def __repr__(self):
return 'addrepr(%s)' % self.data
>>> class addstr(adder):def __str__(self):
return '[Value: %s]' % self.data
>>> class addboth(adder):def __str__(self):
return '[Value: %s]' % self.datadef __repr__(self):
return 'addboth(%s)' % self.data
>>> x0 = adder()>>> print(x0)<__main__.adder object at 0x0299FE90>>>> x1 = addrepr(2)>>> x1addrepr(2)>>> str(x1)'addrepr(2)'>>> x2 = addstr(4)>>> x2<__main__.addstr object at 0x0299FE50>>>> print(x2)[Value: 4]>>> repr(x2)'<__main__.addstr object at 0x0299FE50>'>>> x3 = addboth(4)>>> x3+1>>> x3addboth(5)>>> print(x3)[Value: 5]
>>> str(x3)'[Value: 5]'>>> repr(x3)'addboth(5)'
Operator Overloading 8
● Attribute reference: __getattr__ and __setattr__
– __getattr__ is called automatically for undefined attributequalifications
– __setattr__ intercepts all attribute assignments. If this method isdefined, self.attr = value becomesself.__setattr__('attr', value)
class Empty:def __getattr__(self, attrname):
if attrname == "age":return 40
else:raise AttributeError(attrname)
def __setattr__(self, attr, value):if attr=='age':
self.__dict__[attr] = value ##can we write self.attr=value?else:
raise AttributeError( attr + ' not allowed')>>> X = Empty()>>> X.age40>>> X.age=4
Operator Overloading 9
● Call expressions: __call__
– __call__ is called automatically when an instance is called
– __call__ can pass any positional or keyword arguments
– All of the argument-passing modes are supported by __call__>>> class Prod:
def __init__(self, value):self.value = value
def __call__(self, other):return self.value*other
>>> x=Prod(3)>>> x<__main__.Prod object at 0x9811f4c>>>> x(5)15
Operator Overloading 10
● Function interfaces and Callback-based code
– Functions can be registered as event handlers (callbacks)
– When events occur, these handlers will be called.
class Callback:def __init__(self, color):
self.color = colordef __call__(self):
print('pressed ', self.color)
>>> cb1 = Callback('blue')>>> cb2 = Callback('green')>>> class Button:
def __init__(self):self.cmd = None
def press(self, cmd):self.cmd = cmdself.cmd()
>>> b1 = Button()>>> b1.press(cb1)pressed blue>>> b2 = Button()>>> b2.press(cb2)pressed green
For dialog system: 'press' → voice to your eardrum, 'callback' → voice from your mouth
Operator Overloading 11
● Indexing and Slicing: __getitem__ and __setitem__
– __getitem__ is called automatically for instance-indexing
X[i] calls X.__getitem__(i)
– __setitem__ is called for index assignment>>> class Indexer:
data = [1,2,3,4]def __getitem__(self, index):
print('get item:', index)return self.data[index]
def __setitem__(self, index, value):self.data[index] = value ##can we write self[index]=value?
>>> X = Indexer()>>> X[0]get item: 01>>> X[-1]get item: -14>>>x[3] = -1
Operator Overloading 12
● Index iteration __getitem__ in the for loop
– for statement works by repeatedly indexing a sequence from 0 tohigher indexes.
– If this method is defined, for loop calls the class's __getitem__
>> class stepper:def __getitem__(self, i):
return self.data[i]data = "abcdef"
>>> x = stepper()>>> for item in x:
print(item, end=",")
a,b,c,d,e,f,
Operator Overloading 13
● Iterator objects: __iter__ and __next__
– In all iteration contexts, __iter__ will be firsty tried, beforetrying __getitem__
– __iter__ shall return an iterator object, whose __next__ built-in will be repeated called to iterate items until aStopIteration exception is raised.
Operator Overloading 13 continued
class Squares:def __init__(self, start, stop):
self.value = start -1self.stop = stop
def __iter__(self):return self
def __next__(self):if self.value == self.stop:
raise StopIterationself.value += 1return self.value **2
>>> for i in Squares(1,6):print(i, end=" ")
1 4 9 16 25 36
>>> X = Squares(-1, -3)>>> I = iter(X)>>> next(I)1>>> next(I)0>>> next(I)1>>> next(I)4>>> next(I)9>>> next(I)16>>> next(I)25>>> next(I)36>>> next(I)49
Operator Overloading 14
● Membership: __contains__ , __iter__, __getitem__
– In all iteration contexts, __contains__ is perferred over__iter__, which is perferred over __getitem__
– __contains__ is automatically called by the in built-infunction
class Iters:def __init__(self, value):
self.data = valuedef __getitem__(self, i):
print('get[%s]:' % i, end=' ')return self.data[i]
def __iter__(self):print('iter=> ', end= ' ')self.ix = 0return self
def __next__(self):print('next:', end=' ')if self.ix == len(self.data): raise StopIterationitem = self.data[self.ix]self.ix += 1return item
def __contains__(self, x):print('contains: ', end=' ')return x in self.data
>>> X = Iters([1,2])>>> 3 in Xcontains: False>>> for i in X:
print(i, end='|')
iter=> next: 1|next: 2|next:
>>> [i*2 for i in X]iter=> next: next: next: [2, 4]
Overloading not by Signatures
● In some programming language, polymorphism also meansoverloading with signatures. But not for Python
● In Python, polymorphism means: X.func() depends on X
>>> class C:def func(self):
print(1)def func(self, x):
print(2)
>>> x = C()>>> x.func()
class C:def func(self, *args):
if len(args)==1:print(1)
elif len(args) ==2:print(2)
Class Inheritance and Class Composition
● Inheritance: Is-a relation
– A researcher is a kinds of person
– A graduate-student is a kind of researcher
– A professor is a kind of researcher
● Composition: Has-a relation
– A university has several departments
– A department has several working-groups
– A working-group has one professor and several graduate-students
class Person:def __init__(self, name, age)…
class Researcher(Person):def __init__(self, research_area)…
class GraduateStudent(Researcher):def __init__(self, level)
class Professor(Researcher):def __init__(self, ranks)
class WorkingGroup:def __init__(self, prof, *students)…
class Department():def __init__(self, *groups)…
class University():def __init__(self, *departments)
Multiple Inheritance
● A class can have more than one super-classes.
● When searching for an attribute, Python traverses all super-classes in the class header, from left to right until a match isfound
● Depth-first searching or breadth-first searching?
– In classic classes (until Python 3.0), depth-first searchingis performed
– In new-style classes (all classes in Python 3.0 andabove), breadth-first searching is performed
Pseudo-private Attribute: “name mangling”
● What is “mangling”?
– to injure with deep disfiguring wounds by cutting, tearing, orcrushing <people … mangled by sharks — V. G. Heiser>
● How does Python do “name mangling”?
– Class attributes, starting with two underscores and not endingwith two underscores, are automatically prefixed withunderscore and the class name
>>> class C1:def func1(self): self.X = 11def func2(self): print(self.X)
>>> class C2:def funca(self): self.X = 22def funcb(self): print(self.X)
>>> class C3(C1,C2): …>>> x = C3()
Pseudoprivate Attribute: “name mangling”
>>> class C1:def func1(self): self.__X = 11def func2(self): print(self.__X)
>>> class C2:def funca(self): self.__X = 22def funcb(self): print(self.__X)
>>> class C3(C1,C2): pass
>>> x = C3()
>>> print(x.__dict__){'_C1__X': 11, '_C2__X': 22}
>>> x.func1()11
>>> x.funca()22
Methods are Objects: Bound and Unbound
● Bound class method: self + function
● Unbound class method: no self
– Python 3.0 has dropped the notion of unbound methods, adopted the notion of simple methods
>>>class C:def dosomething(self, message):
print(message)
>>>x = C()>>>obj = x.dosomething>> obj(“hello world”) hello world
>>>class D:def dosimple(message):
print(message)
>>>D.dosimple(“hello world”) hello world
Class Delegation: “Wrapper” Object
● An object wraps another object, in order to control, protect, or trace
>>>class Wrapper:def __init__(self, obj):
self.wrapped = objdef __getattr__(self, attrname):
print("Trace:", attrname)return getattr(self.wrapped, attrname)
def __repr__(self):return "Wrapped in Wrapper: %s" % self.wrapped
>>>x = Wrapper([1,2,3])>>>x.append(4)Trace: append>>>xWrapped in Wrapper: [1, 2, 3, 4]
# x.append(4) # def append(self, x):
pass
Generic Object Factories
● Classes can be passed as parameters to functions
>>>def factory(aClass, *args):return aClass(*args)
>>>class C:def dosomething(self, message):
print(message)
>>>class Person:def __init__(self, name, job):
self.name = nameself.job = job
>>>obj1 = factory(C)>>>obj2 = factory(Person, “Bob”, “dev”)
Instance Slots: __slots__
● The special attribute __slots__, located at the top-level of a classstatement, defines the sequence of string names, only those can beassigned as instance attributes
>>> class C:__slots__ = ['name', 'age']def __init__(self, name):
self.name=name
>>> bob = C('Bob')>>> bob<__main__.C object at 0x028734D0>>>> bob.job = 'dev'Traceback (most recent call last): File "<pyshell#13>", line 1, in <module> bob.job = 'dev'AttributeError: 'C' object has no attribute 'job'>>> bob.__dict__Traceback (most recent call last): File "<pyshell#14>", line 1, in <module> bob.__dict__AttributeError: 'C' object has no attribute '__dict__'
Instance Slots: __slots__
● Without an attribute namespace dictionary __dict__, it is notpossible to assign new names to instances.
● To let an instance with slots accept new attributes, we can include'__dict__' into the __slots__
>>> class D:__slots__ = ['name', 'age', '__dict__']
>>> d = D()>>> d.name = 'Bob'>>> d.age=30>>> d.job='dev'>>> d.__slots__['name', 'age', '__dict__']>>> d.__dict__{'job': 'dev'}>>> for attr in list(d.__dict__) + d.__slots__:
print(attr, '==>', getattr(d,attr))job ==> devname ==> Bobage ==> 30__dict__ ==> {'job': 'dev'}
Class Properties
● <attr>=property(get,set,del,docs) states the access,assignment, delete, and doc-string functions for a class attribute<attr>
>>> class A:def __init__(self):
self._age = 0def getage(self):
return self._agedef setage(self, value):
print('set age:', value)self._age = value
age = property(getage, setage, None, None)
>>> bob = A()>>> bob.age0>>> bob.age=44set age: 44>>> bob.age44>>> bob._age44
Here we have _age and age twoattributes. Can we just use one?Like this:
class A:def getage(self):
return 40def setage(self, value):
print('set age:', value)
self.age = valueage = property(getage,
setage, None, None)
Static Methods and Class Methods
● Static methods are like simple functions without instance inside aclass, declared with staticmethod
● Class methods pass a class instead of an instance, declared withclassmethod >>> class M:
def ifunc(self,x):print(self,x)
def sfunc(x):print(x)
def cfunc(cls,x):print(cls,x)
sfunc = staticmethod(sfunc)cfunc = classmethod(cfunc)
>>> obj = M()>>> obj.ifunc(3)<__main__.M object at 0x028AD9D0> 3>>> M.ifunc(obj,2)<__main__.M object at 0x028AD9D0> 2>>> M.sfunc(12)12>>> obj.sfunc(1239)1239>>> M.cfunc(4)<class '__main__.M'> 4>>> obj.cfunc(89)<class '__main__.M'> 89
>>> class M:def ifunc(self,x):
print(self,x)def sfunc(x):
print(x)def cfunc(cls,x):
print(cls,x)
>>> obj = M()>>> obj.ifunc(3)<__main__.M object at 0x028AD9D0> 3>>> M.ifunc(obj,2)<__main__.M object at 0x028AD9D0> 2>>> M.sfunc(12)12>>> obj.sfunc(1239)>>> M.cfunc(4)