Python是动态的、弱类型的解释型语言。
可变性:
- 列表是可变的,在创建后可以修改它的内容,比如添加、删除或更改元素,并根据需要自动调整其大小。
- 元组是不可变的,一旦创建就不能改变其内容。
语法表示:
- 列表使用方括号`[]`来定义,例如`my_list = [1, 2, 3]`
- 元组使用圆括号`()`来定义,例如`my_tuple = (1, 2, 3)`
性能:
- 由于元组是不可变的,Python在创建元组时会进行一些优化,元组的内容一旦确定就不会改变,可以更有效的分配和管理内存。所以它们通常比列表更轻量级,操作速度更快。
- 列表是可变的,这意味着在运行时可能会被修改,这需要更多的内存开销来动态支持调整大小和内容的变化。
缓存机制:
- 不可变对象可以被缓存以提高性能。Python会对小整数、段字符串等常量进行缓存,元组作为不可变对象也可以利用这种缓存机制。
- 可变对象每次修改都会导致新的对象被创建,从而增加了内存管理和垃圾回收的压力。
哈希能力:
- 元组可以被用作字典的键或存储在集合中,因为元组实现了高效的哈希函数。
- 列表不可以被哈希。
用途:
- 列表适用于需要频繁修改数据的情况。
- 元组适用于不需要修改的数据集,可以作为字典的键或其他集合的元素,因为它们是不可变的。
内置方法:
- 列表有许多内置的方法用于操作列表中的数据,如`append()`、`remove()`、`pop()`、`sort()`等。
- 元组内置方法如`count()`、`index()`,但是没有像列表那样的修改方法。
安全性:
- 因为元组是不可变的,所以在多线程环境下使用元组更加安全,不会被其他线程修改。
其他特性:
- 如果元组中的元素本身是可变对象(如列表、字典等),那么这些可变对象的内容是可以被修改的,但也意味着这个元组失去了哈希能力。
- 元组可以包含任意类型的对象,包括自定义类的实例、函数、模块等复杂对象。
2、列表的内存管理机制
内存分配:
- 初始容量:当创建一个新的空列表时,Python会为其分配一个初始的固定容量,通常是0。
- 元素添加:添加元素时,如果当前列表分配的内存不足以容纳新元素,Python会进行扩容。
内存布局:
- 连续的内存块:通常是C语言中的动态数组,列表中的所有元素在内存中是按顺序排列的。
扩容机制:
- 过量分配:列表不会每次添加一个新的元素就重新分配内存,而是分配比实际需要更多的内存以此来应对在添加更多元素时减少重新分配的次数。
- 扩容过程:当列表长度达到其当前容量时,触发扩容。每次增加大约`1/8`的容量。
- 复制元素:在扩容过程中,Python会创建一个新的更大的内存区域,并将原列表中的所有元素复制到这个新的内存区域。复制的时间复杂度`O(n)`。
- 释放旧元素:完成复制后,释放原来的内存区域,从而节省内存。
删除元素:
- 删除元素后不会立即缩小内存,用于后续添加元素时使用,避免频繁扩容。
3、字符串插值的方法
%, 旧式字符串插值
format, 支持位置参数和关键字参数
f-string方法,支持变量
Template字符串模版,t = Template(“skjkdf $name”), t.subtitle(name=“fanbin”) 用于用户字符串,防止代码注入。
4、map、filter
都返回一个迭代器
map接收一个函数和一组序列,对序列的每个值都作为参数传给函数,将函数返回值作为新序列的元素。
filter接收一个函数和一个可迭代对象,对函数值返回真的元素返回,否则过滤掉该元素,新的可迭代对象的元素都是真值。
对于一个嵌套函数,外部函数返回内部函数,其内部函数在离开其作用域后依然能够记住并访问外部函数的变量。
装饰器,是一种设计模式,在不改变函数内部的代码的情况下,对函数的功能就行扩充。装饰器是一个函数接收另一个函数作为参数,并返回一个新函数替换被装饰的函数。通常使用@语法糖应用。调用被装饰的函数时,实际调用的是装饰器返回的新函数。装饰器实际上改变了原函数,可以通过functools.wrap方法保留原函数的属性__name__、__doc__等。在内部函数中使用@wraps(f)即可保留。
6、range
range方法接受一组参数,包括起始、终止和步长,生成一个序列。该方法返回一个迭代器。
7、类、type元类(类名,一组基类,一组关键字参数): new方法和init方法
type可以获取对象的类型,也是所有类的元类。
type可以动态创建类,type(name, bases, namespace)
自定义元类,要设置__new__方法,__new__(cls, name, bases, namespace)
其中,namespace是一个方法或者属性集合{“__init__”: lambda xxx, “jjj”: say_hello, “name”: “fanbin”}
metaclass,自定义时继承type,重写其new方法。在orm中模型基类中,抽取属性等操作。
8、比较:is,==
is比较的是两个对象是否是同一个对象,具体的比较的是id值,即内存地址
==比较的是两个对象的值
9、实例方法、静态方法、类方法
实例调用,第一个参数是self
静态方法,使用@staticmethod装饰的普通方法
类方法,使用@classmethod装饰的类方法,第一个参数是cls,和该类相关的方法
10、any和all
any对一个序列执行bool值运算,任意一个元素为真,则返回真
all对一个序列执行bool值运算,全部元素都为真,返回真
11、with
with语句用于简化资源管理,特别是在处理文件操作、网络连接、数据库连接等需要确保资源被正确释放的场景。
with语句主要提供了一种上下文管理机制,语法如下
with expression as target: # 使用taget进行操作
expression通常返回上下文管理器的对象
as target: 是可选的,将上下文管理器返回的对象赋值给一个变量,以便使用。
__enter__(): # 可以返回一个对象,会被赋值给as子句。 __exit__(exc_type, exc_value, traceback): # 退出with语句是时被调用,参数包括异常类型、异常实例和异常的回溯信息。 # 该方法如果返回True,表示异常已被处理,不会传播到with语句之外。 # 该方法如果返回False或None,异常会继续传播。
12、迭代器、生成器
迭代器,能够将一个序列的元素按序输出的对象成为迭代器。遵循迭代器协议,实现了iter和next方法。iter返回迭代器自身,next方法输出元素,直到为空引发迭代停止异常
数组和集合等都是可迭代对象,但可迭代对象不一定是迭代器,迭代器一定是可迭代对象。可迭代对象 (Iterable): 只实现了 __iter__() 方法的对象
itertools
# itertools 模块中的迭代器工具
import itertools
# 无限迭代器
count = itertools.count(10, 2) # 10, 12, 14, ...
cycle = itertools.cycle('ABC') # A, B, C, A, B, C, ...
repeat = itertools.repeat(10, 3) # 10, 10, 10
# 有限迭代器
chain = itertools.chain([1, 2], ['a', 'b']) # 1, 2, a, b
filter_iter = filter(lambda x: x % 2, [1, 2, 3, 4]) # 1, 3
map_iter = map(lambda x: x*2, [1, 2, 3]) # 2, 4, 6
生成器是一个特殊的迭代器,通常有2种方法创建,函数配合yield关键字,()方法。生成器基于惰性计算,在需要时生成元素,内存占用更小。
生成器具有以下特点:
- 使用 yield 关键字返回值
- 每次调用 next() 时从上次暂停的位置继续执行
- 自动实现迭代器协议(__iter__() 和 __next__() 方法)
- 状态保持 - 记住局部变量和执行位置
- 惰性求值 - 只在需要时生成值
示例:
def count_up_to(max):
count = 1
while count <= max:
yield count # 每次调用next()时返回count的值,并暂停在这里
count += 1
高级用法:
def accumulator():
total = 0
while True:
value = yield total # yield可以接收外部发送的值
if value is None:
break
total += value
acc = accumulator()
next(acc) # 启动生成器,执行到第一个yield,yield返回值为0
print(acc.send(10)) # 输出: 10,10被赋值给了value
print(acc.send(20)) # 输出: 30
print(acc.send(30)) # 输出: 60
# 斐波那契
def fibonacci():
a, b = 0, 1
while True:
yield a
a, b = b, a+b
f = fibonacci()
print([next(f) for _ in range(10)])
# 递归实现
def fibonacci2(n):
if n <= 1:
return n
return fibonacci2(n-1) + fibonacci2(n-2)
print([fibonacci2(i) for i in range(10)])
13、字符串的统计方法
“hello”.count(‘l’)
str.title()
str.isdigit()
str[::-1]
str.replace(‘ ‘, ‘’)
str.lower()
str.upper()
14、异常模块
else,在没有异常时执行
finally,任何时候都执行
15、GIL锁
在Cpython中,python解释器在执行python字节码时会先获取gil锁,所以python不会真正的利用os的多线程。
16、PEP8
python enhancement proposal#8,python代码风格指南
空格,缩进,命名,蛇式风格,驼峰风格,导入,全局变量等。
优雅、明了、简洁、扁平等。
17、docstring
文档,在pyhon中推崇在代码中写文档,代码即文档。
模块、函数、类、方法里的第一个字符串语句,就是docstring。会自动变成对象的属性doc。
18、类型注解
提高代码的可读性,pydantic2
cython编译时更重要。
def (x: int, y: string) -> bool: xxx
19、打乱一个列表的元素
import random
random.shuffle([1,2,3,])
20、python中的字典和OrderedDict
字典将键值对经过哈希函数放入其内部的哈希数组中。
哈希冲突,开放寻址法,一种线性探测机制。
有序的,通过额外的列表记录了插入顺序。
OrderedDict是collections模块中的有序字典,保持插入顺序
内存使用较多,比dict多20%-30%
性能稍慢,会比较相等性,两个OrderedDict对象的内容和顺序都相等才认为相等。
具有move_to_end等方法,可用于lru设计。活跃的key每次都要执行以下move_to_end
21、将元组变为字典
ks = (1, 2, 3)
vs = ("a", "b", "c")
ds = zip(ks, vs)
print(dict(ds))
22、如何对生成器类型的对象实现切片操作?
from itertools import islice
gen = iter(range(10))
for i in islice(gen, 3,5):
print(i) # 只输出起始3,结束5之间的元素。
23、JSON序列化
自定义JSONEncoder,重写default方法,json.dumps(dict, cls=MyEncoder, ensure_ascii=False)
24、内存管理
内存分配:
- 小型对象分配器
- 通用的大型对象分配器
垃圾回收:
- 引用计数,每个对象都有一个引用计数器,记录指向该对象的引用数量,计数器为0时,内存会被回收。但是无法解决循环引用问题。
- 标记清除,定期运行垃圾收集器,标记所有可达对象,然后清除未标记的对象。基于不同的阈值遍历各个代。
// 标记清除的细节 Python维护了一个全局的根集,包含所有活动的对象引用 从根集出发,递归地标记所有可达对象 标记完成后,清除所有未标记的对象
- 分代收集:每次回收后,将存活的对象放入链表中,共计3个链表,分别表示0代、1代和2代。每个代被回收的频率是不一致的。
内存池:
- 小型对象:预先分配一大块内存,分配对象时,取出可用的一块,回收时也是放在这里,而不是返回给系统。减少频繁调用malloc和free的开销,提高内存分配速度和降低内存碎片。
其他:
- 字符串驻留与复用:将相同的字符串在内存中存储为单个实例,减少内存占用和提高比较效率。
25、接口
通过抽象类实现
26、反射
在运行时检查对象自身的状态,执行动态操作的机制。
在python中,通过字符串的形式导入模块,import_module,对模块执行对象操作等。
通过`type()`方法获取对象的类型也是一种反射。
27、python中的传参形式
python中参数传递采取的是传对象引用的方式。是值传递和地址传递的混合形式。
值传递:复制新的值
地址传递:变量的地址传递,如果变量类型是可变类型,则修改操作会影响原变量。
28、猴子补丁
运行时替换的功能,mockey patch, gevent中使用较多,将阻塞io替换为协程的方式。
29、python程序退出时,是否会释放所有内存分配?
大部分情况内存会被完全释放,有几种例外
- 使用了c/c++编写的扩展模块,且有内存泄漏
- 共享内存,被其他进程使用。例如mmap未刷盘。
- 资源未被正确关闭,例如文件描述符、数据库连接等资源。
执行清理,使用with,使用atexit清理。
解析:如果一个文件被打开,在程序退出时没有关闭会发生什么?
- 操作系统会在程序终止时自动关闭所有为关闭的文件描述符,这是操作系统的标准行为,确保资源不会泄露。
- 如果文件打开后是写操作,则操作系统关闭时可能有数据写入缓冲区但没有刷新到磁盘,数据丢失。
- 由于数据未完全写入磁盘,这个文件内容可能是损坏的。
- 虽然,操作系统会关闭文件描述符,但是程序中打开过多的文件,可能会耗尽可用的文件描述符限制。
解析:如果主进程通过共享内存的方式和子进程通信,主进程退出时没有关闭和释放共享内存,会发生什么?
- 在多进程环境中,每个进程打开共享内存后都会增加一个引用计数,close方法用于减少这个引用计数。
- 如果没有关闭,即使子进程已经退出,共享内存的引用计数不会减少,系统认为该共享内存仍被使用。
- 内存泄漏,每次运行程序而不关闭和释放共享内存,新的共享内存区域会被分配出来,但旧的区域不会被回收,导致可用内存越来越少。
- 文件描述符泄露:操作系统为每个打开共享内存区域分配一个文件描述符。如果这些文件描述符不被释放,最终可能导致系统的文件描述符限制。
- 在多线程或多进程环境下,打开同一个文件,且不加锁控制,那就是会并发,不确定性很多。
30、如何修改全局变量?
使用global关键字,
全局对象,局部对象,nonlocal(闭包)
31、python中的递归?
递归最大次数限制1000次。sys修改。
32、面向对象的mro?
方法解析顺序,对于一个类继承树,当调用类对象的实例方法事,python解释器在继承树上搜索方法的顺序。
33、断言?
防御性编程, assert等。
34、模块或者包中定义__all__,方法,限制被导入的对象。
35、内存泄露检测工具
- gc模块
- tracemalloc:跟踪内存分配
- objgraph: 可视化对象引用关系
- 谨慎使用__del__方法
36、深拷贝和浅拷贝
浅拷贝:拷贝对象,如果是数组、字典等,则只拷贝第一层的对象。
深拷贝:拷贝对象,对象的完整结构,包括其内部元素的值都被拷贝。
37、*args和**kwargs的含义
打包位置参数
打包关键字参数
38、单例模式
- 设置类变量_instance
- 重写new方法
- 使用装饰器
39、多态
多态,是指一类事物有多重形态
多态性是指具有不同功能的函数可以使用相同的函数名,这样就可以用同一个函数名调用不同内容的函数。
调用不同的子类将会产生不同的行为,而无需明确知道这个子类实际是什么。
鸭子类型,是动态语言的重要特性。
40、oom问题排查
top看进程,
psutil看内存占用
tracemalloc,看内存占用点,分析代码。
步骤:
- 观察到python进程异常退出,并提示Killed
- 观察容器输出,程序输出的日志是否包含oom等信息
- 使用系统命令dmesg查看killed情况: `dmesg | grep killed`
- 观察到了具体的kill信息,如下
[120520.364695] Memory cgroup out of memory: Killed process 18459 (python) total-vm:527396kB, anon-rss:237156kB, file-rss:960kB, shmem-rss:0kB, UID:0 pgtables:1072kB oom_score_adj:0
- 使用strace追踪
apt update strace && apt install strace -y strace -p <pid> # 程序异常退出是提示信息 pselect6(0, NULL, NULL, NULL, {tv_sec=0, tv_nsec=1000000}, NULL) = 0 (Timeout) brk(0x557fd77ea000) = 0x557fd77ea000 write(1, "hello: 9803\n", 12) = 12 pselect6(0, NULL, NULL, NULL, {tv_sec=0, tv_nsec=1000000}, NULL) = 0 (Timeout) write(1, "hello: 9804\n", 12) = 12 pselect6(0, NULL, NULL, NULL, {tv_sec=0, tv_nsec=1000000}, NULL) = 0 (Timeout) brk(0x557fd781f000) = 0x557fd781f000 +++ killed by SIGKILL +++
- 分析日志
pselect6(...), 系统调用: pselect6,参数表示文件描述符数量,后面有超时时间等 brk, 系统调用:分配内存,0x557fd77ea000是新的堆顶地址 write,系统调用,文件描述符1对应标准输出,后面是内容和长度 killed by SIGKILL,事件,程序被SIGKILL信号终止,这个信号通常是由操作系统OOM杀手发出的,表示系统内存不足,强制终止。
- 举例通过系统kill命令发出的终止日志
$ kill <pid> 进程日志可以直接看到 Terminal trace log如下 --- SIGTERM {si_signo=SIGTERM, si_code=SI_USER, si_pid=1, si_uid=0} --- +++ killed by SIGTERM +++
- 举例程序异常
pselect6(0, NULL, NULL, NULL, {tv_sec=0, tv_nsec=100000000}, NULL) = 0 (Timeout) write(1, "hello: 100\n", 11) = 11 pselect6(0, NULL, NULL, NULL, {tv_sec=0, tv_nsec=100000000}, NULL) = 0 (Timeout) write(2, "Traceback (most recent call last"..., 35) = 35 write(2, " File \"/app/app.py\", line 16, i"..., 43) = 43 openat(AT_FDCWD, "/app/app.py", O_RDONLY|O_CLOEXEC) = 3 newfstatat(3, "", {st_mode=S_IFREG|0644, st_size=253, ...}, AT_EMPTY_PATH) = 0 ioctl(3, TCGETS, 0x7ffef6e9d3c0) = -1 ENOTTY (Inappropriate ioctl for device) lseek(3, 0, SEEK_CUR) = 0 fcntl(3, F_DUPFD_CLOEXEC, 0) = 4 fcntl(4, F_GETFL) = 0x8000 (flags O_RDONLY|O_LARGEFILE) newfstatat(4, "", {st_mode=S_IFREG|0644, st_size=253, ...}, AT_EMPTY_PATH) = 0 read(4, "import time\n\n\ndef main():\n re"..., 4096) = 253 close(4) = 0 lseek(3, 0, SEEK_SET) = 0 read(3, "import time\n\n\ndef main():\n re"..., 8192) = 253 close(3) = 0 write(2, " main()\n", 11) = 11 write(2, " File \"/app/app.py\", line 10, i"..., 39) = 39 openat(AT_FDCWD, "/app/app.py", O_RDONLY|O_CLOEXEC) = 3 newfstatat(3, "", {st_mode=S_IFREG|0644, st_size=253, ...}, AT_EMPTY_PATH) = 0 ioctl(3, TCGETS, 0x7ffef6e9d3c0) = -1 ENOTTY (Inappropriate ioctl for device) lseek(3, 0, SEEK_CUR) = 0 fcntl(3, F_DUPFD_CLOEXEC, 0) = 4 fcntl(4, F_GETFL) = 0x8000 (flags O_RDONLY|O_LARGEFILE) newfstatat(4, "", {st_mode=S_IFREG|0644, st_size=253, ...}, AT_EMPTY_PATH) = 0 read(4, "import time\n\n\ndef main():\n re"..., 4096) = 253 close(4) = 0 lseek(3, 0, SEEK_SET) = 0 read(3, "import time\n\n\ndef main():\n re"..., 8192) = 253 close(3) = 0 write(2, " i = i/0\n", 12) = 12 write(2, "ZeroDivisionError: division by z"..., 36) = 36 rt_sigaction(SIGINT, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=SA_RESTORER|SA_ONSTACK, sa_restorer=0x7f13f0e8a050}, {sa_handler=0x7f13f10ff4a7, sa_mask=[], sa_flags=SA_RESTORER|SA_ONSTACK, sa_restorer=0x7f13f0e8a050}, 8) = 0 munmap(0x7f13f0aea000, 151552) = 0 exit_group(1) = ? +++ exited with 1 +++
- 总结:
- 在程序自己的错误或者是被其他指令终止,可以看到错误信息或者terminal的日志
- 程序如果是因为oom引起,那日志只能看到kill,基于trace追踪可以看到的是killed by SIGKILL,且看到如上日志都是brk,分配内存的相关系统调用,则基于此日志追踪到代码行,分析代码即可。
- 也可以使用psutil,看系统日志,主要看是否频繁执行brk这个指令。
41、如何看待python语言?
Python是一种解释型语言,在运行时不需要编译。
Python是一种动态类型语言,不需要提前声明变量的类型。
python适合面向对象编程,函数是对象,类也是对象,类的实例也是对象,在python中,一切皆对象。
python代码运行较慢,因为python解释器需要逐行解释代码在运行,可以通过c扩展库,例如numpy、pandas等函数库提高性能。
python是一种通用编程语言。
42、help和dir函数
help函数用于显示输入函数的文档字符串,还可以查看模块、关键字、属性等相关的使用信息。
dir用户显示定义的符号。
43、模块和包
py后缀的文件称为模块,多个模块和以组成一个包,即一个文件夹可以称为一个包
44、进程间通信机制
- 管道,pipe,上一个进程的输出作为下一个进程的输入
import os def main():
pc, cc = os.pipe()
pid = os.fork()
if pid == 0:
os.close(pc) # 关闭父进程端
cc.send("hello world".encode())
cc.close()
else:
os.close(cc) # 关闭子进程端
print(os.read(pc, 1024).decode())
pc.close()
- 队列, Queue,线程和进程都安全的数据结构
from multiprocessing import Process, Queue
def worker(q):
q.put("hello world")
def main():
q = Queue()
p = Process(target=worker, args=(q,))
p.start()
print(q.get())
p.join()
- 共享内存,mmap,多个进程访问共享内存中的数据
from multiprocessing import Process, Value, Array
def worker(val, arr):
val.value = 3.14159
for i in range(len(arr)):
arr[i] = -arr[i]
def main():
num = Value('d', 0.0)
arr = Array('i', range(10))
p = Process(target=worker, args=(num, arr))
p.start()
p.join()
print(num.value)
print(arr[:])
- 信号量,semaphore, 控制临界资源数量
- 套接字,socket,多主机上的进程之间通信,可以通过tcp或者udp协议
45、多线程协作方式
- threading
- Lock, RLock
- Timer
- Condition
- Event
- Barrary等
46、什么是asyncio?它有什么用途?
- asyncio是python标准库中的一个模块,用于编写并发代码,它主要用于io密集型和高并发应用,如网络服务器、爬虫、实时数据等。
- asyncio提供了一个事件循环(event loop)来管理协程(coroutines)、任务(tasks)和回调(callbacks),使异步编程更加高效和易于管理。
47、什么是协程?
- 协程是特殊的函数,可以通过await关键字暂停执行,并在稍后恢复。
- 使用async def定义协程
- 使用await调用其他协程或等待异步操作完成
48、什么是事件循环?
- 事件循环是asyncio的核心组件,负责调度协程的执行
- 它持续运行,监听和调度待执行的人物
- 当一个协程等待某个操作(如io操作)时,事件循环可以切换到其他协程继续执行,从而提高效率
49、什么是asncyio.gather
- gather是一个用于并发运行多个协程的工具
- 它接受多个协程对象,并在一个任务中并发的运行它们
- 返回一个包含所有协程结果的列表
50、什么是asyncio.create_task
- create_task用于将协程包装成一个任务并在事件循环中计划运行
- 它允许你启动一个协程并立即返回一个人物对象,而不必等待协程完成
- 使用await可以等待任务完成并获取其结果
51、什么是asyncio.Queue?
- Queue是一个线程安全的队列,适用于多协程环境
- 提供了生产者和消费者模式,允许多个协程并发的生产和消费数据
52、协程和线程的区别
- 协程的调度方式是协作式,依赖用户主动让出控制权,线程的调度方式是抢占式,依赖于系统强制切换
- 协程的并发模式是单线程,线程的并发方式是多线程,受GIL限制
- 协程的上下文切换开销极低,线程的上下文切换开销高,内核态切换
53、Future和Task的区别
- Future表示一个尚未完成的一步操作的结果,是底层抽象类,通常由asyncio内部使用,如loop.run_in_executor()返回Future
- Task是Future的字类,用于包装协程,使其可并发执行。通过asyncio.create_task()创建任务。
54、asyncio如何与同步代码交互?
同步代码调用异步函数:asyncio.run()或者事件循环的run_until_complete()
- 异步代码调用同步函数:使用asyncio.run_in_executor()将同步函数放入线程池执行,避免阻塞事件循环
import asyncio import time
def sync_function():
time.sleep(1) # 模拟同步阻塞
return "Done"
async def main():
loop = asyncio.get_running_loop() # 在ThreadPool中执行同步函数
result = await loop.run_in_executor(None, sync_function)
print(result)
55、如何优化asyncio应用的性能?
- 避免阻塞事件循环,耗时操作放入线程池,例如复杂计算
- 控制并发数量,使用asyncio.Semaphore限制同时运行的协程数量
- 连接复用,对http请求使用连接池,aiohttp的clientSession
- 减少上下文切换,避免过细粒度的协程,减少调度开销
56、asyncio和其他异步框架(如tornado、gevent)的区别?
- asyncio是官方标准库,语法简洁,生态好,用法标准,适合通用异步开发
- tornado,全栈的异步web框架,自带高性能http服务器,适合构建实时web应用
- gevent是基于greenlet和libevent,通过猴子补丁的方式实现异步,兼容大多数标准库,兼容旧代码
57 yield和return一起,执行到return后,会引发迭代异常终止迭代
58 继承,父类,不用super,一定不会调用父类方法,用了,也会按照mro顺序调用,不穿参数,就会异常。
__new__会使用super,一直传递给所有mro的new,除非不用super