python学习-2

小记录

元组是不可变序列,例如用了*=运算时,会重新创建一个元组并且赋值

原子操作(atomic operation),指不会被线程调度机制打断的操作,这种操作一旦开始,就一直运行到结束,中间不会切换到其他线程。

python特殊方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from math import hypot
class Vector:
def __init__(self, x=0, y=0):
self.x = x
self.y = y
def __repr__(self):
return 'Vector(%r, %r)' % (self.x, self.y)
def __abs__(self):
return hypot(self.x, self.y)
def __bool__(self):
return bool(abs(self))
def __add__(self, other):
x = self.x + other.x
y = self.y + other.y
return Vector(x, y)
def __mul__(self, scalar):
return Vector(self.x * scalar, self.y * scalar)

定义一个二维向量类,使用了python的几个特殊方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# __repr__、__abs__、__add__ 和 __mul__
#repr 就是通过 __repr__这个特殊方法来得到一个对象的字符串表示形式的
def __repr__(self):
return 'Vector(%r, %r)' % (self.x, self.y)
'''
如果没有实现
__repr__,当我们在控制台里打印一个向量的实例时,得到的字符串
可能会是 <Vector object at 0x10e100070>。
__repr__ 和 __str__ 的区别在于,后者是在 str() 函数被使用,或
是在用 print 函数打印一个对象的时候才被调用的,并且它返回的字
符串对终端用户更友好。
例如:
>> v * 3
Vector(9, 12)
'''
# __add__ 和 __mul__,算术运算符+,*
'''
这两个方法的返回值都是新创建的向量对
象,被操作的两个向量(self 或 other)还是原封不动,代码里只是
读取了它们的值而已。
'''

序列构成的数组

1.内置序列类型

容器序列 list、tuple 和 collections.deque 这些序列能存放不同类型的 数据。

扁平序列 str、bytes、bytearray、memoryview 和 array.array,这类 序列只能容纳一种类型。

按照可变和不可变

可变序列 list、bytearray、array.array、collections.deque 和 memoryview。

不可变序列 tuple、str 和 bytes。

第二章 序列组成的数组

2.2 列表推导和生成器表达式

1
2
3
4
#2.2 列表推导和生成器表达式
test = 'abcde'
print ([ord(testvar) for testvar in test])
#[97, 98, 99, 100, 101]

2.4切片

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

list = [1,2,3,4,5,6]
print(list[2:])
print(list[:2])#在下标2的地方分割
#[3, 4, 5, 6]
#[1, 2]

#对对象切片
#[a:b:c]取值方式,在a和b之间以c为间隔进行取值
s='abcdefgh'
print(s[::2])
#aceg

#多维切片和省略
'''
[] 运算符里还可以使用以逗号分开的多个索引或者是切片,外部库
NumPy 里就用到了这个特性,二维的 numpy.ndarray 就可以用 a[i,
j] 这种形式来获取,抑或是用 a[m:n, k:l] 的方式来得到二维切片。

'''
#切片赋值
list = [0,1,2,3,4,5,6]
list[2:5] = [999,888]#下标2开始到下标5之前
print(list)
#[0, 1, 999, 888, 5, 6]

对序列使用+和*

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#对序列使用+和*
'''
。通常 + 号两侧的序列由
相同类型的数据所构成,在拼接的过程中,两个被操作的序列都不会被
修改,Python 会新建一个包含同样类型数据的序列来作为拼接的结果。
如果想要把一个序列复制几份然后再拼接起来,更快捷的做法是把这个
序列乘以一个整数。同样,这个操作会产生一个新序列:
'''
list = [0,1,2,3,4,5,6]
print(list *2)
print(list +[9,8,7])
#[0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6]
#[0, 1, 2, 3, 4, 5, 6, 9, 8, 7]

#建立列表组成的列表
board = [['_'] * 3 for i in range(3)]
print(board)
#[['_', '_', '_'], ['_', '_', '_'], ['_', '_', '_']]
board[1][2] = 'X'
print(board)
#[['_', '_', '_'], ['_', '_', 'X'], ['_', '_', '_']]
'''
如果在 a * n 这个语句中,序列 a 里的元素是对其他可变
对象的引用的话,你就需要格外注意了,因为这个式子的结果可能
会出乎意料。比如,你想用 my_list = [[]] * 3 来初始化一个
由列表组成的列表,但是你得到的列表里包含的 3 个元素其实是 3
个引用,而且这 3 个引用指向的都是同一个列表。这可能不是你想
要的效果。
'''
#错误效果
weird_board = [['_'] * 3] * 3
print(weird_board)
weird_board[1][2] = 'x'
print(weird_board)
#[['_', '_', '_'], ['_', '_', '_'], ['_', '_', '_']]
#[['_', '_', 'x'], ['_', '_', 'x'], ['_', '_', 'x']]

