Skip to content

Latest commit

 

History

History
568 lines (447 loc) · 15.4 KB

python-list.rst

File metadata and controls

568 lines (447 loc) · 15.4 KB

列表

列表是我们用来存储对象的常用数据结构。大多数时候, 程序员关心获取、设置、搜索、过滤、排序。 此外,有时候,我们会把自己跳入内存管理的常见陷阱。 因此,这章备忘录的主要的目标是收集一些常用的操作和陷阱。

从头开始

在Python中,我们有很多方法操作列表。 在我们开始学习这些多功能操作之前,下面的代码片段显示了列表中最常见的操作。

>>> a = [1, 2, 3, 4, 5]
>>> # 包含
>>> 2 in a
True
>>> # 正索引
>>> a[0]
1
>>> # 负索引
>>> a[-1]
5
>>> # 切片 list[start:end:step]
>>> a[1:]
[2, 3, 4, 5]
>>> a[1:-1]
[2, 3, 4]
>>> a[1:-1:2]
[2, 4]
>>> # 反序
>>> a[::-1]
[5, 4, 3, 2, 1]
>>> a[:0:-1]
[5, 4, 3, 2]
>>> # 赋值
>>> a[0] = 0
>>> a
[0, 2, 3, 4, 5]
>>> # 添加元素
>>> a.append(6)
>>> a
[0, 2, 3, 4, 5, 6]
>>> a.extend([7, 8, 9])
>>> a
[0, 2, 3, 4, 5, 6, 7, 8, 9]
>>> # 删除一个元素
>>> del a[-1]
>>> a
[0, 2, 3, 4, 5, 6, 7, 8]
>>> # 列表推导式
>>> b = [x for x in range(3)]
>>> b
[0, 1, 2]
>>> # 两个列表添加
>>> a + b
[0, 2, 3, 4, 5, 6, 7, 8, 0, 1, 2]

初始化

通常来说,如果一个列表中的元素是一个不可变对象,我们可以创建列表通过 * 操作符。

>>> a = [None] * 3
>>> a
[None, None, None]
>>> a[0] = "foo"
>>> a
['foo', None, None]

然而,如果列表表达式中的元素是可变对象时,使用 * 操作符将会拷贝元素的索引N次。 为了避免这个陷阱,我们应该使用列表推导式初始化一个列表。

>>> a = [[]] * 3
>>> b = [[] for _ in range(3)]
>>> a[0].append("Hello")
>>> a
[['Hello'], ['Hello'], ['Hello']]
>>> b[0].append("Python")
>>> b
[['Python'], [], []]

拷贝

把一个列表赋值给一个变量是一个常见的陷阱。这个赋值不会拷贝一个列表到这个变量。 这个变量只是引用这个列表和增加了列表的引用计数。

import sys
>>> a = [1, 2, 3]
>>> sys.getrefcount(a)
2
>>> b = a
>>> sys.getrefcount(a)
3
>>> b[2] = 123456  # a[2] = 123456
>>> b
[1, 2, 123456]
>>> a
[1, 2, 123456]

这里有两种类型的拷贝。第一种叫 浅拷贝 (不会递归拷贝),第二种叫 深拷贝 (递归拷贝)。 大多数情况下,我们通过浅拷贝复制列表就足够了。但是,如果列表是嵌套的话,我们必须使用深拷贝。

>>> # shallow copy
>>> a = [1, 2]
>>> b = list(a)
>>> b[0] = 123
>>> a
[1, 2]
>>> b
[123, 2]
>>> a = [[1], [2]]
>>> b = list(a)
>>> b[0][0] = 123
>>> a
[[123], [2]]
>>> b
[[123], [2]]
>>> # deep copy
>>> import copy
>>> a = [[1], [2]]
>>> b = copy.deepcopy(a)
>>> b[0][0] = 123
>>> a
[[1], [2]]
>>> b
[[123], [2]]

使用 slice

有时,我们的数据也许是一大串连续的,比如数据包。 在这个情况下,我们将使用 slice 对象来解释变量用来表示数据的范围,而不是使用 slicing表达式

>>> icmp = (
...     b"080062988e2100005bff49c20005767c"
...     b"08090a0b0c0d0e0f1011121314151617"
...     b"18191a1b1c1d1e1f2021222324252627"
...     b"28292a2b2c2d2e2f3031323334353637"
... )
>>> head = slice(0, 32)
>>> data = slice(32, len(icmp))
>>> icmp[head]
b'080062988e2100005bff49c20005767c'

List推导式

List推导式 被提出在PEP 202。 它提供了一个优雅的方式基于另一个列表、序列或者可迭代对象创建一个新的列表。 此外,有时候,我们可以用这个表达式中式替换 mapfilter

>>> [x for x in range(10)]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> [(lambda x: x**2)(i) for i in range(10)]
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
>>> [x for x in range(10) if x > 5]
[6, 7, 8, 9]
>>> [x if x > 5 else 0 for x in range(10)]
[0, 0, 0, 0, 0, 0, 6, 7, 8, 9]
>>> [x + 1 if x < 5 else x + 2 if x > 5 else x + 5 for x in range(10)]
[1, 2, 3, 4, 5, 10, 8, 9, 10, 11]
>>> [(x, y) for x in range(3) for y in range(2)]
[(0, 0), (0, 1), (1, 0), (1, 1), (2, 0), (2, 1)]

