View
5
Download
0
Category
Preview:
Citation preview
Python Programming: Lecture 4Object Oriented Programming
Lili Dworkin
University of Pennsylvania
October 3, 2014
Good Question from Last Week
>>> def foo(a, b, c):
... print a, b, c
...
>>> foo(1, 2, 3)
1 2 3
>>> l = [1, 2, 3]
>>> foo(*l)
1 2 3
>>> l.append(4)
>>> foo(*l)
TypeError: foo() takes exactly 3 arguments (4 given)
Last Week’s Quiz
Write a function that takes a variable number of keywordarguments, and prints out a comma separated list of the argumentvalues. Hint: use join and d.values(). An example:
>>> foo(cat=1, dog=2)
1, 2
Last Week’s Quiz
Write a function that takes a variable number of keywordarguments, and prints out a comma separated list of the argumentvalues.
>>> def foo(**kwargs):
... print ', '.join(map(str, kwargs.values()))
I Use two stars (**) for keyword arguments
I .join() is called on the separator/delimiter object
I kwargs is a dictionary
I kwargs.values() is a list of the dictionary values
I .join() only works on lists of strings
Last Week’s Quiz
>>> def foo(**kwargs):
... print ', '.join(map(str, kwargs.values()))
...
>>> foo(cat=1, dog=2)
2, 1
>>> d = {'cat':1, 'dog':2, 'canary':500}>>> foo(**d)
2, 500, 1
Last Week’s Quiz
What about a function that takes a variable number of positionalarguments and prints them out in a space separated list?
Last Week’s Quiz
What about a function that takes a variable number of positionalarguments and prints them out in a space separated list?
>>> def foo(*args):
... print ' '.join(map(str, args))
...
>>> foo('cat', 5, [True, False])
cat 5 [True, False]
Last Week’s Quiz
What does the following print?
>>> l = []
>>> def foo(data=l):
... print data
...
>>> l = ['a']>>> foo()
Last Week’s Quiz
>>> l = []
>>> def foo(data=l):
...
Explicit binding!
Last Week’s Quiz
>>> l = []
>>> def foo(data=l):
...
>>> l = ['a']
Last Week’s Quiz
What about the following?
>>> def outer(l):
... def inner():
... return l
... return inner
...
>>> l = ['a']>>> f = outer(l)
>>> f()
['a']>>> l.append('b')>>> f()
?
Last Week’s Quiz
What about the following?
>>> def outer(l):
... def inner():
... return l
... return inner
...
>>> l = ['a']>>> f = outer(l)
>>> f()
['a']>>> l.append('b')>>> f()
['a', 'b']
I It’s a closure, but we enclosed a mutable object
I (This was a question asked last week)
Last Week’s Quiz
Using map and filter (and the is even function defined in lecture),write an expression equivalent to
>>> [str(x) for x in [1,2,3] if is_even(x)]
Last Week’s Quiz
Using map and filter (and the is even function defined in lecture),write an expression equivalent to
>>> [str(x) for x in [1,2,3] if is_even(x)]
>>> map(str, filter(is_even, [1,2,3]))
I Do not use parentheses when working with function objects!(e.g. str() or str(x))
I In this case, call filter first, because is_even won’t workon string objects
Last Week’s Quiz
Fill in the ... below. custom_sum(x) should return a function thattakes a list of integers, computes a sum, and adds x to it.
def custom_sum(x):
def my_sum(l):
return ...
return ...
>>> s = custom_sum(10)
>>> s([1,2,3])
16
Last Week’s Quiz
Fill in the ... below. custom_sum should return a function thattakes a list of integers, computes a sum, and adds x to it.
def custom_sum(x):
def my_sum(l):
return sum(l) + x
return my_sum
I Because custom_sum returns a function, you should knowthat the last line should be return my_sum (no parentheses!)
Last Week’s Quiz
What about this? foo should return a function that takes avariable number of positional arguments, and returns a list where xis the first element.
def foo(x):
def bar(...):
return ...
return ...
Last Week’s Quiz
What about this? foo should return a function that takes avariable number of positional arguments, and returns a list where xis the first element.
def foo(x):
def bar(*args):
return [x] + list(args)
return bar
Last Week’s Quiz
Sort a list of numbers so that the even numbers come first. Note:In Python, False < True
Last Week’s Quiz
Sort a list of numbers so that the even numbers come first. Note:In Python, False < True
>>> sorted(l, key=is_even, reverse=True)
>>> sorted(l, key=lambda x: not is_even(x))
I Note the use of parentheses :)
Classes
class Point:
def __init__(self, x, y): # constructor
self.x = x # data attribute
self.y = y
def norm(self): # method
return math.sqrt(self.x ** 2 + self.y ** 2)
Construction
>>> p = Point(1,2)
I Calls the __init__ method
I Note that we only passed in two arguments
I self is a reference to the object instance itself
I When you call Point(), Python creates an object for you,and passes it as the first parameter to the __init__ method
Attributes and Methods
Everything is (basically) public:
>>> p = Point(3,4)
>>> p.x # get attribute
3
>>> p.x = 3 # set attribute
Both of the following will call a method:
>>> Point.norm(p)
5.0
>>> p.norm()
5.0
We will always use the second syntax. Note how this syntax“automatically” passes p in as the self parameter.
More About self
I Methods must take at least one argument (called self)
I Attributes and other methods are acccessed through self
def print_norm(self):
print "||(%d, %d)|| = %.1f" %
(self.x, self.y, self.norm())
>>> p.print_norm()
||(3, 4)|| = 5.0
Data vs. Class Attributes
I Java’s instance variables = Python’s data attributes
I Java’s static variables = Python’s class attributes
class Point:
count = 0 # class attribute
def __init__(self, x, y):
self.x = x
self.y = y
Point.count += 1
Data vs. Class Attributes
Class attributes can be accessed directly through the class, ratherthan through an instance (though that works too):
>>> Point.count
0
>>> p = Point(1,2)
>>> Point.count
1
>>> p.count
1
getattr
The following are equivalent:
>>> p = Point(1,2)
>>> p.x
1
>>> getattr(p, 'x')1
getattr takes as input 1) either an instance or a class and 2) thestring name of an attribute or method
getattr
Difference between passing a class vs. instance:
>>> f = getattr(Point, 'norm')>>> f(p)
5.0
>>> g = getattr(p, 'norm')>>> g()
5.0
getattr
Can be used on builtins!
>>> f = getattr(str, 'isalpha')>>> f('a')True
>>> l = [1,2]
>>> g = getattr(l, 'append')>>> g(3)
>>> l
[1, 2, 3]
getattr
Use case: decision of which method to use is decided at runtime.
class Foo:
def method1(self):
print "Calling method1."
def method2(self):
print "Calling method2."
def call_method(foo, num):
f = getattr(foo, "method%d" % num)
f()
>>> foo = Foo()
>>> call_method(foo, 2)
Calling method2.
Inheritance
class Animal(object): # new-style object
def __init__(self, name):
self.name = name
class Cat(Animal):
pass
>>> cat = Cat('Missy')>>> cat.name
'Missy'
Automatically called parent’s __init__.
Inheritance
__init__ is optional, but if you define it, you must remember tocall the parent’s __init__. This is true in general when extendingthe behavior of the parent.
class Cat(Animal):
def __init__(self, name, breed=None):
Animal.__init__(self, name) # don't forget!
self.breed = breed
>>> cat = Cat("Missy", "Persian")
>>> cat.name
'Missy'>>> cat.breed
'Persian'
Inheritance
The previous syntax was pretty ugly – we had to remember thatour superclass was named “Animal.” This is better:
class Cat(Animal):
def __init__(self, name, breed=None):
super(Cat, self).__init__()
self.breed = breed
This also works with multiple inheritance, as we will see shortly.
Inheritance
class Animal(object):
def talk(self):
return 'I say '
class Cat(Animal):
def talk(self):
print super(Cat, self).talk() + 'meow.'
class Dog(Animal):
def talk(self):
print super(Dog, self).talk() + 'bark.'
>>> cat.talk()
I say meow.
>>> dog.talk()
I say bark.
Multiple Inheritance
I A class can inherit from multiple base classes:
class Subclass(Base1, Base2, Base3 ...)
I Resolution rule: depth-first, left-to-right
I If an attribute or method is not found in Subclass, it issearched for in Base1, then in the base classes of Base1, andif not found there, it is searched for in Base2, and so on.
Multiple Inheritance
class A(object):
def foo(self):
print 'Foo!'
class B(object):
def foo(self):
print 'Foo?'
def bar(self):
print 'Bar!'
class C(A,B):
def foobar(self): # what will this print?
super(C, self).foo()
super(C, self).bar()
Multiple Inheritance
>>> c = C()
>>> c.foobar()
Foo!
Bar!
We found the foo method in class A (and stopped there), andthen we found the bar method in class B.
Encapsulation
A language mechanism for restricting access to some of theobject’s components.
I Python doesn’t really do encapsulation.
I No such thing as private or protected members. **
I “We’re all adults here.” – Guido
I ** Well, kind of ...
Encapsulation
I Use a single leading underscore (_var) as a weak “internaluse indicator.”
I from module import * does not import objects whosename starts with an underscore.
Encapsulation
Use two leading underscores to indicate a private method orattribute:
class Private:
def __init__(self, secret):
self.__secret = secret
def __keep_secret(self):
return self.__secret
def release_secret(self):
return self.__secret
Encapsulation
>>> p = Private('I am Iron Man.')>>> p.__secret
AttributeError
>>> p.__keep_secret()
AttributeError
>>> p.release_secret()
'I am Iron Man.'
This looks a lot like “real” privacy, but ...
Encapsulation
I Behind the scenes, Python changes the name of __var to_ClassName__var.
I So __var doesn’t exist, and can’t be accessed.
I But _ClassName__var still works.
>>> p = Private('I am Iron Man.')>>> p._Private__secret
'I am Iron Man.'>>> p._Private__keep_secret()
'I am Iron Man.'
Encapsulation
This seems silly – what’s the point?
I To ensure that subclasses don’t accidentally override theprivate methods and attributes of their superclasses.
I Not designed to prevent deliberate access from outside.
Encapsulation
class Foo(object):
def __init__(self):
self.__baz = 42
def foo(self):
print self.__baz
class Bar(Foo):
def __init__(self):
super(Bar, self).__init__()
self.__baz = 21
def bar(self):
print self.__baz
Encapsulation
>>> x = Bar()
>>> x.foo()
# ?
>>> x.bar()
# ?
Encapsulation
>>> x = Bar()
>>> x.foo()
42
>>> x.bar()
21
Encapsulation
What really happened:
class Foo(object):
def __init__(self):
self._Foo__baz = 42
def foo(self):
print self._Foo__baz
class Bar(Foo):
def __init__(self):
super(Bar, self).__init__()
self._Bar__baz = 21
def bar(self):
print self._Bar__baz
Encapsulation
class Mapping:
def __init__(self, items):
self.items_list = []
self.update(items)
def update(self, items):
for item in items:
self.items_list.append(item)
class MappingSubclass(Mapping):
def update(self, keys, values):
for item in zip(keys, values):
self.items_list.append(item)
Encapsulation
>>> map = Mapping([1,2,3])
>>> map.items_list
[1, 2, 3]
>>> map = MappingSubclass([1,2,3])
# What will happen?
Encapsulation
>>> map = Mapping([1,2,3])
>>> map.items_list
[1, 2, 3]
>>> map = MappingSubclass([1,2,3])
TypeError: update() takes exactly 3 arguments (2
given)
The base __init__ tried to call the update() method of thesubclass, rather than the base.
Encapsulation
class Mapping:
def __init__(self, items):
self.items_list = []
self.__update(items)
def update(self, items):
for item in items:
self.items_list.append(item)
__update = update # private copy
class MappingSubclass(Mapping):
def update(self, keys, values):
...
Now we call the base’s version of update.
Magic Methods
What are they?!
I Special kinds of class methods (like __init__) that areprefixed and suffixed with two underscores.
I Provide a easy way to make our own classes behave likebuilt-in types.
I Essentially, we will redefine the behavior of Python’s built-inoperators, like “==” and “len” and “in.”
I Great tutorial:http://www.rafekettler.com/magicmethods.html
Magic Methods
If you want ... Then define ...
print x __repr__
x == y __eq__
x > y __cmp__
x + y __add__
len(x) __len__
item in x __contains__
for item in x __iter__
x[key] __getitem__
Magic Methods
I Non-traditional kind of polymorphismI e.g. we can use “+” on any type that implements __add__
I Generally, we shouldn’t check type, but just try to use themethod we want
I If the method is implemented, it gets executed, regardless ofthe object’s type
I In some sense, the actual type doesn’t actually matter
I Duck Typing: an object’s methods, rather than its type,determine its valid semantics
Printing
>>> p = Point(1,2)
>>> print p
<__main__.Pair instance at 0x105c95d88>
Need to define __repr__:
def __repr__(self):
return "(%d, %d)" % (self.x, self.y)
>>> print p
(3, 4)
Hashing
>>> p = Point(1,2)
>>> d = {}
>>> d[p] = "Hi"
TypeError: unhashable instance
Need to define __hash__:
def __hash__(self):
return hash((self.x,self.y))
>>> p = Point(1,2)
>>> d[p] = "Hi"
>>> d.keys()
[(1, 2)]
Equality
>>> Point(1,2) == Point(1,2)
False
Need to define __eq__:
def __eq__(self, other):
return self.x == other.x and \
self.y == other.y
>>> Point(1,2) == Point(1,2)
True
Addition
>>> Point(1,2) + Point(1,2)
TypeError
Need to define __add__:
def __add__(self, other):
return Point(self.x + other.x,
self.y + other.y)
>>> Point(1,2) + Point(3,4)
(4, 6)
Protocols
I All the stuff we’ve seen so far is about getting your class tobehave like a number.
I What about a list? A string? A file?!
I Python supports protocols, which are similar to interfaces:they define a set of (magic) methods you need to support toimplement that protocol.
I In Python, protocols are informal and do not require explicitdeclarations.
Protocols
I ComparsionI Most like what we’ve seen so farI Need __eq__, __cmp__
I ContainersI Things like lists and dictionariesI Need __len__, __getitem__, __setitem__, __contains__
I Iterators – will see next week!
I Context Managers (like files) – will see later
I Descriptors – kind of confusing and may never see
Containers
class Buckets(object):
def __init__(self, red, blue):
self.red = red
self.blue = blue
def __len__(self):
return len(self.red) + len(self.blue)
def __getitem__(self, key):
if key in self.red:
return 'red'elif key in self.blue:
return 'blue'
...
Containers
...
def __setitem__(self, key, value):
if key in self.red and value == 'blue':self.red.remove(key)
self.blue.append(key)
elif key in self.blue and value == 'red':self.blue.remove(key)
self.red.append(key)
def __contains__(self, item):
return item in self.red or item in self.blue
Containers
>>> b = Buckets([1,2], [3,4])
>>> len(b)
4
>>> b[1]
'red'>>> b[1] = 'blue'>>> b[1]
'blue'>>> 4 in b
True
Decorators
A few basic OOP things we haven’t covered yet:
I Getters / Setters
I Static methods
To do so, we need to take a detour and talk about decorators.
Decorators
Decorators are functions that:
I Take a function f as input
I Define a new (nested) function new_f that calls f, but alsodoes other stuff before and/or after
I Return new_f
Debug Decorator
def debug(f):
def new_f(*args):
print "About to call %s." % (f.__name__)
result = f(*args)
print "Finished calling %s." % (f.__name__)
return result
return new_f
Debug Decorator
>>> debug_sum = debug(sum)
>>> debug_sum([1,2,3])
About to call sum.
Finished calling sum.
6
Debug Decorator
I What if we are defining a new function g, and we always wantit to have this debug behavior?
I After defining g, we could overwrite it with g = debug(g).
I Or we have the following syntactic sugar:
@debug
def g():
print "Running."
>>> g()
About to call g.
Running.
Finished calling g.
Sorting Decorator
def sort(f):
def new_f(*args):
return sorted(f(*args))
return new_f
@sort
def random_list(n):
return [random.randint(0,100) \
for _ in range(n)]
>>> random_list(5)
[14, 30, 43, 44, 90]
Counting Decorator
def count(f):
c = [0] # can do things before defining new_f
def new_f(*args):
c[0] += 1
return (c[0], f(*args))
return new_f
@count
def id(x):
return x
>>> id(5)
(1, 5)
>>> id(5)
(2, 5)
Function List Decorator
functions = []
def add_func(f):
results.append(f)
return f # don't have to change f
@add_func
def id(x):
return x
>>> functions
[<function id at 0x10f5a2b18>]
Note that the function id will only get added to the list once nomatter how many times we call it.
Parameterized Decorators
def make_decorator(s):
def decorator(f):
def new_f(*args):
print s
return f(*args)
return new_f
return decorator
@make_decorator('foo')def id1(x):
return x
@make_decorator('bar')def id2(x):
return x
Properties
Getting and setting attributes is pretty easy:
class Foo(object):
def __init__(self, x):
self.x = x
>>> f.x
5
>>> f.x = 10
>>> f.x
10
Properties
What if we want more control?
class Foo(object):
def __init__(self, x):
self._x = x
@property
def x(self):
print "x is being accessed."
return self._x
@x.setter
def x(self, new_x):
print "x is being set."
self._x = x
Properties
>>> f = Foo(5)
>>> f.x
x is being accessed.
5
>>> f.x = 10
x is being set.
>>> f.x
x is being accessed.
10
Properties
Which names have to match?
class Foo(object):
def __init__(self, i):
self._eep = i
@property
def data(self):
print "eep is being accessed."
return self._eep
@data.setter
def data(self, new_eep):
self._eep = new_eep
Properties
>>> f = Foo(5)
>>> f.data # note!
eep is being accessed.
5
What happens here?
>>> f._eep
Properties
What happens here?
>>> f._eep
5
Properties
Some use-cases:
I Lazy-load data upon accessing
@property
def x(self):
if not x:
# execute code to get x
return x
I Validation upon setting
@x.setter
def x(self, new_x):
if valid(new_x):
self._x = new_x
else:
raise Exception
Class and Static Methods
I Class and static methods are associated with a class object,rather than an instance object.
class ClassName:
@classmethod / @staticmethod
def foo(...):
pass
>>> ClassName.foo() # didn't create an instance
I The difference is that a class method must have a reference toa class object as the first parameter, whereas a static methodcan have no parameters at all.
Class and Static Methods
class Date(object):
def __init__(self, day=0, month=0, year=0):
self.day = day
self.month = month
self.year = year
Class and Static Methods
Goal: convert a string ’mm-dd-yyyy’ to a Date instance.
month, day, year = map(int, s.split('-'))date1 = Date(day, month, year)
To automate this process:
@classmethod
def from_string(cls, s):
# cls refers the Date class itself
month, day, year = map(int, s.split('-'))return cls(day, month, year)
>>> d = Date.from_string('02-10-2014')
d is now an instance of Date.
Class and Static Methods
Goal: validate a date string
@staticmethod
def is_valid(s):
month, day, year = map(int, s.split('-'))return day <= 31 and month <= 12 and \
year <= 3999
>>> Date.is_valid('02-10-2014')True
Didn’t need a reference to the Date class here.
Live Quiz!
Use getattr to write code equivalent to ’4’.isdigit().
Live Quiz!
Use getattr to write code equivalent to ’4’.isdigit().
>>> f = getattr('4', 'isdigit')>>> f()
True
Live Quiz!
If we have some class Truck, how can we call the init method ofits superclass?
Live Quiz!
If we have some class Truck, how can we call the init method ofits superclass?
super(Truck, self).__init__()
Live Quiz!
If we have a class Penguin with a private method __happy_feet,how can we access this method on an instance called Joe?
Live Quiz!
If we have a class Penguin with a private method __happy_feet,how can we access this method on an instance called Joe?
>>> Joe._Penguin__happy_feet()
Live Quiz!
What is the following “syntactic sugar” for?
@decorator
def function(x):
...
Live Quiz!
What is the following “syntactic sugar” for?
@decorator
def function(x):
...
>>> function = decorator(function)
Live Quiz!
Using a propery, ensure than an attribute called age is onlyupdated if the new value is greater than the old value.
Live Quiz!
Using a propery, ensure than an attribute called age is onlyupdated if the new value is greater than the old value.
@age.setter
def age(self, new_age):
if new_age > self._age:
self._age = new_age
Recommended