多行打印
单引号或者双引号都可以(默认支持转义),r(raw string)也可写在行头
1 2 3 4 5 6 print (''' 1 2 3 4/n ''' )
整数除法
当是/的时候输出的是浮点数
当是//的时候只会输出小数点前的数,也就是整数部分
当是%的时候,取余
1 2 3 4 5 6 7 8 9 10 11 >>> 10 / 3 3.3333333333333335 >>> 9 / 3 3.0 >>> 10 // 3 3 >>> 10 % 3 1
字符串
对于单个字符的编码,Python提供了ord()函数获取字符的整数表示,chr()函数把编码转换为对应的字符:
1 2 3 4 5 6 7 8 9 10 11 >>> ord ('A' )65 >>> ord ('中' )20013 >>> chr (66 )'B' >>> chr (25991 )'文'
如果知道字符的整数编码,还可以用十六进制这么写str:
要注意区分'ABC'和b'ABC',前者是str,后者虽然内容显示得和前者一样,但bytes的每个字符都只占用一个字节。
以Unicode表示的str通过encode()方法可以编码为指定的bytes,例如:
1 2 3 4 5 6 7 8 >>> 'ABC' .encode('ascii' )b'ABC' >>> '中文' .encode('utf-8' )b'\xe4\xb8\xad\xe6\x96\x87' >>> '中文' .encode('ascii' )Traceback (most recent call last): File "<stdin>" , line 1 , in <module> UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-1: ordinal not in range(128)
纯英文的str可以用ASCII编码为bytes,内容是一样的,含有中文的str可以用UTF-8编码为bytes。含有中文的str无法用ASCII编码,因为中文编码的范围超过了ASCII编码的范围,Python会报错。
模式匹配
简单匹配
1 2 3 4 5 6 7 8 9 10 11 12 13 age = 15 match age: case x if x < 10 : print (f'< 10 years old: {x} ' ) case 10 : print ('10 years old.' ) case 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 : print ('11~18 years old.' ) case 19 : print ('19 years old.' ) case _: print ('not sure.' )
列表匹配
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 args = ['gcc' , 'hello.c' , 'world.c' ] match args: case ['gcc' ]: print ('gcc: missing source file(s).' ) case ['gcc' , file1, *files]: print ('gcc compile: ' + file1 + ', ' + ', ' .join(files)) case ['clean' ]: print ('clean' ) case _: print ('invalid command.' )
Dict
要保证hash的正确性,作为key的对象就不能变。在Python中,字符串、整数等都是不可变的,因此,可以放心地作为key。而list是可变的,就不能作为key:
1 2 3 4 5 >>> key = [1 , 2 , 3 ]>>> d[key] = 'a list' Traceback (most recent call last): File "<stdin>" , line 1 , in <module> TypeError: unhashable type : 'list'
Set
set和dict类似,也是一组key的集合,但不存储value。由于key不能重复,所以,在set中,没有重复的key。
要创建一个set,用{x,y,z,...}列出每个元素:
1 2 3 >>> s = {1 , 2 , 3 }>>> s{1 , 2 , 3 }
或者提供一个list作为输入集合:
1 2 3 >>> s = set ([1 , 2 , 3 ])>>> s{1 , 2 , 3 }
注意,传入的参数[1, 2, 3]是一个list,而显示的{1, 2, 3}只是告诉你这个set内部有1,2,3这3个元素,显示的顺序也不表示set是有序的。。
重复元素在set中自动被过滤:
1 2 3 >>> s = {1 , 1 , 2 , 2 , 3 , 3 }>>> s{1 , 2 , 3 }
通过add(key)方法可以添加元素到set中,可以重复添加,但不会有效果:
1 2 3 4 5 6 >>> s.add(4 )>>> s{1 , 2 , 3 , 4 } >>> s.add(4 )>>> s{1 , 2 , 3 , 4 }
通过remove(key)方法可以删除元素:
1 2 3 >>> s.remove(4 )>>> s{1 , 2 , 3 }
set可以看成数学意义上的无序和无重复元素的集合,因此,两个set可以做数学意义上的交集、并集等操作:
1 2 3 4 5 6 >>> s1 = {1 , 2 , 3 }>>> s2 = {2 , 3 , 4 }>>> s1 & s2{2 , 3 } >>> s1 | s2{1 , 2 , 3 , 4 }
set和dict的唯一区别仅在于没有存储对应的value,但是,set的原理和dict一样,所以,同样不可以放入可变对象,因为无法判断两个可变对象是否相等,也就无法保证set内部“不会有重复元素”。试试把list放入set,看看是否会报错。
函数的参数
对参数类型做检查,只允许整数和浮点数类型的参数。数据类型检查可以用内置函数isinstance()实现:
1 2 3 4 5 6 7 def my_abs (x ): if not isinstance (x, (int , float )): raise TypeError('bad operand type' ) if x >= 0 : return x else : return -x
默认参数
默认参数很有用,但使用不当,也会掉坑里。默认参数有个最大的坑,演示如下:
先定义一个函数,传入一个list,添加一个END再返回:
1 2 3 def add_end (L=[] ): L.append('END' ) return L
当你正常调用时,结果似乎不错:
1 2 3 4 >>> add_end([1 , 2 , 3 ])[1 , 2 , 3 , 'END' ] >>> add_end(['x' , 'y' , 'z' ])['x' , 'y' , 'z' , 'END' ]
当你使用默认参数调用时,一开始结果也是对的:
但是,再次调用add_end()时,结果就不对了:
1 2 3 4 >>> add_end()['END' , 'END' ] >>> add_end()['END' , 'END' , 'END' ]
很多初学者很疑惑,默认参数是[],但是函数似乎每次都“记住了”上次添加了'END'后的list。
原因解释如下:
Python函数在定义的时候,默认参数L的值就被计算出来了,即[],因为默认参数L也是一个变量,它指向对象[],每次调用该函数,如果改变了L的内容,则下次调用时,默认参数的内容就变了,不再是函数定义时的[]了。
特别注意
定义默认参数要牢记一点:默认参数必须指向不变对象!
要修改上面的例子,我们可以用None这个不变对象来实现:
1 2 3 4 5 def add_end (L=None ): if L is None : L = [] L.append('END' ) return L
现在,无论调用多少次,都不会有问题:
1 2 3 4 >>> add_end()['END' ] >>> add_end()['END' ]
为什么要设计str、None这样的不变对象呢?因为不变对象一旦创建,对象内部的数据就不能修改,这样就减少了由于修改数据导致的错误。此外,由于对象不变,多任务环境下同时读取对象不需要加锁,同时读一点问题都没有。我们在编写程序时,如果可以设计一个不变对象,那就尽量设计成不变对象。
可变参数
1 2 3 4 >>> calc(1 , 2 , 3 )14 >>> calc(1 , 3 , 5 , 7 )84
我们把函数的参数改为可变参数:
1 2 3 4 5 def calc (*numbers ): sum = 0 for n in numbers: sum = sum + n * n return sum
定义可变参数和定义一个list或tuple参数相比,仅仅在参数前面加了一个*号。在函数内部,参数numbers接收到的是一个tuple,因此,函数代码完全不变。但是,调用该函数时,可以传入任意个参数,包括0个参数:
1 2 3 4 >>> calc(1 , 2 )5 >>> calc()0
如果已经有一个list或者tuple,要调用一个可变参数怎么办?可以这样做:
1 2 3 >>> nums = [1 , 2 , 3 ]>>> calc(nums[0 ], nums[1 ], nums[2 ])14
这种写法当然是可行的,问题是太繁琐,所以Python允许你在list或tuple前面加一个*号,把list或tuple的元素变成可变参数传进去:
1 2 3 >>> nums = [1 , 2 , 3 ]>>> calc(*nums)14
*nums表示把nums这个list的所有元素作为可变参数传进去。这种写法相当有用,而且很常见。
关键词参数
可变参数允许你传入0个或任意个参数,这些可变参数在函数调用时自动组装为一个tuple 。而关键字参数允许你传入0个或任意个含参数名 的参数,这些关键字参数在函数内部自动组装为一个dict 。请看示例:
1 2 def person (name, age, **kw ): print ('name:' , name, 'age:' , age, 'other:' , kw)
函数person除了必选参数name和age外,还接受关键字参数kw。在调用该函数时,可以只传入必选参数:
1 2 >>> person('Michael' , 30 )name: Michael age: 30 other: {}
也可以传入任意个数的关键字参数:
1 2 3 4 >>> person('Bob' , 35 , city='Beijing' )name: Bob age: 35 other: {'city' : 'Beijing' } >>> person('Adam' , 45 , gender='M' , job='Engineer' )name: Adam age: 45 other: {'gender' : 'M' , 'job' : 'Engineer' }
关键字参数有什么用?它可以扩展函数的功能。比如,在person函数里,我们保证能接收到name和age这两个参数,但是,如果调用者愿意提供更多的参数,我们也能收到。试想你正在做一个用户注册的功能,除了用户名和年龄是必填项外,其他都是可选项,利用关键字参数来定义这个函数就能满足注册的需求。
和可变参数类似,也可以先组装出一个dict,然后,把该dict转换为关键字参数传进去:
1 2 3 >>> extra = {'city' : 'Beijing' , 'job' : 'Engineer' }>>> person('Jack' , 24 , city=extra['city' ], job=extra['job' ])name: Jack age: 24 other: {'city' : 'Beijing' , 'job' : 'Engineer' }
当然,上面复杂的调用可以用简化的写法:
1 2 3 >>> extra = {'city' : 'Beijing' , 'job' : 'Engineer' }>>> person('Jack' , 24 , **extra)name: Jack age: 24 other: {'city' : 'Beijing' , 'job' : 'Engineer' }
**extra表示把extra这个dict的所有key-value用关键字参数传入到函数的**kw参数,kw将获得一个dict,注意kw获得的dict是extra的一份拷贝,对kw的改动不会影响到函数外的extra。
命名关键字参数
对于关键字参数,函数的调用者可以传入任意不受限制的关键字参数。至于到底传入了哪些,就需要在函数内部通过kw检查。
仍以person()函数为例,我们希望检查是否有city和job参数:
1 2 3 4 5 6 7 8 def person (name, age, **kw ): if 'city' in kw: pass if 'job' in kw: pass print ('name:' , name, 'age:' , age, 'other:' , kw)
但是调用者仍可以传入不受限制的关键字参数:
1 >>> person('Jack' , 24 , city='Beijing' , addr='Chaoyang' , zipcode=123456 )
如果要限制关键字参数的名字,就可以用命名关键字参数,例如,只接收city和job作为关键字参数。这种方式定义的函数如下:
1 2 def person (name, age, *, city, job ): print (name, age, city, job)
和关键字参数**kw不同,命名关键字参数需要一个特殊分隔符*,*后面的参数被视为命名关键字参数。
调用方式如下:
1 2 >>> person('Jack' , 24 , city='Beijing' , job='Engineer' )Jack 24 Beijing Engineer
如果函数定义中已经有了一个可变参数,后面跟着的命名关键字参数就不再需要一个特殊分隔符*了:
1 2 def person (name, age, *args, city, job ): print (name, age, args, city, job)
命名关键字参数必须传入参数名,这和位置参数不同。如果没有传入参数名,调用将报错:
1 2 3 4 >>> person('Jack' , 24 , 'Beijing' , 'Engineer' )Traceback (most recent call last): File "<stdin>" , line 1 , in <module> TypeError: person() missing 2 required keyword-only arguments: 'city' and 'job'
由于调用时缺少参数名city和job,Python解释器把前两个参数视为位置参数,后两个参数传给*args,但缺少命名关键字参数导致报错。
命名关键字参数可以有缺省值,从而简化调用:
1 2 def person (name, age, *, city='Beijing' , job ): print (name, age, city, job)
由于命名关键字参数city具有默认值,调用时,可不传入city参数:
1 2 >>> person('Jack' , 24 , job='Engineer' )Jack 24 Beijing Engineer
使用命名关键字参数时,要特别注意,如果没有可变参数,就必须加一个*作为特殊分隔符。如果缺少*,Python解释器将无法识别位置参数和命名关键字参数:
1 2 3 def person (name, age, city, job ): pass
小结
Python的函数具有非常灵活的参数形态,既可以实现简单的调用,又可以传入非常复杂的参数。
默认参数一定要用不可变对象,如果是可变对象,程序运行时会有逻辑错误!
要注意定义可变参数和关键字参数的语法:
*args是可变参数,args接收的是一个tuple;
**kw是关键字参数,kw接收的是一个dict。
以及调用函数时如何传入可变参数和关键字参数的语法:
可变参数既可以直接传入:func(1, 2, 3),又可以先组装list或tuple,再通过*args传入:func(*(1, 2, 3));
关键字参数既可以直接传入:func(a=1, b=2),又可以先组装dict,再通过**kw传入:func(**{'a': 1, 'b': 2})。
使用*args和**kw是Python的习惯写法,当然也可以用其他参数名,但最好使用习惯用法。
命名的关键字参数是为了限制调用者可以传入的参数名,同时可以提供默认值。
定义命名的关键字参数在没有可变参数的情况下不要忘了写分隔符*,否则定义的将是位置参数。
递归函数
举个例子,我们来计算阶乘n! = 1 x 2 x 3 x ... x n,用函数fact(n)表示,可以看出:
fact(n)=n!=1×2×3×⋅⋅⋅×(n−1)×n=(n−1)!×n=fact(n−1)×n
所以,fact(n)可以表示为n x fact(n-1),只有n=1时需要特殊处理。
于是,fact(n)用递归的方式写出来就是:
1 2 3 4 def fact (n ): if n==1 : return 1 return n * fact(n - 1 )
上面就是一个递归函数。可以试试:
1 2 3 4 5 6 >>> fact(1 )1 >>> fact(5 )120 >>> fact(100 )93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000
如果我们计算fact(5),可以根据函数定义看到计算过程如下:
1 2 3 4 5 6 7 8 9 10 => fact(5 ) => 5 * fact(4 ) => 5 * (4 * fact(3 )) => 5 * (4 * (3 * fact(2 ))) => 5 * (4 * (3 * (2 * fact(1 )))) => 5 * (4 * (3 * (2 * 1 ))) => 5 * (4 * (3 * 2 )) => 5 * (4 * 6 ) => 5 * 24 => 120
递归函数的优点是定义简单,逻辑清晰。理论上,所有的递归函数都可以写成循环的方式,但循环的逻辑不如递归清晰。
使用递归函数需要注意防止栈溢出。在计算机中,函数调用是通过栈(stack)这种数据结构实现的,每当进入一个函数调用,栈就会加一层栈帧,每当函数返回,栈就会减一层栈帧。由于栈的大小不是无限的,所以,递归调用的次数过多,会导致栈溢出。可以试试fact(1000):
1 2 3 4 5 6 7 >>> fact(1000 )Traceback (most recent call last): File "<stdin>" , line 1 , in <module> File "<stdin>" , line 4 , in fact ... File "<stdin>" , line 4 , in fact RuntimeError: maximum recursion depth exceeded in comparison
解决递归调用栈溢出的方法是通过尾递归 优化,事实上尾递归和循环的效果是一样的,所以,把循环看成是一种特殊的尾递归函数也是可以的。
尾递归是指,在函数返回的时候,调用自身本身,并且,return语句不能包含表达式。这样,编译器或者解释器就可以把尾递归做优化 ,使递归本身无论调用多少次,都只占用一个栈帧,不会出现栈溢出的情况。
上面的fact(n)函数由于return n * fact(n - 1)引入了乘法表达式,所以就不是尾递归了。要改成尾递归方式,需要多一点代码,主要是要把每一步的乘积传入到递归函数中:
1 2 3 4 5 6 7 def fact (n ): return fact_iter(n, 1 ) def fact_iter (num, product ): if num == 1 : return product return fact_iter(num - 1 , num * product)
可以看到,return fact_iter(num - 1, num * product)仅返回递归函数本身,num - 1和num * product在函数调用前就会被计算,不影响函数调用。
fact(5)对应的fact_iter(5, 1)的调用如下:
1 2 3 4 5 6 => fact_iter(5 , 1 ) => fact_iter(4 , 5 ) => fact_iter(3 , 20 ) => fact_iter(2 , 60 ) => fact_iter(1 , 120 ) => 120
尾递归调用时,如果做了优化,栈不会增长,因此,无论多少次调用也不会导致栈溢出。
遗憾的是,大多数编程语言没有针对尾递归做优化,Python解释器也没有做优化,所以,即使把上面的fact(n)函数改成尾递归方式,也会导致栈溢出。
汉诺塔的递归
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 def move (n, a, b, c ): if n == 1 : print (a, '-->' , c) else : move(n-1 , a, c, b) print (a, '-->' , c) move(n-1 , b, a, c) move(3 , 'A' , 'B' , 'C' )
1. 规则回顾
有 3 根柱子 A(起点)、B(辅助)、C(终点)。
有 n 个盘子,从上到下依次变小,在 A 上。
一次只能移动一个盘子,并且大盘子不能放在小盘子上。
目标:把所有盘子从 A 移动到 C(可借助 B)。
2. 递归思路
move(n, a, b, c)的意思是:
将 n 个盘子从柱子 a 借助柱子 b 移动到柱子 c。
分解步骤 (递归):
将上面 n-1 个盘子从 a 借助 c 移动到 b。
→ 调用 move(n-1, a, c, b)
将最大的盘子(第 n 个)从 a 直接移动到 c。
→ 打印 a --> c
将 b 上的 n-1 个盘子借助 a 移动到 c。
→ 调用 move(n-1, b, a, c)
3. 例子 n=3 的执行过程
初始状态 :
1 2 3 A: 3(largest) 2(mid) 1(smallest) B: 空 C: 空
调用:move(3, 'A', 'B', 'C')
步骤 1.1
进入 move(3, 'A', 'B', 'C'):
n=3,执行 else 分支:
调用 move(2, 'A', 'C', 'B')将上面 2 个盘子从 A 经过 C 移到 B。
步骤 2.1
进入 move(2, 'A', 'C', 'B'):
n=2,执行 else 分支:
调用 move(1, 'A', 'B', 'C')将上面 1 个盘子从 A 经过 B 移到 C。
步骤 3.1
进入 move(1, 'A', 'B', 'C'):
n=1,打印 A --> C
输出 1 : A --> C
回到 move(2, 'A', 'C', 'B')的第 1 步完成。
步骤 3.2
move(2, 'A', 'C', 'B')的第 2 步:
打印 A --> B
输出 2 : A --> B
步骤 3.3
move(2, 'A', 'C', 'B')的第 3 步:
调用 move(1, 'C', 'A', 'B')将 C 上的 1 个盘子(1号)从 C 经过 A 移到 B。
进入 move(1, 'C', 'A', 'B'):
打印 C --> B
输出 3 : C --> B
move(2, 'A', 'C', 'B')结束。
步骤 2.2
回到 move(3, 'A', 'B', 'C')的第 1 步完成,现在上面 2 个盘子在 B 上(顺序是小的在上),A 只剩下最大的 3 号盘。
第 2 步:打印 A --> C
输出 4 : A --> C
步骤 2.3
move(3, 'A', 'B', 'C')的第 3 步:
调用 move(2, 'B', 'A', 'C')将 B 上的 2 个盘子经过 A 移到 C。
步骤 4.1
进入 move(2, 'B', 'A', 'C'):
n=2,else 分支:
调用 move(1, 'B', 'C', 'A')将 B 上最小的 1 号盘从 B 经过 C 移到 A。
进入 move(1, 'B', 'C', 'A'):
打印 B --> A
输出 5 : B --> A
回到 move(2, 'B', 'A', 'C')第 1 步完成。
步骤 4.2
move(2, 'B', 'A', 'C')第 2 步:
打印 B --> C
输出 6 : B --> C
步骤 4.3
move(2, 'B', 'A', 'C')第 3 步:
调用 move(1, 'A', 'B', 'C')将 A 上的 1 号盘从 A 移到 C。
进入 move(1, 'A', 'B', 'C'):
打印 A --> C
输出 7 : A --> C
move(2, 'B', 'A', 'C')结束,move(3, 'A', 'B', 'C')结束。
4. 最终输出顺序
1 2 3 4 5 6 7 1. A --> C2. A --> B3. C --> B4. A --> C5. B --> A6. B --> C7. A --> C
这样,我们就用递归完成了 3 个盘子从 A 到 C 的移动,且符合“大盘子不能压小盘子”的规则。
这种算法的思维重在推导,我们先从n=2的情况推导,而不用从脑袋先演算n=3的时候。
切片
Python切片的规则始终是 **[start:stop)** 左闭右开区间:
包含 start索引的元素
不包含 stop索引的元素
从 start开始,到 stop-1结束
1 2 3 4 5 6 7 8 9 10 11 12 >>> L = ['Michael' , 'Sarah' , 'Tracy' , 'Bob' , 'Jack' ]
前10个数,每两个取一个:
1 2 >>> L[:10 :2 ][0 , 2 , 4 , 6 , 8 ]
所有数,每5个取一个:
1 2 >>> L[::5 ][0 , 5 , 10 , 15 , 20 , 25 , 30 , 35 , 40 , 45 , 50 , 55 , 60 , 65 , 70 , 75 , 80 , 85 , 90 , 95 ]
甚至什么都不写,只写[:]就可以原样复制一个list:
1 2 >>> L[:][0 , 1 , 2 , 3 , ..., 99 ]
tuple也是一种list,唯一区别是tuple不可变。因此,tuple也可以用切片操作,只是操作的结果仍是tuple:
1 2 >>> (0 , 1 , 2 , 3 , 4 , 5 )[:3 ](0 , 1 , 2 )
字符串'xxx'也可以看成是一种list,每个元素就是一个字符。因此,字符串也可以用切片操作,只是操作结果仍是字符串:
1 2 3 4 >>> 'ABCDEFG' [:3 ]'ABC' >>> 'ABCDEFG' [::2 ]'ACEG'
迭代
如果要对list实现类似Java那样的下标循环怎么办?Python内置的enumerate函数可以把一个list变成索引-元素对,这样就可以在for循环中同时迭代索引和元素本身:
1 2 3 4 5 6 >>> for i, value in enumerate (['A' , 'B' , 'C' ]):... print (i, value)... 0 A1 B2 C
列表生成式
举个例子,要生成list [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]可以用list(range(1, 11)):
1 2 >>> list (range (1 , 11 ))[1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 ]
但如果要生成[1x1, 2x2, 3x3, ..., 10x10]怎么做?方法一是循环:
1 2 3 4 5 6 >>> L = []>>> for x in range (1 , 11 ):... L.append(x * x)... >>> L[1 , 4 , 9 , 16 , 25 , 36 , 49 , 64 , 81 , 100 ]
但是循环太繁琐,而列表生成式则可以用一行语句代替循环生成上面的list:
1 2 >>> [x * x for x in range (1 , 11 )][1 , 4 , 9 , 16 , 25 , 36 , 49 , 64 , 81 , 100 ]
写列表生成式时,把要生成的元素x * x放到前面,后面跟for循环,就可以把list创建出来,十分有用,多写几次,很快就可以熟悉这种语法。
for循环后面还可以加上if判断,这样我们就可以筛选出仅偶数的平方:
1 2 >>> [x * x for x in range (1 , 11 ) if x % 2 == 0 ][4 , 16 , 36 , 64 , 100 ]
还可以使用两层循环,可以生成全排列:
1 2 >>> [m + n for m in 'ABC' for n in 'XYZ' ]['AX' , 'AY' , 'AZ' , 'BX' , 'BY' , 'BZ' , 'CX' , 'CY' , 'CZ' ]
三层和三层以上的循环就很少用到了。
运用列表生成式,可以写出非常简洁的代码。例如,列出当前目录下的所有文件和目录名,可以通过一行代码实现:
1 2 3 >>> import os >>> [d for d in os.listdir('.' )] ['.emacs.d' , '.ssh' , '.Trash' , 'Adlm' , 'Applications' , 'Desktop' , 'Documents' , 'Downloads' , 'Library' , 'Movies' , 'Music' , 'Pictures' , '
map
Python内建了map()和reduce()函数。
如果你读过Google的那篇大名鼎鼎的论文“MapReduce: Simplified Data Processing on Large Clusters ”,你就能大概明白map/reduce的概念。
我们先看map。map()函数接收两个参数,一个是函数,一个是Iterable,map将传入的函数依次作用到序列的每个元素,并把结果作为新的Iterator返回。
举例说明,比如我们有一个函数f(x)=x2,要把这个函数作用在一个list [1, 2, 3, 4, 5, 6, 7, 8, 9]上,就可以用map()实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 f(x) = x * x │ │ ┌───┬───┬───┬───┼───┬───┬───┬───┐ │ │ │ │ │ │ │ │ │ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ [ 1 2 3 4 5 6 7 8 9 ] │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ [ 1 4 9 16 25 36 49 64 81 ]
现在,我们用Python代码实现:
1 2 3 4 5 6 >>> 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]
map()传入的第一个参数是f,即函数对象本身。由于结果r是一个Iterator,Iterator是惰性序列,因此通过list()函数让它把整个序列都计算出来并返回一个list。
你可能会想,不需要map()函数,写一个循环,也可以计算出结果:
1 2 3 4 L = [] for n in [1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 ]: L.append(f(n)) print (L)
的确可以,但是,从上面的循环代码,能一眼看明白“把f(x)作用在list的每一个元素并把结果生成一个新的list”吗?
所以,map()作为高阶函数,事实上它把运算规则抽象了,因此,我们不但可以计算简单的f(x)=x2,还可以计算任意复杂的函数,比如,把这个list所有数字转为字符串:
1 2 >>> list(map(str, [1, 2, 3, 4, 5, 6, 7, 8, 9])) ['1', '2', '3', '4', '5', '6', '7', '8', '9']
只需要一行代码。
再看reduce的用法。reduce把一个函数作用在一个序列[x1, x2, x3, ...]上,这个函数必须接收两个参数,reduce把结果继续和序列的下一个元素做累积计算,其效果就是:
1 reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)
比方说对一个序列求和,就可以用reduce实现:
1 2 3 4 5 6 >>> from functools import reduce >>> def add(x, y): ... return x + y ... >>> reduce(add, [1, 3, 5, 7, 9]) 25
当然求和运算可以直接用Python内建函数sum(),没必要动用reduce。
但是如果要把序列[1, 3, 5, 7, 9]变换成整数13579,reduce就可以派上用场:
1 2 3 4 5 6 >>> from functools import reduce >>> def fn(x, y): ... return x * 10 + y ... >>> reduce(fn, [1, 3, 5, 7, 9]) 13579
这个例子本身没多大用处,但是,如果考虑到字符串str也是一个序列,对上面的例子稍加改动,配合map(),我们就可以写出把str转换为int的函数:
1 2 3 4 5 6 7 8 9 10 >>> from functools import reduce >>> def fn(x, y): ... return x * 10 + y ... >>> 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
整理成一个str2int的函数就是:
1 2 3 4 5 6 7 8 9 10 from functools import reduceDIGITS = {'0' : 0 , '1' : 1 , '2' : 2 , '3' : 3 , '4' : 4 , '5' : 5 , '6' : 6 , '7' : 7 , '8' : 8 , '9' : 9 } def str2int (s ): def fn (x, y ): return x * 10 + y def char2num (s ): return DIGITS[s] return reduce(fn, map (char2num, s))
还可以用lambda函数进一步简化成:
1 2 3 4 5 6 7 8 9 from functools import reduceDIGITS = {'0' : 0 , '1' : 1 , '2' : 2 , '3' : 3 , '4' : 4 , '5' : 5 , '6' : 6 , '7' : 7 , '8' : 8 , '9' : 9 } def char2num (s ): return DIGITS[s] def str2int (s ): return reduce(lambda x, y: x * 10 + y, map (char2num, s))
也就是说,假设Python没有提供int()函数,你完全可以自己写一个把字符串转化为整数的函数,而且只需要几行代码!