Welcome to xpt’s blog! 2021年准备秋招期间整理的一些笔记,分享给大家!

文档分享的初衷是给师弟师妹们作为参考,主要是适合想去大厂+测试开发岗的朋友们。
建议大家自己整理文档,把我的文档作为参考,有些东西自己整理,自己去写出来,才是最适合你自己的!
文章还未精细整理,如存在错误之处,可以邮件or微信反馈给我呀,感激不尽!

想进大厂,要抓住提前批免笔试的机会!(例如京东、字节、百度等报名时间一般为七月,面试时间为报名后的一周内,面试一般为3轮,面试相关经验后续我会单独再写blog分享^_^,也欢迎大家来跟我talk,一定知无不言。)

本人情况:普通211、研究生、有京东、百度、以及字节提前批测开岗offer。7月初开始准备,准备太迟,一边准备一边投简历+面试。

  • 投递简历时间:京东(7.14),字节(7.30),百度(7.30)
  • 三轮面试时间:京东(7.21-7.22-7.26),字节(8.4-8.6-8.9),百度(8.9-8.12-8.16)
  • 意向书时间:京东(8.12),字节(8.16),百度(9.9)

京东提前批开始很早,我投的时候已经是第二批。经过京东几轮面试,熟悉了面试流程,大概掌握了测开岗会问些什么问题。
字节和百度提前批我是在ddl前一天投递,其实已经算很迟了,hc不多了。
投递要趁早,很多岗位有固定hc。
多拿offer,才有谈薪资的底气。

我面试的岗位有以下:
1、测试开发岗(京东、百度、以及字节提前批)
2、银行java开发岗(所以我会整理一点java,银行问的都很简单,所以我这里对java的整理比较少)

整理的内容均来源于历年网络上分享的面经(主要来源于牛客),以及我面试时被问过的问题,list如下:
(1)——计算机网络
(2)——操作系统
(3)——数据库
(4)——数据结构
(5)——python
(6)——java
(7)——linux
(8)——常考编程题
(9)——测试开发相关知识

面试QA整理(5)——python

python常使用的方法或模块

因为我研究生研究方向是深度学习,所以问了下常使用的方法或模块。

os常用方法:

os.path.isfile()和os.path.isdir()函数分别检验给出的路径是一个文件还是目录。

os.path.join(path,name):连接目录与文件名或目录;使用“\”连接

os.listdir(path)返回指定目录下的所有文件和目录名

import numpy as np

对于矩阵(matrix)而言,multiply是对应元素相乘,而 * 、np.matmul() 函数 与 np.dot()函数 相当于矩阵乘法(矢量积),对应的列数和行数必须满足乘法规则;如果希望以数量积的方式进行,则必须使用 np.multiply 函数

torch

  1. 点乘——torch.mul(a, b)
  2. 矩阵乘

2.1. 二维矩阵乘——torch.mm(a, b)
2.2. 高维矩阵乘——torch.matmul(a, b)

torch.nn.Conv2d() 对由多个输入平面组成的输入信号进行二维卷积

sorted ,sort区别

  • sort()与sorted()的不同在于,sort是在原位重新排列列表,而sorted()是产生一个新的列表

    • list 的 sort 方法返回的是对已经存在的列表进行操作,而内建函数 sorted 方法返回的是一个新的 list,而不是在原来的基础上进行的操作
  • sort 是应用在 list 上的方法,sorted 可以对所有可迭代的对象进行排序操作

☆☆python基本的数据类

  • Number(数字)int、float、bool、complex(复数)

  • String(字符串)

  • List(列表)

  • Tuple(元组)

  • Set(集合)

  • Dictionary(字典)

  • 不可变数据(3 个):Number(数字)、String(字符串)、Tuple(元组);

  • 可变数据(3 个):List(列表)、Dictionary(字典)、Set(集合)。

Python可变数据类型,不可变数据类型

不可变数据类型: 数值型、字符串型string和元组tuple(元组使用逗号和圆括号来表示,如 (2, 4, 6))
不允许变量的值发生变化,如果改变了变量的值,相当于是新建了一个对象,而对于相同的值的对象,在内存中则只有一个对象(一个地址)

可变数据类型: 列表list和字典dict,Set(集合);
允许变量的值发生变化,即如果对变量进行append、+=等这种操作后,只是改变了变量的值,而不会新建一个对象,变量引用的对象的地址也不会变化

list,dict,tuple,set区别

1、list,dict,set都是可变数据类型,tuple是不可变数据类型(不能重新赋值替换)

​ tuple用处:如果你希望一个函数返回多个返回值,其实只要返回一个tuple就可以

2、List和Tuple用下标来访问内容,而Dict用Key来访问

3、dict中Key不可重复,set不能包含重复的元素

元组和列表区别

python 中元组和列表的区别如下:
1、列表是动态数组,它们可变且可以重设长度(改变其内部元素的个数);
2、元组是静态数组,它们不可变,且其内部数据一旦创建便无法改变;
3、元组缓存于 Python 运行时环境,这意味着我们每次使用元组时无须访问内核去分配内存。
tuple 不可变:多线程无需加锁,可以作为 key,速度快,存储占用少。
tuple 只有一个元素时候要加逗号,例如 a=(1,) 否则类型就是 int 而不是 tuple

python 字典删除元素

1、dict.pop(key,default),返回值是对应的value

如果设置default的值,如果key不存在,就返回default的值。

如果不设置default的值,如果key不存在,报错

2、del dict[key]

如果key不存在,报错

并行,并发

并发:不同的代码块交替执行
并行:不同的代码块同时执行

☆☆python【线程】/多线程创建方式,查看线程数

(1)通过threading.Thread进行创建多线程

(2)通过继承threading.Thread定义子类创建多线程

(3)使用线程池ThreadPoolExecutor创建

