基本思想:在Python中,代码不是越多越好,而是越少越好。代码不是越复杂越好,而是越简单越好。
1.1 切片
L[0:3] 表示,从索引0开始取,直到索引3为止,但不包括索引3。即索引0,1,2,正好是3个元素。
Python支持 L[-1] 取倒数第一个元素,而且支持倒数切片(逆序)。
甚至什么都不写,只写[:]就可以原样复制一个list
练习:1_1_Slice.py
利用切片操作,实现一个trim()函数,去除字符串首尾的空格,注意不要调用str的strip()方法:
# -*- coding: utf-8 -*-
def trim(s):
# if s != '':
# while s[0] == ' ': # 要求s至少有一个元素,如果遇到'',会报错IndexError: string index out of range
# s = s[1:]
# while s[-1] == ' ':
# s = s[:-1]
# return s
while s[:1]==' ':
s=s[1:]
while s[-1:]==' ':
s=s[:-1]
return s
# 测试:
if trim('hello ') != 'hello':
print('测试失败!')
elif trim(' hello') != 'hello':
print('测试失败!')
elif trim(' hello ') != 'hello':
print('测试失败!')
elif trim(' hello world ') != 'hello world':
print('测试失败!')
elif trim('') != '':
print('测试失败!')
elif trim(' ') != '':
print('测试失败!')
else:
print('测试成功!')
1.2 迭代
在Python中,迭代是通过for ... in来完成的。而很多语言比如C语言,迭代list是通过下标完成的
也就是说,Python的for循环抽象程度要高于C的for循环。因为Python的for循环不仅可以用在list或tuple上,还可以作用在其他可迭代对象上。
那么,**如何判断一个对象是可迭代对象呢?**方法是通过collections模块的Iterable类型判断:
>>> from collections import Iterable
>>> isinstance('abc', Iterable) # str是否可迭代
True
>>> isinstance([1,2,3], Iterable) # list是否可迭代
True
>>> isinstance(123, Iterable) # 整数是否可迭代
False
**如果要对list实现类似Java那样的下标循环怎么办?**Python内置的enumerate函数可以把一个list变成索引-元素对,这样就可以在for循环中同时迭代索引和元素本身:
>>> for i, value in enumerate(['A', 'B', 'C']):
... print(i, value)
...
0 A
1 B
2 C
练习:1_2_For.py
请使用迭代查找一个list中最小和最大值,并返回一个tuple:
# -*- coding: utf-8 -*-
def findMinAndMax(L):
my_max = None
my_min = None
if L is None: # 特殊处理 L是None的情况
return None, None
else:
for i in L:
if my_max is None and my_min is None: # 初始化
my_max = i
my_min = i
else:
if i > my_max:
my_max = i
if i < my_min:
my_min = i
return my_min, my_max
# 测试
if findMinAndMax([]) != (None, None):
print('测试失败!')
elif findMinAndMax([7]) != (7, 7):
print('测试失败!')
elif findMinAndMax([7, 1]) != (1, 7):
print('测试失败!')
elif findMinAndMax([7, 1, 3, 9, 5]) != (1, 9):
print('测试失败!')
else:
print('测试成功!')
1.3 列表生成式
列表生成式即List Comprehensions
如果要**生成[1x1, 2x2, 3x3, ..., 10x10]**怎么做?
>>> [x * x for x in range(1, 11)]
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
for循环后面还可以加上if判断,这样我们就可以筛选出仅偶数的平方:
>>> [x * x for x in range(1, 11) if x % 2 == 0]
[4, 16, 36, 64, 100]
还可以使用两层循环,可以生成全排列:
>>> [m + n for m in 'ABC' for n in 'XYZ']
['AX', 'AY', 'AZ', 'BX', 'BY', 'BZ', 'CX', 'CY', 'CZ']
使用列表生成式的时候,有些童鞋经常搞不清楚**if...else的用法**
例如,以下代码正常输出偶数:
>>> [x for x in range(1, 11) if x % 2 == 0]
[2, 4, 6, 8, 10]
**情况1:**但是,我们不能在最后的if加上else:
>>> [x for x in range(1, 11) if x % 2 == 0 else 0]
File "<stdin>", line 1
[x for x in range(1, 11) if x % 2 == 0 else 0]
^
SyntaxError: invalid syntax
这是因为跟在for后面的if是一个筛选条件,不能带else,否则如何筛选?
**情况2:**另一些童鞋发现把if写在for前面必须加else,否则报错:
>>> [x if x % 2 == 0 for x in range(1, 11)]
File "<stdin>", line 1
[x if x % 2 == 0 for x in range(1, 11)]
^
SyntaxError: invalid syntax
这是因为for前面的部分是一个表达式,它必须根据x计算出一个结果。
因此,考察表达式:x if x % 2 == 0,它无法根据x计算出结果,因为缺少else,必须加上else:
>>> [x if x % 2 == 0 else -x for x in range(1, 11)]
[-1, 2, -3, 4, -5, 6, -7, 8, -9, 10]
上述for前面的表达式x if x % 2 == 0 else -x才能根据x计算出确定的结果。
小结:
可见,在一个列表生成式中,for前面的if ... else是表达式,而for后面的if是过滤条件,不能带else。
练习:1_3_list.py
如果list中既包含字符串,又包含整数,由于非字符串类型没有lower()方法,所以列表生成式会报错:
请修改列表生成式,通过添加if语句保证列表生成式能正确地执行:
# -*- coding: utf-8 -*-
L1 = ['Hello', 'World', 18, 'Apple', None]
# L2 = [x.lower() if isinstance(x, str) else x for x in L1]
L2 = [x.lower() for x in L1 if isinstance(x, str)]
['hello', 'world', 'apple']
测试通过!
1.4 生成器
通过列表生成式,我们可以直接创建一个列表。但是,受到内存限制,列表容量肯定是有限的。而且,创建一个包含100万个元素的列表,会占用很大的存储空间
试试不创建完整的list,从而节省大量的空间?在Python中,这种一边循环一边计算的机制,称为生成器:generator。
创建一个generator,有很多种方法。
法1:改[]为()
很简单,只要把一个列表生成式的[]改成(),就创建了一个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 0x1022ef630>
创建L和g的区别仅在于最外层的[]和(),L是一个list,而g是一个generator。
我们可以直接打印出list的每一个元素,但我们怎么打印出generator的每一个元素呢?
如果要一个一个打印出来,可以通过next()函数获得generator的下一个返回值:但是更好的是用for循环
>>> 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的错误。
法2:yield关键字
定义generator的另一种方法。如果一个函数定义中包含yield关键字,那么这个函数就不再是一个普通函数,而是一个generator:
在执行过程中,遇到yield就中断,下次又继续执行。
把函数改成generator后,我们基本上从来不会用next()来获取下一个返回值,而是直接使用for循环来迭代:
练习:1_4_generator.py
杨辉三角定义如下:
1
/ \
1 1
/ \ / \
1 2 1
/ \ / \ / \
1 3 3 1
/ \ / \ / \ / \
1 4 6 4 1
/ \ / \ / \ / \ / \
1 5 10 10 5 1
把每一行看做一个list,试写一个generator,不断输出下一行的list:
# -*- coding: utf-8 -*-
def triangles(): # 参考代码
x = [1] # 第一次
while True:
yield x # 【重点理解】在执行过程中,遇到yield就中断并返回,下次又从此处开始继续执行
y = [0] + x + [0] # 学到了:① 先根据旧的x构造一个临时的y
x = [y[i] + y[i + 1] for i in range(len(y) - 1)] # ② 再根据y构造新的x
pass
# 开始测试
n = 0
results = []
for t in triangles():
results.append(t)
n = n + 1
if n == 10:
break
for t in results:
print(t)
if results == [
[1],
[1, 1],
[1, 2, 1],
[1, 3, 3, 1],
[1, 4, 6, 4, 1],
[1, 5, 10, 10, 5, 1],
[1, 6, 15, 20, 15, 6, 1],
[1, 7, 21, 35, 35, 21, 7, 1],
[1, 8, 28, 56, 70, 56, 28, 8, 1],
[1, 9, 36, 84, 126, 126, 84, 36, 9, 1]
]:
print('测试通过!')
else:
print('测试失败!')
注意区分普通函数和generator函数:
普通函数调用直接返回结果:
>>> r = abs(6)
>>> r
6
generator函数的“调用”实际返回一个generator对象:
>>> g = fib(6)
>>> g
<generator object fib at 0x1022ef948>
1.5 迭代器
可迭代对象:Iterable
可以直接作用于for循环的对象统称为可迭代对象:Iterable
包括:
- 集合数据类型
- 如
list、tuple、dict、set、str等
- 如
- generator
- 包括生成器、带
yield的generator function
- 包括生成器、带
可以使用isinstance()判断一个对象是否是Iterable对象:
>>> from collections.abc import Iterable # 【记】
>>> isinstance([], Iterable)
True
>>> isinstance({}, Iterable)
True
>>> isinstance('abc', Iterable)
True
>>> isinstance((x for x in range(10)), Iterable)
True
>>> isinstance(100, Iterable)
False
迭代器:Iterator
可以被next()函数调用并不断返回下一个值的对象称为迭代器:Iterator
可以使用isinstance()判断一个对象是否是Iterator对象:
>>> from collections.abc import Iterator # 【记】
>>> isinstance((x for x in range(10)), Iterator)
True
>>> isinstance([], Iterator)
False
>>> isinstance({}, Iterator)
False
>>> isinstance('abc', Iterator)
False
生成器都是Iterator对象,可以被next()函数调用并不断返回下一个值。
但list、dict、str虽然是Iterable,却不是Iterator。
把list、dict、str等Iterable变成Iterator可以使用iter()函数:
>>> isinstance(iter([]), Iterator)
True
>>> isinstance(iter('abc'), Iterator)
True
为什么 list、dict、str等数据类型不是Iterator?
因为Iterator的计算是惰性的,只有在需要返回下一个数据时它才会计算。
Iterator甚至可以表示一个无限大的数据流,例如全体自然数。而使用list是永远不可能存储全体自然数的。
小结
- 凡是可作用于
for循环的对象都是Iterable类型; - 凡是可作用于
next()函数的对象都是Iterator类型,它们表示一个惰性计算的序列; - 集合数据类型如
list、dict、str等是Iterable但不是Iterator,不过可以通过iter()函数获得一个Iterator对象。 - Python的
for循环本质上就是通过不断调用next()函数实现的。