Python for Joe Cross

advertisement
Useful Python Techniques:
A brief introduction to List Comprehensions,
Functional Programming, and Generators
October 4, 2011
Joe Cross
Contents
1.
2.
3.
4.
Looping and List Comprehensions
Filter, Map, Reduce (and lambda!)
Functional Programming Basics + Decorators
Generators
1. Looping and List Comprehensions
for(int i = 0; i < 5; i++){
cout << witty_banter(i) << endl;
}
Looping
compared to Java/C++
for i in range(0, 10, 1):
a /= 2.0
for i in xrange(10):
a /= 2.0
Python
Java/C++
for(int i = 0; i < 10; i++){
a /= 2.0;
}
Looping
Looping
Range + Iter
aList = [0, 1, 2, 'hello', 2**-4]
for i in range(len(aList)):
print aList[i]
for item in aList:
print item
Looping
Looping
still not as fun as…
To double the values in a list and assign to a new variable:
winning_lottery_numbers = [0, 4, 3, 2, 3, 1]
fake_lottery_numbers = []
for i in range(len(winning_lottery_numbers)):
fake_lottery_numbers.append(2 * winning_lottery_numbers[i])
fake_lottery_numbers = []
for number in winning_lottery_numbers:
fake_lottery_numbers.append(2 * number)
Even though it’s an improvement over the tedium of c++ et. al, we can still do better.
Looping
List Comprehensions
Woohooo!
Syntax:
[<expression> for <value> in <collection> if <condition>]
winning_lottery_numbers = [0, 4, 3, 2, 3, 1]
fake_lottery_numbers = [2*n for n in winning_lottery_numbers]
List Comprehensions allow us to do all sorts of things:
• Single-function single-line code
• Apply a function to each item of an iterable
• Filter using conditionals
• Cleanly nest loops
List Comprehensions
List Comprehensions
Don’t nest too many!
Multi-variable functions in a single line using zip:
vec1 = [3, 10, 2]
vec2 = [-20, 5, 1]
dot_mul = [u*v for u, v in zip(vec1, vec2)]
dot_prod = sum(dot_mul)
Filtering:
readings = [-1.2, 0.5, 12, 1.8, -9.0, 5.3]
good_readings = [r for r in readings if r > 0]
Bad:
orig = [15, 30, 78, 91, 25]
finals = [min(s, 100) for s in [f+5 for f in orig]]
List Comprehensions
2. Filter, Map, Reduce
Life = map(evolution, irreducible complexity)
assert(sum(Life) == 42)
Filter
Syntax:
result = filter(aFunction, aSequence)
def isPos(number, lim = 1E-16):
return number > lim
>>> a = [-1,2,-3,4,-5,6,-7,8,-9,10]
>>> filter(isPos, a)
[2, 4, 6, 8, 10]
>>> filter(not isPos, a)
Traceback (most recent call last):
File "<pyshell#7>", line 1
filter(not isZero, a)
TypeError: 'bool' object is not callable
Filter, Map, Reduce
Filter + Lambda
Syntax:
[fnName] = lambda [args]: expression
def isPos(number, lim = 1E-16):
return number > lim
>>> filter(lambda n: not isPos(n), a)
[-1, -3, -5, -7, -9]
Filter, Map, Reduce
Lambda vs. def
def add(x, y):
return x + y
Ladd = lambda x, y: x+y
def printWords():
print "Words"
LprintWords = lambda: print "Words"
Filter, Map, Reduce
So… why use lambda?
When using verbose function declaration it is often the case that the function’s
verbose declaration can be verbose, even for functions that don’t require such verbosity.
Verbose
def ispos(n):
return n > 0
b = filter(ispos, aList)
b = []
for a in aList:
if a > 0:
b.append(a)
Vs.
b = filter(lambda n: n > 0, aList)
Not Verbose
Also, there are some valid concerns about namespace clutter and the like.
Verbose verbose verbose.
Filter, Map, Reduce
Map
Syntax:
result = map(aFunction, aSequence)
Compare to list comprehension:
winning_lottery_numbers = [0, 4, 3, 2, 3, 1]
1. fake_lottery_numbers = [2*n for n in winning_lottery_numbers]
2. fake_lottery_numbers = map(lambda n: 2*n, winning_lottery_numbers)
Filter, Map, Reduce
Reduce
Syntax:
result = reduce(aFunction, aSequence, [initial])
NOTE: results get accumulated on the left, and new values applied to the right.
so reduce(add, [1,2,3,4]) is processed as (((1+2)+3)4)
lambda factorial n: reduce(operator.mul, xrange(1, n))
Filter, Map, Reduce
3. Functional Programming +
Decorators
This isn’t your dad’s
Procedural (imperative) programming
A Simple Example
def add(x, y):
return x + y
def sub(x, y):
return x - y
def op(fn, x, y):
return fn(x, y)
def mul(x, y):
return x * y
def div(x, y):
return x / y
def mod(x, y):
return x % y
Functional Programming
Nested Functions
Speed vs. Obfuscation
def randNoGap(min_, max_):
#random.random() -> [0,1)
v = random.random()
return (max_ - min_) * v - min_
def randWithGap(min_, max_):
s = random.random()
v = randNoGap(min_, max_)
if s < 0.5:
return v
else:
return -v
#Same conditional using Python’s
#Ternary operator
#return v if s < 0.5 else -v
def rand(min_, max_, hasGap = False):
if hasGap:
return randWithGap(min_, max_)
else:
return randNoGap(min_, max_)
Functional Programming
Nested Functions
Speed vs. Obfuscation
(Continued)
def randomExplosion(minv, maxv, n):
particles = []
for _ in xrange(n):
vx = rand(minv, maxv, True)
vy = rand(minv, maxv, True)
vz = rand(minv, maxv, True)
vx2 = rand(minv, maxv, True)
vy2 = rand(minv, maxv, True)
vz2 = rand(minv, maxv, True)
r = rand(0,255,False)
g = rand(0,255,False)
b = rand(0,255,False)
mainParticle = [vx,vy,vz,r,g,b]
secondParticle = [vx2,vy2,vz2,r,g,b]
particleGroup = (mainParticle, secondParticle)
particles.append(particleGroup)
return particles
NO
Functional Programming
Nested Functions
Speed vs. Obfuscation
(Continued)
What we’d like to do:
velocities = [rndV() for _ in xrange(6)]
What it actually looks like:
velocities = [rand(minv,maxv,True) for i in xrange(6)]
With a functional wrapper, we re-map:
rndV -> make_rand_fnc(minv, maxv, True)
rndV() -> make_rand_fnc(minv, maxv, True)()
Functional Programming
Nested Functions
Speed vs. Obfuscation
def
(Continued)
rand(min_, max_, hasGap = False):
rand(minv, maxv, True)
rand(minv, maxv, True)
rand(minv, maxv, True)
def mkRand(min_, max_, hasGap = False):
def wrapper():
return rand(min_, max_, hasGap)
return wrapper
def randomExplosion(minv, maxv, n):
rVel = mkRand(minv, maxv, True)
rCol = mkRnd(0,255,False)
for _ in xrange(n):
vx = rVel()
vy = rVel()
vz = rVel()
vx2 = rVel()
vy2 = rVel()
vz2 = rVel()
r = rCol()
g = rCol()
b = rCol()
Functional Programming
Nested Functions
Speed vs. Obfuscation
(Continued)
def randomExplosion(minv, maxv, n):
particles = []
rndV = mkRand(minv, maxv, True)
rndC = mkRnd(0,255,False)
for _ in xrange(n):
velocities = [rndV() for i in xrange(6)]
r,g,b = [rndC() for i in xrange(3)]
mainParticle = velocities[:3] + [r,g,b]
secondParticle = velocities[3:] + [r,g,b]
particleGroup = (mainParticle, secondParticle)
particles.append(particleGroup)
return particles
Functional Programming
Decorators
Quickly apply common tasks to methods
Common pre + post function call tasks, such as:
• Caching
• Timing
• Counting function calls
• Access rights
@decorator
def myFunc(arg1):
print “arg1: “, arg1
@f1(arg)
@f2
def func(): pass
myFunc = decorator(myFunc)
def func(): pass
func = f1(arg)(f2(func))
Decorators
Decorators
Quickly apply common tasks to methods
def decorator(f):
print "This line is run once during func = decorator(func)"
def wrapper(*args, **kwargs):
print "This line is executed just before the function is called"
#Call the function
ret = f(*args, **kwargs)
print "This line is executed just after the function is called"
#Return the function's return
return ret
return wrapper
@decorator
def foo(bar):
print bar
On running, we get this output:
>>> ================================ RESTART ================================
>>>
This line is run once during func = decorator(func)
>>> foo(1)
This line is executed just before the function is called
1
This line is executed just after the function is called
Decorators
Decorators
Quickly apply common tasks to methods
Decorators using classes
class decorator(object):
def __init__(self, f):
print "This line is run once during func = decorator(func)"
self.f = f
def __call__(self, *args, **kwargs):
print "This line is executed just before the function is called"
#Call the function
ret = self.f(*args)
print "This line is executed just after the function is called"
#Return the function's return
return ret
Decorators
Decorators
Quickly apply common tasks to methods
(Rough) Timing
import time
class TIMED(object):
def __init__(self, f):
self.f = f
def __call__(self, *args):
start = time.clock()
ret = self.f(*args)
stop = time.clock()
print "{0}: {1} ms.".format(self.f.func_name, 1000*(stop-start))
return ret
Decorators
Decorators
Quickly apply common tasks to methods
@TIMED
def euler(f, t0, y0, h):
""" Euler's Method """
yn = y0 + h*f(t0,y0)
return yn
@TIMED
def RK2(f, t0, y0, h):
""" Heun's Method """
y_hat = y0 + h*f(t0,y0)
yn = y0 + h/2.0*(f(t0,y0)+f(t0+h, y_hat))
return yn
@TIMED
def RK4(f, t0, y0, h):
""" Standard RK4 """
k1 = f(t0, y0)
k2 = f(t0+h/2.0,y0 + h*k1/2.0)
k3 = f(t0+h/2.0,y0 + h*k2/2.0)
k4 = f(t0+h/2.0,y0 + h*k3)
yn = y0 + 1.0/6.0*h*(k1 + 2.0*k2 + 2.0*k3 + k4)
return yn
Decorators
Decorators
Quickly apply common tasks to methods
fns = [euler, RK2, RK3, RK4, jRK4]
t0 = scipy.linspace(-1,1)
y0 = scipy.ones(50)
h = 0.025
args = (f, t0, y0, h)
for fn in fns:
print fn(*args)
print
>>>
euler: 0.0181114469778 ms.
[ ... ]
RK2: 0.041656328049 ms.
[ ... ]
RK3: 0.0606733473757 ms.
[ ... ]
RK4: 0.0745587900587 ms.
[ ... ]
jRKN: 0.00150928724815 ms.
jRK4: 1.57358288492 ms.
[ ... ]
Decorators
4. Generators
Memory-conscious patterns
are kind of a big deal in scientific computing
Binary Tree from Array
(simplified interface)
class T(object):
def __init__(self, values = None, index = 0):
self.left = None
self.right = None
self.v = None
if values is not None:
self.loadValues(values, index)
def loadValues(self, values, index):
self.v = values[index]
n = len(values)
if index * 2 + 1 < n:
self.left = T(values, index * 2 + 1)
if index * 2 + 2 < n:
self.right = T(values, index * 2 + 2)
Generators
Guessing Game
def makeT(val, delta, levels, level = 0):
if level < levels:
t = T()
t.v = val
t.left = makeT(val-delta, delta/2, levels, level+1)
t.right = makeT(val+delta, delta/2, levels, level+1)
return t
Generators
Clean Code
def inorder(t):
if t:
for v in inorder(t.left):
yield v
yield t.v
for v in inorder(t.right):
yield v
Generators
Using our Generator
for v in inorder(a_tree):
print v
a = []
for v in inorder(a_tree):
a.append(v)
b = [v for v in inorder(a_tree)]
Generators
Questions?

“Yes, the slide with the code. What did that one do?”
Download