length=len(threading.enumerate())#查看线程数


  1. 通过threading.Thread进行创建多线程
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import threading
from time import sleep # 由于代码执行过快,为了显示效果,使用sleep延时执行。
def eat():
for i in range(3):
print("正在吃饭...%d" % i)
sleep(1)
#如果创建Thread时执行的函数,运行结束那么意味着这个子线程结束了.
def read():
for i in range(3):
print("正在读书...%d" % i)
sleep(1)
if __name__ == '__main__':
t1 = threading.Thread(target=eat)
t2 = threading.Thread(target=read)
t1.start()#启动线程,即让线程开始执行
t2.start()
length=len(threading.enumerate())#查看线程数
print(length)

#当调用Thread的时候,不会创建线程
#当调用Thread创建出来的实例对象的start()方法地时候才会创建线程以及让这个线程开始运行
  1. 通过继承threading.Thread定义子类创建多线程

定义一个新的子类class,只要继承threading.Thread就可以了,然后重写run方法

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
import threading
import time
class MyThread(threading.Thread): # 定义一个类,继承threading.Thread
def run(self): # 重写run方法
for i in range(3):
time.sleep(1)
msg = "I'm " + self.name + ' @ ' + str(i) # name属性中保存的是当前线程的名字
print(msg)
self.login()
self.register()
def login(self):
print("这是登录")
def register(self):
print("这是注册")
if __name__ == '__main__':
t = MyThread() # 创建对象
t.start()
"""
输出结果:
I'm Thread-1 @ 0
I'm Thread-1 @ 1
I'm Thread-1 @ 2
这是登录
这是注册
"""
  1. 使用线程池ThreadPoolExecutor创建
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from concurrent.futures import ThreadPoolExecutor
import time
import os
def sayhello(a):
for i in range(10):
time.sleep(1)
print("hello: " + a)
def main():
seed = ["a", "b", "c"]
# 最大线程数为3,使用with可以自动关闭线程池,简化操作
with ThreadPoolExecutor(3) as executor:
for each in seed:
# map可以保证输出的顺序, submit输出的顺序是乱的
executor.submit(sayhello, each)
print("主线程结束")
if __name__ == '__main__':
main()

参考:https://www.cnblogs.com/lxy0/p/11403555.html

多线程之间共享全局变量,args参数

image-20210811141251924

互斥锁 多线程同步控制

当多个线程几乎同时修改某一个共享数据的时候,需要进行同步控制

线程同步能够保证多个线程安全访问竞争资源,最简单的同步机制是引入互斥锁。

互斥锁为资源引入一个状态:锁定/非锁定

某个线程要更改共享数据时,先将其锁定,此时资源的状态为“锁定”,其他线程不能更改;

直到该线程释放资源,将资源的状态变成“非锁定”,其他的线程才能再次锁定该资源。

互斥锁保证了每次只有一个线程进行写入操作,从而保证了多线程情况下数据的正确性。

1
2
3
4
5
6
7
8
9
10
11
12
threading模块中定义了Lock类,可以方便的处理锁定:
#创建锁
#创建一个互斥锁,默认是没有上锁的
mutex = threading.Lock()

#锁定
#上锁,如果之前没有被上锁,那么此时,上锁成功
#如果上锁之前已经被上锁了,那么此时会堵塞在这里,直到这个锁被解开
mutex.acquire()

#释放
mutex.release()

死锁,避免死锁

在线程间共享多个资源的时候,如果两个线程分别占有一部分资源并且同时等待对方的资源,就会造成死锁。
尽管死锁很少发生,但一旦发生就会造成应用的停止响应。

  • 程序设计时要尽量避免(银行家算法)
  • 添加超时时间等

什么是死锁?
所谓死锁,是指多个进程在运行过程中因争夺资源而造成的一种僵局,当进程处于这种僵持状态时,若无外力作用,它们都将无法再向前推进。 因此我们举个例子来描述,如果此时有一个线程A,按照先锁a再获得锁b的的顺序获得锁,而在此同时又有另外一个线程B,按照先锁b再锁a的顺序获得锁。

1、死锁是指在一组进程中的各个进程均占有不会释放的资源,但因互相申请被其他进程所站用不会释放的资源而处于的一种永久等待状态。死锁的四个必要条件

互斥条件(Mutual exclusion):资源不能被共享,只能由一个进程使用。

请求与保持条件(Hold and wait):已经得到资源的进程可以再次申请新的资源。

不可抢占条件(No pre-emption):已经分配的资源不能从相应的进程中被强制地剥夺。

循环等待条件(Circular wait):系统中若干进程组成环路,该环路中每个进程都在等待相邻进程正占用的资源。

java中产生死锁可能性的最根本原因是:1)是多个线程涉及到多个锁,这些锁存在着交叉,所以可能会导致了一个锁依赖的闭环;2)默认的锁申请操作是阻塞的。

如,线程在获得一个锁L1的情况下再去申请另外一个锁L2,也就是锁L1想要包含了锁L2,在获得了锁L1,并且没有释放锁L1的情况下,又去申请获得锁L2,这个是产生死锁的最根本原因。

2、避免死锁:

只要破坏这四个必要条件中的任意一个条件,死锁就不会发生

〈1〉打破互斥条件。即允许进程同时访问某些资源。但是,有的资源是不允许被同时访问的,像打印机等等,这是由资源本身的属性所决定的。所以,这种办法并无实用价值。

〈2〉打破不可抢占条件。即允许进程强行从占有者那里夺取某些资源。就是说,当一个进程已占有了某些资源,它又申请新的资源,但不能立即被满足时,它必须释放所占有的全部资源,以后再重新申请。它所释放的资源可以分配给其它进程。这就相当于该进程占有的资源被隐蔽地强占了。这种预防死锁的方法实现起来困难,会降低系统性能。

〈3〉打破请求与保持条件。可以实行资源预先分配策略。即进程在运行前一次性地向系统申请它所需要的全部资源。如果某个进程所需的全部资源得不到满足,则不分配任何资源,此进程暂不运行。只有当系统能够满足当前进程的全部资源需求时,才一次性地将所申请的资源全部分配给该进程。由于运行的进程已占有了它所需的全部资源,所以不会发生占有资源又申请资源的现象,因此不会发生死锁。