解包

有时,我们想要解包我们的列表到变量,为了我们的代码更有可读性。 在这个情况下,我们分配N个元素到N个变量,如下面的🌰展示的一样。

>>> arr = [1, 2, 3]
>>> a, b, c = arr
>>> a, b, c
(1, 2, 3)

基于PEP 3132,在Python3中,我们可以使用单个星号来解包N个元素, 分配的变量数可以小于元素N。

>>> arr = [1, 2, 3, 4, 5]
>>> a, b, *c, d = arr
>>> a, b, d
(1, 2, 5)
>>> c
[3, 4]

使用 enumerate

enumerate 是一个内建的方法。它帮助我们同时获取索引(或者一个数量)和元素,不需要使用 range(len(list))。 更多信息可以看 Looping Techniques

>>> for i, v in enumerate(range(3)):
...     print(i, v)
...
0 0
1 1
2 2
>>> for i, v in enumerate(range(3), 1): # start = 1
...     print(i, v)
...
1 0
2 1
3 2

合并列表

zip 使我们一次能够迭代多个列表中的包含的元素。 当其中之一的列表元素被用完,迭代结束。作为结果,迭代的长度和最短的列表长度相同。 如果不希望出现这个行为,我们在 Python3 中可以使用 itertools.zip_longest,或者在 Python2 中可以使用 itertools.izip_longest

>>> a = [1, 2, 3]
>>> b = [4, 5, 6]
>>> list(zip(a, b))
[(1, 4), (2, 5), (3, 6)]
>>> c = [1]
>>> list(zip(a, b, c))
[(1, 4, 1)]
>>> from itertools import zip_longest
>>> list(zip_longest(a, b, c))
[(1, 4, 1), (2, 5, None), (3, 6, None)]

过滤元素

filter 是一个内建的函数用来帮助我们移除不需要的元素。 在 Python 2 中,filter 返回一个列表。 然而,在 Python 3 中, filter 返回一个 可迭代对象。请注意 列表推导式 或者 生成器表达式 提供一种更简洁的方法来删除元素。

>>> [x for x in range(5) if x > 1]
[2, 3, 4]
>>> l = ['1', '2', 3, 'Hello', 4]
>>> f = lambda x: isinstance(x, int)
>>> filter(f, l)
<filter object at 0x10bee2198>
>>> list(filter(f, l))
[3, 4]
>>> list((i for i in l if f(i)))
[3, 4]

这里不需要一个额外的数据结构,栈,因为在Python中,list 提供了 appendpop 方法, 使我们能够将列表用作栈。

>>> stack = []
>>> stack.append(1)
>>> stack.append(2)
>>> stack.append(3)
>>> stack
[1, 2, 3]
>>> stack.pop()
3
>>> stack.pop()
2
>>> stack
[1]

in 操作

我们可以实现 __contains__ 方法,使一个类可以使用 in 操作。 这是一个常用的方式对程序员来说,实现一个自定义类进行模拟会员测试操作。

class Stack:

    def __init__(self):
        self.__list = []

    def push(self, val):
        self.__list.append(val)

    def pop(self):
        return self.__list.pop()

    def __contains__(self, item):
        return True if item in self.__list else False

stack = Stack()
stack.push(1)
print(1 in stack)
print(0 in stack)

Example

python stack.py
True
False

访问元素

使自定义类执行get和set操作就像列表一样简单。 我们可以实现 __getitem____setitem__ 方法,使一个类可以通过索引获取和重写数据。 此外,如果我们想要这个函数 len,去计算元素的数量,我们可以实现一个 __len__ 方法。

class Stack:

    def __init__(self):
        self.__list = []

    def push(self, val):
        self.__list.append(val)

    def pop(self):
        return self.__list.pop()

    def __repr__(self):
        return "{}".format(self.__list)

    def __len__(self):
        return len(self.__list)

    def __getitem__(self, idx):
        return self.__list[idx]

    def __setitem__(self, idx, val):
        self.__list[idx] = val


stack = Stack()
stack.push(1)
stack.push(2)
print("stack:", stack)

stack[0] = 3
print("stack:", stack)
print("num items:", len(stack))

Example

$ python stack.py
stack: [1, 2]
stack: [3, 2]
num items: 2

委托迭代

如果一个自定义容器类包含一个列表,我们想要迭代在容器上工作, 我们可以实现 __iter__ 方法将迭代委托给列表。 注意这个方法,__iter__,应该返回一个 可迭代对象, 因此我们不能直接返回这个列表,否则,Python会抛出 TypeError

class Stack:

    def __init__(self):
        self.__list = []

    def push(self, val):
        self.__list.append(val)

    def pop(self):
        return self.__list.pop()

    def __iter__(self):
        return iter(self.__list)

stack = Stack()
stack.push(1)
stack.push(2)
for s in stack:
    print(s)

Example

$ python stack.py
1
2

排序

