Python 进阶学习笔记之三:常用数据类型(下)

Python 进阶系列笔记文章链接:
Python 进阶学习笔记之一:内置常用类型及方法
Python 进阶学习笔记之二:常用数据类型(上)
Python 进阶学习笔记之三:常用数据类型(下)
Python 进阶学习笔记之四:高效迭代器工具
Python 进阶学习笔记之五:异步 IO
Python 进阶学习笔记之六:多线程编程
Python 进阶学习笔记之七:互联网支持
Python 进阶学习笔记之八:面向对象高级编程
Python 进阶学习笔记之九:IO 编程
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)    # 输出
o2 = r() # 获取弱引用所指向的对象。注意:大部分的对象不能通过弱引用直接来访问。
o = None
o2 = None
print(r)    # 当对象引用计数为零时,弱引用失效 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))       # 输出[, , ]

# 或者
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

——————
继续阅读请点击:Python 进阶学习笔记之四:高效迭代器工具

你可能感兴趣的:(Python,types,heapq,bisect,weakref,copy)