(4)打破循环等待条件,实行资源有序分配策略。采用这种策略,即把资源事先分类编号,按号分配,使进程在申请,占用资源时不会形成环路。所有进程对资源的请求必须严格按资源序号递增的顺序提出。进程占用了小号资源,才能申请大号资源,就不会产生环路,从而预防了死锁。这种策略与前面的策略相比,资源的利用率和系统吞吐量都有很大提高

• 方案一:破坏死锁的循环等待条件。

• 方法二:破坏死锁的请求与保持条件,使用lock的特性,为获取锁操作设置超时时间。这样不会死锁(至少不会无尽的死锁)

• 方法三:设置一个条件遍历与一个锁关联。该方法只用一把锁,没有chopstick类,将竞争从对筷子的争夺转换成了对状态的判断。仅当左右邻座都没有进餐时才可以进餐。提升了并发度。

函数里面修改全局变量

在一个函数中对全局变量进行修改的时候,到底是否需要使用global进行说明?

不可变数据类型都要加global

要看是否对全局变量的指向进行了修改,
如果修改了指向,即让全局变量指向了一个新的地方,那么必须使用global
如果,仅仅是修改了指向的空间中的数据,此时不用必须使用global

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
num = 100
nums = [11, 22]
nums2 = [1, 2]

def test():
global num
num += 100

nums.append(33)

global nums2
nums2 += [33, 44]

test()
print(num)
print(nums)
print(nums2)
"""
200
[11, 22, 33]
[1, 2, 33, 44]
"""

☆☆python 【进程】,状态,多进程创建

进程:一个程序运行起来后,代码+用到的资源称之为进程,它是操作系统分配资源的基本单元。

不仅可以通过线程完成多任务,进程也是可以的

image-20210811154716810
  • 就绪态:运行的条件都已经准备好了,正在等在cpu执行
  • 执行态: cpu正在执行其功能
  • 等待态:等待某些条件满足,例如一个程序sleep了,此时就处于等待态

python 进程的几种创建方式

(1)使用multiprocessing模块Process类创建

(2)继承multiprocessing模块Process定义子类创建

(3)使用进程池Pool创建

使用multiprocessing模块Process类创建

multiprocessing模块提供了一个Process类来代表一个进程对象

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
37
from multiprocessing import Process
import time

def test(name, age):
for i in range(5):
print("--test--%s\t%d" % (name, age))
time.sleep(1)
print("子进程结束")

if __name__ == '__main__':
p = Process(target=test, args=("aaa", 18))
p.start()
# 等待进程实例执⾏结束,或等待多少秒;
p.join() # 等待的最长时间
print("主进程结束")
"""
输出结果:
--test--aaa 18
--test--aaa 18
--test--aaa 18
--test--aaa 18
--test--aaa 18
子进程结束
主进程结束
"""
join()方法表示主进程等待子进程执行完成后继续往下执行,如果把join()注释掉,则主进程开启子进程后不停顿继续往下执行,然后等待子进程完成程序结束。
把join()方法注释掉的结果:
"""
输出结果:
主进程结束
--test--aaa 18
--test--aaa 18
--test--aaa 18
--test--aaa 18
--test--aaa 18
子进程结束
"""

继承multiprocessing模块Process定义子类创建

创建新的进程还能够使用类的方式,可以自定义一个类,继承Process类,每次实例化这个类的时候,就等同于实例化一个进程对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from multiprocessing import Process
import time

class MyProcess(Process):
def __init__(self, name):
super(MyProcess, self).__init__()
self.name = name

# 重写run方法
def run(self):
print('hello', self.name)
time.sleep(1)

if __name__ == '__main__':
for i in range(3):
p = MyProcess('xpt')
p.start()
"""
hello xpt
hello xpt
hello xpt
"""

使用进程池Pool创建

当需要创建的子进程数量不多时,可以直接利用multiprocessing中的Process动态成生多个进程,但如果是上百甚至上千个目标,手动的去创建进程的工作量巨大,此时就可以用到multiprocessing模块提供的Pool方法

初始化Pool时,可以指定一个最大进程数,当有新的请求提交到Pool中时,如果池还没有满,那么就会创建一个新的进程用来执行该请求;但如果池中的进程数已经达到指定的最大值,那么该请求就会等待,直到池中有进程结束,才会创建新的进程来执行

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
37
38
39
40
41
42
43
44
45
from multiprocessing import Pool
import os
import time

def worker(num):
print("----pid=%d num=%d---" % (os.getpid(), num))
time.sleep(1)

if __name__ == '__main__':
# 定义一个进程池,最大进程数3
pool = Pool(3)
for i in range(10):
print("---%d--" % i)
# 向进程中添加任务
# 注意:如果添加的任务数量超过了进程池中进程的个数的话,那么就不会接着往进程池中添加,
# 如果还没有执行的话,他会等待前面的进程结束,然后在往进程池中添加新进程
# Pool() .apply_ async(调用的目标,(传递给目标的参数元祖,))
pool.apply_async(worker, (i,)) # 使用非阻塞方式调用func(并行执行),一般用这个。
# apply堵塞方式必须等待上一个进程退出才能执行下一个进程,用的不多。
pool.close() # 关闭进程池
# 等待所有子进程结束,主进程一般用来等待
# 一定要添加join函数,否则主进程直接崩了,看不到进程池中子进程的现象
pool.join()
"""
---0--
---1--
---2--
---3--
---4--
---5--
---6--
---7--
---8--
---9--
----pid=25096 num=0---
----pid=22080 num=1---
----pid=24196 num=2---
----pid=22080 num=3---
----pid=25096 num=4---
----pid=24196 num=5---
----pid=22080 num=6---
----pid=25096 num=7---
----pid=24196 num=8---
----pid=25096 num=9---
"""

