- 发表在
Python 数据结构
- 作者
- 名字
- Wisdom Keeper
这里不涉及具体的数据结构实现,以及对应方法,最高效的学习方法是实操,照着文档试就行。 具体数据结构的实现请关注我们后续的教程。 了解更多
List
append( ), extend( ) and insert( )
特性 | append 方法 | extend 方法 | insert 方法 |
---|---|---|---|
添加元素 | 将单个元素添加到列表末尾 | 将可迭代对象的所有元素添加到列表末尾 | 在列表指定位置插入元素 |
参数类型 | 任何类型(包括列表) | 任何可迭代对象(列表、元组、字符串等) | 索引位置和要插入的元素 |
示例 | my_list.append([4, 5]) | my_list.extend([4, 5]) | my_list.insert(3, 4) |
结果 | [1, 2, 3, [4, 5]] | [1, 2, 3, 4, 5] | [1, 2, 3, 4, 1, 2, 3] |
- extend 相当于抽象了在append基础上自动解包可迭代元素的操作
- 使用 append 方法时,整个对象作为单个元素添加到列表中。
- 使用 extend 方法时,可迭代对象的每个元素分别添加到列表中。
- 使用 insert 方法时,元素插入到指定位置,后续元素依次后移。
index( )
list.index(x[, start[, end]])
返回列表中第一个值为 x 的元素的零基索引。未找到指定元素时,触发 ValueError 异常。 可选参数 start 和 end 是切片(slice)符号,用于将搜索限制为列表的特定子序列。返回的索引是相对于整个序列的开始计算的,而不是 start 参数。 这里主要是提一点,[]在文档里的意思是可选参数,你可以提供也可以不提供。
前置 & Bonus:
- 前置:
pop()
常用于 LIFO(后进先出)结构,例如栈(Stack)。- 在这种结构中,pop() 方法用于移除并返回栈顶的元素。
- 惯例是
push()
用于添加元素,pop()
用于移除元素。
- Bonus:
extend(iterable)
iterable 为可迭代对象,如列表、元组、字符串等。- 我们首先明确extend接收的是一个对象,那怎么保证他是可迭代的呢?
- 实现可迭代的对象,需要实现
__iter__()
方法,返回一个迭代器对象。- 迭代器对象需要实现__next__()
方法,返回迭代器的下一个元素。当迭代器没有元素时,__next__()
方法会触发 StopIteration 异常。- 举例:
class MyIterable:
def __init__(self, n):
self.n = n
def __iter__(self):
return MyIterator(self.n)
class MyIterator:
def __init__(self, n):
self.i = 0
self.n = n
def __next__(self):
if self.i < self.n:
result = self.i
self.i += 1
return result
else:
raise StopIteration
# 使用示例
my_iterable = MyIterable(5)
for value in my_iterable:
print(value)
# 输出:
# 0
# 1
# 2
# 3
# 4
- 关于迭代器的内容将在后面介绍,这里不在赘述。另对于Java来说,Iterable 接口表示一个可以迭代的集合。任何实现了 Iterable 接口的类都可以用于增强型 for 循环(也称为 "for-each" 循环)。而Iterator 接口用于遍历集合中的元素。它提供了 hasNext() 和 next() 方法来检查是否有更多元素以及获取下一个元素。
// java
public interface Iterable<T> {
Iterator<T> iterator();
/** T 和下面的 E 是泛型,表示任意类型,在使用时会被具体类型替换,
/* 如 Iterable<String> 表示一个字符串类型的集合,具体用法将在
/* 后续课程介绍,因为泛型和接口在java里是一个比较大的概念,需要
/* 一定的基础才能理解
/*
/*
*/
}
public interface Iterator<E> {
boolean hasNext();
E next();
void remove(); // 可选操作
}
Bonus: 浅拷贝
copy()
方法返回列表的浅拷贝。浅拷贝是指创建一个新的列表对象,新列表中的元素是原列表中元素的引用。因此,新列表中的元素和原列表中的元素指向同一内存地址。- 更好的理解就是,浅拷贝只拷贝了原列表中的元素的引用,而不是元素本身。比如我们用空间跳跃技术,在洞头克隆了另一个WKU,我们进入洞头wku就会被传送到丽岙的WKU, 那么我们对洞头的WKU做的任何操作,都会影响到丽岙的WKU,这就是浅拷贝。 那这样肯定不是我们想要的呀,也许我们复制WKU只是为了实验对WKU的改造,而不对原有WKU产生影响,那么我们就应该克隆WKU的每一个元素,而不是克隆整个WKU的引用。这就是深拷贝,我们将在后续的课程中介绍。 对于很多语言如
JavaScript
是不支持深拷贝的,所以直接进行复制会导致意想不到的结果,所以就算是简单的复制工作,对JavaScript
开发者来说还要用到第三方库,如lodash
等。那你可能想知道我们能不能自己实现深拷贝? Wait a minute,走远啦 🥹 首先抛出个结论,python 的 copy 模块提供了深拷贝的方法,我们可以直接使用。如果你还想了解更深入的内容,请继续往下看。如果你感觉上述内容对你没有任何收获,那就请继续查看后续内容。 - 如何自己实现深拷贝,可以作为Labs,大家自己可以尝试哦。
- 思想很简单,就是递归遍历对象,然后对每一个元素进行复制,这样就可以实现深拷贝了。
- 但是实现起来可能会有一些问题,比如循环引用,比如对象的属性是函数等等,这些都是需要考虑的问题。
循环引用发生在两个或多个对象直接或间接地引用自己,形成一个闭环。在进行深拷贝时,如果不正确处理循环引用,可能会导致无限递归或栈溢出。
class MyClass:
def __init__(self, value, func=None):
self.value = value
self.func = func
self.self_reference = None
def set_self_reference(self):
self.self_reference = self
- Solution:
- 使用备忘录(Memoization):在深拷贝过程中,可以使用一个字典(或其他映射类型)作为备忘录,存储已经拷贝过的对象。在拷贝新对象之前,先检查它是否已经在备忘录中。如果已存在,直接使用备忘录中的对象,避免再次拷贝。
- Bonus: 字典或叫做映射数据类型是很经典的重复计数模型,首先字典的键是唯一的,你可以通过查看键是否存在于字典而决定是否增加计数或进行其他操作,然后呢,字典(java叫map)一般都是基于哈希表(Hash table)实现的,他的各种操作如查找插入删除很快(平均时间复杂度为O(1)也指可在常数时间内完成), 因为不像其他数据结构需要遍历整个结构。
- Bonus:大 O 表示法用于描述算法的时间复杂度或空间复杂度,表示在最坏情况下,算法的运行时间或所需空间如何随着输入规模的增长而增长。中的为随着输入规模的增长,运行时间(这里专指时间复杂度)以何种增长速度增长, 为常数,无论输入规模多大,都是以常数时间完成。其他的我们会在后续课程介绍,这里最后拓展一下, 指数复杂度(Exponential Complexity)是在大规模数据下接受不了的算法。
- 检测循环引用:在递归拷贝过程中,如果发现当前对象已经在处理栈中,说明存在循环引用。这时可以跳过当前对象的拷贝,或者根据需求进行特殊处理。
- 对象的属性是函数,通常只是复制了函数的引用,而不是函数对象本身。这意味着原始对象和深拷贝对象将共享同一个函数对象,函数对象可能包含对外部作用域的引用,包括全局变量和自由变量。深拷贝时,这些外部引用不会被复制,导致深拷贝后的对象可能无法按预期行为,如果函数对象内部有状态(例如,闭包中捕获的变量),深拷贝不会复制这些状态,因为状态是存储在函数的闭包环境中,而不是函数对象本身。 那为什么外部引用不会在深拷贝时被复制呢?
因为当你深拷贝一个对象时,Python 会递归地复制对象的所有属性。但是,函数对象和闭包中的外部引用是特殊的: - 函数对象:函数对象本身是不可变的,深拷贝时只会复制函数对象的引用,而不会复制函数对象本身。
- 闭包中的外部引用:闭包捕获的外部变量存储在函数对象的
__closure__
属性中,这些变量的引用不会被深拷贝。
def outer_function():
external_var = "I am external"
def inner_function():
print(external_var)
return inner_function
# 创建一个函数对象,它是 outer_function 的返回值
func_obj = outer_function()
# 创建一个对象,其属性是上面创建的函数对象
class MyClass:
def __init__(self):
self.method = None
obj = MyClass()
obj.method = func_obj
# 尝试深拷贝 obj
import copy
copied_obj = copy.deepcopy(obj)
# 调用原始对象和深拷贝对象中的函数属性
obj.method() # 输出: I am external
copied_obj.method() # 输出: I am external
- Solution:
- 如果对象的属性是函数,可以选择不复制函数属性,或者复制函数引用而不复制函数的闭包环境
- 将闭包状态存储在对象属性中:将闭包中的状态显式地存储在对象的属性中。
- 在深拷贝时处理这些属性:确保在深拷贝时,这些属性也被正确地复制。
import copy
def outer_function():
external_var = "I am external"
def inner_function():
print(external_var)
return inner_function, external_var
# 创建一个函数对象,它是 outer_function 的返回值
func_obj, state = outer_function()
# 创建一个对象,其属性是上面创建的函数对象和状态
class MyClass:
def __init__(self, method=None, state=None):
self.method = method
self.state = state
def __deepcopy__(self, memo):
# 手动深拷贝方法和状态
new_copy = type(self)(
method=copy.deepcopy(self.method, memo),
state=copy.deepcopy(self.state, memo)
)
return new_copy
obj = MyClass(method=func_obj, state=state)
# 尝试深拷贝 obj
copied_obj = copy.deepcopy(obj)
# 调用原始对象和深拷贝对象中的函数属性
obj.method() # 输出: I am external
copied_obj.method() # 输出: I am external
- 使用其他库:使用第三方库,如 dill
pip install dill
import dill
def outer_function():
external_var = "I am external"
def inner_function():
print(external_var)
return inner_function
# 创建一个函数对象,它是 outer_function 的返回值
func_obj = outer_function()
# 创建一个对象,其属性是上面创建的函数对象
class MyClass:
def __init__(self):
self.method = None
obj = MyClass()
obj.method = func_obj
# 使用 dill 库深拷贝 obj
copied_obj = dill.loads(dill.dumps(obj))
# 修改原始对象的外部变量
func_obj.__closure__[0].cell_contents = "I am modified"
# 调用原始对象和深拷贝对象的方法
obj.method() # 输出: I am modified
copied_obj.method() # 输出: I am external
dill 是一个扩展了 pickle 模块的库,可以序列化和反序列化更多类型的 Python 对象,包括函数对象和闭包。通过序列化和反序列化,可以实现深拷贝的效果。
- dill.dumps:将对象序列化为字节流。
- dill.loads:从字节流反序列化对象,创建一个深拷贝。
通过序列化和反序列化,可以实现深拷贝的效果,因为反序列化后的对象是一个全新的对象,与原始对象在内存中是独立的。我们不会指向被复制对象的内存地址,而是通过序列化和反序列化创建一个新的对象,分配一个新的内存地址。
- 自行实现深拷贝代码参考
import copy
def deep_copy(obj, memo=None):
if memo is None:
memo = {}
# 检查对象是否已经被复制
if id(obj) in memo:
return memo[id(obj)]
# 复制不可变类型
if isinstance(obj, (int, float, str, tuple)):
return obj
# 创建新对象
cls = obj.__class__
new_obj = cls.__new__(cls)
# 复制对象的 __dict__ 属性
new_obj.__dict__.update(obj.__dict__)
# 将新对象添加到 memo 中
memo[id(obj)] = new_obj
# 递归复制对象的元素
for k, v in obj.__dict__.items():
if isinstance(v, (list, dict, set)):
new_obj.__dict__[k] = deep_copy(v, memo)
return new_obj
汇总
方法 | 描述 | 示例 | 结果 |
---|---|---|---|
append(x) | 将元素x 添加到列表末尾 | my_list.append(1) | [1, 2, 3, 1] |
extend(iterable) | 将iterable 中的元素添加到列表末尾 | my_list.extend([4, 5]) | [1, 2, 3, 4, 5] |
insert(i, x) | 在索引i 的位置插入元素x | my_list.insert(1, 'a') | [1, 'a', 2, 3] |
remove(x) | 移除列表中第一个值为x 的元素 | my_list.remove(2) | [1, 3] |
pop([i]) | 移除列表中索引为i 的元素,并返回该元素。如果不指定索引,则默认移除并返回列表最后一个元素 | my_list.pop() | 返回移除的元素,列表长度减少 |
clear() | 清空列表 | my_list.clear() | [] |
index(x[, start[, end]]) | 返回列表中第一个值为x 的元素的索引,可选参数start 和end 用于指定搜索范围 | my_list.index(2) | 1 |
count(x) | 返回x 在列表中出现的次数 | my_list.count(2) | 1 |
sort(key=None, reverse=False) | 对列表进行排序,key 为一个函数,reverse 为布尔值,表示是否降序排序 | my_list.sort(reverse=True) | 列表元素按降序排序 |
reverse() | 反转列表中的元素顺序 | my_list.reverse() | 列表元素顺序反转 |
copy() | 返回列表的浅拷贝 | my_list.copy() | 返回一个新的列表,内容与原列表相同 |
这里解释一下本系列课程的前置和 bonus 的关系,前置是编者认为为了更好地理解教程内容所必须要掌握的,而 bonus 为 由课程衍生出的知识点,不影响对课程的理解,而且理解bonus的内容需要更多的前置,而为了篇幅控制,将不在教程中展开讲解。大家不用死磕bonus内容,知识积累到一定程度,回头再看也不失一种好的选择。
用列表实现堆(heap)栈(stack)
- 堆(heap)是一种优先队列(FIFO),可理解为先来的请求先处理
列表也可以用作队列,最先加入(append()
)的元素,最先取出(pop(0)
)(“先进先出”);然而,列表作为队列的效率很低。因为,在列表末尾添加和删除元素非常快,但在列表开头插入或移除元素却很慢(因为所有其他元素都必须移动一位)。 实现队列最好用 collections.deque,可以快速从两端添加或删除元素。例如:
>>> from collections import deque
>>> queue = deque(["Eric", "John", "Michael"])
>>> queue.append("Terry") # Terry arrives
>>> queue.append("Graham") # Graham arrives
>>> queue.popleft() # The first to arrive now leaves
# output: 'Eric'
>>> queue.popleft() # The second to arrive now leaves
# output: 'John'
>>> queue # Remaining queue in order of arrival
# output: deque(['Michael', 'Terry', 'Graham'])
- 栈(stack)是一种后进先出(LIFO)的数据结构,可理解为后进来的先处理
append()
添加pop()
提取
>>> stack = [3, 4, 5]
>>> stack.append(6)
>>> stack.append(7)
>>> stack
[3, 4, 5, 6, 7]
>>> stack.pop()
7
>>> stack
[3, 4, 5, 6]
>>> stack.pop()
6
>>> stack.pop()
5
>>> stack
[3, 4]
FIFO (/ˈfaɪfo/) = First in first out
LIFO (/'lai,fəu/) = Last in first out
列表推导式
列表推导式创建列表的方式更简洁。常见的用法为,对序列或可迭代对象中的每个元素应用某种操作,用生成的结果创建新的列表;或用满足特定条件的元素创建子序列。
>>> squares = []
>>> for x in range(10):
squares.append(x**2)
>>> squares
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
前置:
map()
函数用于将一个函数应用到一个或多个序列(如列表或元组)的每个元素,并返回一个迭代器。你可以使用 map 函数来简化对序列中每个元素的操作。list()
函数用于将一个可迭代对象(如字符串、元组或集合)转换为列表。它可以将任何可迭代对象转换为列表,可以不指定参数,返回一个空列表。
注意,这段代码创建(或覆盖)变量 x。注意,x 在 for 循环中是局部变量,但它会覆盖之前的全局变量 x。该变量在循环结束后仍然存在,但其值会是最后一次迭代的值。下述方法可以无副作用地计算平方列表:
>>> squares = list(map(lambda x: x**2, range(10)))
>>> squares = [x**2 for x in range(10)]
列表推导式的方括号内包含以下内容:一个表达式,后面为一个 for 子句,然后,是零个或多个 for 或 if 子句。结果是由表达式依据 for 和 if 子句求值计算而得出一个新列表。 举例来说,以下列表推导式将两个列表中不相等的元素组合起来:
>>> [(x, y) for x in [1,2,3] for y in [3,1,4] if x != y]
[(1, 3), (1, 4), (2, 3), (2, 1), (2, 4), (3, 1), (3, 4)]
//等价于
>>> combs = []
>>> for x in [1,2,3]:
for y in [3,1,4]:
if x != y:
combs.append((x, y))
>>> combs
[(1, 3), (1, 4), (2, 3), (2, 1), (2, 4), (3, 1), (3, 4)]
列表推导式中的初始表达式可以是任何表达式,甚至可以是另一个列表推导式。
>>> matrix = [
[1, 2, 3, 4],
[5, 6, 7, 8],
[9, 10, 11, 12],
]
>>> [[row[i] for row in matrix] for i in range(4)]
[[1, 5, 9], [2, 6, 10], [3, 7, 11], [4, 8, 12]]
del语句
按索引而不是值从列表中移除条目 与返回一个值的 pop() 方法不同。 del 语句也可用于从列表中移除切片(slice)或清空整个列表
a = [-1, 1, 66.25, 333, 333, 1234.5]
del a[0:2]
del a[0]
元组和序列(tuple and sequence)
序列
list, tuple and range 是 Python 中最常见的序列类型。list 是可变序列,tuple 是不可变序列。range 对象本身是不可变的,但可以通过索引和切片生成新的 range 对象。 range 对象在内存中占用很少,因为它们不会立即生成所有数字,而是按需生成。
元组
- 元组是由逗号分隔的一组值,通常用圆括号括起来。元组是不可变序列,通常用于存储异构数据(不同类型的数据)。
# 定义一个包含异构数据的元组
heterogeneous_tuple = (1, "string", 3.14, True)
print(heterogeneous_tuple) # 输出: (1, 'string', 3.14, True)
- 元组的元素可以是任何类型,包括元组本身。元组的元素可以通过索引访问,切片,拼接,重复,长度,成员资格测试(检查某个元素是否属于某个集合或序列)等操作。
- 元组的长度为零时,称为空元组。元组的长度为一时,称为单元组。元组的元素可以通过逗号和圆括号来定义,例如,t = (2,)。
# 定义一个包含一个元素的元组
t = (2,)
print(type(t)) # 输出: <class 'tuple'>
# 如果没有逗号,则不是元组
t = (2)
print(type(t)) # 输出: <class 'int'>
# 但是哈,这里有个坑,用一对空圆括号 () 可以创建一个空元组
empty_tuple = ()
print(type(empty_tuple)) # 输出: <class 'tuple'>
- 输出时,元组都要由圆括号标注,这样才能正确地解释嵌套元组。输入时,圆括号可有可无,不过经常是必须的,特别是当元组只有一个元素时,必须使用圆括号和逗号来区分。 例如:
# 输出时,元组由圆括号标注
nested_tuple = (1, (2, 3), 4)
print(nested_tuple) # 输出: (1, (2, 3), 4)
# 输入时,圆括号可有可无
t1 = 1, 2, 3
t2 = (1, 2, 3)
print(t1) # 输出: (1, 2, 3)
print(t2) # 输出: (1, 2, 3)
# 当元组只有一个元素时,必须使用圆括号和逗号
single_element_tuple = (2,)
print(single_element_tuple) # 输出: (2,)- 元组是不可变的,但是可以包含可变对象,如列表。元组的不可变性意味着元组的元素不能被更改,但元组的元素可以是可变对象,如列表,列表的元素可以被更改。
虽然,元组与列表很像,但使用场景不同,用途也不同。元组是 immutable (不可变的),一般可包含异质元素序列,通过解包(见本节下文)或索引访问(如果是 namedtuples,可以属性访问)。列表是 mutable (可变的),列表元素一般为同质类型,可迭代访问。 Bonus:namedtuple
from collections import namedtuple
# 定义 namedtuple
Point = namedtuple('Point', ['x', 'y'])
p = Point(1, 2)
# 通过属性访问
print(p.x) # 输出: 1
print(p.y) # 输出: 2
# 通过索引访问
print(p[0]) # 输出: 1
print(p[1]) # 输出: 2
构造 0 个或 1 个元素的元组比较特殊:为了适应这种情况,对句法有一些额外的改变。用一对空圆括号就可以创建空元组;只有一个元素的元组可以通过在这个元素后添加逗号来构建(圆括号里只有一个值的话不够明确)。丑陋,但是有效。例如:
empty = ()
singleton = 'hello', # <-- note trailing comma
len(empty)
0
len(singleton)
1
singleton
('hello',)
语句
t = 12345, 54321, 'hello!'
是 元组打包 的例子:值 12345, 54321 和 'hello!' 一起被打包进元组。逆操作也可以:
语句
>>> t = 12345, 54321, 'hello!'
>>> x, y, z = t
称之为序列解包也是妥妥的,适用于右侧的任何序列。序列解包时,左侧变量与右侧序列元素的数量应相等。注意,多重赋值其实只是元组打包和序列解包的组合。
集合(set)
集合是由不重复元素组成的无序容器。基本用法包括成员检测、消除重复元素。集合对象支持合集、交集、差集、对称差分等数学运算。
创建集合用花括号或 set() 函数。注意,创建空集合只能用 set(),不能用 , 创建的是空字典,下一小节介绍数据结构:字典。
有必要提一下只是空元素不能用
{}
来构建,其实通过x = {1,2,3}
和x = set({1,2,3})
有区别吗?如果不咬文嚼字,那他们没有任何区别,都是在构建一个集合对象。
那他们作用都是一样的,为啥长得不一样咧,其实啊x = {1,2,3}
即字面量语法(literal)是x = set({1,2,3})
(构造函数)的语法糖,提供了更简洁直观的方法实现相同功能。
Bonus: 语法糖(syntactic sugar)是指在编程语言中添加的一种语法特性,它使代码更简洁、更易读,但并不增加语言的功能。语法糖的目的是让代码更具可读性和可维护性,简化常见的编程模式
想一想我们之前接触了几种语法糖了呢 🍬 🤔 要不再来块 🍬 看看能得到启发吗?
# 使用列表推导式(语法糖)
squares = [x**2 for x in range(10)]
# 等价的普通语法
squares = []
for x in range(10):
squares.append(x**2)
方法演示
>>> basket = {'apple', 'orange', 'apple', 'pear', 'orange', 'banana'}
>>> print(basket) # show that duplicates have been removed
{'orange', 'banana', 'pear', 'apple'}
>>> 'orange' in basket # fast membership testing
True
>>> 'crabgrass' in basket
False
# Demonstrate set operations on unique letters from two words
>>> a = set('abracadabra')
>>> b = set('alacazam')
>>> a # unique letters in a
{'a', 'r', 'b', 'c', 'd'}
>>> a - b # letters in a but not in b
{'r', 'd', 'b'}
>>> a | b # letters in a or b or both
{'a', 'c', 'r', 'd', 'b', 'm', 'z', 'l'}
>>> a & b # letters in both a and b
{'a', 'c'}
>>> a ^ b # letters in a or b but not both
{'r', 'd', 'b', 'm', 'z', 'l'}
集合也支持推导式这里不进行重复
字典
一个非常有用的 Python 内置数据类型是 字典 (映射类型 --- dict,其他语言多称为map,本质都是键对值(键-->值)的映射,这里不是键值对打错啦😉)。 字典在其他语言中 可能会被称为“关联存储”或“关联数组”。 不同于以固定范围的数字进行索引的序列,字典是以 键 进行索引的,键可以是任何不可变类型;字符串和数字总是可以作为键(因为咱兄弟俩是immutable滴)。 如果 一个元组(immutable)只包含字符串、数字或元组(immutable)则也可以作为键;如果一个元组(immutable)直接或间接地包含了任何可变对象(mutable),则不能作为键。很简单的道理, 要不变,你拥有的所用东西必须同时保持不变,你自己就算是不可变的东西,只要你所拥有的东西有一样是可变的,那你就不可靠了。 列表不能作为键,因为列表可以使用索引赋值、切片赋值或者 append() 和 extend() 等方法进行原地修改列表。 哎呀呀,别这么麻烦了,其实呢,就是因为列表是可变滴。
可以把字典理解为键值对的集合,字典的键必须是唯一的。花括号 用于创建空字典。另一种初始化字典的方式是,在花括号里输入逗号分隔的键值对,这也是字典的输出方式。
字典的主要用途是通过关键字存储、提取值。用 del
可以删除键值对。用已存在的关键字存储值,与该关键字关联的旧值会被取代。通过不存在的键提取值,则会报错。
对字典执行 list(d)
操作,返回该字典中所有键的列表,按插入次序排列(如需排序,请使用 sorted(d)
)。检查字典里是否存在某个键,使用关键字 in
。
>>> tel = {'jack': 4098, 'sape': 4139}
>>> tel['guido'] = 4127
>>> tel
{'jack': 4098, 'sape': 4139, 'guido': 4127}
>>> tel['jack']
4098
>>> del tel['sape']
>>> tel['irv'] = 4127
>>> tel
{'jack': 4098, 'guido': 4127, 'irv': 4127}
>>> list(tel)
['jack', 'guido', 'irv']
>>> sorted(tel)
['guido', 'irv', 'jack']
>>> 'guido' in tel
True
>>> 'jack' not in tel
False
- dict() 构造函数直接从键值对元组列表中构建字典。字典推导式可以从任意键值表达式中构建字典。
>>> dict([('sape', 4139), ('guido', 4127), ('jack', 4098)])
{'sape': 4139, 'guido': 4127, 'jack': 4098}
- 字典推导式可以用任意键值表达式创建字典:
>>> {x: x**2 for x in (2, 4, 6)}
{2: 4, 4: 16, 6: 36}
- 关键字是比较简单的字符串时,直接用关键字参数指定键值对更便捷:
dict(sape=4139, guido=4127, jack=4098)
{'sape': 4139, 'guido': 4127, 'jack': 4098}
字典遍历
>>> knights = {'gallahad': 'the pure', 'robin': 'the brave'}
>>> for k, v in knights.items():
print(k, v)
gallahad the pure
robin the brave
Bonus: 为什么我们一直强调要键不可变,什么又是哈希,看看这个视频,相信你能找到答案(无前置要求) 。
序列和其他类型的比较
序列对象可以与相同序列类型的其他对象比较。这种比较使用 字典式 顺序:首先,比较前两个对应元素,如果不相等,则可确定比较结果;如果相等,则比较之后的两个元素,以此类推,直到其中一个序列结束。如果要比较的两个元素本身是相同类型的序列,则递归地执行字典式顺序比较。如果两个序列中所有的对应元素都相等,则两个序列相等。如果一个序列是另一个的初始子序列,则较短的序列可被视为较小(较少)的序列。 对于字符串来说,字典式顺序使用 Unicode
码位序号排序单个字符。下面列出了一些比较相同类型序列的例子:
>>> x = [(1, 2, 3) < (1, 2, 4),
[1, 2, 3] < [1, 2, 4],
'ABC' < 'C' < 'Pascal' < 'Python',
(1, 2, 3, 4) < (1, 2, 4),
(1, 2) < (1, 2, -1),
(1, 2, 3) == (1.0, 2.0, 3.0),
(1, 2, ('aa', 'ab')) < (1, 2, ('abc', 'a'), 4)]
>>> for item in x:
print(item)
True
True
True
True
True
True
True
注意,当比较不同类型的对象时,只要待比较的对象提供了合适的比较方法,就可以使用 <
和 >
进行比较。例如,混合的数字类型通过数字值进行比较,所以,0 等于 0.0,等等。如果没有提供合适的比较方法,解释器不会随便给出一个比较结果,而是引发 TypeError 异常