python3内置类型序列

三种基本的序列类型:

  • 列表(list)
  • 元组(tuple)
  • range

iterator

迭代器,用于表示一连串数据流的对象。

重复调用迭代器对象的__next__()方法(或将迭代器对象传递给内置函数next())会逐个返回数据流中的元素。当没有元素可用时会抛出StopIteration异常。

迭代器对象必须包含__iter__()方法返回迭代器对象自身,因此迭代器对象默认是可迭代的,可以被用于适用于迭代器对象的大部分场合。

iterable

可迭代的,是指对象具备一次返回一个成员的能力。所有的序列类型的对象都是可迭代的。任何实现了__iter__()或实现了sequence语义的__getitem__()的对象也都是可迭代的。

可迭代的对象可以被用在for循环或其他所有需要序列的地方(如zip(), map()等)

当一个可迭代的对象作为参数传递给内置函数iter()时就会返回一个迭代器。

当使用一个可迭代的对象时,通常不需要自己调用iter()生产迭代器然后再处理迭代器。for语句会自动完成这个操作,它创建一个临时的未命名变量用于在循环期间保存迭代器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
>>> range_obj = range(3)
>>> print(range_obj)
range(0, 3)
>>> iter_obj = iter(range_obj)
>>> print(iter_obj)
<range_iterator object at 0x0000022DC27816F0>
>>> print(next(iter_obj))
0
>>> print(next(iter_obj))
1
>>> print(next(iter_obj))
2
>>> print(next(iter_obj))
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
>>>

判断一个对象是不是可迭代的方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
>>> tmp_list = [1,2,3]
>>> tmp_str = "abc"
>>> tmp_dict = {"name": "john"}
>>> tmp_range = range(10)
>>> from collections import Iterable
>>> print(isinstance(tmp_list, Iterable))
True
>>> print(isinstance(tmp_str, Iterable))
True
>>> print(isinstance(tmp_dict, Iterable))
True
>>> print(isinstance(tmp_range, Iterable))
True
>>> from collections import Iterator
>>> print(isinstance(tmp_range, Iterator))
False
>>> print(isinstance(tmp_list, Iterator))
False
>>> print(isinstance(tmp_dict, Iterator))
False
>>> print(isinstance(tmp_str, Iterator))
False
>>> print(isinstance(iter(tmp_str), Iterator))
True
>>> print(isinstance(iter(tmp_list), Iterator))
True
>>> print(isinstance(iter(tmp_dict), Iterator))
True
>>> print(isinstance(iter(tmp_range), Iterator))
True
>>>

generator

通常指生成器函数,一个返回generator iterator的函数。他与普通函数的不同点在于其包含了yield表达式用于产生一系列值提供给for循环使用或通过next()函数依此获取。

generator iterator

生成器迭代器,generator 函数所创建的对象。生成器迭代器实质上也是迭代器。

每个 yield 会临时暂停处理,记住当前位置执行状态(包括局部变量和挂起的 try 语句)。当该 生成器迭代器 恢复时,它会从离开位置继续执行(这与每次调用都从新开始的普通函数差别很大)。

  1. 生成器迭代器使用 yield 关键字实现一次返回一个值给调用者,然后暂停执行,等待下一次调用,好处是节省内存空间。
  2. 生成器可以用next()函数,也可以用for迭代的方式获取元素值,中间还可以用close()来随时终止生成器
  3. next()函数每次执行时,都会继续执行挂起的生成器函数,直到执行完毕。

生成器的这种特点被称为”延迟计算”或”惰性求值(Lazy evaluation)”,可以有效的节省内存。惰性求值实际上是体现了协同程序的思想。

虽然生成器的这种行为类似于迭代器,但两者有较大差别,迭代器不具备这种执行-暂停-再执行-再暂停的特性,所以迭代器不具有延迟计算,没有协同程序的思想。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
>>> def generator_func(x):
... for i in range(x):
... yield i * 2
...
>>> for v in generator_func(3):
... print(v)
...
0
2
4
>>>
>>> generator_iter = generator_func(3)
>>> print(next(generator_iter))
0
>>> print(next(generator_iter))
2
>>> print(next(generator_iter))
4
>>> print(next(generator_iter))
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
>>>

generator expression

生成器表达式,返回一个生成器迭代器的表达式。他是一种特殊的生成器函数。

格式: (结果 for 变量 in 可迭代对象 if 条件筛选)

生成器表达式如果作为某个函数的参数,则可以省略掉()

生成器表达式和列表推导式的区别:

  1. 列表推导式比较消耗内存,一次性加载所有数据。生成器表达式几乎不占用内存,使用的时候才分配和使用内存

  2. 得到的值不一样。列表推导式得到的是一个列表。生成器表达式获取的是一个生成器。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    def add(n, i):
    return n + i

    def test():
    for i in range(4):
    yield i

    g = test()
    for n in [1, 10]:
    # n == 1 时,g = (add(n, i) for i in g),此时 n 没有替换为 1。 惰性机制,不到最后不会拿值
    # n == 10时,g = (add(n, i) for i in (add(n, i) for i in g))
    # g = (add(n, i) for i in (10, 11, 12, 13))
    # g = (10 + 10, 10 + 11, 10 + 12, 10 + 13)
    g = (add(n, i) for i in g) # 生成器表达式,在此之上所有的代码都没有进行计算

    print(list(g)) # [20, 21, 22, 23]

