欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2022-06-04 09:02:12
...

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

相关标签: Python 进阶