1.函数式风格
为何要把大多人忽略掉的函数式风格作为序幕呢?既然我们选择的是一门脚本语言,
就是因为他的可移植性和书写方便的特性,试想下如果我们的程序运行在远端Linux
服务器上,而本地则是Windows环境,每次我们要完成一些小的功能时,都要打开
Eclipse,import一堆头文件,然后编写一个main函数,然后编译出可执行文件,然
后再上传到服务器上,然后还要处理好程序的崩溃处理,错误输出等,是不是很烦?
而在已经濒临崩溃的情绪下,还要去写出类似于for (int i = 0; i < max; i++)这
样的代码,是不是感到血压急剧上升?所以我们需要的是更快更方便!而函数式风格
则是超越一般快一般方便的最好的利器,没有之一!
函数式方面的应用主要包括:列表解析、Lambda表达式、迭代器和生成器以及函数装
饰器。列表解析(List Comprehension)是函数式编程语言的特色之一,类似于数学
中的集合表示方法,列表解析相比于普通的循环速度更快,书写更方便。配合Lambda
表达式以及map、reduce、filter等工具可以极大的提高生产力。下面举几个常见的
例子:
列表解析
0)背景
在函数式编程语言Miranda里面,list comprehension的功能相比python更为强大,
它的语法形式如下:
[<expression> | <qualifier>; ...; <qualifier>]
<qualifier>可以是生成表达式(比如:x in lst)或者是过滤表达式(比如:if
x > 0)。那么快速排序就可以如下生成:
sort [] = []
sort (x:xs) = sort [y | y <- xs; y < x]
++ [x] ++
sort [y | y <- xs; y >= x]
列表解析看起来很复杂其实它并没有实质上增加函数式编程语言的功能实际上函数
式编程语言的根基-Lambda演算的全部就是Lambda表达式加上α规约和β规约,还有
curry算子将多参数转化为多个单一参数函数链调用。数啊,布尔值啊,与或非运算
啊,加减乘除啊,全都能用Church Encoding方法表示出来。比如数字N表示将一个
函数应用到它自身N次,TRUE表示为λx.λy.x,IF表示为λv.λt.λf.v t f,终极
数据抽象CONS可以表示为λs.if s M N,那么CAR就可以表示为λp.p true,而CDR
就是λp.p false。而Y combinator更是可以在人类智慧史上留下一笔。
看似复杂的列表推导实际上可以用下面的五个规则进行推导:
(1) [E | v <- [];Q] -> []
(2) [E | v <- E':L';Q] -> [E | Q][E'/v] ++ [E | v <- L';Q]
(3) [E | False; Q] -> []
(4) [E | True; Q] -> [E | Q]
(5) [E | ] -> [E]
比如忽略Lazy Evaluation的话可以有如下推导过程:
[square x | x <- [1, 2, 3]; odd x]
>>> [square 1 | odd 1] ++ [square 2 | odd 2] ++ [square 3 | odd 3]
>>> [square 1 | True] ++ [square 2 | False] ++ [square 3 | True]
>>> [square 1 | ] ++ [] ++ [square | 3]
>>> [square 1] ++ [] ++ [square 3]
>>> [1, 9]
说白了就是对生成式产生的元素逐个调用过滤表达式进行判断
1)去重
lst = [1, 2, 3, 3]
lst = list(set(lst))
lst = [1, 2, 3]
2)索引分片
lst = [1, 2, 3, 4, 5]
lst[2] # 3
lst[2:] # [3, 4, 5]
lst[1:-1] # [2, 3, 4]
lst[0:-1:2] # [1, 3]
3)修改删除
lst = [1, 2, 3, 4, 5]
lst[0] = 99 # [99, 2, 3, 4, 5]
del lst[0] # [2, 3, 4, 5]
4)列表属性
lst = [
{'name':'a', 'price':1},
{'name':'b', 'price':2},
{'name':'c', 'price':3},
{'name':'d', 'price':4},
{'name':'e', 'price':5}]
len(lst) # 5
min(lst, key = lambda x : x['price'])
# {'price': 1, 'name': 'a'}
max(lst, key = lambda x : x['price'])
# {'price': 5, 'name': 'e'}
sum([b['price'] for b in lst])
# 15
sorted(lst, key = lambda x : x['price'], reverse = True)[:2]
# [{'price': 5, 'name': 'e'}, {'price': 4, 'name': 'd'}]
print(list(map((lambda x : dict(name = x['name'], price = x['price'] * 2)), [b for b in lst[:2]])))
# [{'price': 2, 'name': 'a'}, {'price': 4, 'name': 'b'}]
reduce((lambda x, y : x + y), [b['price'] for b in lst])
# 15
a = [1, 2, 3, 4, 5]
b = ['a', 'b', 'c', 'd', 'e']
zip(a, b) # [(1, 'a'), (2, 'b'), (3, 'c'), (4, 'd'), (5, 'e')]
zip(*zip(a, b))
# [(1, 2, 3, 4, 5), ('a', 'b', 'c', 'd', 'e')]
5)遍历文件
lines = [line.rstrip() for line in open('test.txt')]
6)求素数
primes = [x for x in xrange(3, 100, 2) if not [y for y in xrange(2, x / 2) if x % y == 0]]
7)编辑距离为1的单词表
alphabet = 'abcdefghijklnmopqrstuvwxyz'
def edit1(word):
splits = [(word[:i], word[i:]) for i in xrang(len(word) + 1)]
deletes = [a + b[1:] for a, b in splits if b]
transposes = [a + b[1] + b[0] + b[2:] for a, b in splits if len(b) > 1]
replaces = [a + c + b[1:] for a, b in splits for c in alphabet if b]
inserts = [a + c + b for a, b in splits for c in alphabet]
return set(deletes + transposes + replaces + inserts)
8)SQL语句
books=[
{"name":"C#从入门到精通","price":23.7,"store":"卓越"},
{"name":"ASP.NET高级编程","price":44.5,"store":"卓越"},
{"name":"C#从入门到精通","price":24.7,"store":"当当"},
{"name":"ASP.NET高级编程","price":45.7,"store":"当当"},
{"name":"C#从入门到精通","price":26.7,"store":"新华书店"},
{"name":"ASP.NET高级编程","price":55.7,"store":"新华书店"}
]
1)求《ASP.NET高级编程》价格最便宜的店
min([b for b in books if b['name'] == 'ASP.NET高级编程'], key = lambda x : x['price'])['store']
# '卓越'
2)求在新华书店购买两本书一样一本要花的钱
sum([b['price'] for b in books if b['store'] == '新华书店'])
# 82.4
3)求列表中有那几本书
list(set([b['name'] for b in books]))
# ['C#从入门到精通', 'ASP.NET高级编程']
4)《C#从入门到精通》的平均价格
(lambda x : sum(x) / len(x))([b['price'] for b in books if b['name'] == 'C#从入门到精通'])
# 25.03
5)求每本书的平均价格
print(list(map((lambda x : (lambda y : sum(y) / len(l))([b['price'] for b in books if b['name'] == x])), list(set([b['name'] for b in books])))))
# [8.344444444444443, 16.211111111111112]
迭代器与生成器
0)背景
赋值让函数式编程尴尬,流作为另一种模拟真实世界变动的解决方案让函数式
重新优雅起来,在C++/Java中通过setter、getter去模拟状态的变化,流将一
个变量的变化用x(t)来模拟,t代表离散时间变化,也就是说我们打算用一个序
列来模拟时间流逝。但是换来的代价是计算的代价太大。比如下面两个计算区间
素数个数的方案:
(define (sum-primes a b)
(define (iter count accum)
(cond ((> count b) accum)
((prime? count) (iter (+ count 1) (+ count accum)))
(else (iter (+ count 1) accum))))
(iter a 0))
(define (sum-primes a b)
(accumulate +
0
(filter prime? (enumerate-interval a b))))
第一种是采用递增求解,而第二种会先将区间内所有的素数准备好在进行计数。
一下子准备好的缺点就在于如果我们从头到尾只用了前几个,那么浪费就太大了。
流实现了一种按需求解的方案,流机制需要的操作包括:
cons-stream
(stream-car (cons-stream x y)) = x
(stream-cdr (cons-stream x y)) = y
(define (stream-ref s n)
(if (= n 0)
(stream-car s)
(stream-ref (stream-cdr s) (- n 1))))
(define (stream-map proc s)
(if (stream-null? s)
the-empty-stream
(cons-stream (proc (stream-car s))
(stream-map proc (stream-cdr s)))))
const-stream需要满足构造时可以延迟cdr的求值,只在我们真正需要的时候,
才去对cdr真正的做求值,这在Scheme里其实很简单,就是在cons的时候将
expr放到一个lambda环境里,而在stream-cdr的时候才去求值expr。
通过流机制我们可以构造无穷流,比如我们定义整个自然数为1和1的后继,那么
cdr就会延迟求值。SICP3.5.3节里介绍了如何用无穷流的方法来计算sqrt。
1)迭代器
迭代器是一个对象,实现了__iter__和next方法
class Fib:
def __init__(self):
self.a,self.b = 0,1
def next(self):
self.a,self.b = self.b,self.a+self.b
return self.a
def __iter__(self):
return self
fibs = Fib()
for f in fibs:
if f < 10000:
print f
else:
break
2)生成器
生成器是一个函数,简单的说就是将return val,变成了yield val,每次循环都
会yield一个值,然后将函数停止,下次再从停止的位置重新调用
def fib():
a,b = 0,1
while 1:
a,b = b,a+b
yield a
for f in fib():
if f < 10000:
print f
else:
break
函数装饰器
0)背景
Lexical Scoping和Dynamic Scoping。在函数式编程语言里,函数是头等对象,具有
和传统意义上的变量一样的语义,可以作为参数和返回值(其实从Lambda Calculus的
角度来看,我觉得不是函数和变量意义一样,而是变量和函数意义一样)。当内层函数
被返回时就会遇到问题,比如:(lambda (y) (lambda (x) (* y 2))),如果内层函数
lambda (x) (* y 2)被返回,那么y的值应该取多少呢?如果取值是从词法作用域角度
来看的话就是外层的y值,而如果从调用的角度来看,就是当调用这个函数时外层y的值,
显然,Lexical Scoping更加具有实际意义并且不容易出错。但是内层函数返回时,我们
还必须要记住外层环境,不能仅仅返回这个函数,所以我们返回的实际上是一个闭包:函
数定义加上外层环境。
1)DEMO
def deco(*decoargs, **decokeywords): # 输入参数,返回generator函数
def generator(func): # 输入一个函数参数func,返回wrapper函数
def wrapper(*varargs, **keywords): # wrapper使用和func同样的参数
print('deco-arg----')
for arg in decoargs:
print('\t%s' % arg)
for key in decokeywords:
print('\t%s:%s' % (key, decokeywords[key]))
print('deco-arg----')
print('wrapper-----')
rtn = func(*varargs, **keywords) # 返回func的返回值,保持和func一样的行为
print('wrapper-----')
return rtn
return wrapper
return generator
@deco(2012, 2013, d = 'deco') # 等价于 foo = deco(2012, 2013, d = 'deco')(foo)
def foo(*varargs, **keywords):
print(' foo----')
for v in varargs:
print('\t%s' % v)
for k in keywords:
print('\t%s:%s' % (k, keywords[k]))
print(' foo----')
return 'FOO_RTN_VAL'
foo(1, 2, f = 'foo')
# 输出类似于下面
deco-arg----
2012
2013
d:deco
deco-arg----
wrapper-----
foo----
1
2
f:foo
foo----
wrapper-----
'FOO_RTN_VAL'
2.文本处理
文件读写
1) f = open('file.txt', 'r') # 读字符文件
f = open('file.txt', 'rb') # 读二进制文件(解析文本保存的网络数据或者是对象序列化数据等)
f = open('file.txt', 'w') # 写字符文件
2) str = f.read() # 将文件内容一次性全部读取到一个字符串里
str = f.read(bytes) # 指定读取多少个字节
str = f.readline() # 读取下一行
str = f.readlines() # 将所有文件内容按行存到一个列表里
f.write("%s %d %f\n" % (s, i, f)) # 格式化写入文件
f.write(str) # 写入一个字符串到文件(换行符需要自己控制)
f.writelines(lst) # 将一个列表里的内容全部写入到文件(只能是字符列表)
3) f.flush() # 强迫将文件更新内容刷新到磁盘上
f.close() # 关闭文件
字符串
0) 字符串对象不可以原地更改
1) +/+= # 连结字符串
2) 'a b c d'.split(' ') # 分割字符串
3) '-'.join('a b c d'.split(' ')) # 连结字符串
4) str.startswith('a') # 判断是否以给定字符串开始
str.endswith('z') # 判断是否以给定字符串终止
5) str.find('pattern') # 正向查找(返回匹配的起始位置)
str.rfind('pattern') # 反响查找
6) str.replace('l', 'L') # 查找并替换
7) len(str) # 字符串长度
8) open(r'C:\new\text.dat') # 抑制转义
9) manta = """ # 多行字符
Always Look On
The Bright Side
Of Life
"""
10)s[::-1] # 字符反转
11)str(42) # 将整形转换为字符串类型
int('42') # 将字符串型转换为整形
12)"endwitheacape\t\r\n".rstrip() # 将末尾的连续转义字符过滤掉
13)"aAbBcCdD".lower() # 全部转换为小写
"aAbBcCdD".upper() # 全部转换为大写
14)ord('s') # 获取字符的ASCII码
chr(115) # 获取ASCII码对应的字符
正则表达式
1) 和通常语言里的元字符含义一样
. ~ $ * + ? { } [ ] \ | ( )
2) 预定义特殊字符
\d [0-9]
\D [^0-9]
\w [a-zA-Z0-9_]
\W [^a-zA-Z0-9_]
\s [ \t\n\r\f\v]
\S [^ \t\n\r\f\v]
3) re.Compile(pattern, flag = 0)
OS文件操作
1) os.getcwd() # 当前工作目录
2) os.listdir() # 指定目录下的所有目录名
3) os.remove() # 删除一个文件
4) os.removedirs() # 删除目录
5) os.path.isfile() # 判断是否是文件
6) os.path.isdir() # 判断是否是目录
7) os.path.isabs() # 判断是否是绝对路径
8) os.path.exists() # 路径是否存在
9) os.path.split() # 切分目录名和文件名
10)os.path.dirname() # 获取路径名
11)os.path.basename() # 获取文件名
12)os.system() # 运行shell命令
13)os.getenv() # 读取环境变量
14)os.putenv() # 设置环境变量
15)os.linesep # 当前平台换行符
16)os.name # 操作系统名称
17)os.rename(old, new) # 重命名
18)os.makedirs() # 创建多级目录
19)os.mkdir() # 创建单个目录
20)os.stat() # 获取文件属性
21)os.chmodd() # 修改文件权限和时间戳
22)os.exit() # 终止当前进程
23)os.path.getsize() # 获取文件大小
24)shutil.copyfile(old, new)# 拷贝文件
25)shutil.copy(old, new) # old只能是文件夹,new可以是文件
26)shutil.copytree(old, new)# 拷贝文件夹
27)os.chdir() # 切换路径
解析XML
TODO
读写Excel
TODO
3.反射机制
TODO
4.Python与C/C++模块互相调用
TODO
参考
[1] AstralWind, Python函数式编程指南, http://www.cnblogs.com/huxi/archive/2011/07/14/2106863.html
[2] Peter Norvig, How to Write a Spelling Corrector, http://norvig.com/spell-correct.html
[3] AncerHaides, python中的迭代器与生成器, http://lamoop.diandian.com/post/2011-11-04/40029544769
[4] Harold Abelson, Gerald Jay Sussman, Julie Sussman, SICP
[5] 用python的列表解析以及函数式计算来简化代码, http://www.csharpwin.com/csharpspace/12296r8006.shtml
[6] C.A.R Hore, slpj
[7] 学而不迷, python 字符串处理, http://blog.sina.com.cn/s/blog_5ff7f94f0100pujt.html
[8] AstralWind, Python正则表达式指南, http://www.cnblogs.com/huxi/archive/2010/07/04/1771073.html
[9] AstralWind, Python装饰器实例:调用参数合法性验证, http://www.cnblogs.com/huxi/archive/2011/03/31/2001522.html
[10]王垠, 怎样写一个解释器, http://blog.sina.com.cn/s/blog_5d90e82f01018ge9.html
[11]关于python文件操作, http://www.cnblogs.com/rollenholt/archive/2012/04/23/2466179.html