进程间通信Queue和Pipe

python的multiprocessing 模块包装了底层的机制,提供了多种进程通信的方式,主要 Queue(队列)、Pipes(管道)这两种方式,Queue用于多个进程间实现通信,Pipe是两个进程的通信

1、通过multiprocessing.Queue()完成进程间通信

Queue的使用
可以使用multiprocessing模块的Queue实现多进程之间的数据传递,Queue本身是一个消息列队程序。

  • Queue.qsize() 返回当前队列包含的消息数量
  • Queue.empty() 如果队列为空,返回 True,否则返回 False
  • Queue.full() 如果队列满了,返回 True,否则返回 False
  • Queue.get([block[, timeout]]) 获取队列中的一条消息,然后将其从队列中移除,block 默认值为 True。如果 block 使用默认值,且没有设置 timeout(单位:秒),消息队列为空,此时程序将被阻塞(停在读取状态),直到从消息队列读到消息为止,如果设置了 timeout,则会等待 timeout 秒,若还没有读取到任何消息,则抛出 Queue.Empty 异常
  • Queue.get_nowait() 相当于 Queue.get(False)
  • Queue.put(item, [block[, timeout]]) 将 item 消息写入队列,block 默认值为 True。如果 block 使用默认值,且没有设置 timeout(单位:秒),消息队列如果已经没有空间可写入,此时程序将被阻塞(停在写入状态),直到从消息队列腾出空间为止,如果设置了 timeout,则会等待 timeout 秒,若还没有空间,则抛出 Queue.full 异常
  • Queue.put_nowait(item) 相当于 Queue.put(item, False)
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
from multiprocessing import Queue

q = Queue(3) # 初始化一个 Queue 对象,最多可接收 3 条 put 消息
print(q.empty()) # True
q.put("消息1")
q.put("消息2")
print(q.full()) # False
q.put("消息3")
print(q.full()) # True

# 因为消息队列已满,再 put 会报异常,第一个 try 等待 2 秒后再抛出异常,第二个 try 立刻抛出
try:
q.put("消息4", True, 2)
except:
print("消息队列已满,现有消息数量: %s" % q.qsize())

try:
q.put_nowait("消息4")
except:
print("消息队列已满,现有消息数量: %s" % q.qsize())

# 读取消息时,先判断消息队列是否为空,再读取
if not q.empty():
print("----从消息队列中获取消息--")
for i in range(q.qsize()):
print(q.get_nowait())

下面通过一个实例结合 Process 和 Queue 实现进程间通信。创建两个子进程,一个子进程负责向队列中写入数据,另一个子进程负责从队列中读取数据。

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
from multiprocessing import Process, Queue
import time

# 向对列中写入数据
def write_task(q):
if not q.full():
for i in range(5):
message = "消息" + str(i)
q.put(message)
print("写入: %s" % message)
time.sleep(0.5)

# 从队列读取数据
def read_task(q):
while not q.empty():
print("读取: %s" % q.get(True, 0.5)) # 等待 2 秒,如果还没有读取到任何消息,则抛出异常
time.sleep(1)

if __name__ == '__main__':
q = Queue() # 父进程创建 Queue,并传递给子进程
pw = Process(target=write_task, args=(q,))
pr = Process(target=read_task, args=(q,))
pw.start()
pr.start()
"""
写入: 消息0
读取: 消息0
写入: 消息1
读取: 消息1
写入: 消息2
写入: 消息3
读取: 消息2
写入: 消息4
读取: 消息3
读取: 消息4
"""

2、通过multiprocessing.Pipe()完成进程间通信

Pipe()函数返回一对由管道连接的连接对象,默认情况下是双工(双向)。

Pipe()返回的两个连接对象代表管道的两端。 每个连接对象都有send()和recv()方法(等等)。 请注意,如果两个进程(或线程)尝试同时读取或写入管道的同一端,管道中的数据可能会损坏。 当然,同时使用管道不同端的过程也不会有风险。

1
2
3
4
5
6
Pipe()返回表示管道末端的一对Connection(conn1,conn2)对象。
如果duplex为True(默认),则管道是双向的。
如果duplex是False,那么管道是单向的:conn1只能用于接收消息,conn2只能用于发送消息。

parent_conn, child_conn = Pipe(False)
#表示parent_conn只能用于接收消息,child_conn只能用于发送消息
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# - * -coding: utf - 8 - * -
from multiprocessing import Process, Pipe

def f(conn):
conn.send([42, None, 'hello'])

while True:
print(conn.recv()) # 666

if __name__ == '__main__':
# Pipe()返回的两个连接对象代表管道的两端
# 每个连接对象都有send()和recv()方法
parent_conn, child_conn = Pipe()

p = Process(target=f, args=(child_conn,))
p.start()

print(parent_conn.recv()) # [42, None, 'hello']
parent_conn.send('666')
p.terminate()

python 可迭代对象,迭代器,区别

迭代是访问集合元素的一种方式。迭代器是一个可以记住遍历的位置的对象。迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束。迭代器只能往前不会后退。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#判断classmate是否是可迭代对象
from collections import Iterable
isinstance(classmate, Iterable)
#True

#判断classmate_iterator是否是迭代器
from collections import Iterator
isinstance(classmate_iterator, Iterator)
#True

classmate = Classmate() # 创建了一个实例对象
#for循环内部实现细节
for temp in classmate:
pass
1.判断classmate是否是可迭代对象
2.在第1步成立的前提下,调用iter函数得到classmate对象的__iter__方法的返回值,iter(classmate)
3.__iter__方法的返回值是一个迭代器

如果想要一个对象成为一个可迭代的对象,即可以使用for,那么必须实现__iter__ 方法

创建一个迭代器,把一个类作为一个迭代器使用需要在类中实现两个方法 iter() 与 next() 。

  • __iter__() 方法返回一个特殊的迭代器对象, 这个迭代器对象实现了__next__()方法并通过StopIteration异常标识迭代的完成。

  • __next__() 方法会返回下一个迭代器对象。

