3.3 元组与生成器表达式

3.3.1 元组创建与元素访问

可以把元组看作是轻量级列表或者简化版列表,支持与列表类似的操作,但功能不如列表强大。在形式上,元组的所有元素放在一对圆括号中,元素之间使用逗号分隔,如果元组中只有一个元素则必须在最后增加一个逗号。

     > > > x = (1, 2, 3)    #直接把元组赋值给一个变量
     > > > type(x)          #使用type()函数查看变量类型
    <class 'tuple' >
     > > > x[0]             #元组支持使用下标访问特定位置的元素
    1
     > > > x[-1]            #最后一个元素,元组也支持双向索引
    3
     > > > x[1] = 4         #元组是不可变的
    TypeError: 'tuple' object does not support item assignment
     > > > x = (3, )        #如果元组中只有一个元素,必须在后面多写一个逗号
     > > > x
    (3, )
     > > > x = ()           #空元组
     > > > x = tuple()      #空元组
     > > > tuple(range(5))  #将其他迭代对象转换为元组
    (0, 1, 2, 3, 4)

3.3.2 元组与列表的异同点

元组和列表都属于有序序列,都支持使用双向索引随机访问其中的元素,均可以使用count()方法统计指定元素的出现次数和使用index()方法获取指定元素的索引;len()、map()、filter()等大量内置函数以及+、*、in等运算符也都可以作用于列表和元组。虽然有着一定的相似之处,但元组和列表在本质上和内部实现上都有着很大的不同。

元组属于不可变序列,不可以直接修改元组中元素的值,也无法为元组增加或删除元素。因此,元组没有提供append()、extend()和insert()等方法,无法向元组中添加元素。同样,元组也没有remove()和pop()方法,也不支持对元组元素进行del操作,不能从元组中删除元素。元组也支持切片操作,但是只能通过切片来访问元组中的元素,而不允许使用切片来修改元组中元素的值,也不支持使用切片操作来为元组增加或删除元素。从一定程度上讲,可以认为元组是轻量级的列表,或者是“常量列表”。

元组的访问速度比列表更快。如果定义了一系列常量值,主要用途仅是对它们进行遍历或其他类似用途,而不需要对其元素进行任何修改,那么一般建议使用元组而不用列表。元组在内部实现上不允许修改其元素值,从而使得代码更加安全,例如,调用函数时使用元组传递参数可以防止在函数中修改元组,而使用列表则无法保证这一点。

最后,作为不可变序列,与整数、字符串一样,元组可用作字典的键,也可以作为集合的元素。而列表则永远都不能当作字典键使用,也不能作为集合中的元素,因为列表不是不可变的,或者说不可散列。

3.3.3 生成器表达式

生成器表达式的用法与列表推导式非常相似,在形式上生成器表达式使用圆括号作为定界符,而不是列表推导式所使用的方括号。生成器表达式的结果是一个生成器对象,具有惰性求值的特点,只在需要时生成新元素,比列表推导式具有更高的效率,空间占用非常少,尤其适合大数据处理的场合。

使用生成器对象的元素时,可以根据需要将其转化为列表或元组,也可以使用生成器对象的__next__()方法或者内置函数next()进行遍历,或者直接使用for循环来遍历其中的元素。但是不管用哪种方法访问其元素,只能从前往后正向访问每个元素,没有任何方法可以再次访问已访问过的元素,也不支持使用下标访问其中的元素。当所有元素访问结束以后,如果需要重新访问其中的元素,必须重新创建该生成器对象,enumerate、filter、map、zip等其他迭代器对象也具有同样的特点。

     > > > g = ((i+2)**2 for i in range(10))     #创建生成器对象
     > > > g
    <generator object <genexpr > at 0x0000000003095200 >
     > > > tuple(g)                               #将生成器对象转换为元组
    (4, 9, 16, 25, 36, 49, 64, 81, 100, 121)
     > > > list(g)                                #生成器对象已遍历结束
    []
     > > > g = ((i+2)**2 for i in range(10))     #重新创建生成器对象
     > > > g.__next__()              #使用生成器对象的__next__()方法获取元素
    4
     > > > g.__next__()              #获取下一个元素
    9
     > > > next(g)                   #使用函数next()获取生成器对象中的元素
    16
     > > > g = ((i+2)**2 for i in range(10))
     > > > for item in g:            #使用循环直接遍历生成器对象中的元素
        print(item, end=' ')
    4 9 16 25 36 49 64 81 100 121
     > > > g = map(str, range(20))   #map对象也具有同样的特点
     > > > '2' in g
    True
     > > > '2' in g                  #这次判断会把所有元素都给“看”没了
    False
     > > > '8' in g
    False