python programming: lecture 4 object oriented programmingcis192/fall2014/files/lec4.pdf · multiple...

Post on 06-Jun-2020

7 Views

Category:

Documents

0 Downloads

Preview:

Click to see full reader

TRANSCRIPT

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

top related