可迭代对象与迭代器区别

1)可迭代对象包含迭代器。(如果是迭代器,那么一定是可迭代对象,反之不成立)
2)如果一个对象拥有__iter__()方法,其是可迭代对象;如果一个对象拥有__next__() 方法,其是迭代器。
3)定义可迭代对象,必须实现__iter__()方法;定义迭代器,必须实现__iter__()__next__() 方法。

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
import time
class Classmate(object):
def __init__(self):
self.names = list()
def add(self, name):
self.names.append(name)
def __iter__(self):
# 如果想要一个对象成为一个可迭代的对象,即可以使用for,那么必须实现__iter__方法
return ClassIterator(self) # 返回一个对象引用,把self传进去
#================这里其实还可以优化,直接return self,怎么做?看下一个代码====================
class ClassIterator(object): # 这是一个迭代器
def __init__(self, obj):
self.obj = obj # 想获取name怎么办,接收Classmate传进来的self
self.current_num = 0 # 用来取下一个元素
def __iter__(self):
pass
def __next__(self):
# 如何避免报错IndexError: list index out of range
if self.current_num < len(self.obj.names):
ret = self.obj.names[self.current_num]
self.current_num += 1
return ret
# 后面一直输出None,如何让for循环停
else:
raise StopIteration

classmate = Classmate() # 创建了一个实例对象
classmate.add("老王")
classmate.add("老高")
classmate.add("大宇")

for name in classmate:
print(name)
time.sleep(1)
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
#那直接return self,怎么做?
import time
class Classmate(object):
def __init__(self):
self.names = list()
self.current_num = 0 # 用来取下一个元素
def add(self, name):
self.names.append(name)
def __iter__(self):
# 如果想要一个对象成为一个可迭代的对象,即可以使用for,那么必须实现__iter__方法
return self
def __next__(self):
# 如何避免报错IndexError: list index out of range
if self.current_num < len(self.names):
ret = self.names[self.current_num]
self.current_num += 1
return ret
# 后面一直输出None,如何让for循环停
else:
raise StopIteration

classmate = Classmate() # 创建了一个实例对象
classmate.add("老王")
classmate.add("老高")
classmate.add("大宇")

for name in classmate:
print(name)
time.sleep(1)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 迭代器的优点,占用极小的空间去迭代
# python-迭代器生成斐波那契数列
class Fibonacci(object):
def __init__(self, all_num):
self.all_num = all_num
self.current_num = 0
self.a = 0
self.b = 1

def __iter__(self):
return self

def __next__(self):
if self.current_num < self.all_num: # 判断是否可以迭代
ret = self.a
self.a, self.b = self.b, self.a + self.b
self.current_num += 1
return ret
else:
raise StopIteration # 迭代完成,抛出异常,退出迭代

fib = Fibonacci(10)
for num in fib:
print(num)

并不是只有for循环能接收可迭代对象
除了for循环能接收可迭代对象, list、tuple等也能接收。

1
2
3
4
li = list(FibIterator(15) )
print(li)
tp = tuple(FibIterator(6))
print(tp)

Python 生成器,yield

生成器是一种特殊的迭代器

为什么要有生成器
列表所有数据都在内存中,如果有海量数据的话将会非常耗内存。

如:仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。

如果列表元素按照某种算法推算出来,那我们就可以在循环的过程中不断推算出后续的元素,这样就不必创建完整的list,从而节省大量的空间。

简单一句话:我又想要得到庞大的数据,又想让它占用空间少,那就用生成器!

创建生成器方法1,第一种方法很简单,只要把一个列表生成式的[]改成()

生成器表达式与列表推导式长的非常像,但是它俩返回的对象不一样,前者返回生成器对象,后者返回列表对象。

image-20210812133035812

创建生成器方法2, 如果一个函数中包含yield关键字,那么这个函数就不再是一个普通函数,而是一个generator。调用函数就是创建了一个生成器(generator)对象。

yield返回值的过程(关注点:每次停在哪,下次又开始在哪

image-20210812132426231 image-20210812135251018

我们除了可以使用next()函数来唤醒生成器继续执行外,还可以使用send()函数来唤醒执行。使用send()函
数的一个好处是可以在唤醒的同时向断点处传入一个附加数据

image-20210812142542982

生成器的工作原理
(1)生成器(generator)能够迭代的关键是它有一个next()方法,

  工作原理就是通过重复调用next()方法,直到捕获一个异常。

(2)带有 yield 的函数不再是一个普通函数,而是一个生成器generator

  可用next()调用生成器对象来取值。next 两种方式 t.__next__() | next(t)

  可用for 循环获取返回值(每执行一次,取生成器里面一个值)

  (基本上不会用next()来获取下一个返回值,而是直接使用for循环来迭代)。

(3)yield相当于 return 返回一个值,并且记住这个返回的位置,下次迭代时,代码从yield的下一条语句开始执行。

(4).send() 和next()一样,都能让生成器继续往下走一步(下次遇到yield停),但send()能传一个值,这个值作为yield表达式整体的结果

  ——换句话说,就是send可以强行修改上一个yield表达式值。比如函数中有一个yield赋值,a = yield 5,第一次迭代到这里会返回5,a还没有赋值。第二次迭代时,使用.send(10),那么,就是强行修改yield 5表达式的值为10,本来是5的,那么a=10

【协程】使用yield,greenlet,gevent完成多任务

image-20210812143257904

greenlet
为了更好地使用协程来完成多任务,python中的greenlet模块对其封装,从而使得切换任务变的更加简单

image-20210812143840147

greenlet已经实现了协程,但是这个还的人工切换,是不是觉得太麻烦了,不要捉急,python还有一个比greenlet更强大的并且能够自动切换任务的模块gevent

gevent
其原理是当一个greenlet遇到IO(指的是input output输入输出,比如网络、文件操作等)操作时,比如访问
网络,就自动切换到其他的greenlet,等到IO操作完成,再在适当的时候切换回来继续执行。
由于IO操作非常耗时,经常使程序处于等待状态,有了gevent为我们自动切换协程,就保证总有greenlet在
运行,而不是等待IO

image-20210812145116087

gevent打补丁,joinall更简洁:

image-20210812145044628

Python中生成器和迭代器的区别(可迭代对象

image-20210804125716180

可迭代对象与迭代器

1)可迭代对象包含迭代器。
2)如果一个对象拥有__iter__方法,其是可迭代对象;如果一个对象拥有next方法,其是迭代器。
3)定义可迭代对象,必须实现__iter__方法;定义迭代器,必须实现__iter__和next方法。

