4.高级特性
4.1切片
在使用list或者tuple的时候,截取操作如下,
>>> A=['aaa','bbb','ccc','ddd','eee']
>>> [A[0],A[1],A[2]]
['aaa', 'bbb', 'ccc']
>>> A[0:3]
['aaa', 'bbb', 'ccc’]
可见我们可以通过:来截取需要的内容
举例
L=
list(
range(
100))
print(L[
0:
10]) #前十
print(L[-
10:]) #后十
print(L[
10:
20]) #10至20
print(L[:
10:
2]) #前十中每跳2个
print(L[::
5]) #所有值每跳5个
4.2 迭代
如果给定一个list或者tuple,我们可以通过for循环来遍历,这种遍历我们称为迭代
可以通过hasattr(obj,’__iter__’)来验证对象是否可以被迭代
print(
hasattr(
'wyz'
,
'__iter__
’))
结果是true
或者使用collection模块
>>> import collections
>>> isinstance('abc',collections.Iterable)
True
>>> isinstance([1,2,3],collections.Iterable)
True
>>> isinstance(123,collections.Iterable)
False
4.3 列表生成式
python内置强大的列表生成器,可以用来生产列表
L=
list(
range(
1
,
11))
print(L)
M=[x*x
for x
in
range(
1
,
11)]
print(M)
/Users/wangyizhe/Desktop/pythonIDE/python3/venv/bin/python /Users/wangyizhe/Desktop/pythonIDE/python3/test.py
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
写列表生成式时,把要生成的元素x * x放到前面,后面跟for循环,就可以把list创建出来,十分有用,多写几次,很快就可以熟悉这种语法。
还可以在循环后面增加if条件删选出偶数,或者使用两重循环做遍历组合全排列
L=[m + n
for m
in
'ABC'
for n
in
'XYZ']
print(L)
M=[x * x
for x
in
range(
1
,
11)
if x %
2 ==
0]
print(M)
/Users/wangyizhe/Desktop/pythonIDE/python3/venv/bin/python /Users/wangyizhe/Desktop/pythonIDE/python3/test.py
['AX', 'AY', 'AZ', 'BX', 'BY', 'BZ', 'CX', 'CY', 'CZ']
[4, 16, 36, 64, 100]
还可以通过列表生成器完成简洁代码编写,例如列出当前目录下所有文件和目录
import os
# 导入os模块,模块的概念后面讲到
D=[d
for d
in os.listdir(
'.')]
print(D)
/Users/wangyizhe/Desktop/pythonIDE/python3/venv/bin/python /Users/wangyizhe/Desktop/pythonIDE/python3/test.py
['test.py', 'venv', '.idea’]
或者将所有大写的list变成小写的list
L = [
'Hello'
,
'World'
,
'IBM'
,
'Apple']
S=[s.lower()
for s
in L]
print(S)
/Users/wangyizhe/Desktop/pythonIDE/python3/venv/bin/python /Users/wangyizhe/Desktop/pythonIDE/python3/test.py
['hello', 'world', 'ibm', 'apple']
4.4生成器
通过列表生成式,我们可以直接创建一个列表。但是,受到内存限制,列表容量肯定是有限的。而且,创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。
所以,如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间。在Python中,这种一边循环一边计算的机制,称为生成器:generator。
要创建一个generator,有很多种方法。第一种方法很简单,
只要把一个列表生成式的[]改成(),就创建了一个generator:
>>> L = [x * x
for x
in
range(
10)]
>>> L
[
0
,
1
,
4
,
9
,
16
,
25
,
36
,
49
,
64
,
81]
>>> g = (x * x
for x
in
range(
10))
>>> g
object at
0x1022ef630>
创建L和g的区别仅在于最外层的[]和(),L是一个list,而g是一个generator。
我们可以直接打印出list的每一个元素,但我们怎么打印出generator的每一个元素呢?
如果要一个一个打印出来,可以通过next()函数获得generator的下一个返回值:
>>> next(g)
0
>>> next(g)
1
>>> next(g)
4
>>> next(g)
9
>>> next(g)
16
>>> next(g)
25
>>> next(g)
36
>>> next(g)
49
>>> next(g)
64
>>> next(g)
81
>>> next(g)
Traceback (most recent call last):
File "", line 1, in
StopIteration
我们讲过,generator保存的是算法,每次调用next(g),就计算出g的下一个元素的值,直到计算到最后一个元素,没有更多的元素时,抛出StopIteration的错误。
正确的方法是使用for循环,因为generator也是可迭代对象:
>>> g = (x * x for x in range(10))
>>> for n in g:
... print(n)
...
0
1
4
9
16
25
36
49
64
81
所以,我们创建了一个generator后,基本上永远不会调用next(),而是通过for循环来迭代它,并且不需要关心StopIteration的错误。
另举例,斐波那契数列,正常用循环的代码如下,没有用到generator
def
fib(max):
n
, a
, b =
0
,
0
,
1
while n < max:
print(b)
a
, b = b
, a + b
n = n +
1
return
'done'
print(fib(
10))
(注意:
注意
,赋值语句:
a, b = b, a + b
相当于:
t = (b, a + b) # t是一个tuple
a = t[
0]
b = t[
1])
也就是说,上面的函数和generator仅一步之遥。要把fib函数变成generator,只需要把print(b)改为
yield b就可以了:
这里,最难理解的就是generator和函数的执行流程不一样。函数是顺序执行,遇到return语句或者最后一行函数语句就返回。而变成
generator的函数,在每次调用next()的时候执行,遇到yield语句返回,再次执行时从上次返回的yield语句处继续执行。
所以使用yield就是一个生成器函数,每次yield好比一次导演喊cut,直到next无法输出值为止,如果要打印出结果,需要下面代码的下半段
def
fib(max):
n
, a
, b =
0
,
0
,
1
while n < max:
yield b
a
, b = b
, a + b
n = n +
1
return
'done'
x=fib(
5)
while True:
try:
y=
next(x)
print(
'fib(x):'
,y)
except
StopIteration
as e:
print(
'Generator return value:'
, e.value)
break
4.5 迭代器
我们已经知道,可以直接作用于for循环的数据类型有以下几种:
一类是集合数据类型,如list、tuple、dict、set、str等;
一类是generator,包括生成器和带yield的generator function。
这些可以直接作用于for循环的对象统称为可迭代对象:Iterable。
可以使用isinstance()判断一个对象是否是Iterable对象:
>>> import collections
>>> isinstance([1,2,3],collections.Iterable)
True
>>> isinstance({'a':1,'b':2,'c':3},collections.Iterable)
True
>>> isinstance('abc',collections.Iterable)
True
>>> isinstance((x for x in range(10)),collections.Iterable)
True
>>> isinstance(123,collections.Iterable)
False
可见list、tuple、dict、generator等都是interable的
而生成器不但可以作用于for循环,还可以被next()函数不断调用并返回下一个值,直到最后抛出StopIteration错误表示无法继续返回下一个值了。
可以被next()函数调用并不断返回下一个值的对象称为迭代器:Iterator。
同样的可以使用isinstance()判断一个对象是否是Iterator对象:
>>> import collections
>>> isinstance((x for x in range(10)),collections.Iterator)
True
>>> isinstance('abc',collections.Iterator)
False
>>> isinstance([1,2,3],collections.Iterator)
False
>>> isinstance({1,2,3},collections.Iterator)
False
>>>
总结:生成器都是Iterator对象,但list、dict、str虽然是Iterable,却不是Iterator。
我们如果要将list、dict、str等Iterable变成Iterator可以使用iter()函数:
凡是可作用于for循环的对象都是Iterable类型;
凡是可作用于next()函数的对象都是Iterator类型,它们表示一个惰性计算的序列;
Python的for循环本质上就是通过不断调用next()函数实现的,例如:
for x in [1, 2, 3, 4, 5]:
pass
实际上完全等价于:
# 首先获得Iterator对象:
it = iter([1, 2, 3, 4, 5])
# 循环:
while True:
try:
# 获得下一个值:
x = next(it)
except StopIteration:
# 遇到StopIteration就退出循环
break
5.函数式编程
5.1 高阶函数
传入函数
既然变量可以指向函数,函数的参数能接收变量,那么一个函数就可以接收另一个函数作为参数,这种函数就称之为高阶函数。
一个最简单的高阶函数:
def
add(x
, y
, f):
return f(x) + f(y)
print(add(
3
,-
4
,
abs))
5.1.1 map/reduce
Python内建了map()和reduce()函数。
我们先看map。
map()函数接收两个参数,一个是函数,一个是Iterable,map将传入的函数依次作用到序列的每个元素,并把结果作为新的Iterator返回。
下面的例子,把f(x)=x平方,用map函数代替
def
f(x):
return x * x
r =
map(f
, [
1
,
2
,
3
,
4
,
5
,
6
,
7
,
8
,
9])
print(
list(r))
/Users/wangyizhe/Desktop/pythonIDE/python3/venv/bin/python /Users/wangyizhe/Desktop/pythonIDE/python3/test.py
[1, 4, 9, 16, 25, 36, 49, 64, 81]
我们还可以通过map函数把一串数字转换成字符串
>>>
list(
map(
str, [1, 2, 3, 4, 5, 6, 7, 8, 9]))
['1', '2', '3', '4', '5', '6', '7', '8', '9']
reduce把一个函数作用在一个序列[x1, x2, x3, ...]上,这个函数必须接收两个参数,reduce把结果继续和序列的下一个元素做累积计算,其效果就是:
reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)
from functools
import reduce
def
fn(x
, y):
return x *
10 + y
print(reduce(fn
, [
1
,
3
,
5
,
7
,
9]))
例子 把L1的列表中所有名字首字母大写,其余小写打印出来
L1 = [
'adam'
,
'LISA'
,
'barT']
def
normalize(name):
s =
''
for k
,v
in
enumerate(name):
if(k ==
0):
s = v.upper()
else:
s = s + v.lower()
return s
L2 =
list(
map(normalize
, L1))
print(L2)
5.1.2 fliter函数
Python内建的filter()函数用于过滤序列。
和map()类似,filter()也接收一个函数和一个序列。和map()不同的是,filter()把传入的函数依次作用于每个元素,然后根据返回值是True还是False决定保留还是丢弃该元素。
例如下例子中在一个序列中筛选出来奇数
def
is_odd(n):
return n %
2 ==
1
print(
list(
filter(is_odd
, [
1
,
2
,
4
,
5
,
6
,
9
,
10
,
15])))
/Users/wangyizhe/Desktop/pythonIDE/python3/venv/bin/python /Users/wangyizhe/Desktop/pythonIDE/python3/test.py
[1, 5, 9, 15]
可见用filter()这个高阶函数,关键在于正确实现一个“筛选”函数。
注意到filter()函数返回的是一个Iterator,也就是一个惰性序列,所以要强迫filter()完成计算结果,需要用list()函数获得所有结果并返回list。
5.1.3 sort函数
排序也是在程序中经常用到的算法。无论使用冒泡排序还是快速排序,排序的核心是比较两个元素的大小。如果是数字,我们可以直接比较,但如果是字符串或者两个dict呢?直接比较数学上的大小是没有意义的,因此,比较的过程必须通过函数抽象出来。
Python内置的sorted()函数就可以对list进行排序:
>>> sorted([36, 5, -12, 9, -21])
[-21, -12, 5, 9, 36]
此外,sorted()函数也是一个高阶函数,它还可以接收一个key函数来实现自定义的排序,例如按绝对值大小排序:
5.2 返回函数
函数作为返回值
高阶函数除了可以接受函数作为参数外,还可以把函数作为结果值返回。
我们来实现一个可变参数的求和。通常情况下,求和的函数是这样定义的
5.3 匿名函数
当我们在传入函数时,有些时候,不需要显式地定义函数,直接传入匿名函数更方便。
在Python中,对匿名函数提供了有限支持。还是以map()函数为例,计算f(x)=x2时,除了定义一个f(x)的函数外,还可以直接传入匿名函数:
>>> list(map(lambda x: x * x, [1, 2, 3, 4, 5, 6, 7, 8, 9]))
[1, 4, 9, 16, 25, 36, 49, 64, 81]
通过对比可以看出,匿名函数lambda x: x * x实际上就是:
def f(x):
return x * x
关键字lambda表示匿名函数,冒号前面的x表示函数参数。
匿名函数有个限制,就是只能有一个表达式,不用写return,返回值就是该表达式的结果。
用匿名函数有个好处,因为函数没有名字,不必担心函数名冲突。此外,匿名函数也是一个函数对象,也可以把匿名函数赋值给一个变量,再利用变量来调用该函数:
>>> f = lambda x: x * x
>>> f
at 0x101c6ef28>
>>> f(5)
25
同样,也可以把匿名函数作为返回值返回,比如:
def build(x, y):
return lambda: x * x + y * y
5.4 装饰器
由于函数也是一个对象,而且函数对象可以被赋值给变量,所以,通过变量也能调用该函数。
def
wyz():
print(
'aaa')
f = wyz
f()
print(f.
__name__)
/Users/wangyizhe/Desktop/pythonIDE/python3/venv/bin/python /Users/wangyizhe/Desktop/pythonIDE/python3/test.py
aaa
wyz
函数对象有一个__name__属性,可以拿到函数的名字:
现在,
假设我们要增强wyz()函数的功能,比如,在函数调用前后自动打印日志,但又不希望修改wyz()函数的定义,这种在代码运行期间动态增加功能的方式,称之为“装饰器”(Decorator)。
def
hello(name):
def
wrapper():
print(
"hello, %s" % name.
__name__)
name()
print(
"goodby, %s" % name.
__name__)
return wrapper
@hello
def
wyz():
print(
'i am wyz')
wyz()
/Users/wangyizhe/Desktop/pythonIDE/python3/venv/bin/python /Users/wangyizhe/Desktop/pythonIDE/python3/test.py
hello, wyz
i am wyz
goodby, wyz
上面的例子,首先定义一个函数wyz,然后将wyz函数当做参数,回传至hello函数中,
@hello
def wyz():
print('i am wyz’)
被解释成了:
wyz = hello(wyz)
换个角度更深入的理解下装饰器,可以看一下下面一行简单的代码
def
test(name):
print(
"test %s!" % name.
__name__[::-
1].upper())
@test
def
wyz():
pass
/Users/wangyizhe/Desktop/pythonIDE/python3/venv/bin/python /Users/wangyizhe/Desktop/pythonIDE/python3/test.py
test ZYW!
可以看到,上面的代码中,wyz函数没有内容,但是在test函数中被当做参数调用了,并且倒置了且大写,结果就是test ZYW!
5.5 偏函数
Python的functools模块提供了很多有用的功能,其中一个就是偏函数(Partial function)。要注意,这里的偏函数和数学意义上的偏函数不一样。
在介绍函数参数的时候,我们讲到,通过设定参数的默认值,可以降低函数调用的难度。而偏函数也可以做到这一点。举例如下:
int()函数可以把字符串转换为整数,当仅传入字符串时,int()函数默认按十进制转换:
>>> int('12345')
12345
但int()函数还提供额外的base参数,默认值为10。如果传入base参数,就可以做N进制的转换:
>>> int('12345', base=8)
5349
>>> int('12345', 16)
74565
假设要转换大量的二进制字符串,每次都传入int(x, base=2)非常麻烦,于是,我们想到,可以定义一个int2()的函数,默认把base=2传进去:
def
int2(x
, base=
2):
return
int(x
, base)
print(int2(
'10010001'))
这样,我们转换二进制就非常方便了:
>>> int2('1000000')
64
>>> int2('1010101')
85
functools.partial就是帮助我们创建一个偏函数的,不需要我们自己定义int2(),可以直接使用下面的代码创建一个新的函数int2:
import functools
int2 = functools.partial(
int
,
base=
2)
print(int2(
'1000
’))