2.6序列的增量赋值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
'''
+= 背后的特殊方法是 __iadd__ (用于“就地加法”)。但是如果一个类
没有实现这个方法的话,Python 会退一步调用 __add__ 。考虑下面这
个简单的表达式:
a += b
a 实现了 __iadd__ 方法,就会调用这个方法(像 a.extend(b) 一样),如果没有实现的话,首先计算 a +
b,得到一个新的对象,然后赋值给 a
但是*=的效果不一样
'''
list = [1,2,3]
print(id(list),list)
list *=2
print(id(list),list)
tuple1 = (1,2,3)
print(id(tuple1),tuple1)
tuple1 *=2
print(id(tuple1),tuple1)

"""
2418655396032 [1, 2, 3]
2418655396032 [1, 2, 3, 1, 2, 3]
2418657982848 (1, 2, 3)
2418655659776 (1, 2, 3, 1, 2, 3)
可以看到元组进行增量计算时重新创建了对象
"""

一个关于+=的谜题

如果对一个元组中的列表使用+=运算

image-20220319163622333

原理

image-20220319163949271

不要把可变对象放在元组里面。

增量赋值不是一个原子操作。我们刚才也看到了,它虽然抛出了异常,但还是完成了操作。

查看 Python 的字节码并不难,而且它对我们了解代码背后的运行机 制很有帮助。

什么是原子操作?

原子操作(atomic operation),指不会被线程调度机制打断的操作,这种操作一旦开始,就一直运行到结束,中间不会切换到其他线程。

它有点类似数据库中的 事务

2.7 list.sort方法和内置函数sorted

list.sort 方法会就地排序列表,也就是说不会把原列表复制一份,返回值是 None。

不管是 list.sort 方法还是 sorted 函数,都有两个可选的关键字参数。

reverse 如果被设定为 True,被排序的序列里的元素会以降序输出(也就 是说把最大值当作最小值来排序)。这个参数的默认值是 False。

key 一个只有一个参数的函数,这个函数会被用在序列里的每一个元素 上,所产生的结果将是排序算法依赖的对比关键字。比如说,在对一些 字符串排序时,可以用 key=str.lower 来实现忽略大小写的排序,或 者是用 key=len 进行基于字符串长度的排序。这个参数的默认值是恒 等函数(identity function),也就是默认用元素自己的值来排序。

例子:

1
2
3
4
5
6
7
8
fruits = ['grape', 'raspberry', 'apple', 'banana']
sorted(fruits)
print(fruits) #['grape', 'raspberry', 'apple', 'banana']
print(sorted(fruits)) #['apple', 'banana', 'grape', 'raspberry']
print( sorted(fruits, reverse=True) )#['raspberry', 'grape', 'banana', 'apple']
print(sorted(fruits, key=len, reverse=True))#['raspberry', 'banana', 'grape', 'apple']
print(fruits.sort())#None
print(fruits)#['apple', 'banana', 'grape', 'raspberry']

2.8 用bisect来管理已排序的序列

书里的解释:

bisect(haystack, needle) 在 haystack(干草垛)里搜索 needle(针)的位置,该位置满足的条件是,把 needle 插入这个位置 之后,haystack 还能保持升序。也就是在说这个函数返回的位置前面 的值,都小于或等于 needle 的值。其中 haystack 必须是一个有序的 序列。你可以先用 bisect(haystack, needle) 查找位置 index,再用 haystack.insert(index, needle) 来插入新值。但你也可用 insort 来一步到位,并且后者的速度更快一些

示例代码:

偷了那里(11条消息) 关于python中‘bisect管理已排序序列’记_runner-liu的博客-CSDN博客的解释,有修改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# BEGIN BISECT_DEMO
import bisect
import sys

num_hay = [1, 4, 5, 6, 8, 10, 19, 23, 23, 35] # 定义被插入数列
num_need = [0, 1, 2, 5, 8, 10, 14, 20, 23, 38] # 定义插入数列的数列
row_fmt = '{0:2d} @ {1:2d} {2}{0:<2d}' # 定义数据显示格式 0,1,2对应needle, position, offset
'''
^, <, > 分别是居中、左对齐、右对齐,后面带宽度, : 号后面带填充的字符,只能是一个字符,不指定则默认是用空格填充。
+ 表示在正数前显示 +,负数前显示 -; (空格)表示在正数前加空格
b、d、o、x 分别是二进制、十进制、八进制、十六进制。
'''