生成器是一种特殊的迭代器,生成器自动实现了“迭代器协议”(即__iter__和next方法),不需要再手动实现两方法。只需要一个yiled关键字。 生成器一定是迭代器(反之不成立)

具有yield关键字的函数都是生成器,yield可以理解为return,返回后面的值给调用者。不同的是return返回后,函数会释放,而生成器则不会。在直接调用next方法或用for语句进行下一次迭代时,生成器会从yield下一句开始执行,直至遇到下一个yield。

image-20210804130113694

【进程、线程、协程】对比

1.进程是资源分配的单位
2.线程是操作系统调度(CPU调度)的最小单位
3.进程切换需要的资源很最大,效率很低
4.线程切换需要的资源一般,效率一般(当然了在不考虑GIL的情况下)
5.协程切换任务资源很小,效率高,利用线程等待的时间
6.多进程、多线程根据cpu核数不一样可能是并行的,但是协程是在一个线程中所以是并发

协程,是一种比线程更加轻量级的存在,协程不是被操作系统内核所管理,而完全是由程序所控制(也就是在用户态执行)。这样带来的好处就是性能得到了很大的提升,不会像线程切换那样消耗资源。

协程的特点在于是一个线程执行,那和多线程比,协程有何优势?

  • 极高的执行效率:因为子程序切换不是线程切换,而是由程序自身控制,因此,没有线程切换的开销,和多线程比,线程数量越多,协程的性能优势就越明显;

  • 不需要多线程的锁机制:因为只有一个线程,也不存在同时写变量冲突,在协程中控制共享资源不加锁,只需要判断状态就好了,所以执行效率比多线程高很多。

Python file打开为什么要close,with用法

打开的文件句柄没有被close掉的话,那这个句柄只会在程序退出时才会被释放掉

1
2
3
4
5
6
7
8
9
10
11
12
13
import os

class OpenFileTest:
def openfile(self, filePath):
handle = open(filePath, 'wb')
pass

if __name__ == "__main__":
t = OpenFileTest()
filePath = 'a.txt'
t.openfile(filePath)
os.remove(filePath)
print("sucess")

将代码稍作改动,将 handle = open(filePath, ‘wb’) 替换为 self.handle = open(filePath, ‘wb’),执行结果如下:

image-20210806004346065

由以上的表现可以得知,在第一个程序里由于handle是一个临时的变量(对象),在函数openfile退出时,handle对象被释放了,同时也把文件句柄给关闭了;

而在第二个程序里,self.handle不再是一个临时的对象,因此在openfile退出时就没有释放句柄,因此就无法删除test.txt这个文件了;

这里也可以猜测一下,在python里,open返回的是一个类似C++里智能指针的东西,这样就做到了handle释放时关闭文件句柄;不过从编程规范上来说,还是主动调用close来关闭文件句柄吧(注意这里指的是用with,一个比较pythonic的方式,如

with open(r’test1.txt’,’a’) as handler:
… handler.write(‘123123’))

有时候在编码的时候老忘记关闭文件,现在我们给出一种自动关闭文件的方法,从而不需要我们手动每次写关闭函数。Python--file 的with用法

python GIL 全局解释器锁

一、什么是GIL?
即全局解释器锁(global interpreter lock),每个线程在执行时候都需要先获取GIL,保证同一时刻只有一个线程可以执行代码,即同一时刻只有一个线程使用CPU,也就是说多线程并不是真正意义上的同时执行。