Python的列表提供一个内建的方法 list.sort,它在不使用额外内存的情况下 就地 对列表进行排序。 此外,list.sort 的返回值式 None,以避免与 sorted 混淆,这个函数只能用于列表。

>>> l = [5, 4, 3, 2, 1]
>>> l.sort()
>>> l
[1, 2, 3, 4, 5]
>>> l.sort(reverse=True)
>>> l
[5, 4, 3, 2, 1]

这个 sorted 函数不会就地修改任何可迭代的对象。 相反,它返回一个新的排序的列表。 使用 sortedlist.sort 更安全,如果列表的元素是只读的或者是不可变类型。 除此之外,list.sortsorted 的另一个区别是,sorted 可以用在任何 可迭代对象 上。

>>> l = [5, 4, 3, 2, 1]
>>> new = sorted(l)
>>> new
[1, 2, 3, 4, 5]
>>> l
[5, 4, 3, 2, 1]
>>> d = {3: 'andy', 2: 'david', 1: 'amy'}
>>> sorted(d)  # sort iterable
[1, 2, 3]

为了排序列表中的元素是元组,使用 operator.itemgetter 是有帮助的,因为它可以作为 sorted 函数的key参数。 注意,这个key应该有可比性,否则,它会抛出一个 TypeError

>>> from operator import itemgetter
>>> l = [('andy', 10), ('david', 8), ('amy', 3)]
>>> l.sort(key=itemgetter(1))
>>> l
[('amy', 3), ('david', 8), ('andy', 10)]

operator.itemgetter 是有用的,因为这个函数返回一个getter方法,它可以通过 __getitem__ 应用于其他对象。 举个🌰,排序一个列表而且它的元素都是字典,可以通过使用 operator.itemgetter 实现,由于它所有的元素都支持 __getitem__

>>> from pprint import pprint
>>> from operator import itemgetter
>>> l = [
...     {'name': 'andy', 'age': 10},
...     {'name': 'david', 'age': 8},
...     {'name': 'amy', 'age': 3},
... ]
>>> l.sort(key=itemgetter('age'))
>>> pprint(l)
[{'age': 3, 'name': 'amy'},
 {'age': 8, 'name': 'david'},
 {'age': 10, 'name': 'andy'}]

如果必须要对一个列表元素即没有可比性也没有 __getitem__ 方法进行排序,指定一个自定义的key函数是可能的。

>>> class Node(object):
...     def __init__(self, val):
...         self.val = val
...     def __repr__(self):
...         return f"Node({self.val})"
...
>>> nodes = [Node(3), Node(2), Node(1)]
>>> nodes.sort(key=lambda x: x.val)
>>> nodes
[Node(1), Node(2), Node(3)]
>>> nodes.sort(key=lambda x: x.val, reverse=True)
>>> nodes
[Node(3), Node(2), Node(1)]

以上的代码片段可以简单的使用 operator.attrgetter 实现。 这个函数返回一个获取属性的方法,基于属性名。 注意,这个属性应该是有可比性的,否则 sorted 或者 list.sort 将会抛出 TypeError

>>> from operator import attrgetter
>>> class Node(object):
...     def __init__(self, val):
...         self.val = val
...     def __repr__(self):
...         return f"Node({self.val})"
...
>>> nodes = [Node(3), Node(2), Node(1)]
>>> nodes.sort(key=attrgetter('val'))
>>> nodes
[Node(1), Node(2), Node(3)]

如果一个对象有 __lt__ 方法,它意味着这个对象是可比的, sorted 或者 list.sort 就不必要传入key函数了到它的key参数了。 一个列表或者一个可迭代对象可以直接排序了。

>>> class Node(object):
...     def __init__(self, val):
...         self.val = val
...     def __repr__(self):
...         return f"Node({self.val})"
...     def __lt__(self, other):
...         return self.val - other.val < 0
...
>>> nodes = [Node(3), Node(2), Node(1)]
>>> nodes.sort()
>>> nodes
[Node(1), Node(2), Node(3)]

如果一个对象没有 __lt__ 方法,可以在声明对象的类之后,修补这个方法。 换句话说,在这个布丁之后,这个对象就是可比的了。

>>> class Node(object):
...     def __init__(self, val):
...         self.val = val
...     def __repr__(self):
...         return f"Node({self.val})"
...
>>> Node.__lt__ = lambda s, o: s.val < o.val
>>> nodes = [Node(3), Node(2), Node(1)]
>>> nodes.sort()
>>> nodes
[Node(1), Node(2), Node(3)]

注意,在Python3中,sorted 或者 list.sort 不支持 cmp 参数,这是在Python2 唯一 有效的参数。 如果非要使用一个老的比较函数,例如一些遗留代码,functools.cmp_to_key 是有用的,因为它将cmp函数转换为key函数。

>>> from functools import cmp_to_key
>>> class Node(object):
...     def __init__(self, val):
...         self.val = val
...     def __repr__(self):
...         return f"Node({self.val})"
...
>>> nodes = [Node(3), Node(2), Node(1)]
>>> nodes.sort(key=cmp_to_key(lambda x,y: x.val - y.val))
>>> nodes
[Node(1), Node(2), Node(3)]