Berkeley CS 61A Lecture10-15

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} dxnxdyny=dxdynxny

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=dxdynxdy+nydx

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 computationwhole data valuesadd_rational, mul_rational, rationals_are_equal, print_rational
Create rationals or implement rational operationsnumerators and denominatorsrational, numer, denom
Implements selectors and constructor for rationalstwo_element listslist 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

StatusEffect
No nonlocal statement & “x” is not bound locallyCreate a new binding from name “x” to object 2 in the first of the current environment
No nonlocal statement & “x” is bound locallyRe-bind name “x” to object 2 in the first frame of the current environment
nonlocal x & “x” is bound in a non-local frameRe-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 frameSyntax Error: no binding for non-local ‘x’ found
nonlocal x & “x” is bound in a non-local frame & “x” also bound locallySyntax 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:

  1. A new instance of that class is created:
  2. The __init__ method of the class is called with the new object as its first argument (named self ), 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
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值