前面的话:切片是Python非常强大的操作,适合于经常指定索引范围的操作,此时用循环时十分繁琐,但用Python提供的切片(Slice)操作符能大大简化这种操作。
我们先来看一个简单的示例,感受一下切片的强大。
假如我们要取一个list或者tuple中的部分元素,比如一个list如下:
`L = ["AFeng", "Javascript", "Python", "CSS", "HTML"]
取前三个元素,我们该怎么做?
最传统的方法:
但当我们需求改变时,这种方式就不再使用。比如取前n个元素。我们可是使用for循环实现。
>>> r = []
>>> n = 3
>>> for i in range(n):
r.append(L[i])
>>> r
['AFeng', 'Javascript', 'Python']
针对这种繁琐的操作,我们使用切片,一行代码就可以实现等效的操作。
>>> L[0:3]
['AFeng', 'Javascript', 'Python']
注意:索引是从0开始的,且这种规则是包左不包右。也就是取的是下标为0、1、2的元素。到下标为3时已经截止了。
索引范围可以从任意开始到任意截止。
在Python中,最强大的之处莫过于支持负索引,因此,可以像下面这样操作。
>>> L[-3:] #那么省略的默认是什么呢?
['Python', 'CSS', 'HTML']
>>> L[-3:0]
[]
# 经过验证省略的默认是取到最后
注意点:倒数第一个元素的索引是-1。
切片参数详解:L【start, stop, step】分别代表的是起始、截止和步长。
如何复制一个列表到另一个列表。
>>> L[:]
['AFeng', 'Javascript', 'Python', 'CSS', 'HTML']
tuple也可以看成一种特殊的list,唯一区别就是tuple具有不可变性。也可以对其进行切片操作,只不过操作的结果仍是一个tuple。
同时字符串也是一种list,每一个元素就是一个字符。因此字符串也可以进行切片操作。操作的结果仍是一个字符串。
迭代:通过for循环遍历一个可迭代对象的方式称为迭代(Iteration)。
在Python中,迭代是通过for in
来完成的。在很多其他语言中,遍历是通过下标完成的。这是因为Python的for循环的抽象程度更高。因为在Python中的for循环不仅可以用在list或者tuple上,还可以用在一切可迭代对象上,比如字符串,字典,集合等。
遍历一个字典:
>>> dicted = {"name": "AFeng", "age": 18, "love": "web"}
>>> for key in dicted:
print(key)
name
age
love
从输出可以看到,默认情况下,对于字典来说,迭代是key,并且输出的顺序是任意的。但如果我们想迭代值,可以使用for value in dicted.values
,如果同时迭代key和value,可以使用for k, v in dicted.items()
。这种操作是把字典中的每一个键值对当做一个整体来处理。
当我们使用for循环时,只要作用于一个可迭代对象,for循环就能正常运行。
那么我们如何判断一个对象是不是可迭代对象了,方法是通过collections模块的Iterable类型来判断。具体如下:
>>> from collections import Iterable
>>> isinstance("AFeng", Iterable)
True
>>> isinstance([1, 2, 3], Iterable)
True
>>> isinstance({"name": "AFeng"}, Iterable)
True
>>> isinstance((1, 2, 3), Iterable)
True
>>> isinstance({"name", "age"}, Iterable)
True
经过以上测试,我们发现了5中可迭代对象。字符串、列表、元组、字典、集合。
如果我们真的想通过下标访问可迭代对象,该怎么办?可以使用Python内置的enumerate函数,可以把一个list变成索引——元素对。
# 列表
>>> for i ,value in enumerate([1, 2, 3]):
print(i, value)
0 1
1 2
2 3
# 字典
>>> for i, value in enumerate({"name": "AFeng", "age": 18}):
print(i, value)
0 name
1 age
列表生成式:即就是List Comprehensions,是Python内置的非常简单但却很强大的,用来创建List的生成式。
结合使用list函数和range函数,我们几乎可以生成我们想要的列表。但对于具有运算参与的列表,我们就要使用列表生成式。
>>> list(range(0,10))
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> list(range(0, 100, 5))
[0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95]
range函数的三个参数和切片操作的三个参数用法类似。
但如果我们想生成[1x1, 2x2, 3x3, …, 10x10]或者立方的形式,方法一就是使用循环。
>>> L = []
>>> for i in range(1,11):
L.append(i*i)
>>> L
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
我们使用列表生成式一行代码就可以达到上面的效果。
>>> [i*i for i in range(1, 11)]
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
for循环后面还可以加上if判断,从而可以控制我们想要生成的项。
>>> [i*i for i in range(0, 20) if i%5 == 0]
[0, 25, 100, 225]
生成器
通过列表生成式,我们可以直接创建一个列表,但是,受到内存空间的限制,创建的列表容量肯定是有限的。所以,列表元素如果可以按照某种算法推算出来,我们在循环的过程中不断推算后续元素,这样就不必创建完整的list,从而节省了内存空间,在Python中,这种一边循环一边计算的机制叫做生成器(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
<generator object <genexpr> at 0x03290FC0>
我们直接访问一个生成器对象,打印出来的是一个对象表示。而不是具体的数值。
我们可以通过next()函数获得generator的下一个返回值。
>>> next(g)
0
>>> for n in g:
print(n)
1
4
9
16
25
36
49
64
81
generator保存的是算法,每次调用next(g),就会计算出g的下一个元素的值,直到计算到最后一个值,没有任何元素时,抛出StopIteration的错误。
生成器函数
我们先看一个简单的例子
>>> def generator_function():
print("hello 1")
yield 1
print("hello 2")
yield 2
print("hello 3")
>>> g = generator_function()
#函数没有立即执行,而是返回一个生成器,当然也是一个迭代器。
>>> g
<generator object generator_function at 0x03D30780>
# 使用next(g)的时候就开始执行,遇到yield暂停。
>>> next(g)
hello 1
1
# 从原来暂停的地方继续执行
>>> next(g)
hello 2
2
# 从原来暂停的地方继续执行,没有 yield,抛出异常
>>> next(g)
hello 3
Traceback (most recent call last):
File "<pyshell#7>", line 1, in <module>
next(g)
StopIteration
可以看到,上面的函数没有return语句返回值,而是使用yield出生一个值,一个带有yield
的函数就是一个生成器,当我们使用yeild
时,它自动帮我们创建了__iter()__
和__next()
方法,而且在没有数据时,也会抛出StopIteration异常。这样,我们不费吹灰之力就获得了一个迭代器。
下面分析带有yield
函数的执行过程。
- 调用该函数的时候,并不会立即执行代码,而是返回一个生成器对象。
- 当使用
next()
(在for循环中会自动调用next())作用于返回的生成器对象时,函数开始执行,在遇到yield
时会暂停,并返回当前的迭代值。 - 当再次使用next()函数的时候,函数从原来暂停的地方开始执行,直到遇到
yield
语句,如果没有遇到yield
语句,则抛出异常。
整个过程看起来就是不断地执行-->中断-->执行-->中断
的过程。刚开始,调用生成器函数的时候,函数不会立即执行,而是返回一个生成器对象;然后,当我们使用 next() 作用于它的时候,它开始执行,遇到 yield 语句的时候,执行被中断,并返回当前的迭代值,要注意的是,此刻会记住中断的位置和所有的变量值,也就是执行时的上下文环境被保留起来;当再次使用 next() 的时候,从原来中断的地方继续执行,直至遇到 yield,如果没有 yield,则抛出异常。
简而言之:就是next使函数执行,yield
使函数暂停。
我们来看一个 Fibonacci 数列的示例:
我们先用自定义迭代器的方法解决
class Fib(object):
def __init__(self):
self.a, self.b = 0, 1
def __iter__(self):
return self
def __next__(self):
self.a, self.b = self.b, self.a + self.b
return self.a
f = Fib()
for item in f:
if item > 10:
break
print(item)
1
1
2
3
5
8
>>>
我们通过在自定义类中实现__iter__
和__next__
方法,从而构造了一个迭代器。
而使用生成器的方法,是这样的。
def fib():
a, b = 0, 1
while True:
a, b = b, a+b
yield a
f = fib()
for item in f:
if item > 10:
break
print(item)
可以看到,使用生成器的方法非常简洁,不用自定义__iter__
和__next__
方法。
最后,我们可以看到用函数解决这个问题:
def fib(max):
n, a, b = 0, 0, 1
while n < max:
print(b)
a, b = b, a+b
n = n + 1
return "Done"
fib(6)
生成器的进阶使用
我们除了能对生成器进行迭代使它返回值外,还可以:
- 使用
send()
方法给它发送消息; - 使用
throw()
方法给它发送异常; - 使用
close()
方法关闭生成器;
send()方法的使用
>>> def generator_function():
value1 = yield 0
print("value1 is", value1)
value2 = yield 1
print("value2 is", value2)
value3 = yield 2
print("value3 is", value3)
>>> g = generator_function()
>>> next(g)
0
>>> g.send(2)
value1 is 2
1
>>> g.send(3)
value2 is 3
2
>>> g.send(4)
value3 is 4
Traceback (most recent call last):
File "<pyshell#7>", line 1, in <module>
g.send(4)
StopIteration
>>>
在上面的代码中,我们先调用next()
方法,使函数开始执行,代码执行到yield 0
的时候暂停,它返回了0;接着,我们执行send()方法,它会恢复生成器的运行,并将发送的值赋值给上次中断时yield表达式的执行结果,也就是value1,这时控制台打印出value1的值,并继续执行,直到遇到yield后暂停,此时返回1。
简单地说,send()
方法就是next()
的功能,加上传值给yield
。
throw()方法
除了可以给生成器传值,我们还可以给它传异常。
>>> def generator_function():
... try:
... yield 'Normal'
... except ValueError:
... yield 'Error'
... finally:
... print 'Finally'
...
>>> g = generator_function()
>>> g.next()
'Normal'
>>> g.throw(ValueError)
'Error'
>>> g.next()
Finally
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
throw()方法向生成器函数传递了ValueError异常,此时代码进入except ValueError语句,遇到yield “Error”,暂停并返回Error字符串。
简单的说,throw()就是next()的功能,加上传异常的yield。
close()方法
我们可以使用close()方法来关闭一个生成器。生成器被关闭后,再次调用next()方法,无论会不会遇到yield关键字,都会抛出StopIteration异常。
>>> def generator_function():
yield 1
yield 2
yield 3
>>> g = generator_function()
>>> next(g)
1
# 生成器被关闭了,再次调用就会抛出异常。
>>> g.close()
>>> next(g)
Traceback (most recent call last):
File "<pyshell#9>", line 1, in <module>
next(g)
StopIteration