[Python] Functional Programming

Higher-order Function

A function can use another functions as arguments. This type of function is called Higher-order function.

1
2
3
4
def add(x, y, f):
return f(x) + f(y)

add(3, -5, abs)

map/reduce

map 函数接收两个参数,一个是函数,一个是Iterablemap将传入的函数依次作用到序列的每个元素,并把结果作为新的Iterator返回。

1
2
3
4
5
6
7
8
def f(x):
return x * x

r = map(f, [1, 2, 3, 4, 5, 6, 7, 8, 9])
list(r)
# [1, 4, 9, 16, 25, 36, 49, 64, 81]

list(map(str, [1, 2, 3, 4, 5, 6, 7, 8, 9]))

reduce 把一个函数作用在一个序列[x1, x2, x3, ...]上,这个函数必须接收两个参数,reduce把结果继续和序列的下一个元素做累积计算,其效果就是:

1
reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)

Examples:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
from functools import reduce
def add(x, y):
return x + y

reduce(add, [1, 3, 5, 7, 9])
# 25

def fn(x, y):
return x * 10 + y
reduce(fn, [1, 3, 5, 7, 9])
# 13579

def char2num(s):
digits = {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}
return digits[s]

reduce(fn, map(char2num, '13579'))
# 13579

# OR
def str2int(s):
def fn(x, y):
return x * 10 + y
def char2num(s):
return DIGITS[s]
return reduce(fn, map(char2num, s))

# OR
def char2num(s):
return DIGITS[s]

def str2int(s):
return reduce(lambda x, y: x * 10 + y, map(char2num, s))

filter

map()类似,filter()也接收一个函数和一个序列。和map()不同的是,filter()把传入的函数依次作用于每个元素,然后根据返回值是True还是False决定保留还是丢弃该元素。

The returned value is a Iterator

1
2
3
4
5
# filter odd numbers
def is_odd(n):
return n % 2 == 1

list(filter(is_odd, [1, 2, 4, 5, 6, 9, 10, 15]))

Function as returned value

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def calc_sum(*args):
ax = 0
for n in args:
ax = ax + n
return ax

def lazy_sum(*args):
def sum():
ax = 0
for n in args:
ax = ax + n
return ax
return sum

f = lazy_sum(1,2,3,4,5)
>>> f # <function lazy_sum.<locals>.sum at 0x101c6ed90>
>>> f()
# 120

Closure

In the above example, sum can have access to the arguments and properties of the outer function lazy_sum.

The problem of using a loop within closure:

1
2
3
4
5
6
7
8
9
10
11
12
def count():
fs = []
for i in range(1, 4):
def f():
return i*i
fs.append(f)
return fs

f1, f2, f3 = count()
>>> f1() # 9
>>> f2() # 9
>>> f3() # 9

Decorator

在代码运行期间动态增加功能的方式,称之为“装饰器”(Decorator). 本质上,decorator就是一个返回函数的高阶函数。所以,我们要定义一个能打印日志的decorator,可以定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
def log(func):
def wrapper(*args, **kw):
print('call %s():' % func.__name__)
return func(*args, **kw)
return wrapper

# Using decorator
@log
def now():
print('2015-3-25')

# Is the same as:
now = log(now)

由于log()是一个decorator,返回一个函数,所以,原来的now()函数仍然存在,只是现在同名的now变量指向了新的函数,于是调用now()将执行新函数,即在log()函数中返回的wrapper()函数。

More complex decorator

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def log(text):
def decorator(func):
def wrapper(*args, **kw):
print('%s %s():' % (text, func.__name__))
return func(*args, **kw)
return wrapper
return decorator

@log('text')
def now():
print('2015-3-25')

# @log('execute') is the same as
now = log('execute')(now)

以上两种decorator的定义都没有问题,但还差最后一步。因为我们讲了函数也是对象,它有name等属性,但你去看经过decorator装饰之后的函数,它们的name已经从原来的’now’变成了’wrapper’.因为返回的那个wrapper()函数名字就是’wrapper’,所以,需要把原始函数的name等属性复制到wrapper()函数中,否则,有些依赖函数签名的代码执行就会出错。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import functools

def log(func):
@functools.wraps(func)
def wrapper(*args, **kw):
print('call %s():' % func.__name__)
return func(*args, **kw)
return wrapper

def log(text):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kw):
print('%s %s():' % (text, func.__name__))
return func(*args, **kw)
return wrapper
return decorator

Partial Function

1
2
3
4
5
6
7
8
9
10
11
12
13
def int2(x, base=2):
return int(x, base)

# Can be written as
import functools
int2 = functools.partial(int, base=2)
>>> int2('1000000')
64
>>> int2('1010101')
85
# Can also change the arguments of the original function
>>> int2('1000000', base=10)
1000000

functools.partial的作用就是,把一个函数的某些参数给固定住(也就是设置默认值),返回一个新的函数,调用这个新函数会更简单。

Module

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

' a test module '

__author__ = 'Michael Liao'

import sys

def test():
args = sys.argv
if len(args)==1:
print('Hello, world!')
elif len(args)==2:
print('Hello, %s!' % args[1])
else:
print('Too many arguments!')

if __name__=='__main__':
test()

The use of

1
2
if __name__=='__main__':
test()

当我们在命令行运行hello模块文件时,Python解释器把一个特殊变量__name__置为__main__,而如果在其他地方导入该hello模块时,if判断将失败,因此,这种if测试可以让一个模块通过命令行运行时执行一些额外的代码,最常见的就是运行测试。

Scope

在一个模块中,我们可能会定义很多函数和变量,但有的函数和变量我们希望给别人使用,有的函数和变量我们希望仅仅在模块内部使用。在Python中,是通过 _ 前缀来实现的。

类似__xxx__这样的变量是特殊变量,可以被直接引用,但是有特殊用途,比如上面的__author____name__就是特殊变量,hello模块定义的文档注释也可以用特殊变量__doc__访问,我们自己的变量一般不要用这种变量名.

类似_xxx__xxx这样的函数或变量就是非公开的(private),不应该被直接引用,比如_abc__abc等;

Python并没有一种方法可以完全限制访问private函数或变量,但是,从编程习惯上不应该引用private函数或变量。

Example:

1
2
3
4
5
6
7
8
9
10
11
def _private_1(name):
return 'Hello, %s' % name

def _private_2(name):
return 'Hi, %s' % name

def greeting(name):
if len(name) > 3:
return _private_1(name)
else:
return _private_2(name)

外部不需要引用的函数全部定义成private,只有外部需要引用的函数才定义为public。

Import Module

默认情况下,Python解释器会搜索当前目录、所有已安装的内置模块和第三方模块,搜索路径存放在sys模块的path变量中:

1
2
3
>>> import sys
>>> sys.path
['', '/Library/Frameworks/Python.framework/Versions/3.6/lib/python36.zip', '/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6', ..., '/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages']

如果我们要添加自己的搜索目录,有两种方法:

  1. Change the sys.path, this will expire when environment is deactivate:

    1
    2
    >>> import sys
    >>> sys.path.append('/Users/michael/my_py_scripts')
  2. Set environment variable PYTHONPATH,该环境变量的内容会被自动添加到模块搜索路径中。设置方式与设置Path环境变量类似。注意只需要添加你自己的搜索路径,Python自己本身的搜索路径不受影响。