list comprehension

列表推导式,处理一个序列中的所有或部分元素并返回结果列表的一种紧凑写法。

格式: [结果 for 变量 in 可迭代对象 if 条件筛选]

如[‘{:#04x}’.format(x) for x in range(256) if x % 2 == 0] 将生成一个 0 到 255 范围内的十六进制偶数对应字符串(0x..)的列表。其中 if 子句是可选的,如果省略则 range(256) 中的所有元素都会被处理。

序列操作

range类型不支持序列的s1 + s2s * n操作

s * n

如果 n 小于0,会按照0来处理,会产生一个空序列
如果s是可修改序列, 注意该操作不会创建序列s的副本,而是创建序列s的引用。这种行为通常不是期望的行为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
>>> ori_list = []
>>> new_list = [ori_list] * 3 # 创建一个list,里面每一个元素都是ori_list的引用
>>> print(ori_list, new_list)
[] [[], [], []]
>>> ori_list.append(1) # 修改ori_list后,会影响new_list中的每一个元素
>>> print(ori_list, new_list)
[1] [[1], [1], [1]]
>>>
>>> ### 示例2
>>> ori_list = [1]
>>> new_list = [ori_list] * 3
>>> print(ori_list, new_list)
[1] [[1], [1], [1]]
>>> ori_list = [1,2] # 对ori_list重新赋值不会影响new_list中的元素
>>> print(ori_list, new_list)
[1, 2] [[1], [1], [1]]
>>> new_list[0].append(3)
>>> print(ori_list, new_list)
[1, 2] [[1, 3], [1, 3], [1, 3]]
>>>

s1 + s1

对不可变序列(str, bytes等)使用连接操作时,如果有大量(N个)不可变序列需要连接时效率会很慢,原因是需要进行N-1次的内存申请和搬运操作
官方推荐使用str.join()bytes.join()方法替代,在执行join操作时,会首先统计出在list中一共有多少个对象,并统计这些对象所维护的字符串一共有多长,然后申请内存,将list中所有的对象维护的字符串都拷贝到新开辟的内存空间中。这里只进行了一次内存空间的申请,就完成了N个对象的连接操作。相比于 + 操作符,待连接的对象越多,效率的提升也会越明显。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import time
count = 10000000
start_time = time.perf_counter()
s = ''
for n in range(0,count):
s += str(n)
end_time = time.perf_counter()

print('Time elapse:{}'.format(end_time - start_time))

start_time = time.perf_counter()
s = []
for n in range(0,count):
s.append(str(n))
''.join(s)
end_time = time.perf_counter()
print('Time elapse:{}'.format(end_time - start_time))

start_time = time.perf_counter()
s = ''.join(map(str,range(0,count)))
end_time = time.perf_counter()
print('Time elapse:{}'.format(end_time - start_time))
方式 十万数据 百万数据 千万数据
方式一 0.05587520000000001 3.3037078 457.09966000000003
方式二 0.04598980000000001 0.45585239999999994 6.734024699999964
方式三 0.030382799999999988 0.3231406999999997 3.248821899999996

切片操作s[i:j:k]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
>>> word = "Hello World"
>>> print(word[1:9]) # 切片的范围为[i,j),包括i,但是不包含j
ello Wor
>>> print(word[9:1]) # 如果 i > j,则返回空序列, k默认是1

>>> print(word[-1:3])

>>> print(word[1:9:2]) # 返回符合 i + n * k < j 的所有元素
el o
>>> print(word[1:100]) # 如果 i / j > len(word), 则按照len(word)处理
ello World
>>> print(word[100:9])

>>>
>>> print(word[1:9:-2]) # 如果k为负值,则要求 i > j

>>> print(word[9:1:-2])
lo l

创建多维数组

  • 先创建一个所需长度的列表,然后对列表中的所有元素进行填充

    1
    2
    3
    4
    5
    >>> row, column = 3, 2  # 创建 3 行 2 列的二维数组
    >>> A = [ [0] * column ] * row
    >>> print(A)
    [[0, 0], [0, 0], [0, 0]]
    >>>
  • 使用numpy库

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    >>> a1 = numpy.zeros([3,2], int)   # 创建 3行2列的二维数组
    >>> print(a1)
    [[0 0]
    [0 0]
    [0 0]]
    >>>
    >>> a2 = numpy.empty([3, 2]) # empty不会初始化为0
    >>> print(a2)
    [[5.11798224e-307 3.44897992e-307]
    [1.69118108e-306 9.34609790e-307]
    [6.23037657e-307 8.34445138e-308]]
    >>> help(numpy.zeros)
    Help on built-in function zeros in module numpy:

    zeros(...)
    zeros(shape, dtype=float, order='C')

range类型

range是一个内置的class类型,是Sequence[int]的子类,他是一个不可变的数字序列,通常用于for循环中指定循环次数。

range()返回的对象再很多方面表现的像是list,但是实际上不是。他返回的是一个可迭代的对象。

range相对于list和tuple的优点在于,不管range表示的范围是多大,他总是占用很小的相同大小的内存(仅仅保存start/stop/step三个值)。