数据类型
Python 的基本数据类型
数据类型
Python 是 强类型(安全,不同类型禁止相加)、动态(不显示数据类型)、脚本(通过解释器执行)语言,开发效率高、灵活,但不健壮
Python3上统一编码为 Unicode ,部分 Windows 系统,cmd
无法输入中文,请执行如下命令
$ chcp 936 # 改为支持 GBK 编码
$ chcp 65001 # 改为支持 UTF-8 编码
1. 数字类(Number)
- API方法 文档
- 此类对象由数字字面值创建,并会被作为 算术运算符 和 算术内置函数 的返回结果
- 数字对象是 不可变 的,一旦创建其值就不再改变
注
数字类的字符串表示形式,由 __repr__()
和 __str__()
得出
1.1 整型(int)—— numbers.Integral
Python3 不区分长、整型,都统一为整型 (Integral)
Linux下占用长度
通常 32 位机器上
-2**31~2**31-1
64 位为
-2**63~2**63-1
提示
- Python2 上超过长度限制后自动转为
long
(长整型),如type(2\*\*31)
- Python3 如
type(2\*\*65)
,依然为<class 'int'>
,因此不用考虑 大整数溢出 的问题 - 但其他语言如:C/C++, Java 等都严格区分
1.2 布尔值(bool)—— numbers.Integral
表示逻辑值 True 和 False, 布尔类型是 整型的子类型,两个布尔值在各种场合的行为 类似于数值 0 和 1
print(True, False)
### 输出结果
"True","False"
- 只有在转为字符串的情况下,才会返回
"True"
和"False"
字符串
注
print()
会默认执行 str()
强制类型转换为字符串
1.3 浮点型(float)—— numbers.Real
处理实数,类似 C 的 double
,表示 机器级的双精度浮点数,取值范围和溢出处理将受制于底层的机器架构,如 Cpython 就是 C 的实现,故与其相同
Linux下占用长度
64 位机器下占 8 个字节,52 位表示底,11 位表示指数,1 位表示符号
8个字节: 52位表示底 + 11位表示指数 + 一位表示符号
提示
浮点数 不全是小数,而是所有 实数和无理数的集合
Python 不支持 单精度浮点数 (节省 CPU 和内存消耗),这点开销对于 Python 使用对象的开销来说,微不足道
1.4 复数(complex)—— numbers.Complex
应用领域: 量子力学 、相对论、信号分析、应用数学 专业工程学等...
提示
通过调用 id()
方法可以打印任意对象的内存地址
2. 序列 —— 不可变序列
什么是序列对象
- 以非负整数作为索引的有限有序集,并可以根据可变性区分
- 可被
len()
返回条目数量 - 大多数序列类型可变/不可变 都支持 通用序列 操作 ,如支持
[:]
切片操作
注意
不可变序列 类型的对象(不可变对象)一旦创建就 不能再改变,只读,不能增删改
2.1 字节串(bytes 二进制序列)
bytes: 十六进制类型,ASCII 编码转换,离计算机二进制最近的数据类型,不可变序列
表示 bytes
字面值的语法与字符串字面值的大致相同,只是添加了一个 b
前缀
bytearray: 即字节数组,bytes
对象的可变对应物,是可变序列 写此处方便对应
注
bytes 和 bytearray 对象都支持 通用 序列操作
内置常用方法
转字符串:
bytes to str
foo = b'www.abc.com' bar = str(foo, "utf-8") print(bar) ## "www.abc.com"
2.2 字符串(str 文本序列)
字符串为 文本序列类型, 由 Unicode 码位构成的不可变序列
声明方式如下:
str = "" / = '' / = """""" # 基本赋值
内置方法
转字节串:
str to bytes
foo = "www.abc.com" bar = foo.encode("utf-8") print(bar) ## b"www.abc.com"
切片: 字符串支持切片
str[1:]
str[start:end:step]
判断相关:
str in str2
"el" in "Hello" # 包含 字符串在另一个字符串中 "abc123嗯".isalnum() # 仅数字&字母 组成(中文相同) 否则报错 "614.2".isdigit() # 仅数字 有小数点则不行 "614".isnumeric() # 仅数字 与上一致 遇到小数点返回False "abc".islower() # 仅小写 "ABC".isupper() # 仅大写 "Ryougi Shiki".istitle() # 全首字母大写 是否为标题 "Ryougi shiki".capitalize() # 仅首字母大写 " ".isspace() # 仅空格 "Ryougi Shiki".endswith("K", 1, -1) # 结尾为str 可以输入首尾 "Ryougi shiki".startswith("Ryo", ) # 开头为str
查询
"Ryougi Shiki".find("i") # 有则返回索引,无则返回-1 默认返回第一个 "Ryougi Shiki".index("i") # 与find几乎一致 区别在于找不到时报错 因此一般用find更好一些 "Ryougi Shiki".rfind("i") # 从右向左找 但返回的索引不是逆序的索引
转换
.lower() # 转小写 将字符串全部转为小写 .upper() # 转大写 将字符串全部转为大写 .title() # 转标题 将字符串转为标题样式,首字母大写 .swapcase() # 反转 将字符串字母大小写反转
替换分割
.split("i", 2) # 切指定 按指定的字符串 分割目标字符串 # 返回 分割后的元素列表 可指定分割次数 .rsplit(" ") # 切右向左 从右开始 指定分割 .replace("Ki", "ki") # 替换 指定的一段字符串 " \tRyougi Shiki\n".strip() # 删首尾 空格、换行等 "\tRyougir ".lstrip() # 删左 "Ryougi \n".rstrip() # 删右
不常用
.count("hi") # 计算字符串出现次数 .center(50, "*") # 根据字符及数量居中显示 .ljust(50, "*") # 左显示 .rjust(50, "*") # 右显示 "Ryo\tugi ShiKi".expandtabs(15) # 修改tab的默认空格数量
字符串输入
当 b = 'he's happy \n Yes'
一行输出
b = r'he\'s happy \n Yes'
r:
原生字符串 不再进行转义- 转义
's:
可以用\
, 也可以用""
、""" """
若以上是用户输入的话的,可用 repr
来解决,使其一行输出
b = repr(b) # 返回便电脑阅读的字符串
repr()
转化为供解释器读取的形式
字符串拼接与格式化
% 字符
拼接name = "zz" age = "17" print("My name is " + name + " and i am " + age + " years old.") # 此时计算机开辟了5块内存,存储字符串
此方式效率低,每一个字符串就开辟一块对应内存,禁用
% 字符
格式化输出foo = "zz" bar = "17" ret = '{name:%s - age:%s}' % (foo, bar) ### 占位符说明: - `%s s=string` 字符串 - `%d d=digit` 整数 - `%f f=float` 浮点数
format
格式化输出foo = "is" bar = "apple" msg = "This {a} an {b}".format(a=foo, b=bar) # 简化 msg = 'This {0} an {1}'.format(foo, bar) # 字典形式 "Ryougi {name}ki {age}".format_map({"name": "Shi", "age": 17})
注意
转义字符(含有花括号)时, 利用
{{}}
双重括号转义处理多个参数和更长的字符串时,显得有些冗长
zone = """ zone "{addr}" in {{ type master; file "{addr}"; }}; zone "{ip}.in-addr.arpa" in {{ type master; file "{ip}"; }}; """ add_conf = zone.format(addr=addr, ip=ip)
join
拼接a="www" b="deadline" c="cn" print(".".join([a, b, c])) # www.deadline.cn
F-strings
拼接 ( python3.6.2/PEP 498)
Python3.6 引入的一种便捷的,字符串拼接方式,而且还 安全
s1 = "hello"
s2 = "world"
print(f'{s1} {s2}!') # hello world!
甚至可以执行函数
x = 5 def power(x): return x*x print(f'{x} * {x} = {power(x)}') # 5 * 5 = 25
和拼字符串一样简单:(括号转义与
format
相同)addr = 'mytest1.com' ip = '192.168.1.1' zone = f""" zone "{addr}" in {{ type master; file "{addr}"; }}; zone "{ip}.in-addr.arpa" in {{ type master; file "{ip}"; }}; """
保留小数后 n 位,
:.2f
这种形式position = 3 f"{number:.{position}f}"
转义
{}
f"\"YKB\" {a}" name = "YKB!" f"my name is {{{name}}}"
lambda 表达式
f"{(lambda x: x * 37) (2)}"
这种形式加了括号,可以保证万无一失
2.3 元组(tuple)
Cpython 实现 PyTuple_Type
基于 array 形式
不应该浅显的理解为只读列表,首先它不能 增、删、改,其次常用于 记录
- 元组 用
(,)
声明,虽然元素不能改变,但能包含 可变序列 list ,也能切片
# tuple 构造包含 0 个或 1 个元素的元组比较特殊
tuple1 = ()
tuple2 = (a,)
t.__add__
内置方法
t1 = (1, )
t2 = ("USA", "33")
print(t1 + t2) # (1, 'USA', '33') tuple.__add__
print(t2 * 2, 2 * t2) # 乘积拼接(正反) tuple.__mul__(n) tuple.__rmul__(n)
t[:1] # 可切片 tuple.__getitem__(p)
t.count(e) # 统计 e 出现次数
t.index(e) # 索引 第一次出现位置
- **tuple.__add__: ** 此操作效率极低,每次都重新开辟内存,并复制原对象内元素
相关信息
元组是 不可变类型,虽然可以快速的和 List 相互转化, 但它 比 List 省内存且高效,这也是由于只读,不需要像列表那样,预先进行内存分配
li = [1, 2, 3]
tup = (1, 2, 3)
print(li.__sizeof__())
print(tup.__sizeof__())
### py3.9
# 104
# 48
Python 在后台还会针对静态数据,做一些 资源缓存(不进入垃圾回收 ),比如元组,若占用内存不大时,会暂时缓存住这部分内存,这样下次创建同样大小的元组时,就不会重新开辟内存,如下初始化
s = time.perf_counter()
for i in range(20000000):
x = [1, 2, 3, 4, 5]
print(time.perf_counter() - s)
s = time.perf_counter()
for i in range(20000000):
t = (1, 2, 3, 4, 5)
print(time.perf_counter() - s)
### py3.9
# 2.2250951
# 1.1721277999999997
2.4 range 对象
表示不可变的数字序列,常伴随 for
循环 指定次数
class range(start, stop[, step])
[i for i in range(1, 11, 3)]
## [1, 4, 7, 10]
从 1 开始到 11 结束,步长为 3 的列表
3. 可变序列
3.1 列表(list)
Cpython 实现 PyList_Type
是一个 over-allocate (过度分配 )的 array
list(列表)通常用于存放同类项目的集合,有序
class list([iterable])
- 方括号表示,
[]
、[a, b, c]
- 列表推导式,
[x for x in iterable]
- 类型构造器,
list()
、list(iterable)
内置方法
查找
li = ["apple", "banana", "apple", "orange"] li.count("apple") # 次数 计算列表中元素次数 li.index("banana") # 位置 获取列表中元素位置 "orange" in li # 判存在 检查列表中是否存在该元素
增加
li.append("pear") # 追加 向列表尾部追加元素 li.insert(2, "pineapple") # 前插 向指定列表位置前面插入元素 list_a.extend(list_b) # 扩容 列表a 合并 列表b 的所有元素 a+b 有些时候不想用 a.extend(),因为 a 会改变 因此用+,但它会返回一个全新的列表
注意
list_a.extend(list_b)
会改变原有list_a
,虽然 使用list_a + list_b
可以直接扩容,但一次还好,多次会影响效率- 每次
+=
操作,都会malloc
动态开辟一块内存区,高频次循环会导致 大量消耗内存,影响效率
- 每次
修改
li[2] = "new_value" # 直接赋值 li[1:4] = [a, b, c] # 切片赋值 不推荐 # 如下,则会删除第三索引的元素(三个位置用两个值赋) a[1:4] = ["A","B"]
删除
li.remove("element") # 删除 指定元素删除 若相同删第一 li.pop(2) # 出栈 指定索引删除 并返回删除的元素 默认栈尾 last 出栈 li.clear() # 清空 清空所有元素 del a # 擦除 直接从内存中删除对象 del a[3]
排序
排序 数据类型要相同(如:
str
和int
是无法进行排序的)li.sort() # 排序 根据 ASCII 码排序 可以指定 reverse=True 逆序 # 不会生成新的对象 li.reserve() # 逆序 列表元素逆序 sorted(li) # 排序 不改变原有列表 返回新排序后的 list 对象 # sorted() 对所有的可迭代序列都有效
类型判断
a = [] type(a) is list
解包 zip()
在 Python3 中,
zip
为了减少内存,会返回一个对象# 可迭代的对象为参 -- 对应的元素打包为一个个元组, 元素个数与最短的列表一致 a = [1, 2, 3] b = [4, 5, 6] zipped = zip(a, b) ## python2: [(1,4),(2,5),(3,6)] ## python3: <zip at 0x7fb34fe38540> ## [i for i in zipped] => [(1,4),(2,5),(3,6)] # 解压,返回二维矩阵式 ss = list(zip(*zipped)) # zip(*zipped) = [(1,2,3),(4,5,6)] ## ss [(1,2,3),(4,5,6)]
关于列表内存
li = []
print(li.__sizeof__())
li.append(1)
print(li.__sizeof__())
for i in range(2, 6):
li.append(i)
print(li, li.__sizeof__())
### py3.9
# 40
# 72
# [1, 2] 72
# [1, 2, 3] 72
# [1, 2, 3, 4] 72
# [1, 2, 3, 4, 5] 104
对于一个空列表,其存储空间为 40 字节
一旦加入了元素 1 之后,列表就会为其分配可以存储 4 个元素的空间 (72 - 40)/8 = 4
当加入到元素 5 之后,列表的空间不足,所以又额外分配了可以存储 4 个元素的空间 +32
后续每次空间不足,则是按照 last_add_len \* 2
进行递增,因此 远远比元组(Tuple)消耗内存
进阶
4. 集合类型
包含 和
4.1 集合(set)
class set([iterable])
set 对象是由具有唯一性的 hashable 对象 所组成的 无序多项集,常用于 成员检测、去重、集合类 交、并、补、差 运算
- 集合不记录顺序,因此不支持索引
- 支持
in
、len
、for
循环迭代操作 - 可变类型,无哈希值, 不能作为字典键 或其他集合的元素
- 可使用
add()
、remove()
来增删 - 哈希,故很快 O(1), 比 list 遍历查找快太多
集合的创建
使用 花括号 内以逗号分隔元素来创建:
{'jack', 'sjoerd'}
set_a = {"apple", "banana"}
使用 集合推导式 来创建:
{c for c in 'abracadabra' if c not in 'abc'}
set_a = {i for i in 'apple'}
使用 类型构造器 来创建:
set()
,set('foobar')
,set(['a', 'b', 'foo'])
li = [1, 2, 3] set_a = set(li)
集合操作
CURD 操作
set_a.add("pear") # 添加 向集合中增加一个元素 set_a.update(["orange", "pineapple"]) # 批量加 向集合中增加可迭代对象 # set_a.update(iterable) 有去重,无加入 set_a.remove("orange") # 移除 集合中移除指定元素,不存在会报错 set_a.discard("orange") # 移除 集合中移除指定元素,不会报错 set_a.pop() # 随机删除 并返回删除的值 set_a.clear() # 清空 清空集合 del set_a # 擦除 直接从内存中删除集合对象
特有操作
a = {'apple', 'banana', 'pineapple'} b = set(["apple"]) a.isdisjoint(b) # 空集 集合A 和 集合B 的交集为空,不相干 b.issubset(a) # 子集 集合B 是 A 的子集 b <= a b < a # 真子集 a.issuperset(b) # 父集 集合A 是 B 的父集 a >= b a > b # 真超集 a.intersection(b) # 交集 取集合A 和 B 共有部分 a & b a.union(b) # 并集 取集合A 和 B 所有部分 a | b a.difference(b) # 差集 取在集合A 中,但不在集合 B 的部分 a - b a.symmetric_difference(b) # 对称差集 取(集合A 有 B没有的) 和 (B有 A没有的) a ^ b
注意
使用方法可以接收 iterable (可迭代对象),但使用运算符 只能为集合对象
集合的内部结构也是一张 哈希表 ,区别于字典在于没有 key - value 的配对,只有单一元素
4.2 冻结集合(frozenset)
class frozenset([iterable])
集合的元素必须为 hashable。 要表示由集合对象构成的集合,所有的内层集合必须为
frozenset
对象。 如果未指定 iterable,则将返回一个新的空集合
- 类型是不可变的,并且为 hashable
- 其内容在被创建后不能再改变,因此它 可被用作字典的键 或其他集合的元素
5. 映射
Dictionary(字典) 是 Python 除 list 外,最灵活的内置数据结构类型,是 无序可变对象集合
5.1 字典(dict)
class dict(**kwargs)
class dict(mapping, **kwargs)
class dict(iterable, **kwargs)
- 采用 key-value 形式存储数据,对 key 进行哈希运算,根据计算结果决定 value 的存储地址,因此 hashable 可以为键
- 因为索引必须唯一,故 key 值唯一,通过 key 来存取,不是像 C 的数组那样,通过偏移
字典的创建
使用 花括号 内以逗号分隔 键: 值 对的方式
dict1 = {"name": "ShiKi", "age": 17, "hobby": "hagendas"}
使用 字典推导式
dict_a = {x: x ** 2 for x in range(10)}
使用 类型构造器
dict_a = dict() dict_a = dict([('foo', 100), ('bar', 200)]) dict_a = dict(foo=100, bar=200)
提示
直接使 {}
效率较高
字典操作
CURD 操作
dict_a = {"name": "zz", "age": 18} dict_a["drink"] = "coffee" # 增加 指定 键值对 直接添加(改同) dict_a.setdefault("drink", "coffee") # 增加 通过方法添加,若存在则返回 value name = dict_a["name"] # 取值 直接通过 key 取 value age = dict_a.get("age", 17) # 取值 通过方法,可指定默认值 未指定默认值则报错 keys_view = dict_a.keys() # 取所有 key 拿到所有key 值构建的视图 values_view = dict_a.values() # 取所有 value 拿到所有value 值构建的视图 items_view = dict_a.items() # 取所有 k-v对 拿到所有key - value 键值对构建的视图 # 可用 list(dict|dictview) 返回列表 dict_a.update({"drink": "java"}) # 修改 有则改,无增加 参数为字典 dict_a.pop("age", None) # 删除 指定 key 删对应键值对,可给默认 无则报错 dict_a.popitem() # 随机删除 无参随机删 并返回删除的 key-value dict_a.clear() # 清空 del dict_a # 擦除
其他方法
for i in dict_a: # 遍历 i 为 key 效率高 print(i, dict_a[i]) for key,value in dict.items(): # .items()转换效率太低, 比索引 O(1) 效率低,不推荐 print(i, dict_a[i]) # 类方法 classmethod dict.fromkeys(iterable, value=None) # iterable -> 字典生成的每个 key # value -> 每个key键 相同的值,多用于 通过迭代对象 初始化一个空字典,注意深浅拷贝 create_dict_a = dict.fromkeys(["key1", "key2", "key3"]) print(create_dict_a) ## {'key1': None, 'key2': None, 'key3': None} create_dict_b = dict.fromkeys(["key1", "key2", "key3"], ["value1", "value2"]) print(create_dict_b) ## {'key1': ['value1', 'value2'], 'key2': ['value1', 'value2'], 'key3': ['value1', 'value2']} ## 深浅拷贝 故几乎不用 同 li = [[]] * 3 create_dict_b["key2"][1]="new_value" print(create_dict_b) ## {'key1': ['value1', 'new_value'], 'key2': ['value1', 'new_value'], 'key3': ['value1', 'new_value']}
新增功能(多为 3.8/9 版后)
# d | other 和 d |= other dict_a = {"name": "zz", "age": 18} dict_b = {"first_name": "igr", "age": 19} dict_a | dict_b # 合并 融合 dict_a 和 dict_b 中的键值对, dict_a |= dict_b # 更新 通过 dict_b 的键值对 来更新 dict_a # 当 dict_a 和 dict_b 有相同键时 dict_b 优先 ## {'name': 'zz', 'age': 19, 'first_name': 'igr'} list(reversed(dict_a)) # 逆序键迭代 获取字典键、值或项 与插入时 相反的 key键的 迭代器 ## ['age', 'name'] # 与 list(iter(dict_a)) 相反
6. 排序、去重和搜索
6.1 关于排序
字典是无序的(针对 3.6 前 ),因为散列哈希,非有序存储结构,哪怕 排序后再转字典依旧无序,但某些场景又需要字典顺序获取,解决如下
sorted()
内置函数,可用其来对字典的 键/值排序 ,并返回类似 List[Tuple] 结构dict_a = {"banana": 11.2, "apple": 5.6, "pear": 17.1} result = sorted(dict_a.items(), key=lambda x:x[0], reverse=True) # x[0]/[1] 键/值 ## [('pear', 17.1), ('banana', 11.2), ('apple', 5.6)] # 如上是根据 key 值首字母逆序,返回了 元素为 元组 tuple类型 的 list 列表 # 若 lambda 用 值value 则是根据价格排序
- 使用了
.items()
返回iterable
的dictview
时,就已经转化为对列表的排序
- 使用了
list.sort()
故使用sort()
也相同,排序后再转字典依旧无序,区别sorted()
在于不会重新生成新的列表# 先按key排序 key_list = [i for i in dict_a.keys()] key_list.sort() ## ['apple', 'banana', 'pear'] # 利用字典生成式,再构造字典,此时看似有序,其实依旧无序 result = {key: dict_a[key] for key in key_sort} ## {'apple': 5.6, 'banana': 11.2, 'pear': 17.1}
此时会发现,有些场景 需要返回的也是字典,而不是如上列表
OrderedDict
使用 有序字典
operator 复杂排序
若对某些较为复杂的数据结构排序,可使用 operator
模块提供的 itemgetter()
、attrgetter()
快速字段提取器,来简化操作
from operator import itemgetter, attrgetter
itemgetter()
通常用于 tuple ,dict 等取值itemgetter(1) == lambda x: x[1] # 通常用于元组、字典
sorted()
直接用itemgetter()
对字典 key 或 value 排序teacher_map = {"teacher1": 10, "teacher2": 9, "teacher3": 11} # 对 key键 排序 result = sorted(teacher_map.items(), key=itemgetter(0)) ## [('teacher1', 10), ('teacher2', 9), ('teacher3', 11)] # 对 值value 排序 result = sorted(teacher_map.items(), key=itemgetter(1)) ## [('teacher2', 9), ('teacher1', 10), ('teacher3', 11)]
.sort()
嵌套内层排序# 透过字典,对列表中的字典排序 from operator import itemgetter class_map = { "class1": [ {"name": "teacher2", "rank": 2}, {"name": "teacher1", "rank": 1}, {"name": "teacher3", "rank": 3} ], "class2": [ {"name": "teacher2", "rank": 3}, {"name": "teacher1", "rank": 1}, {"name": "teacher1", "rank": 2} ], "class3": [ {"name": "teacher3", "rank": 1}, {"name": "teacher1", "rank": 1}, {"name": "teacher1", "rank": 2}] } # 解构 出待排序的列表 # 若对象为dict,itemgetter("rank") 则取到字典的键 "rank" for k,v in class_map.items(): v.sort(key=itemgetter('rank')) # 支持 多级排序,若 条件 'rank' 相同, 可根据 "name" 再排序 for k,v in class_map.items(): v.sort(key=itemgetter('rank', "name"))
多权重排序
# 加入 teacher 有如 (年限, 带班数, 人数, 等级) 的权值 teacher_map = { "teacher1": (1, 2, 2, 3), "teacher2": (2, 4, 4, 2), "teacher3": (1, 3, 3, 3), "teacher4": (1, 3, 3, 4) } # 转化数据结构 为多重字典形式 teacher_items = [] for name, weights in teacher_map.items(): print(name, weights) teacher_items.append({ "name": name, "YEAR": weights[0], "CNUM": weights[1], "PNUM": weights[2], "RANK": weights[3], }) # 表示先按 'PNUM' 排序,再按 'RANK' 排序 sorted(teacher_items ,key=itemgetter('PNUM', 'RANK')) # 改用 lambda 可控制条件,按人数少,且级别高的 逆序输出 sorted(teacher_items, key=lambda x: (-x['PNUM'], x['RANK']), reverse=True) # 若用 itemgetter 需两次,先按人数少的排,再逆序级别高的
attrgetter()
通过对象属性名,获取指定属性from operator import attrgetter from collections import namedtuple Student = namedtuple("Student", ["name", "age"]) s1 = Student("zz", 18) get_attr = attrgetter("age") get_attr(s1) ## 18 get_attr2 = attrgetter("name", "age") get_attr2(s1) ## ('zz', 18)
max()/min()
求迭代器的 最大/小 值max(iterable, key, default)
使用
for i in …
遍历,将迭代器每个返回值,当做参数传给key=lambda_func
,进行大小的判断student_list = [ Student("zz", 18), Student("bb", 19), Student("cc", 20), ] get_attr = attrgetter("age") max(student_list, key=lambda x: get_attr(x)) ## Student(name='cc', age=20) get_attr2 = attrgetter("name", "age") max(student_list, key=lambda x: get_attr2(x)) ## Student(name='zz', age=18)
6.2 关于去重
通用去重思路:如
reduce
、动态转字符串set(str(i)) for i in xx
去重,重构 map 去重
重构 map 去重
快速去重,如下是要对列表中,字典的 重量、产地、审核人 唯一的数据进行去重,类似要将 ("重量_产地_审核人")
联合唯一,进行去重
遍历数据,并对 3 个 key的值 进行拼接为字符串 new_key ,和之前的字典元素为 value, 联合构成新的字典,
此时由于字典 key 值唯一,会仅保留遍历是最先 set 的数据
import pprint
def generate_key(item):
return "_".join(str(v) for k, v in item.items() if k != "型号")
if __name__ == '__main__':
a_list = [
{"型号": 12, "重量": 16, "产地": 19, "审核人": 33},
{"型号": 22, "重量": 92, "产地": 87, "审核人": 34},
{"型号": 71, "重量": 55, "产地": 21, "审核人": 36},
{"型号": 1, "重量": 55, "产地": 21, "审核人": 36},
]
b_list = [
{"产地": 87, "型号": 22, "重量": 92, "审核人": 34},
{"产地": 86, "型号": 15, "重量": 27, "审核人": 35},
{"产地": 44, "型号": 65, "重量": 91, "审核人": 33},
{"产地": 44, "型号": 11, "重量": 91, "审核人": 33},
]
hash_map = {}
for item in a_list:
hash_map.setdefault(generate_key(item), item)
for item in b_list:
hash_map.setdefault(generate_key(item), item)
c_list = list(hash_map.values())
pprint.pprint(c_list)
6.3 关于搜索
已排序的序列,可以用来进行快速搜索,标准库的 bisect 模块提供了二分查找,其包含了 bisect
和 insort 两个主要函数
速度比较
a_list = [i for i in range(1000000)]
random.shuffle(a_list)
a_list.sort()
x_list = [i for i in range(10000)]
random.shuffle(x_list)
# index
s = time.perf_counter()
for i in x_list:
tmp = a_list.index(i)
e_t = time.perf_counter() - s
print(e_t)
# bisect
s = time.perf_counter()
for i in x_list:
tmp = bisect.bisect(x_list, i)
e_t = time.perf_counter() - s
print(e_t)
杂谈
内置创建效率
比如使用 str(), list(), dict()
这类的内置方法创建数据,和直接 “a”, [1, 2], {"name": "zz"}
创建,谁更高效?
区别在于一个是通过 function call 创建的,过程中 Python 会创建 stack ,并进行一系列参数检查的操作,因此反而效率更低
字节码
使用 dis 来查看 Python 的字节码十分方便
import dis
t = (1, 2, [30, 40])
dis.dis("t[2] += [50, 60]")
-------------------------------------------------
1 0 LOAD_NAME 0 (t)
2 LOAD_CONST 0 (2)
4 DUP_TOP_TWO
6 BINARY_SUBSCR
8 LOAD_CONST 1 (50)
10 LOAD_CONST 2 (60)
12 BUILD_LIST 2
14 INPLACE_ADD
16 ROT_THREE
18 STORE_SUBSCR
20 LOAD_CONST 3 (None)
22 RETURN_VALUE
元组的本质
鼻祖是 ABC 解释器里的 compounds 类型,既支持 平行赋值(解包) 又可用作字典的 合成键 ,但不属于序列,也不是迭代类型,因此处处受限,仅用于没有字段名的记录
而在 Python 中取巧的实现为序列、迭代类型、甚至切片,使其可以当做不可变列表 (frozenlist)来使用,体现出了其语言的实用主义,比起列表,其常用于 存放彼此之间没有关系的数据的记录
key 参数
list.sort
、sorted
、max
和 min
函数的 key
参数 是绝佳设计,不同于 cmp(a, b)
这种双参函数,仅需单参或提供一个值用作比较标准即可
而且这样的好处更为 高效,排序时每个元素上,key 函数 只会被调用一次,双参比较函数则是每次两两比较都会调用,而计算是在 C 那层处理,这样比调用自定义的 Python 函数会更快
另外,key 参数可以让其对混有 数字&字符 的列表排序,仅需决定是看做谁
l = [28, 14, "28", 5, "9", "1", 0 , 6, "23", 19]
print(sorted(l, key=int))
print(sorted(l, key=str))
###
# [0, '1', 5, 6, '9', 14, 19, '23', 28, '28']
# [0, '1', 14, 19, '23', 28, '28', 5, 6, '9']
Timsort 算法
CPython 的排序算法使用的是 Timsort (2002),一种自适应算法,会根据原始数据的顺序特点,交替使用 插入排序 和 归并排序 达到最佳效率(真实世界的数据通常是有一定顺便的特点 )
稳定: 即就算两个元素比不出大小,每次排序结果而相对位置也是固定的