二、python为什么要有全局解释器锁(GIL
GIL的目的是解决多线程同时竞争程序中的全局变量而出现的线程安全问题它并不是python语言的特性,仅仅是由于历史原因,GIL在CPython解释器中难以移除,因为python语言运行环境大部分默认在CPython解释器中。
吉多在创建python时就只考虑到单核cpu,没有考虑到我们计算机发展这么快哈。解决多线程之间数据完整性和状态同步的最简单方法自然就是加锁, 于是有了GIL这把超级大锁。cpython在解析多线程时,会上GIL锁,保证了同时只有一个线程占用cpu。
吉多有一个声明叫做:It isn’t Easy to Remove the GIL。有些消息就说吉多已经尝试过解决GIL的问题,要把GIL抹掉,就需要加其他东西来保证程序稳定运行,但是最终的效率没有GIL快,所以一直保留GIL到今天。


三、由于GIL的存在,即使是多线程,事实上同一时刻只能保证一个线程在运行,既然这样多线程的运行效率不就和单线程一样了吗,那为什么还要使用多线程呢?
例如在使用多线程抓取网页内容时,遇到IO阻塞时,正在执行的线程会暂时释放GIL锁这时其它线程会利用这个空隙时间,执行自己的代码,因此多线程抓取比单线程抓取性能要好。
计算密集型(程序没有延时,一刻不停在计算):要进行大量的数值计算,例如进行上亿的数字计算、计算圆周率、对视频进行高清解码等等。这种计算密集型任务虽然也可以用多任务完成,但是花费的主要时间在任务切换的时间,此时CPU执行任务的效率比较低。这时候用进程!!!
IO密集型(有等待的间隙):读写,U盘的读写,网络的收与发、磁盘IO的任务都是IO密集型任务,这类任务的特点是CPU消耗很少,任务的大部分时间都在等待IO操作完成(因为IO的速度远远低于CPU和内存的速度)。对于IO密集型任务,任务越多,CPU效率越高,但也有一个限度。这时候可以考虑用线程、协程!


四、怎么解决GIL的问题

  1. 更换解释器,更换cpython为jpython(不建议)
  2. 使用多进程完成多线程的任务
  3. 多线程使用其他语言来实现,在使用多线程可以使用c语言去实现

五、什么时候会释放GIL锁?

  1. 遇到像 i/o操作这种,会有时间空闲情况,造成cpu闲置的情况,可以暂时释放GIL,但在执行完毕后,必须重新获取GIL。
  2. Python 3.x使用计时器(执行时间达到阈值后,当前线程释放GIL)
    Python 2.x,有一个专门ticks进行计数 一旦ticks数值达到100,这个时候释放Gil锁,线程之间开始竞争Gil锁

六、互斥锁和Gil锁的关系
Gil锁 : 保证同一时刻只有一个线程能使用到cpu
互斥锁 : 多线程时,保证修改共享数据时有序的修改,不会产生数据修改混乱

深拷贝浅拷贝

浅拷贝b=a,指向
浅拷贝:拷贝了引用,并没有拷贝内容
完成浅拷贝c=copy . copy(a)

深拷贝是重新开辟内存空间并将数据完完全全的拷贝出来一份
完成深拷贝import copy
c = copy . deepcopy(a)

如果copy.copy拷贝的是元组,那么它不会进行浅拷贝,仅仅是引用指向
原因:因为元组时不可变类型,那么意味着数据定不能修改,因此用copy.copy的时候它会自动判断,如果是元组它就是指向了它

如果copy.deepcopy拷贝的是元组,真真实实的数据,那也是引用指向,但如果是(a,b),a和b是列表,还是会完完全全的拷贝出来一份

闭包

一个函数定义中引用了函数外定义的变量,并且该函数可以在其定义环境外被执行。

我们可以将闭包理解为一种特殊的函数,这种函数由两个函数的嵌套组成,且称之为外函数和内函数,外函数返回值是内函数的引用,此时就构成了闭包。

image-20210812164059595

python装饰器

python装饰器就是用于拓展原来函数功能的一种函数,这个函数的特殊之处在于它的返回值也是一个函数,使用python装饰器的好处就是在不用更改原函数的代码前提下给函数增加新的功能。

image-20210812165905345

@set_ func 等价于test1 = set_ func(test1)如果我们有其他的类似函数,我们可以继续调用装饰器来修饰函数,而不用重复修改函数或者增加新的封装。这样,我们就提高了程序的可重复利用性,并增加了程序的可读性。

装饰器的实现过程:

image-20210812170627134

(1)对有参数,无返回值的函数进行装饰:

image-20210812170943972

(2)对不定长参数的函数装饰:

image-20210812171857286

(3)对应有返回值函数进行装饰、通用装饰器:

image-20210812172356604 image-20210812172420428

(4)多个装饰器对同一个函数装饰

image-20210812173846673

python 反射

在做程序开发中,我们常常会遇到这样的需求:需要执行对象里的某个方法,或需要调用对象中的某个变量,但是由于种种原因我们无法确定这个方法或变量是否存在,这是我们需要用一个特殊的方法或机制去访问和操作这个未知的方法或变量,这中机制就称之为反射。接下记录下反射几个重要方法:

hasattr判断对象中是否有这个方法或变量

getattr获取对象中的方法或变量的内存地址

setattr为对象添加变量或方法

delattr删除对象中的变量。注意:不能用于删除方法

这就是python的反射,它的核心本质其实就是利用字符串的形式去对象(模块)中操作(查找/获取/删除/添加)成员,一种基于字符串的事件驱动

python 抽象类,接口

首先,我们必须明确的一点是:python中无接口类型,定义接口只是人为规定,在编程过程自我约束!

定义一个接口对继承类进行约束,接口里有什么方法,继承类就必须有什么方法

抽象类,可以说是类和接口的混合体,既可以定义常规方法,也可以约束子类的方法(抽象方法)

在其他的语言里,比如Java,继承类没有重写接口方法是会报错的,而在python里不会,就是因为python没这个类型,所以只是在我们编程过程的一个规定,以I开头的类视为接口

python 中 *

1、运算中

一个 * :乘法
两个 **: 乘幂

2、收集列表中多余的值,只用于列表。

1
2
3
4
a,b,*c=[1,2,3,4]
print(c)
#输出
#[3, 4]

3、 解包——用在变量前面

向函数传递参数,将变量中可迭代对象的元素拆解出来,作为独立的参数传递给函数:

1
2
3
4
a=[1,2,3,4] #元祖、列表、字符串都可以用
print(*a)
#输出
#1 2 3 4

而两个星号是给字典解包的:

1
2
3
4
dic={'a':1,'b':2,'c':3}
print('{a},{b},{c}'.format(**dic))
#输出
#1,2,3

4、在函数定义中使用,收集参数。【*代表收集参数,**代表收集关键字参数】

一个*的情况
该位置接受任意多个非关键字(non-keyword)参数,在函数中将其转化为元组(1,2,3,4)

也就是将调用时提供的所有值,放在一个元组里:

1
2
3
4
5
6
7
#定义函数
def myprint(*params):
print(params)
#调用函数
myprint(1,2,3)
#输出
#(1, 2, 3)

两个**的情况
该位置接受任意多个关键字 (keyword)参数,在函数**位置上转化为字典[key:value, key:value]

1
2
3
4
5
6
7
#定义函数
def myprint2(**params):
print(params)
#调用函数
myprint2(x=1,y=2,z=3)
#输出
#{'x': 1, 'y': 2, 'z': 3}

Python垃圾回收机制

三种情况触发垃圾回收
1、调用 gc.collect()
2、 GC 达到阀值时
3、程序退出时


如果不垃圾回收会发生什么问题? –内存泄漏

python的垃圾回收机制实际上是一个引用计数器+一个循环垃圾收集器来工作;

  • 垃圾回收器是一段独立的代码,用来寻找引用计数为0的对象;

  • 垃圾回收器还负责检查虽然引用计数>0但也应该被销毁的对象; —循环引用

    • 循环引用发生在有至少两个对象互相引用,垃圾回收器也会清理未引用的循环

      python解释器会跟踪对象的引用技术,垃圾回收器负责释放内存

python的垃圾回收机制,以引用计数为主,标记清除和分代回收为辅。

总体来说,在Python中,主要通过引用计数进行垃圾回收;通过 “标记-清除” 解决容器对象可能产生的循环引用问题;通过 “分代回收” 以空间换时间的方法提高垃圾回收效率。

一、Python中,主要通过引用计数(Reference Counting)进行垃圾回收。

1
2
3
4
typedef struct_object {
int ob_refcnt;
struct_typeobject *ob_type;
} PyObject;

在Python中每一个对象的核心就是一个结构体PyObject,它的内部有一个引用计数器(ob_refcnt)。程序在运行的过程中会实时的更新ob_refcnt的值,来反映引用当前对象的名称数量。当某对象的引用计数值为0,那么它的内存就会被立即释放掉。

以下情况是导致引用计数加一的情况:

  • 对象被创建,例如a=2
  • 对象被引用,b=a
  • 对象被作为参数,传入到一个函数中
  • 对象作为一个元素,存储在容器中

下面的情况则会导致引用计数减一:

  • 对象别名被显示销毁 del
  • 对象别名被赋予新的对象
  • 一个对象离开他的作用域
  • 对象所在的容器被销毁或者是从容器中删除对象

二、标记清除
标记清除可以处理这种循环引用的情况,它分为两个阶段:

  • 第1阶段,标记阶段
    GC会把所有活动对象打上标记,这些活动的对象就如同一个点,他们之间的引用关系构成边,最终点个
    边构成了一个有向图,如下图所示

  • image-20210804111020532
  • 第2阶段,搜索清除阶段
    从根对象(root) 出发,沿着有向边遍历整个图,不可达的对象就是需要清理的垃圾对象。这个根对象
    就是全局对象,调用栈,寄存器。
    在上图中,从root出发后,可以到达1234,而5,6,7均不能到达, 其中6和7互相引用,这3个对
    象都会被回收。

三、分代回收

  • 分代回收建立标记清除的基础之上,是一种以空间换时间的操作方式。标记清除可以回收循环引用的垃
    圾,但是,回收的频次是需要控制的,如果时时刻刻做标记清除,可以想象, python的程序会慢成什么
    样子。
  • 分代回收,根据内存中对象的存活时间将他们分为3代,新生的对象放入到0代,如果-个对象能在第0
    代的垃圾回收过程中存活下来,GC就会将其放入到1代中,如果1代里的对象在第1代的垃圾回收过程中
    存活下来,则会进入到2代。
  • image-20210804111257575
  • image-20210804111426153
  • 轻易不要改动!!!!

如何提高python执行效率

1、 使用函数,局部变量比全局变量快很多。尽量使用函数,尽量不使用全局变量

2、 有选择性的消除属性访问。如多用from math import sqrt 而不要直接在程序中多次调用 math.sqrt(),或直接声明局部变量。

3、尽量使用内建的字符串,元组,列表,集合,字典等容器

4、 避免不必要的数据结构或拷贝动作

5、 多使用内置方法(例如del方法、max 方法和min方法),内置操作符(例如”in 和not in”的使用)。

6、用If语句进行优化。

7、优化循环,避免在一个循环中使用点操作。

1
2
3
4
5
6
7
8
lowerlist = ['this', 'is', 'lowercase']
upper = str.upper
upperlist = []
append = upperlist.append
for word in lowerlist:
append(upper(word))
print(upperlist)
#['THIS', 'IS', 'LOWERCASE']

优化循环的关键,是要减少Python在循环内部执行的工作量,因为Python原生的解释器在那种情况下,真的会减缓执行的速度。

8、 元素排序尽可能使用键(key)和默认的sort()排序方法;

1
2
3
4
import operator
somelist = [(1, 5, 8), (6, 2, 4), (9, 7, 5)]
somelist.sort(key=operator.itemgetter(1))
print(somelist)#[(6, 2, 4), (1, 5, 8), (9, 7, 5)]

9、 使用较新版本的Python

python 单例模式

单例模式(Singleton Pattern)是一种常用的软件设计模式,该模式的主要目的是确保某一个类只有一个实例存在。当你希望在整个系统中,某个类只能出现一个实例时,单例对象就能派上用场。

比如,某个服务器程序的配置信息存放在一个文件中,客户端通过一个 AppConfig 的类来读取配置文件的信息。如果在程序运行期间,有很多地方都需要使用配置文件的内容,也就是说,很多地方都需要创建 AppConfig 对象的实例,这就导致系统中存在多个 AppConfig 的实例对象,而这样会严重浪费内存资源,尤其是在配置文件内容很多的情况下。事实上,类似 AppConfig 这样的类,我们希望在程序运行期间只存在一个实例对象

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
import threading
class Singleton(object):
_instance_lock = threading.Lock()

def __init__(self):
pass

def __new__(cls, *args, **kwargs):
if not hasattr(Singleton, "_instance"):
with Singleton._instance_lock:
if not hasattr(Singleton, "_instance"):
Singleton._instance = object.__new__(cls)
return Singleton._instance

obj1 = Singleton()
obj2 = Singleton()
print(obj1,obj2)

def task(arg):
obj = Singleton()
print(obj)

for i in range(10):
t = threading.Thread(target=task,args=[i,])
t.start()

https://www.cnblogs.com/huchong/p/8244279.html

python的面向对象

封装性、继承性、多态性

多态意味着可以对不同的对象使用同样的操作,但它们可能会以多种形态呈现出结果。

方法多态,运算符也多态

Python是支持多重类继承的