http://inst.eecs.berkeley.edu/~cs61a/sp18/
文章目录
CS 61A LECTURE 10 - CODE
Data Abstraction
n x d x ∗ n y d y = n x ∗ n y d x ∗ d y \frac{nx}{dx} * \frac{ny}{dy} = \frac{nx*ny}{dx*dy} dxnx∗dyny=dx∗dynx∗ny
n x d x + n y d y = n x ∗ d y + n y ∗ d x d x ∗ d y \frac{nx}{dx} + \frac{ny}{dy} = \frac{nx*dy+ny*dx}{dx*dy} dxnx+dyny=dx∗dynx∗dy+ny∗dx
def mul_rational(x, y):
return rational(numer(x) * numer(y),
denom(x) * denom(y))
def add_rational(x, y):
nx, dx = numer(x), denom(x)
ny, dy = numer(y), denom(y)
return rational(nx * dy + ny * dx, dx * dy)
def equal_rational(x, y):
return numer(x) * denom(y) == numer(y) * denom(x)
Pairs
pair = [1, 2] # pair[0] = 1, pair[1] = 2
x, y = pair # x = 1, y = 2
from operator import getitem
getitem(pair, 0) # Output: 1
getitem(pair, 1) # Output: 2
def rational(n, d):
"""Construct a rational number that represents N/D"""
return [n, d]
def numer(x):
"""Return the numerator of rational number X."""
return x[0]
def denom(x):
"""Return the denominator of rational number X."""
return x[1]
Reducing to Lowest Terms
3 2 ∗ 5 3 = 5 2 2 5 + 1 10 = 1 2 \frac{3}{2} \ * \ \frac{5}{3} \ = \ \frac{5}{2} \qquad \frac{2}{5} \ + \ \frac{1}{10} \ = \ \frac{1}{2} 23 ∗ 35 = 2552 + 101 = 21
from fractions import gcd
# redefine rational function
def rational(n, d):
"""Construct a rational number x that represents n/d"""
g = gcd(n, d)
return [n // g, d // g]
Just change the rational function instead of the whole function
Abstraction Barriers
Parts of the program that … | Treat rationals as … | Using … |
---|---|---|
Use rational numbers to perform computation | whole data values | add_rational, mul_rational, rationals_are_equal, print_rational |
Create rationals or implement rational operations | numerators and denominators | rational, numer, denom |
Implements selectors and constructor for rationals | two_element lists | list literals and element selection |
Violating Abstraction Barriers
add_rational([1, 2], [1, 4]) # terrible wrong
def divide_rational(x, y):
return [x[0] * y[1], x[1] * y[0]] # also pretty bad
we use data abstraction so that we can change our data representation without having to rewrite the entire program
Data Representations
# Rational arithmetic
def add_rational(x, y):
"""Add rational numbers x and y."""
nx, dx = numer(x), denom(x)
ny, dy = numer(y), denom(y)
return rational(nx * dy + ny * dx, dx * dy)
def mul_rational(x, y):
"""Multiply rational numbers x and y."""
return rational(numer(x) * numer(y), denom(x) * denom(y))
def rationals_are_equal(x, y):
"""Return whether rational numbers x and y are equal"""
return numer(x) * denom(y) == numer(y) * denom(x)
def print_rational(x):
"""Print rational x."""
print(numer(x), "/", denom(x))
# Constructor and selectors
def rational(n, d):
"""Construct a rational number x that represents n/d"""
return [n, d]
def numer(x):
"""Return the numerator of rational number x."""
return x[0]
def denom(x):
"""Return the denominator of rational number x."""
return x[1]
>>> x, y = rational(1, 2), rational(3, 8)
>>> print_rational(mul_rational(x, y))
3 16
change the code~instead of use a list, we gonna use a function
select
# Rational arithmetic
def add_rational(x, y):
"""Add rational numbers x and y."""
nx, dx = numer(x), denom(x)
ny, dy = numer(y), denom(y)
return rational(nx * dy + ny * dx, dx * dy)
def mul_rational(x, y):
"""Multiply rational numbers x and y."""
return rational(numer(x) * numer(y), denom(x) * denom(y))
def rationals_are_equal(x, y):
"""Return whether rational numbers x and y are equal"""
return numer(x) * denom(y) == numer(y) * denom(x)
def print_rational(x):
"""Print rational x."""
print(numer(x), "/", denom(x))
# Constructor and selectors
def rational(n, d):
"""Construct a rational number x that represents n/d"""
def select(name): # change!
if name == 'n':
return n
elif name == 'd':
return d
return select
def numer(x):
"""Return the numerator of rational number x."""
return x('n') # change!
def denom(x):
"""Return the denominator of rational number x."""
return x('d') # change!
>>> x, y = rational(1, 2), rational(3, 8)
>>> print_rational(mul_rational(x, y))
3 / 16
>>> x
<function rational.<locals>.select at 0x0000025DDD4F6950>
here
x
is a function
Rational Data Abstraction Implemented as Functions
def rational(n, d):
"""Construct a rational number x that represents n/d"""
def select(name): # high-order function
if name == 'n':
return n
elif name == 'd':
return d
return select
def numer(x):
"""Return the numerator of rational number x."""
return x('n') # x got a parameter 'n', then select(n)
def denom(x):
"""Return the denominator of rational number x."""
return x('d')
CS 61A LECTURE 11 - CODE
List
odds = [41, 43, 47, 49]
odds[0] # 41
odds[1] # 43
len(odds) # 4
odds[3] - odds[2] # 2
odds[odds[3] - odds[2]] # 47
getitem(odds, 3) # 49
digits = [1, 8, 2, 8]
[2, 7] + digits * 2 # [2, 7, 1, 8, 2, 8, 1, 8, 2, 8]
add([2, 7], mul(digits, 2)) # [2, 7, 1, 8, 2, 8, 1, 8, 2, 8]
# Nested lists
pairs = [[10, 20], [30, 40]]
pairs[1] # [30, 40]
pairs[1][0] # 30
Contains
digits = [1, 8, 2, 8]
1 in digits # Print: True
5 in digits # Print: False
5 not in digits # Print: True
not(5 in digits) # Print: True
# string
'1' == 1 # False
'1' in digits # False
[1, 8] in digits # False
[1, 2] in [3, [1, 2], 4] # True
[1, 2] in [3, [[1, 2]], 4] # False
For Statements
def count(s, value):
"""Count the number of times that value occurs
in sequence s.
>>> count([1, 2, 1, 2, 1,], 1)
3
"""
total, index = 0, 0
while index < len(s):
element = s[index]
if element == value:
total = total + 1
index = index + 1
return total
make it shorter
def count(s, value):
"""Count the number of times that value occurs
in sequence s.
>>> count([1, 2, 1, 2, 1,], 1)
3
"""
total = 0
for element in s:
if element == value:
total += 1
return total
Sequence Unpacking in For Statements
pairs = [[1, 2], [2, 2], [3, 2], [4, 4]]
same_count = 0 # [2, 2] and [4, 4]
# unpack pairs
for x, y in pairs:
if x == y:
same_count = same_count + 1
same_count # Print: 2
Ranges
range(5, 8) # range(5, 8)
range(4) # range(0, 4)
list(range(-2, 2)) # [-2, -1, 0, 1]
list(range(4)) # [0, 1, 2, 3]
def sum_below(n):
total = 0
for i in range(n):
total += i
return total
sum_below(5) # Print: 10
def cheer():
for _ in range(3): # we don't care what '_' is
print('Go Bears!')
cheer() # Print: Go Bears!
# Go Bears!
# Go Bears!
List Comprehensions
letters = ['a', 'b', 'c', 'd', 'e', 'f', 'm', 'n', 'o', 'p']
[letters[i] for i in [3, 4, 6, 8]] # ['d', 'e', 'm', 'o']
odds = [1, 3, 5, 7, 9]
[x + 1 for x in odds] # [2, 4, 6, 8, 10]
[x for x in odds if 25 % x == 0] # [1, 5]
[x + 1 for x in odds if 25 % x == 0] # [2, 6]
def divisors(n):
return [1] + [x for x in range(2, n) if n%x == 0]
divisors(1) # [1]
divisors(4) # [1, 2]
divisors(9) # [1, 3]
divisors(18) # [1, 2, 3, 6, 9]
Strings
Representing data: '200'
, '1.2e-5'
, 'False'
, '(1, 2)'
Representing language: """And, hello world"""
Representing programs: 'curry = lambda f: lambda x: lambda y: f(x, y)'
>>> 'curry = lambda f: lambda x: lambda y: f(x, y)'
'curry = lambda f: lambda x: lambda y: f(x, y)'
>>> exec('curry = lambda f: lambda x: lambda y: f(x, y)')
>>> curry
<function <lambda> at 0x00000129510E6598>
>>> from operator import add
>>> curry(add)(3)(4)
7
Strings are Sequences
city = 'Berkeley'
len(city) # 8
city[3] # 'k'
# look for whole strings
'here' in "Where's Waldo?" # True
234 in [1, 2, 3, 4, 5] # False
[2, 3, 4] in [1, 2, ,3, 4, 5] # False
Dictionaries
{}
>>> numerals = {'I': 1, 'V': 5, 'X': 10}
>>> numerals
{'I': 1, 'V': 5, 'X': 10}
>>> numerals['X']
10
>>> numerals['10']
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: '10'
>>> numerals.keys()
dict_keys(['I', 'V', 'X'])
>>> numerals.values()
dict_values([1, 5, 10])
>>> numerals.items()
dict_items([('I', 1), ('V', 5), ('X', 10)])
>>> items = [('I', 1), ('V', 5), ('X', 10)]
>>> items
[('I', 1), ('V', 5), ('X', 10)]
>>> dict(items)
{'I': 1, 'V': 5, 'X': 10}
>>> dict(items)['X']
10
>>> 'X' in numerals
True
>>> 'X-ray' in numerals
False
>>> numerals.get('X', 0)
10
>>> numerals.get('X-ray', 0)
0
Dictionary Comprehensions
>>> {x : x*x for x in range(10)}
{0: 0, 1: 1, 2: 4, 3: 9, 4: 16, 5: 25, 6: 36, 7: 49, 8: 64, 9: 81}
>>> squares = {x : x*x for x in range(10)}
>>> squares[7]
49
# can't have the same key twice
>>> {1: 2, 1: 3}
{1: 3}
>>> {1: [2, 3]}
{1: [2, 3]}
>>> {[1]: 2}
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'list'
CS 61A LECTURE 12 - CODE
The Closure Property of Data Types
List can contain lists as elements ( in addition to anything else)
Box-and-Pointer Notation in Environment Diagrams
pair = [1, 2]
nested_list = [[1, 2], [],
[[3, False, None],
[4, lambda: 5]]]
Slicing
>>> odds = [3, 5, 7, 9, 11]
>>> list(range(1, 3))
[1, 2]
>>> [odds[i] for i in range(1, 3)]
[5, 7]
>>> odds[1:3]
[5, 7]
>>> odds[:3]
[3, 5, 7]
>>> odds[1:]
[5, 7, 9, 11]
>>> odds[:]
[3, 5, 7, 9, 11]
Slicing Create New Values
Processing Container Values
# Sequence Aggregation
# sum(iterable[, start]) -> value
>>> sum([2, 3, 4])
9
>>> sum(['2', '3', '4'])
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for +: 'int' and 'str'
>>> sum([2, 3, 4], 5)
14
>>> [2, 3] + [4]
[2, 3, 4]
>>> sum([[2, 3], [4]],[])
[2, 3, 4]
>>> 0 + [2, 3]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for +: 'int' and 'list'
>>> sum([[2, 3], [4]])
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for +: 'int' and 'list'
# max(iterable[, key = func]) -> value
# max(a, b, c, ...[, key - func]) -> value
>>> max(range(5))
4
>>> max(0, 1, 2, 3, 4)
4
>>> max(range(10), key = lambda x: 7-(x-4)*(x-2))
3
# all(iterable) -> bool
>>> bool(5)
True
>>> bool(0)
False
>>> bool(-1)
True
>>> bool('hello')
True
>>> bool('')
False
>>> range(5)
range(0, 5)
>>> [x < 5 for x in range(5)]
[True, True, True, True, True]
>>> all([x < 5 for x in range(5)])
True
>>> all(range(5)) # contain 0
False
Trees
Recursive description (wooden trees) :
- A tree has a root label and a list of branches
- Each branch is a tree
- A tree with zero branches is called a leaf
Relative description (family trees) :
- Each location in a tree is called a node
- Each node has a label that can be any value
- One node can be the parent / child of another
tree(3, [tree(1),
tree(2, [tree(1),
tree(1)])]) # [3, [1], [2, [1], [1]]]
# Trees
def tree(label, branches = []):
# verifies the tree definition
for branch in branches:
assert is_tree(branch), 'branches must be trees'
return [label] + list(branches)
def label(tree):
return tree[0]
def branches(tree):
return tree[1:]
def is_tree(tree):
# verifies that tree is bound to a list
if type(tree) != list or len(tree) < 1:
return False
for branch in branches(tree):
if not is_tree(branch):
return False
return True
def is_leaf(tree):
return not branches(tree)
>>> tree(1)
[1]
>>> is_leaf(tree(1))
True
>>> tree(1,[5])
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "ex.py", line 5, in tree
assert is_tree(branch), 'branches must be trees'
AssertionError
>>> t = tree(1, [tree(5, [tree(7)]), tree(6)])
>>> t
[1, [5, [7]], [6]]
>>> label(t)
1
>>> branches(t)
[[5, [7]], [6]]
>>> is_tree(branches(t)[0])
True
>>> label(branches(t)[0])
5
Tree Processing
def fib_tree(n):
if n <= 1:
return tree(n)
else:
left, right = fib_tree(n-2), fib_tree(n-1)
return tree(label(left)+label(right), [left, right])
>>> fib_tree(0)
[0]
>>> fib_tree(1)
[1]
>>> fib_tree(2)
[1, [0], [1]]
>>> fib_tree(4)
[3, [1, [0], [1]], [2, [1], [1, [0], [1]]]]
>>> label(fib_tree(4))
3
Tree Processing Uses Recursion
def count_leaves(t):
"""Count the leaves of a tree."""
if is_leaf(t):
return 1
else:
branch_counts = [count_leaves(b) for b in branches(t)]
return sum(branch_counts)
>>> fib_tree(4)
[3, [1, [0], [1]], [2, [1], [1, [0], [1]]]]
>>> count_leaves(fib_tree(4))
5
>>> fib_tree(10)
[55, [21, [8, [3, [1, [0], [1]], [2, [1], [1, [0], [1]]]], [5, [2, [1], [1, [0], [1]]], [3, [1, [0], [1]], [2, [1], [1, [0], [1]]]]]], [13, [5, [2, [1], [1, [0], [1]]], [3, [1, [0], [1]], [2, [1], [1, [0], [1]]]]], [8, [3, [1, [0], [1]], [2, [1], [1, [0], [1]]]], [5, [2, [1], [1, [0], [1]]], [3, [1, [0], [1]], [2, [1], [1, [0], [1]]]]]]]], [34, [13, [5, [2, [1], [1, [0], [1]]], [3, [1, [0], [1]], [2, [1], [1, [0], [1]]]]], [8, [3, [1, [0], [1]], [2, [1], [1, [0], [1]]]], [5, [2, [1], [1, [0], [1]]], [3, [1, [0], [1]], [2, [1], [1, [0], [1]]]]]]], [21, [8, [3, [1, [0], [1]], [2, [1], [1, [0], [1]]]], [5, [2, [1], [1, [0], [1]]], [3, [1, [0], [1]], [2, [1], [1, [0], [1]]]]]], [13, [5, [2, [1], [1, [0], [1]]], [3, [1, [0], [1]], [2, [1], [1, [0], [1]]]]], [8, [3, [1, [0], [1]], [2, [1], [1, [0], [1]]]], [5, [2, [1], [1, [0], [1]]], [3, [1, [0], [1]], [2, [1], [1, [0], [1]]]]]]]]]]
>>> count_leaves(fib_tree(10))
89 # 89 = fib(11)
Implement leaves, which returns a list of the leaf labels of a tree
def leaves(tree):
"""Return a list containing the leaf labels of tree
>>> leaves(fib_tree(5))
[1, 0, 1, 0, 1, 1, 0, 1]
"""
Hint: If you sum
a list of lists, you get a list containing the elements of those lists
sum([[1], [2, 3], [4]], []) # [1, 2, 3, 4]
sum([[1]], []) # [1]
sum([[[1]], [2]], []) # [[1], 2]
def leaves(tree):
"""Return a list containing the leaf labels of tree
>>> leaves(fib_tree(5))
[1, 0, 1, 0, 1, 1, 0, 1]
"""
if is_leaf(tree):
return [label(tree)]
else:
return sum([leaves(b) for b in branches(tree)] ,[])
Creating Trees (recursive)
def increment_leaves(t):
"""Return a tree like t but with leaf labels incremented."""
if is_leaf(t):
return tree(label(t) + 1)
else:
bs = [increment_leaves(b) for b in branches(t)]
return tree(label(t), bs)
def increment(t):
"""Return a tree like t but with all labels incremented."""
return tree(label(t) + 1, [increment(b) for b in branches(t)])
Example: Printing Trees
def print_tree(t):
print(label(t))
for b in branches(t):
print_tree(b)
>>> print_tree(fib_tree(4))
3
1
0
1
2
1
1
0
1
# oops, no structure
>>> ' ' * 5 + str(5)
' 5'
>>> print(' ' * 5 + str(5))
5
def print_tree(t, indent = 0):
print(' ' * indent + str(label(t)))
for b in branches(t):
print_tree(b, indent+1)
>>> print_tree(fib_tree(4))
3
1
0
1
2
1
1
0
1
>>> print_tree(fib_tree(5))
5
2
1
1
0
1
3
1
0
1
2
1
1
0
1
# now we can see the structure
CS 61A LECTURE 13 - CODE
Objects
>>> from datetime import date
>>> date
<class 'datetime.date'>
>>> today = date(2015, 2, 20)
>>> today
datetime.date(2015, 2, 20)
>>> freedom = date(2015, 5, 12)
>>> str(freedom - today)
'81 days, 0:00:00'
>>> today.year
2015
>>> today.month
2
>>> today.strftime('%A %B %d')
'Friday February 20'
- Objects represent information
- They consist of data and behavior, bundled together to create abstractions
- Objects can represent things, but also properties, interactions, & process
- A type of object is called a class; classes are first_class values in Python
- Object-oriented programming:
- A metaphor for organizing large programs
- Special syntax that can improve the composition of programs
- In Python, every value is an object
- All objects have attributes
- A lot of data manipulation happens through object methods
- Functions do one thing; objects do many related things
Strings
>>> s = 'Hello'
>>> s.upper()
'HELLO'
>>> s.lower()
'hello'
>>> s.swapcase()
'hELLO'
>>> s
'Hello'
ASCII Standard
English Specific
>>> a = 'A'
>>> ord(a)
65
>>> hex(ord(a))
'0x41'
>>> print('\n\n\n')
>>> print('\a\a\a') # ring
>>>
Unicode Standard
For all different languages
>>> from unicodedata import name, lookup
>>> name('A')
'LATIN CAPITAL LETTER A'
>>> name('a')
'LATIN SMALL LETTER A'
>>> lookup('WHITE SMILING FACE')
'☺'
>>> lookup('SNOWMAN')
'☃'
>>> lookup('SOCCER BALL')
'⚽'
>>> lookup('BABY')
'👶'
>>> lookup('BABY').encode()
b'\xf0\x9f\x91\xb6'
>>> 'A'.encode()
b'A'
Mutation Operations
Some Objects Can Change
>>> suits = ['coins', 'string', 'myriad']
>>> original_suits = suits
>>> suits.pop()
'myriad'
>>> suits.remove('string')
>>> suits
['coins']
>>> suits.append('cup')
>>> suits.extend(['sword', 'club'])
>>> suits
['coins', 'cup', 'sword', 'club']
>>> suits[2] = 'spade'
>>> suits[0:2] = ['heart', 'diamond']
>>> suits
['heart', 'diamond', 'spade', 'club']
>>> original_suits # change one will influence one another
['heart', 'diamond', 'spade', 'club']
Only objects of mutable types can change: lists & dicitionaries
>>> numerals = {'I':1, 'V':5, 'X':10}
>>> numerals
{'I': 1, 'V': 5, 'X': 10}
>>> numerals['X']
10
>>> numerals['X'] = 11
>>> numerals['X']
11
>>> numerals
{'I': 1, 'V': 5, 'X': 11}
>>> numerals['L'] = 50
>>> numerals
{'I': 1, 'V': 5, 'X': 11, 'L': 50}
>>> numerals.pop('X')
11
>>> numerals.get('X')
>>> numerals
{'I': 1, 'V': 5, 'L': 50}
Mutation Can Happen Within a Function Call
four = [1, 2, 3, 4]
len(four) # 4
def mystery(s):
s.pop() # remove the last element
s.pop()
# or
def mystery(s):
s[2:] = []
mystery(four)
len(four) # 2
four = [1, 2, 3, 4]
len(four) # 4
def another_mystery(s):
four.pop()
four.pop()
another_mystery() # no argument
len(four) # 2
Tuples
can not be changed
>>> (3, 4, 5, 6)
(3, 4, 5, 6)
>>> 3, 4, 5, 6
(3, 4, 5, 6)
>>> ()
()
>>> tuple()
()
>>> tuple([3, 4, 5])
(3, 4, 5)
>>> 2,
(2,)
>>> (2, )
(2,)
>>> 2
2
>>> (3, 4) + (5, 6)
(3, 4, 5, 6)
>>> 5 in (3, 4, 5)
True
>>> {(1, 2): 3} # allowed to use as the key in dict
{(1, 2): 3}
>>> {[1, 2]: 3} # while lists are not
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'list'
>>> {(1, [2]):3}
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'list'
Tuples are Immutable Sequences
# Name change:
>>> x = 2
>>> x + x
4
>>> x = 3
>>> x + x
6
# Object mutation:
>>> x = [1, 2]
>>> x + x
[1, 2, 1, 2]
>>> x.append(3)
>>> x + x
[1, 2, 3, 1, 2, 3]
# An immutable sequence may still change if
# it contains a mutable value as an element
>>> s = ([1, 2], 3)
>>> s[0] = 4
ERROR
>>> s = ([1, 2], 3)
>>> s[0][0] = 4
>>> s
([4, 2], 3)
Mutation
# a list is still "the same" list if we change its contents
>>> a = [10]
>>> b = a
>>> a == b
True
>>> a.append(20)
>>> a == b
True
>>> a
[10, 20]
>>> b
[10, 20]
# Conversely, we could have two lists that happen to have the
# same contents, but are different
>>> a = [10]
>>> b = [10]
>>> a == b
True
>>> b.append(20)
>>> a
[10]
>>> b
[10, 20]
>>> a == b
False
Identity Operators
**Identity: ** <exp0> is <exp1>
evaluates to True
if both <exp0>
and <exp1>
evaluate to the same object
**Equality: ** <exp0> == <exp1>
evaluates to True
if both <exp0>
and <exp1>
evaluate to equal values
Identical objects are always equal values
>>> [10] == [10]
True
>>> a = [10]
>>> b = [10]
>>> a == b
True
>>> a is b
False
>>> c = b
>>> c is b
True
>>> c.pop()
10
>>> c
[]
>>> b
[]
Mutable Default Arguments are Dangerous
def f(s = []):
s.append(5)
return len(s)
# everytime we called f(), it will be bound to the same value
>>> f()
1
>>> f()
2
>>> f()
3
CS 61A LECTURE 14 - CODE
Mutable Functions
# Let's model a bank account that has a balance of $100
>>> withdraw(25)
75
>>> withdraw(25) # leads to a different return value
50
>>> withdraw(60)
'Insufficient funds'
# Where's this balance stored?
>>> withdraw = make_withdraw(100) # high-order
# Reminder : Local Assignment
def percent_difference(x, y):
difference = abs(x - y) # bind the names on the left to the resulting values in the current frame
return 100 * difference / x
diff = percent_difference(40, 50)
# Non-Local Assignment & Persistent Local State
def make_withdraw(balance):
"""Return a withdraw function with a starting balance."""
def withdraw(amount):
nonlocal balance # nonlocal
if amount > balance:
return 'Insufficient funds'
balance = balance - amount
return balance
return withdraw
>>> w = make_withdraw(100)
>>> w(10)
90
>>> w(50)
40
>>> w(50)
'Insufficient funds'
Non-Local Assignment
nonlocal <name>
Effect: Future assignments to that name change its pre-existing binding in the first non-local frame of the current environment in which that name is bound.
first non-local frame = an “enclosing scope”
From the Python3 language reference:
Names listed in a nonlocal statement must refer to pre-existing bindings in an enclosing scope.
Names listed in a nonlocal statement must not collide with pre-existing bindings in the local scope (Current frame).
x = 2
Status | Effect |
---|---|
No nonlocal statement & “x” is not bound locally | Create a new binding from name “x” to object 2 in the first of the current environment |
No nonlocal statement & “x” is bound locally | Re-bind name “x” to object 2 in the first frame of the current environment |
nonlocal x & “x” is bound in a non-local frame | Re-bind “x” to 2 in the first non-local frame of the current environment in which it is bound |
nonlocal x & “x” is not bound in a non-local frame | Syntax Error: no binding for non-local ‘x’ found |
nonlocal x & “x” is bound in a non-local frame & “x” also bound locally | Syntax Error: name ‘x’ is parameter and nonlocal |
def make_withdraw(balance):
"""Return a withdraw function with a starting balance."""
def withdraw(amount):
# nonlocal balance
if amount > balance:
return 'Insufficient funds'
balance = balance - amount
return balance
return withdraw
>>> w = make_withdraw(100)
>>> w(10)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "ex.py", line 5, in withdraw
if amount > balance:
UnboundLocalError: local variable 'balance' referenced before assignment
def make_withdraw(balance):
"""Return a withdraw function with a starting balance."""
def withdraw(amount):
# nonlocal balance
if amount > balance:
return 'Insufficient funds'
# balance = balance - amount
return balance
return withdraw
>>> w = make_withdraw(100)
>>> w(10) # doesn't do anything
100
>>> w(10)
100
Mutable values can be changed without a nonlocal statement
def make_withdraw_list(balance):
b = [balance]
def withdraw(amount):
if amount > b[0]:
return 'Insufficient funds'
b[0] = b[0] - amount
return b[0]
return withdraw
withdraw = make_withdraw_list(100)
Multiple Mutable Functions
>>> john = make_withdraw(100)
>>> steven = make_withdraw(100000)
>>> john
<function make_withdraw.<locals>.withdraw at 0x000001E146B86620>
>>> steven
<function make_withdraw.<locals>.withdraw at 0x000001E146B866A8>
>>> john is not steven
True
>>> john == steven
False
>>> john(10)
90
>>> john(10)
80
>>> steven(1000)
99000
>>> john(10)
70
>>> john(1000)
'Insufficient funds'
>>> steven(98000)
1000
>>> steven(930)
70
>>> john != steven
True
>>> john is not steven
True
>>> john(0) == steven(0)
True
>>> john(0)
70
>>> steven(0)
70
# Referential Transparency
mul(add(2, mul(4, 6)), add(3, 5))
mul(add(2, 24 ), add(3, 5))
mul( 26 , add(3, 5))
Mutation operations violate the condition of referential transparency because they do more than just return a value ; they change the environment.
# Referential Transparency Lost
def f(x):
x = 4
def g(y):
def h(z):
nonlocal x
x = x + 1
return x + y + z
return h
return g
a = f(1)
b = a(2)
total = b(3) + b(4) # 10 + 12 = 22
def f(x):
x = 4
def g(y):
def h(z):
nonlocal x
x = x + 1
return x + y + z
return h
return g
a = f(1)
b = a(2)
total = 10 + b(4) # 10 + 11 = 21
# Go Bears!
def oski(bear):
def cal(berk):
nonlocal bear
if bear(berk) == 0:
return [berk_1, berk-1]
bear = lambda ley: berk - ley
return [berk, cal(berk)]
return cal(2)
oski(abs) # Print: [2, [3, 1]]
CS 61A LECTURE 15 - CODE
Object-Oriented Programming
A method for organizing modular programs
- Abstraction barriers
- Bundling together information and related behavior
A metaphor for computation using distributed state
- Each object has its own local state
- Each object also knows how to manage its own local state, based on method calls.
- Method calls are messages passed between objects.
- Several objects may all be instances of a common type.
- Different types may relate to each other.
Specialized syntax & vocabulary to support this metaphor
Classes
# Idea: All bank accounts have a balance and an account holder;
# the Account class should add those attributes to each newly created instance.
>>> a = Account('Jim')
>>> a.holder
'Jim'
>>> a.balance
0
# Idea: All bank accounts should have "withdraw" and "deposit"
# behaviors that all work in the same way.
>>> a.deposit(15)
15
>>> a.withdraw(10)
5
>>> a.balance
5
>>> a.withdraw(10)
'Insufficient funds'
# Better idea: All bank accounts share a "withdraw" method
# and a "deposit" method.
Class Statements
# class <name>:
# <suite>
>>> class Clown:
nose = 'big and red'
def dance():
return 'No thanks'
>>> Clown.nose
'big and red'
>>> Clown.dance()
'No thanks'
>>> Clown
<class '__main__.Clown'>
When a class is called:
- A new instance of that class is created:
- The
__init__
method of the class is called with the new object as its first argument (namedself
), along with any additional arguments provided in the call expression.
class Account:
def __init__(self, account_holder):
self.balance = 0
self.holder = account_holder
a = Account('Jim') # account_holder: 'Jim'
# balance: 0
# holder: 'Jim'
# Every object that is an instance of a user-defined class
# has a unique identity:
>>> a = Account('Jim')
>>> b = Account('Jack')
>>> a.balance
0
>>> b.holder
'Jack'
# Identity operators "is" and "is not" test
# if two expressions evaluate to the same object:
>>> a is a
True
>>> a is not b
True
# Binding an object to a new name using assignment
# does not create a new object:
>>> c = a
>>> c is a
True
Methods
class Account:
def __init__(self, account_holder):
self.balance = 0
self.holder = account_holder
def deposit(self, amount):
self.balance = self.balance + amount
return self.balance
def withdraw(self, amount):
if amount > self.balance:
return 'Insufficient funds'
self.balance = self.balance - amount
return self.balance
All invoked methods have access to the object via the
self
parameter, and so they can all access and manipulate the object’s state.
# Invoking Methods
class Account:
...
def deposit(self, amount): # defined with two arguments
self.balance = self.balance + amount
return self.balance
>>> tom_account = Account('Tom')
>>> tom_account.deposit(100) # invoked with one argument
100
# python -i doctest -v ex.py
class Account:
"""An account has a balance and a holder
>>> a = Account('John')
>>> a.deposit(100)
100
>>> a.withdraw(90)
10
>>> a.withdraw(90)
'Insufficient funds'
>>> a.balance
10
"""
def __init__(self, account_holder):
self.balance = 0
self.holder = account_holder
def deposit(self, amount):
self.balance = self.balance + amount
return self.balance
def withdraw(self, amount):
if amount > self.balance:
return 'Insufficient funds'
self.balance = self.balance - amount
return self.balance
Attributes
>>> john = Account('John')
>>> john.balance
0
>>> getattr
<built-in function getattr>
>>> getattr(john, 'balance')
0
>>> john.deposit(100)
100
>>> getattr(john, 'balance')
100
>>> john.balance
100
>>> hasattr(john, 'balance')
True
>>> hasattr(john, 'lens')
False
Methods and Functions
Python distinguishes between:
- Functions, which we have been creating since the beginning of the course, and
- Bound methods, which couple together a function and the object on which that method will be invoked.
Object + Function = Bound Method
>>> type(Account.deposit)
<class 'function'>
>>> type(tom_account.deposit)
<class 'method'>
>>> Account.deposit(tom_account, 1001) # two argument
1011
>>> tom_account.deposit(1000) # one argument
2011
Class Attributes
Class attributes are “shared” across all instances of a class because they are attributes of the class, not the instance.
class Account:
interset = 0.02 # A class attribute
def __init__(self, account_holder):
self.balance = 0
self.holder = account_holder
# Additional methods would be defined here
>>> tom_account = Account('Tom')
>>> jim_account = Account('Jim')
>>> tom_account.interest
0.02
>>> jim_account.interest
0.02