Python 进阶学习笔记之二:常用数据类型(下)
Python 进阶系列笔记第三篇,前面文章传送门:
Python 进阶学习笔记之一:内置常用类型及方法
Python 进阶学习笔记之二:常用数据类型(上)
本篇文章接上篇文章,但可以单独阅读。
8. heapq — 堆队列算法
这个模块提供了堆队列算法的实现,也称为优先队列算法。堆是一个二叉树,它使用了数组来实现:从零开始计数,对于所有的 k ,都有 heap[k] <= heap[2k+1] 和 heap[k] <= heap[2k+2] 。
这个模块中,原生的 list 就可以看作是堆形式。要创建一个堆,可以使用list来初始化为 [] ,或者你可以通过函数 heapq.heapify() ,来把一个list转换成堆。
模块中提供的方法包括:
-
heapq.heappush(heap, item)
:将 item 的值加入 heap 中,保持堆的不变性。其中参数heap其实就是一个list -
heapq.heappop(heap)
:弹出并返回 heap 的最小的元素,保持堆的不变性。如果堆为空,抛出 IndexError 。使用 heap[0] ,可以只访问最小的元素而不弹出它。 -
heapq.heappushpop(heap, item)
:将 item 放入堆中,然后弹出并返回 heap 的最小元素。该组合操作比先调用 heappush() 再调用 heappop() 运行起来更有效率。 -
heapq.heapify(x)
:将list x 转换成堆,原地,线性时间内。 -
heapq.heapreplace(heap, item)
:从heap中弹出最小值,然后把item push进heap,如果heap为空,则会报IndexError。这个方法有一个情况需要注意,就是可能弹出的值比item还大,如果要避免这种情况,推荐使用heappushpop
,它总会返回一个最小值。 -
heapq.merge(*iterables, key=None, reverse=False)
:合并多个iter,并从小到大排序后返回,当然reverse=True的话,会是从大到小,key可以指定一个item处理方法。 -
heapq.nlargest(n, iterable, key=None)
:从指定的iterable中返回前 N 大的结构,key可以指定一个item处理函数。 -
heapq.nsmallest(n, iterable, key=None)
:从指定的iterable中返回前 N 小的结构,key可以指定一个item处理函数。
这个模块的方法使用都比较简单,下面写几个简单的例子展示一下:
>>> import heapq
>>> h = [9,8,7,1,2,3,6,10]
>>> h[0]
9
>>> heapq.heapify(h) # 列表h被转成了堆
>>> h[0] # 访问堆的最小值
1
>>> heapq.heappop(h) # 弹出堆的最小值
1
>>> h[0]
2
>>> heapq.nlargest(2, h) # 返回最大的两个值
[10, 9]
>>> h
[2, 8, 3, 10, 9, 7, 6]
>>>
需要注意的是,上面代码中列表 h 本身并不会改变,只是使用优先队列算法对其进行操作。
9. bisect — 数组二分查找算法
这个模块对有序列表提供了支持,使得他们可以在插入新数据仍然保持有序。对于长列表,如果其包含元素的比较操作十分昂贵的话,这可以是对常见方法的改进。
模块提供一下方法:
-
bisect.bisect_left(a, x, lo=0, hi=len(a))
:在列表 a 中找到 x 合适的插入点以维持有序,返回这个点的索引,如果 a 中存在与 x 相等的值,返回其相等值左侧的索引。要注意,实际只是查询合适的插入点。 -
bisect.bisect_right(a, x, lo=0, hi=len(a))
:在列表 a 中找到 x 合适的插入点以维持有序,返回这个点的索引,如果 a 中存在与 x 相等的值,返回其相等值右侧的索引。 -
bisect.bisect(a, x, lo=0, hi=len(a))
:与bisect_right
类似。 -
bisect.insort_left(a, x, lo=0, hi=len(a))
:在列表 a 中插入 x 并保持有序。 -
bisect.insort_right(a, x, lo=0, hi=len(a))
:类似insort_left
方法,注意相同值的处理方式。 -
bisect.insort(a, x, lo=0, hi=len(a))
:同上。
示例代码:
>>> import bisect
>>> h = [2, 1, 4, 7, 10]
>>> h.sort() # bisect 模块是为了处理有序列表,因此列表本身要保证有序
>>> h
[1, 2, 4, 7, 10]
>>> bisect.bisect_left(h, 3) # 在列表h中查询能保证有序情况下,3应该插入的位置
2
>>> bisect.bisect_left(h, 2) # 在列表中存在元素2,这个方法会返回列表中2左边的位置
1
>>> bisect.bisect_right(h, 2) # 在列表中存在元素2,这个方法会返回列表中2右边的位置
2
>>> h
[1, 2, 4, 7, 10]
>>> bisect.insort_left(h, 3) # 保证有序情况下进行插入操作
>>> h
[1, 2, 3, 4, 7, 10]
此模块目的是操作有序列表,无序列表可能会得到预想不到的结果,参考三方模块 https://code.activestate.com/recipes/577197-sortedcollection/ ,这是使用 bisect 构造了一个功能完整的集合类,提供了直接的搜索方法和对用于搜索的 key 方法的支持。所有用于搜索的键都是预先计算的,以避免在搜索时对 key 方法的不必要调用。
10. weakref — 弱引用
和许多其它的高级语言一样,Python使用了垃圾回收器来自动销毁那些不再使用的对象。每个对象都有一个引用计数,当这个引用计数为0时Python能够安全地销毁这个对象,由于一次仅能有一个对象被回收,引用计数无法回收循环引用的对象,而太多无法回收的对象会引起内存泄露。在对象群组内部使用弱引用(即不会在引用计数中被计数的引用)有时能避免出现引用环,因此弱引用可用于解决循环引用的问题。使用weakref模块,可以创建到对象的弱引用,Python在对象的引用计数为0或只存在对象的弱引用时将回收这个对象。
首先说明,日常业务中,使用弱引用的场景非常少见,这个模块作为知识储备即可。
10.1 创建弱引用
可以通过调用weakref模块的ref(obj[,callback])来创建一个弱引用,obj是你想弱引用的对象,callback是一个可选的函数,要求单个参数(弱引用的对象)。
import weakref
class Man:
def __init__(self,name):
print self.name = name
o = Man('Jim')
r = weakref.ref(o) # 创建一个弱引用
print(r) # 输出<weakref at 00D3B3F0; to 'instance' at 00D37A30>
o2 = r() # 获取弱引用所指向的对象。注意:大部分的对象不能通过弱引用直接来访问。
o = None
o2 = None
print(r) # 当对象引用计数为零时,弱引用失效 <weakref at 00D3B3F0; dead>de>
10.2 创建代理对象
代理对象是弱引用对象,它们的行为就像它们所引用的对象,这就便于你不必首先调用弱引用来访问背后的对象。通过weakref模块的proxy(obj[,callback])函数来创建代理对象。使用代理对象就如同使用对象本身一样:
class Man:
def __init__(self, name):
self.name = name
def test(self):
print "this is a test!"
def callback(self):
print "callback"
o = Man('Jim')
p = weakref.proxy(o, callback)
p.test()
o=None
p.test()
删除了引用的对象之后,使用代理将会导致一个weakref.ReferenceError错误。
10.2 弱引用集合
有了单个对象的弱引用,自然也会有其集合形式,分别是:
class weakref.WeakKeyDictionary([dict])
class weakref.WeakValueDictionary([dict])
-
class weakref.WeakSet([elements])
。
其中前两者的区别是 WeakKeyDictionary([dict])
的key是弱引用,而 WeakValueDictionary([dict])
的值是弱引用。其具体使用方式和普通的 dict 和 set 一样,区别是其内的元素会因为没有强引用而被回收。
11. types – 动态类型创建以及内置类型命名
模块 types
,定义了一些工具方法来协助动态创建类(型),它还定义了标准Python解释器使用的某些对象类型的名称,但未公开为int或str等内置类型的类型。
python中函数type实际上是一个元类,元类就是类的类。type就是Python在背后用来创建所有类的元类。Python中所有的东西——都是对象。这包括整数、字符串、函数以及类。它们全部都是对象,而且它们都是从一个类创建而来,这个类就是type。type就是Python的内建元类,当然了,也可以创建自己的元类。
这里引用一句前辈的话:
“元类就是深度的魔法,99%的用户应该根本不必为此操心。如果你想搞清楚究竟是否需要用到元类,那么你就不需要它。那些实际用到元类的人都非常清楚地知道他们需要做什么,而且根本不需要解释为什么要用元类。”
— Python界的领袖 Tim Peters
下面是一个最简单的例子:
>>> import types
>>> Student = types.new_class("Student", (object,))
>>> s1 = Student()
>>> s1.name = "kety"
>>> s1.age = 100
>>> print(type(s1))
<class 'types.Student'>
>>> print(f"{s1.name}, {s1.age}")
kety, 100
>>> print(type(Student))
<class 'type'>
从输出中可以看到 Student
类和我们普通的类没太大区别。
其实不用 types
模块,也可以实现动态创建类,那就是用内置函数 type()
。
type函数语法:type(args1,args2,args3)
其中args1是字符串类型指类的名称,args2是元组类型指定继承自那个父类,args3是字典类型,指包含属性的字典(名称和值)。
Student = type("Student", (object,), {"name": "", "age": 0})
s1 = Student()
s1.name = "madaha"
print(s1.name)
print(s1.age)
我们还可以对动态类绑定方法,包括类方法和静态方法
def s_speak(self): # 要带有参数self,因为类中方法默认带self参数。
print("这是给类添加的普通方法")
@classmethod
def s_run(cls):
print("这是给类添加的类方法")
@staticmethod
def s_eat():
print("这是给类添加的静态方法")
# 创建类,给类添加静态方法,类方法,普通方法。跟添加类属性差不多
Person = type("Person", (object, ), {"speak": s_speak, "run": s_run, "eat": s_eat})
person = Person()
Person.run()
person.eat()
person.run()
print(dir(Person))
# 输出
"""
这是给类添加的类方法
这是给类添加的静态方法
这是给类添加的类方法
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'eat', 'run', 'speak']
"""
由于实际场景并不常见,这里不在详细展开。
12. copy — 浅层 (shallow) 和深层 (deep) 复制操作
Python 中赋值操作实际上是把对象和变量名进行了一种绑定,对于自身可变或者包含可变项的集合对象,开发者有时会需要生成其副本用于改变操作,进而避免改变原对象。copy
模块提供了通用的浅层复制和深层复制操作,接口如下。
-
copy.copy(x)
:返回 x 的浅层复制 -
copy.deepcopy(x[, memo])
:返回 x 的深层复制
比较简单,不在进行编码演示,需要注意的有两点:
一是深copy 的递归复制问题;二是复制不应该复制的问题,比如需要共享的数据;
这两个问题,在自定义类中出现概率比较大,必要时需要在类中定义特殊方法 __copy__()
和__deepcopy__()
来定制复制行为。
另外要注意,日常编码中,字典的浅层复制可以使用 dict.copy()
方法,而列表的浅层复制可以通过赋值整个列表的切片完成,例如,copied_list = original_list[:]
。
13. enum – 枚举类型
做过企业级项目的都知道,一定会常用到枚举常量来保证接口的输入规范性。enum
模块提供了枚举基类,这个模块时从 3.4
版本新加入的特性。
模块内容:
-
class enum.Enum
:创建枚举最基本的类 -
class enum.IntEnum
:创建枚举中 value 是 int 的枚举,因为 IntEnum 继承Enum同时,还继承了 int,因此它可以直接和 int 值做比较 -
class enum.IntFlag
:Python 3.6 新增,暂未深入了解 -
class enum.Flag
:Pthon 3.6 新增,暂未深入了解 -
enum.unique()
:用来约束枚举中value的值不重复 -
class enum.auto
:Python 3.6 新增,可以用来支持枚举值从1自增,用于值不重要的枚举类型中
代码示例:
from enum import Enum, IntEmun
# 一个简单的枚举
class Color(Enum):
RED = 1
GREEN = 2
BLUE = 3
print(Color.RED.name) # 输出 RED
print(Color.RED.value) # 输出 1
for shake in Shake: # 可以用 for 循环遍历
print(shake)
# 一个常见的用法
class ErrorCode(Enum):
SUCCESS = (0, 'SUCCESS')
ERROR = (-1, 'ERROR')
def __init__(self, code, msg):
self.code = code
self.msg = msg
def what_ever(error_code):
print(f'{error_code.code}, {error_code.msg}')
print(f'{error_code.name}, {error_code.value}')
what_ever(ErrorCode.ERROR)
'''
这种写法,就是定义了新的属性,不过要注意,在枚举中 name 和 value 都是定义了属性,自定义属性的名称不要取者两个值
'''
class EC(IntEnum):
S = 1
B = 2
EC.SU == 2 # 可以和int类型直接比较
'''
使用 IntEnum 要注意的是,每个枚举项的值必须是 int 值,否则会报错
'''
# 下面这种写法,是合法的,如果逻辑上要限制 value 不重复,可以使用unique,对比下面两种实现
class Shape(Enum):
SQUARE = 2
SQUARE = 2
@unique
class Shape(Enum):
SQUARE = 2
SQUARE = 3
# 某些场景下,我们对枚举的各项值并不敏感,只要不重复就行,我们可以这样写
class Color(Enum):
RED = auto()
BLUE = auto()
GREEN = auto()
print(list(Color)) # 输出[<Color.RED: 1>, <Color.BLUE: 2>, <Color.GREEN: 3>]
# 或者
class Color(Enum):
RED = object()
BLUE = object()
GREEN = object()
# 更简单的是使用 Functional API
Color = Enum('Color', 'RED BLUE GREEN') # 结果和使用 auto() 相同
更多特殊场景使用方式,参考官网文档:https://docs.python.org/zh-cn/3/library/enum.html
下一篇: jieba(结巴)分词的使用