def demo(bisect_fn):
"""定义测试bisect的函数"""
for needle in reversed(num_need): # reversed() 返回一个反转的迭代器即将数列倒序。只能进行一次循环遍历。显示一次所包含的值
position = bisect_fn(num_hay, needle) # 使用bisect(haystack, needle)函数查询指针位置(第1个数之前为0,第1个数之后为1,第2个数之后为2 )
offset = position * ' |' # 通过 pisition 计算制表需要的 “ |” 数
print(row_fmt.format(needle, position, offset)) # 将数据填入定义好的数据显示格式(row_fmt)中


if __name__ == '__main__':
if sys.argv[-1] == 'left': # 执行脚本文件时,python3 脚本文件名 left 即定义数值从位置左侧插入
bisect_fn_1 = bisect.bisect_left
else:
bisect_fn_1 = bisect.bisect # bisect.bisect 实际上就是bisect.bisect_right 前者是别名
print('DEMO:', bisect_fn_1.__name__) # 打印上面判断的函数名
print('haystack ->', ' '.join('%2d' % n for n in num_hay)) # str.join()通过指定字符连接序列中元素后生成的新字符串。str为指定字符
demo(bisect_fn_1) # 调用函数demo

# END BISECT_DEMO

实际效果:left和right

left是往相同数据的左边插入,right是右边

image-20220319201443565

可以用来建立索引

1
2
3
4
5
6
7
8
9
#根据一个分数查询成绩
def greae(score,breakpoints=[10,20,30,40,50],grades='abcdef'):
i = bisect.bisect(breakpoints,score)
print(grades[i])
return grades[i]

print([greae(score) for score in [11,13,55,44,33]])
#['b', 'b', 'f', 'e', 'd']

用bisect.insort插入新元素

**作用:**insort(seq, item) 把变量 item 插入到序列 seq 中,并能保持 seq 的升序顺序。

insort 跟 bisect 一样,有 lo 和 hi 两个可选参数用来控制查找的范围。它也有个变体叫 insort_left,这个变体在背后用的是 bisect_left。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
SIZE=7
random.seed(1729)# 用了随机种子
my_list = []
for i in range(SIZE):
new_item = random.randrange(SIZE*2)
bisect.insort(my_list, new_item)
print('%2d ->' % new_item, my_list)

'''
用了随机种子,所以必定是这个列表
10 -> [10]
0 -> [0, 10]
6 -> [0, 6, 10]
8 -> [0, 6, 8, 10]
7 -> [0, 6, 7, 8, 10]
2 -> [0, 2, 6, 7, 8, 10]
10 -> [0, 2, 6, 7, 8, 10, 10]
'''

2.9 列表不是首选时

2.9.1数组

如果我们需要一个只包含数字的列表,那么 array.array 比 list 更 高效。数组支持所有跟可变序列有关的操作,包括 .pop、.insert 和 .extend。另外,数组还提供从文件读取和存入文件的更快的方法,如 .frombytes 和 .tofile。

image-20220319211831384
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from array import array
from random import random

floats = array('d',(random() for i in range(10**7))) #双精度浮点数组(类型码是 'd'),生成10000000个
print (len(floats),floats[-1])
#print (random())
fp = open('floats.bin', 'wb')
floats.tofile(fp)
fp.close()
floats2 = array('d')
fp = open('floats.bin', 'rb')
floats2.fromfile(fp,10**7)
print(floats2[-1])
print(floats == floats)
fp.close()
'''
10000000 0.9887800460985485
0.9887800460985485
True
'''

说明array.tofile 和 array.fromfile 用于读取和写入二进制文件非常快

列表和数组的属性和方法:这个用了再参考吧

2.9.2 内存视图

memoryview 是一个内置类,它能让用户在不复制内容的情况下操作同 一个数组的不同切片。

例子:

试了试改书里的代码,我觉得这个更直观体现了

1.memoryview只取内存,不是重新生成的对象,可以修改其内容

2.memv_oct[7] = 0xc 把高位修改成了1100,所以值变成了3073

1
2
3
4
5
6
7
8
9
10
11
12
numbers = array('h',[-2,-1,0,1,2])#h对应c类型signed short
memv = memoryview(numbers)
print (memv.tolist())
#[-2, -1, 0, 1, 2]
memv_oct = memv.cast('B') #是把 memv 里的内容转换成 'B' 类型(无符号字符)
print(memv_oct.tolist())
#[254, 255, 255, 255, 0, 0, 1, 0, 2, 0]
print(bin(memv[3])) #0b1
memv_oct[7] = 0xc
print(bin(3073)) #0b110000000001
print(bin(memv[3])) #0b110000000001
print(numbers) #array('h', [-2, -1, 0, 3073, 2])

