数据类型

五十岚2020年4月17日
大约 22 分钟

Python 的基本数据类型

数据类型

Python强类型安全,不同类型禁止相加)、动态不显示数据类型)、脚本通过解释器执行)语言,开发效率高、灵活,但不健壮

官方文档open in new window

Python3上统一编码为 Unicode ,部分 Windows 系统,cmd 无法输入中文,请执行如下命令

$ chcp 936	# 改为支持 GBK 编码
$ chcp 65001 # 改为支持 UTF-8 编码

1. 数字类(Number)

  • API方法 文档open in new window
  • 此类对象由数字字面值创建,并会被作为 算术运算符算术内置函数 的返回结果
  • 数字对象是 不可变 的,一旦创建其值就不再改变

数字类的字符串表示形式,由 __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)
  • Python3type(2\*\*65),依然为 <class 'int'>,因此不用考虑 大整数溢出 的问题
  • 但其他语言如:C/C++Java 等都严格区分

1.2 布尔值(bool)—— numbers.Integral

表示逻辑值 TrueFalse, 布尔类型是 整型的子类型,两个布尔值在各种场合的行为 类似于数值 0 和 1

print(True, False)

### 输出结果
"True","False"
  • 只有在转为字符串的情况下,才会返回 "True""False" 字符串

print() 会默认执行 str() 强制类型转换为字符串

1.3 浮点型(float)—— numbers.Real

处理实数,类似 Cdouble,表示 机器级的双精度浮点数,取值范围和溢出处理将受制于底层的机器架构,如 Cpython 就是 C 的实现,故与其相同

Linux下占用长度

  • 64 位机器下占 8 个字节,52 位表示底,11 位表示指数,1 位表示符号

    8个字节: 52位表示底 + 11位表示指数 + 一位表示符号
    

提示

  • 浮点数 不全是小数,而是所有 实数和无理数的集合

  • Python 不支持 单精度浮点数 (节省 CPU 和内存消耗),这点开销对于 Python 使用对象的开销来说,微不足道

1.4 复数(complex)—— numbers.Complex

应用领域: 量子力学 、相对论、信号分析、应用数学 专业工程学等...

提示

通过调用 id() 方法可以打印任意对象的内存地址

2. 序列 —— 不可变序列

什么是序列对象

  • 以非负整数作为索引的有限有序集,并可以根据可变性区分
  • 可被 len() 返回条目数量
  • 大多数序列类型可变/不可变 都支持 通用序列 操作open in new window ,如支持 [:] 切片操作

注意

不可变序列 类型的对象(不可变对象)一旦创建就 不能再改变,只读,不能增删改

2.1 字节串(bytes 二进制序列)

bytes: 十六进制类型,ASCII 编码转换,离计算机二进制最近的数据类型,不可变序列

表示 bytes 字面值的语法与字符串字面值的大致相同,只是添加了一个 b 前缀

bytearray: 即字节数组,bytes 对象的可变对应物,是可变序列 写此处方便对应

bytesbytearray 对象都支持 通用open in new window 序列操作

内置常用方法

API 文档open in new window

  • 转字符串: bytes to str

    foo = b'www.abc.com'
    
    bar = str(foo, "utf-8")
    print(bar)
    ## "www.abc.com"
    

2.2 字符串(str 文本序列)

字符串为 文本序列类型open in new window, 由 Unicode 码位构成的不可变序列

  • 声明方式如下:

    str = "" / = '' / = """"""  # 基本赋值
    

内置方法

API 文档open in new window

  • 转字节串: 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 实现open in new window PyTuple_Type 基于 array 形式

不应该浅显的理解为只读列表,首先它不能 增、删、改,其次常用于 记录

  • 元组(,) 声明,虽然元素不能改变,但能包含 可变序列 list ,也能切片
# tuple 构造包含 0 个或 1 个元素的元组比较特殊
tuple1 = ()
tuple2 = (a,)
t.__add__	
  • 元组 可以在 集合 set映射 dict 中当作 key 键 使用(不可变类型 ), 而列表不行(可变
  • 元组 常作为很多 内联函数 和方法的 返回值

内置方法

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 实现open in new window 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]
    
  • 排序

    排序 数据类型要相同如:strint 是无法进行排序的

    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 对象open in new window 所组成的 无序多项集,常用于 成员检测去重、集合类 交、并、补、差 运算

  • 集合不记录顺序,因此不支持索引
  • 支持 inlenfor 循环迭代操作
  • 可变类型,无哈希值, 不能作为字典键 或其他集合的元素
  • 可使用 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])

集合的元素必须为 hashableopen in new window。 要表示由集合对象构成的集合,所有的内层集合必须为 frozensetopen in new window 对象。 如果未指定 iterable,则将返回一个新的空集合

  • 类型是不可变的,并且为 hashable
  • 其内容在被创建后不能再改变,因此它 可被用作字典的键 或其他集合的元素

5. 映射

Dictionary字典) 是 Pythonlist 外,最灵活的内置数据结构类型,是 无序可变对象集合

5.1 字典(dict)

class dict(**kwargs)

class dict(mapping, **kwargs)

class dict(iterable, **kwargs)

  • 采用 key-value 形式存储数据,对 key 进行哈希运算,根据计算结果决定 value 的存储地址,因此 hashableopen in new window 可以为键
  • 因为索引必须唯一,故 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() 返回 iterabledictview 时,就已经转化为对列表的排序
  • 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 使用 有序字典

operatoropen in new window 复杂排序

若对某些较为复杂的数据结构排序,可使用 operator 模块提供的 itemgetter()attrgetter() 快速字段提取器,来简化操作

from operator import itemgetter, attrgetter
  • itemgetter() 通常用于 tupledict 等取值

    itemgetter(1) == lambda x: x[1]
    # 通常用于元组、字典
    
  • sorted() 直接用 itemgetter() 对字典 keyvalue 排序

    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 关于搜索

已排序的序列,可以用来进行快速搜索,标准库的 bisectopen in new window 模块提供了二分查找,其包含了 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.sortsortedmaxmin 函数的 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 的排序算法使用的是 Timsort2002),一种自适应算法,会根据原始数据的顺序特点,交替使用 插入排序归并排序 达到最佳效率(真实世界的数据通常是有一定顺便的特点

稳定: 即就算两个元素比不出大小,每次排序结果而相对位置也是固定的

上次编辑于: 2023/3/8 05:05:29