切片
取一个list或tuple的部分元素是非常常见的操作.
对一个kist, 取前3个元素, 应该怎么做?
笨方法, 用索引直接取前3个元素.
如果是取前N个元素, 也就是索引为0-(N-1)的元素, 可以用循环.
对这种经常取指定索引范围的操作, 用循环十分繁琐. 因此, Python提供了切片(Slice)操作符, 能大大简化这种操作.
Python 切片是一种访问列表或字符串的简便方法.
切片语法格式:
name[start:stop:step]
- start: 切片开始的索引, 默认为 0.
- stop: 切片结束的索引, 默认为列表或字符串的长度.
- step: 切片的步长, 默认为 1.
示例:
# 切片
my_list = [1, 2, 3, 4, 5]
print(my_list[:3]) # 访问列表的前 3 个元素 [1, 2, 3]
print(my_list[-3:]) # 切片从倒数第 3 个元素开始到结尾 [3, 4, 5]
print(my_list[::2]) # 切片步长为 2 [1, 3, 5]
print(my_list[::-1]) # 切片步长为 -1 [5, 4, 3, 2, 1]
print(my_list[1:4:2]) # 切片步长为 2, 访问列表的第 2, 4 个元素 [2, 4]
print(my_list[:]) # 切片不指定索引, 复制整个列表 [1, 2, 3, 4, 5]
在很多编程语言中, 针对字符串提供了很多各种截取函数(例如substring), 其实目的就是对字符串切片. Python没有针对字符串的截取函数, 只需要切片一个操作就可以完成, 非常简单.
迭代
如果给定一个list或tuple, 我们可以通过for循环来遍历这个list或tuple, 这种遍历我们称为迭代(Iteration).
在Python中, 迭代是通过for ... in
来完成的, 而很多语言比如C语言, 迭代list是通过下标完成的, 比如C代码:
for (i=0; i<length; i++) {
n = list[i];
}
可以看出, Python的for循环抽象程度要高于C的for循环,因为Python的for循环不仅可以用在list或tuple上, 还可以作用在其他可迭代对象上.
list这种数据类型虽然有下标, 但很多其他数据类型是没有下标的, 但是, 只要是可迭代对象, 无论有无下标, 都可以迭代, 比如dict就可以迭代:
因为dict的存储不是按照list的方式顺序排列, 所以迭代出的结果顺序很可能不一样.
默认情况下for key in dict
, dict迭代的是key. 如果要迭代value, 可以用for value in dict.values()
, 如果要同时迭代key和value, 可以用for keys, values in dict.items()
.
dict = {'a': 1, 'b': 2, 'c': 3}
for key in dict:
print(key) # 输出 a b c
for value in dict.values():
print(value) # 输出 1 2 3
for keys, values in dict.items():
print(keys, values) # 输出 a 1 b 2 c 3
由于字符串也是可迭代对象, 因此也可以作用于for循环:
string = 'hello world'
for char in string:
print(char) # 输出 h e l l o w o r l d
所以当我们使用for循环时, 只要作用于一个可迭代对象, for循环就可以正常运行, 而我们不太关心该对象究竟是list还是其他数据类型.
推导式
Python 推导式是一种独特的数据处理方式, 可以从一个数据序列构建另一个新的数据序列的结构体.
Python 推导式是一种强大且简洁的语法, 适用于生成列表, 字典, 集合和生成器。
在使用推导式时, 需要注意可读性, 尽量保持表达式简洁, 以免影响代码的可读性和可维护性.
Python 支持各种数据结构的推导式:
- 列表(list)推导式
- 字典(dict)推导式
- 集合(set)推导式
- 元组(tuple)推导式
列表推导式
列表推导式是一种创建列表的简洁方式.
语法格式:
[表达式 for 变量 in 列表]
[out_exp_res for out_exp in input_list]
或者 [表达式 for 变量 in 列表 if 条件] [out_exp_res for out_exp in input_list if condition]
- 表达式: 用于生成元素的表达式, 可以是任意有效的 Python 表达式, 如变量赋值, 算术运算, 条件语句, 函数调用等.
- 变量: 用于迭代的变量, 一般是列表中的元素, 也可以是其他可迭代对象中的元素.
- 列表: 用于迭代的列表, 可以是任意可迭代对象, 如列表, 元组, 字符串, 字典等.
- 条件: 用于过滤元素的条件, 只有满足条件的元素才会被包含到结果中.
示例:
# 列表推导式
# 计算列表中每个元素的平方
squares = [x**2 for x in range(1, 11)]
print(squares) # [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
# 列表推导式
# 计算列表中每个元素的平方, 并过滤掉大于 5 的元素
squares = [x**2 for x in range(1, 11) if x**2 <= 25]
print(squares) # [1, 4, 9, 16, 25]
字典推导式
字典推导式是一种创建字典的简洁方式.
语法格式:
{ key_expr: value_expr for value in collection }
或
{ key_expr: value_expr for value in collection if condition }
- key_expr: 用于生成键的表达式, 可以是任意有效的 Python 表达式, 如变量赋值, 算术运算, 条件语句, 函数调用等.
- value_expr: 用于生成值的表达式, 可以是任意有效的 Python 表达式, 如变量赋值, 算术运算, 条件语句, 函数调用等.
- collection: 用于迭代的集合, 可以是任意可迭代对象, 如列表, 元组, 字符串, 字典等.
- condition: 用于过滤元素的条件, 只有满足条件的元素才会被包含到结果中.
示例:
# 字典推导式
# 使用字符串及其长度创建字典
string_dict = {s: len(s) for s in ['apple', 'banana', 'orange']}
print(string_dict) # {'apple': 5, 'banana': 6, 'orange': 6}
# 字典推导式
#提供三个数字,以三个数字为键,三个数字的平方为值来创建字典
num_dict = {num: num**2 for num in [1, 2, 3]}
print(num_dict) # {1: 1, 2: 4, 3: 9}
集合推导式
集合推导式是一种创建集合的简洁方式.
语法格式:
{ expression for item in Sequence }
或
{ expression for item in Sequence if conditional }
示例:
# 集合推导式
# 计算集合中每个元素的平方
squares = {x**2 for x in range(1, 11)}
print(squares) # {1, 4, 9, 16, 25, 36, 49, 64, 81, 100}
# 集合推导式
# 判断不是 abc 的字母并输出
letters = {'a', 'b', 'c', 'd', 'e'}
filtered_letters = {letter for letter in letters if letter not in 'abc'}
print(filtered_letters) # {'d', 'e'}
元组推导式(生成器表达式)
元组推导式是一种创建元组的简洁方式.元组推导式可以利用 range 区间, 元组, 列表, 字典和集合等数据类型, 快速生成一个满足指定需求的元组.
语法格式:
(expression for item in Sequence)
或
(expression for item in Sequence if conditional)
元组推导式和列表推导式的用法也完全相同, 只是元组推导式是用 () 圆括号将各部分括起来, 而列表推导式用的是中括号 [], 另外元组推导式返回的结果是一个生成器对象.
示例:
# 元组推导式
# 生成一个包含数字 1~9 的元组
numbers = (num for num in range(1, 10))
print(numbers) # <generator object <genexpr> at 0x0000018260D76740>
print(type(numbers)) # <class 'generator'>
print(list(numbers)) # 使用list()函数将生成器对象转换为列表, 输出 [1, 2, 3, 4, 5, 6, 7, 8, 9]
迭代器
可以直接作用于for循环的数据类型有以下几种:
- 基本数据类型,如list, tuple, dict, set, str等;
- 生成器generator, 包括生成器和带yield的generator function.
这些可以直接作用于for循环的对象统称为可迭代对象:Iterable.
可以使用isinstance(name, Iterable)
函数判断一个对象是否是可迭代对象.
可迭代对象除了可以用for循环外, 还可以被next()函数调用, 得到一个迭代器对象.
可以被next()
函数调用并不断返回下一个值的对象称为迭代器.
可以用isinstance(name, Iterator)
函数判断一个对象是否是迭代器.
生成器都是Iterator对象, 但list, dict, str等虽然是Iterable, 却不是Iterator.
把list, dict, str等Iterable变成Iterator可以使用iter()
函数:
# 将列表转换为迭代器
my_list = [1, 2, 3, 4, 5]
my_iter = iter(my_list)
print(my_iter) # <list_iterator at 0x0000018260D76740>
print(type(my_iter)) # <class 'list_iterator'>
print(next(my_iter)) # 1
print(next(my_iter)) # 2
Iterator的计算是惰性的, 只有在需要返回下一个数据时它才会计算.
- 凡是可作用于for循环的对象都是Iterable类型;
- 凡是可作用于next()函数的对象都是Iterator类型,它们表示一个惰性计算的序列.
Python的for循环本质上就是通过不断调用next()函数实现的.
for x in [1, 2, 3, 4, 5]:
pass
完全等价于:
it = iter([1, 2, 3, 4, 5])
while True:
try:
x = next(it)
except StopIteration:
break
生成器
通过列表生成式, 我们可以直接创建一个列表. 但是受到内存限制, 列表容量肯定是有限的. 而且创建一个包含100万个元素的列表, 不仅占用很大的存储空间, 如果我们仅仅需要访问前面几个元素, 后面绝大多数元素占用的空间都白白浪费了.
所以如果列表元素可以按照某种算法推算出来, 那我们是否可以在循环的过程中不断推算出后续的元素呢? 这样就不必创建完整的list, 从而节省大量的空间. 在Python中, 这种一边循环一边计算的机制, 称为生成器: generator.
要创建一个generator,有很多种方法.
第一种方法很简单, 只要把一个列表生成式的[]改成(), 就创建了一个generator:
L = [x**2 for x in range(10)]
print(L) # [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
g = (x**2 for x in range(10))
print(g) # <generator object <genexpr> at 0x0000018260D76740>
print(type(g)) # <class 'generator'>
我们可以使用next()
函数获取generator的下一个返回值, 直到没有更多的元素时抛出StopIteration
错误时退出循环.
当然, 我们也可以用for
循环来迭代generator.
generator非常强大, 如果推算的算法比较复杂, 用类似列表生成式的for循环无法实现的时候, 还可以用函数来实现(函数将在后面讲到).
比如, 著名的斐波拉契数列(Fibonacci), 除第一个和第二个数外, 任意一个数都可由前两个数相加得到.
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函数实际上是定义了斐波拉契数列的推算规则, 可以从第一个元素开始, 推算出后续任意的元素, 这种逻辑其实非常类似generator.要把fib函数变成generator函数, 只需要把print(b)改为yield b就可以了.
def fib(max):
n, a, b = 0, 0, 1
while n < max:
yield b
a, b = b, a + b
n = n + 1
return 'done'
这就是定义generator的另一种方法,.
在 Python 中, 使用了 yield 的函数被称为生成器(generator).
生成器函数是一个带yield关键字的函数, 它返回一个生成器对象.
与普通函数不同的是, 生成器是一个返回迭代器的函数, 它不存储所有结果, 而是每次迭代时生成一个结果.更简单点理解生成器就是一个迭代器.
最难理解的就是generator函数和普通函数的执行流程不一样. 普通函数是顺序执行, 遇到return语句或者最后一行函数语句就返回. 而变成generator的函数, 在每次调用next()的时候执行, 遇到yield语句返回, 再次执行时从上次返回的yield语句处继续执行.
# 斐波拉契数列的生成器函数
def fib(max):
n, a, b = 0, 0, 1
while n < max:
yield b
a, b = b, a + b
n = n + 1
return 'done'
# 调用生成器函数
g = fib(6)
print(g) # <generator object fib at 0x0000018260D76740>
print(type(g)) # <class 'generator'>
# 迭代生成器
for n in g:
print(n) # 1 1 2 3 5 8
注意:调用generator函数会创建一个generator对象, 多次调用generator函数会创建多个相互独立的generator
生成器函数的优势是它们可以按需生成值, 避免一次性生成大量数据并占用大量内存. 此外生成器还可以与其他迭代工具(如for循环)无缝配合使用, 提供简洁和高效的迭代方式.