2.9.3 NumPy和SciPy

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import numpy

a = numpy.arange(12)

print(a) # [ 0 1 2 3 4 5 6 7 8 9 10 11]
print(type(a))
print(a.shape) #查看数组维度,显示(12,)表示1维,12个元素的数组
a.shape = 3,4 #把a分成3行4列的数组
print(a)
print(a[2]) #打印第二行
print(a[2,1]) #打印第二行第一列
print(a[:,1]) #打印每行的第一列
print(a.transpose()) #行和列交换

#输出
'''
<class 'numpy.ndarray'>
(12,)
[[ 0 1 2 3]
[ 4 5 6 7]
[ 8 9 10 11]]
[ 8 9 10 11]
9
[1 5 9]
[[ 0 4 8]
[ 1 5 9]
[ 2 6 10]
[ 3 7 11]]
'''

2.9.4 双向队列和其他形式的队列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#双向队列和其他形式的队列
from collections import deque

dq = deque(range(10), maxlen=10)
print(dq) #deque([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], maxlen=10)
dq.rotate(3) # 队列的旋转操作接受一个参数 n,当 n > 0 时,队列的最右边的 n个元素会被移动到队列的左边。当 n < 0 时,最左边的 n 个元素会被移动到右边。
print(dq)
dq.rotate(-4)
print(dq)
dq.appendleft(-1) #的队列做尾部添加操作的时候,它头部的元素会被删除掉。deque([-1, 1, 2, 3, 4, 5, 6, 7, 8, 9], maxlen=10)
print(dq)
dq.appendleft(2) #deque([2, -1, 1, 2, 3, 4, 5, 6, 7, 8], maxlen=10)
print(dq)
dq.extend([11,22,33])#在队列尾部添加多个元素
#deque([2, 3, 4, 5, 6, 7, 8, 11, 22, 33], maxlen=10)
print(dq)
dq.extendleft([-1,-2,-3])#在队列开始逐个添加 extendleft(iter) 方法会把迭代器里的元素逐个添加到双向队列的左边,因此迭代器里的元素会逆序出现在队列里。
#deque([-3, -2, -1, 2, 3, 4, 5, 6, 7, 8], maxlen=10)
print(dq)
'''
双向队列实现了大部分列表所拥有的方法,也有一些额外的符合自身设
计的方法,比如说 popleft 和 rotate。但是为了实现这些方法,双向
队列也付出了一些代价,从队列中间删除元素的操作会慢一些,因为它
只对在头尾的操作进行了优化。
append 和 popleft 都是原子操作,也就说是 deque 可以在多线程程序
中安全地当作先进先出的栈使用,而使用者不需要担心资源锁的问题。
'''

除了 deque 之外,还有些其他的 Python 标准库也有对队列的实现。

queue 提供了同步(线程安全)类 Queue、LifoQueue 和 PriorityQueue,不同的线程可以利用这些数据类型来交换信息。这三 个类的构造方法都有一个可选参数 maxsize,它接收正整数作为输入 值,用来限定队列的大小。但是在满员的时候,这些类不会扔掉旧的元 素来腾出位置。相反,如果队列满了,它就会被锁住,直到另外的线程 移除了某个元素而腾出了位置。这一特性让这些类很适合用来控制活跃 线程的数量。

multiprocessing 这个包实现了自己的 Queue,它跟 queue.Queue 类似,是设计给 进程间通信用的。同时还有一个专门的 multiprocessing.JoinableQueue 类型,可以让任务管理变得更方 便。

asyncio ,Python 3.4 新提供的包,里面有 Queue、LifoQueue、PriorityQueue 和 JoinableQueue,这些类受 到 queue 和 multiprocessing 模块的影响,但是为异步编程里的任务 管理提供了专门的便利。 heapq 跟上面三个模块不同的是,heapq 没有队列类,而是提供了 heappush 和 heappop 方法,让用户可以把可变序列当作堆队列或者优 先队列来使用。


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!,本博客仅用于交流学习,由于传播、利用此文所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,文章作者不为此承担任何责任。 文章作者拥有对此站文章的修改和解释权。如欲转载此站文章,需取得作者同意,且必须保证此文章的完整性,包括版权声明等全部内容。未经文章作者允许,不得任意修改或者增减此文章内容,不得以任何方式将其用于商业目的。若造成严重后果,本人将依法追究法律责任。 阅读本站文章则默认遵守此规则。