python
1、元组VS列表
- 列表是动态的,长度可变,可以随意的增加、删减或改变元素。列表的存储空间略大于元组,性能略逊于元组。
- 元组是静态的,长度大小固定,不可以对元素进行增加、删减或者改变操作。元组相对于列表更加轻量级,性能稍优。
l = []
a = l.__sizeof__() #空列表的存储空间为40字节
l.append(1)
b = l.__sizeof__() # 加入了元素1之后,列表为其分配了可以存储4个元素的空间 (72 - 40)/8 = 4
l.append(2)
c = l.__sizeof__()# 由于之前分配了空间,所以加入元素2,列表空间不变
l.append(3)
d = l.__sizeof__() # 同上
l.append(4)
e = l.__sizeof__() # 同上
l.append(5)
f = l.__sizeof__() # 加入元素5之后,列表的空间不足,所以又额外分配了可以存储4个元素的空间
print('a=,b=,c=,d=,e=,f=',a,b,c,d,e,f)
#结果a=,b=,c=,d=,e=,f= 40 72 72 72 72 104
2、字典VS集合
dict和set底层都是哈希
d = {'b': 1, 'a': 2, 'c': 10}
d_sorted_by_key = sorted(d.items(), key=lambda x: x[0]) # 根据字典键的升序排序
d_sorted_by_value = sorted(d.items(), key=lambda x: x[1]) # 根据字典值的升序排序
3、字符串
- Python 中字符串使用单引号、双引号或三引号表示,三者意义相同,并没有什么区别。其中,三引号的字符串通常用在多行字符串的场景。
- Python 中字符串是不可变的(前面所讲的新版本 Python中拼接操作’+='是个例外)。因此,随意改变字符串中字符的值,是不被允许的。
- 对于字符串拼接问题,除了使用加法操作符,我们还可以使用字符串内置的 join
函数。string.join(iterable),表示把每个元素都按照指定的格式连接起来。
4、异常处理
当程序中存在多个 except block 时,最多只有一个 except block 会被执行。换句话说,如果多个 except 声明的异常类型都与实际相匹配,那么只有最前面的 except block 会被执行,其他则被忽略。
finally,经常和 try、except 放在一起来用。无论发生什么情况,finally中的语句都会被执行,哪怕前面的 try 和 excep中使用了 return 语句。
try:
s = input('please enter two numbers separated by comma: ')
num1 = int(s.split(',')[0].strip())
num2 = int(s.split(',')[1].strip())
...
except ValueError as err:
print('Value Error: {}'.format(err))
except IndexError as err:
print('Index Error: {}'.format(err))
except:
print('Other error')
print('continue')
...
5、作用域
如果我们在函数内部调用其他函数,函数间哪个声明在前、哪个在后就无所谓,因为 def 是可执行语句,函数在调用之前都不存在,我们只需保证调用时,所需的函数都已经声明定义。
- 不能在函数内部随意改变全局变量的值,如果我们一定要在函数内部改变全局变量的值,就必须加上
global 这个声明
。 - 对于嵌套函数来说,内部函数可以访问外部函数定义的变量,但是无法修改,
若要修改,必须加上 nonlocal 这个关键字
。如果不加上 nonlocal 这个关键字,而内部函数的变量又和外部函数变量同名,那么同样的,内部函数变量会覆盖外部函数的变量。
def outer():
x = "local"
def inner():
nonlocal x # nonlocal关键字表示这里的x就是外部函数outer定义的变量x
x = 'nonlocal'
print("inner:", x)
inner()
print("outer:", x)
6、lambda
- 第一,
lambda 是一个表达式(expression),并不是一个语句(statement)
。
所谓的表达式,就是用一系列“公式”去表达一个东西,比如x + 2、 x**2等等;而所谓的语句,则一定是完成了某些功能,比如赋值语句x = 1完成了赋值,print 语句print(x)完成了打印,条件语句 if x < 0:完成了选择功能等等。因此,lambda 可以用在一些常规函数 def 不能用的地方
,比如,lambda 可以用在列表内部,而常规函数却不能;lambda 可以被用作某些函数的参数,而常规函数 def 也不能。 - 第二,lambda 的主体是只有一行的简单表达式,并不能扩展成一个多行的代码块。
l = [1, 2, 3, 4, 5]
new_map_list = map(lambda x: x * 2, l) # [2, 4, 6, 8, 10]
new_filter_list = filter(lambda x: x % 2 == 0, l) # [2, 4]
print(list(new_map_list))
print(list(new_filter_list))
map(function, iterable)
:对 iterable 中的每个元素,都运用 function 这个函数,最后返回一个新的可遍历的集合。
filter(function, iterable)
:对 iterable 中的每个元素,都使用 function 判断,并返回 True 或者 False,最后将返回 True 的元素组成一个新的可遍历的集合。
reduce(function, iterable)
:它通常用来对一个集合做一些累积操作。对 iterable 中的每个元素以及上一次调用后的结果,运用 function 进行计算,所以最后返回的是一个单独的数值。
Python对象的拷贝
1、赋值:对象的引用
2、浅拷贝:拷贝父对象,不会拷贝对象的内部的子对象
3、深拷贝:copy 模块的 deepcopy 方法,完全拷贝了父对象及其子对象
- b = a: 赋值引用,a 和 b 都指向同一个对象(所以a变则b变,b变则a变(可变对象))
- b = a.copy(): 浅拷贝, a 和 b 是一个独立的对象,但他们的子对象还是指向同一对象(所以a的子对象改变,b才改变,父对象改变,b不会变)
- b = copy.deepcopy(a): 深度拷贝, a 和 b 完全拷贝了父对象及其子对象,两者是完全独立的(所以b不受a影响)
对于元组,使用 tuple() 或者切片操作符’:'不会创建一份浅拷贝,相反,它会返回一个指向相同元组的引用。
t1 = (1, 2, 3)
t2 = tuple(t1)
print(t1 == t2) #True
print(t1 is t2) #True
深度拷贝也不是完美的,往往也会带来一系列问题。如果
被拷贝对象中存在指向自身的引用,那么程序很容易陷入无限循环
,因此深度拷贝中会维护一个字典,记录已经拷贝的对象及其 ID,来提高效率并防止无限递归的发生。
传参
1、常见的参数传递
- 值传递:通常就是拷贝参数的值,然后传递给函数里的新变量。这样,原变量和新变量之间互相独立,互不影响。
- 引用传递:通常是指把参数的引用传给新的变量,这样,原变量和新变量就会指向同一块内存地址。如果改变了其中任何一个变量的值,那么另外一个变量也会相应地随之改变。
赋值 b = a,并不表示重新创建了新对象,只是让同一个对象被多个变量指向或引用。同时,指向同一个对象,也并不意味着两个变量就被绑定到了一起。如果你给其中一个变量重新赋值,并不会影响其他变量的值。
Python 的数据类型,例如整型(int)、字符串(string)等等,是不可变的。所以,a = a + 1,并不是让 a 的值增加 1,而是表示重新创建了一个新的值为 2 的对象,并让 a 指向它
。但是 b 仍然不变,仍然指向 1 这个对象。
a = 1
b = a
a = a + 1
print("a=%d,b=%d",a,b) #结果2,1
- 变量的赋值,只是表示让变量指向了某个对象,并不表示拷贝对象给变量;而一个对象,可以被多个变量所指向。
- 可变对象(列表,字典,集合等等)的改变,会影响所有指向该对象的变量。
- 对于不可变对象(字符串、整型、元组等等),所有指向该对象的变量的值总是一样的,也不会改变。但是通过某些操作(+= 等等)更新不可变对象的值时,会返回一个新的对象。
- 变量可以被删除,但是对象无法被删除。
2、Python 函数的参数传递
和其他语言不同的是,Python 中参数的传递既不是值传递,也不是引用传递,而是赋值传递,或者是叫对象的引用传递。需要注意的是,这里的赋值或对象的引用传递,不是指向一个具体的内存地址,而是指向一个具体的对象。如果对象是可变的,当其改变时,所有指向这个对象的变量都会改变。如果对象不可变,简单的赋值只能改变其中一个变量的值,其余变量则不受影响。
def my_func3(l2):
l2.append(4) #由于列表可变,执行 append() 函数,对其末尾加入新元素 4 时,变量 l1 和 l2 的值也都随之改变了
#创建了新的对象,并赋值给一个本地变量,因此原变量仍然不变。
def my_func4(l2):
l2 = l2 + [4] #l2 = l2 + [4],表示创建了一个“末尾加入元素 4“的新列表,并让 l2 指向这个新的对象。这个过程与 l1 无关,因此 l1 的值不变。
l1 = [1, 2, 3]
# my_func3(l1)
# print(l1)
my_func4(l1)
print(l1)
装饰器
所谓的装饰器,其实就是通过装饰器函数,来修改原函数的一些功能,使得原函数不需要修改。
1、函数
1、可以把函数赋予变量;
def func(message):
print('Got a message: {}'.format(message))
send_message = func
send_message('hello world')
2、可以把函数当作参数,传入另一个函数中;
def get_message(message):
return 'Got a message: ' + message
def root_call(func, message):
print(func(message))
root_call(get_message, 'hello world')
3、可以在函数里定义函数,也就是函数的嵌套;
# 可以在函数里定义函数,也就是函数的嵌套
def func(message):
def get_message(message):
print('Got a message: {}'.format(message))
return get_message(message) #返回函数名(参数)
func('hello world')
4、函数的返回值也可以是函数对象(闭包);
# 函数的返回值也可以是函数对象(闭包)
def func_closure():
def get_message(message):
print('Got a message: {}'.format(message))
return get_message #返回函数名
send_message = func_closure()
send_message('hello world')
当一个内嵌函数引用其外部作用域的变量,我们就会得到一个闭包。创建闭包必须满足以下几点:
- 必须有一个内嵌函数
- 内嵌函数必须引用外部函数中的变量
- 外部函数的返回值必须是内嵌函数
2、函数装饰器用法
1、无参装饰器
def my_decorator(func):#入参为函数
def wrapper():
print('wrapper of decorator')
func()
return wrapper #返回值为内嵌函数
@my_decorator
def greet():
print('hello world')
greet()
2、带有参数的装饰器
通常把*args和kwargs,作为装饰器内部函数 wrapper() 的参数。*args和kwargs,表示接受任意数量和类型的参数
def my_decorator(func):
def wrapper(*args, **kwargs):
print('wrapper of decorator')
func(*args, **kwargs)
return wrapper
3、带有自定义参数的装饰器
def repeat(num):
def my_decorator(func):
def wrapper(*args, **kwargs):
for i in range(num):
print('wrapper of decorator')
func(*args, **kwargs)
return wrapper
return my_decorator
@repeat(4)
def greet(message):
print(message)
greet('hello world')
print(greet.name)发现元信息被改变了,使用
内置的装饰器@functools.wrap
,它会帮助保留原函数的元信息。
import functools
def my_decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
print('wrapper of decorator')
func(*args, **kwargs)
return wrapper
@my_decorator
def greet(message):
print(message)
print(greet.__name__) # 输出'greet'
3、类装饰器用法
类装饰器主要依赖于函数__call__(),每当你调用一个类的示例时,函数__call__()就会被执行一次。
class Count:
def __init__(self, func):
self.func = func
self.num_calls = 0
def __call__(self, *args, **kwargs):
self.num_calls += 1
print('num of calls is: {}'.format(self.num_calls))
return self.func(*args, **kwargs)
@Count
def example():
print("hello world")
example()
4、装饰器的嵌套
执行顺序从里到外
5、装饰器用法实例
1、身份认证
import functools
def authenticate(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
request = args[0]
if check_user_logged_in(request): # 如果用户处于登录状态
return func(*args, **kwargs) # 执行函数post_comment()
else:
raise Exception('Authentication failed')
return wrapper
@authenticate
def post_comment(request, ...)
...
2、日志记录
import time
import functools
def log_execution_time(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
start = time.perf_counter()
res = func(*args, **kwargs)
end = time.perf_counter()
print('{} took {} ms'.format(func.__name__, (end - start) * 1000))
return res
return wrapper
@log_execution_time
def calculate_similarity(items):
...
3、输入合理性检查
import functools
def validation_check(input):
@functools.wraps(func)
def wrapper(*args, **kwargs):
... # 检查输入是否合法
@validation_check
def neural_network_training(param1, param2, ...):
...
4、缓存
@lru_cache
def check(param1, param2, ...) # 检查用户设备类型,版本号等等
...
https://time.geekbang.org/column/article/100914
迭代器和生成器
1、生成器generator
1)只要把一个列表生成式的[]改成(),就创建了一个generator:
创建L和g的区别仅在于最外层的[]和(),L是一个list,而g是一个generator。
可通过next()函数获得generator的下一个返回值
2)函数定义中包含yield关键字
在每次调用next()的时候执行,遇到yield语句返回,再次执行时从上次返回的yield语句处继续执行。
生成器并不会像迭代器一样占用大量内存,只有在被使用的时候才会调用
。而且生成器在初始化的时候,并不需要运行一次生成操作。在你调用 next() 函数的时候,才会生成下一个变量。
2、迭代器
1)凡是可作用于next()函数的对象都是Iterator类型
2)集合数据类型如list、dict、str等是Iterable但不是Iterator,但可通过iter()函数获得一个Iterator对象:
迭代器(iterator)提供了一个 next 的方法,而可迭代对象,通过 iter() 函数返回一个迭代器,再通过 next() 函数就可以实现遍历。
Python 协程
1、多进程 vs 多线程 vs 多协程
- 多进程:适用CPU密集型计算
优点:可以利用多核CPU并行计算
缺点:占用资源最多 - 多线程:适用IO密集型计算
优点:相比进程,更轻量级、占用资源少
缺点:
vs进程:多线程只能并发执行,不能利用多CPU(GIL)
vs协程:启动数目有限,占用内存资源,有线程切换开销 - 多协程:
优点:内存开销最小,启动协程数量最多
缺点:支持的库有限( aoihttp vs requests),代码实现复杂
协程和多线程的区别,主要在于两点,一是协程为单线程
;二是协程由用户决定,在哪些地方交出控制权,切换到下一个任务。主程序想要切换任务时,必须得到此任务可以被切换的通知
。而对于 多线程,操作系统知道每个线程的所有信息,因此它会做主在适当的时候做线程切换。
2、asyncio
协程 通过 async/await 语法进行声明
。
简单地调用一个协程并不会使其被调度执行,asyncio.create_task() 函数用来并发运行作为 asyncio 任务 的多个协程,或通过asyncio.run 来触发运行。
import asyncio
import random
async def consumer(queue, id):
while True:
val = await queue.get()
print('{} get a val: {}'.format(id, val))
await asyncio.sleep(1)
async def producer(queue, id):
for i in range(5):
val = random.randint(1, 10)
await queue.put(val)
print('{} put a val: {}'.format(id, val))
await asyncio.sleep(1)
async def main():
queue = asyncio.Queue()
consumer_1 = asyncio.create_task(consumer(queue, 'consumer_1'))
consumer_2 = asyncio.create_task(consumer(queue, 'consumer_2'))
producer_1 = asyncio.create_task(producer(queue, 'producer_1'))
producer_2 = asyncio.create_task(producer(queue, 'producer_2'))
await asyncio.sleep(10)
consumer_1.cancel()
consumer_2.cancel()
await asyncio.gather(consumer_1, consumer_2, producer_1, producer_2, return_exceptions=True)
asyncio.run(main())
Python并发编程之Futures
1、Executor类
线程池的基类是 concurrent.futures 模块中的 Executor,Executor 提供了两个子类,即 ThreadPoolExecutor 和 ProcessPoolExecutor,其中 ThreadPoolExecutor 用于创建线程池,而 ProcessPoolExecutor 用于创建进程池。
另外,由于线程池实现了上下文管理协议(Context Manage Protocol),因此,程序可以使用 with 语句来管理线程池,这样即可避免手动关闭线程池。
with ThreadPoolExecutor(max_workers=50) as executor: # 创建一个最大容纳数量为5的线程池
for data2 in executor.map(SetUfs, list2):
i += 1
if i % 1000 == 0:
print(f"result {data2}")
Executor.map(func, *iterables, timeout=None, chunksize=1)
:该方法的功能类似于全局函数map(),返回一个迭代器,迭代器的__next__方法调用各个future的result方法,得到各个future的结果。区别在于线程池的map() 方法会为 iterables 的每个元素启动一个线程,以并发方式来执行 func 函数。这种方式相当于启动len(iterables) 个线程,井收集每个线程的执行结果。Executor.submit(fn, *args, **kwargs)
:提交执行的函数fn(*args,**kwargs)到线程池,返回可调用的 Future 对象(立即返回)shutdown(wait=True)
关闭线程池,不再接收新线程任务,关闭后调用 submit() 和map() 引发RuntimeError 异常。wait: 默认True,执行完所有线程后在关闭线程池;False表示立即关闭线程池,但是线程还是可以执行。
2、Future类
使用Executor.submit()将Future类实例化,返回一个future对象。
Future类方法:
done(),表示相对应的操作是否完成——True 表示完成,False 表示没有完成。不过,要注意,done() 是 non-blocking 的,会立即返回结果。
result():它表示当 future 完成后,返回其对应的结果或异常。
add_done_callback(fn):将fn绑定到future对象上。当future对象被取消或完成运行时,fn函数将会被调用。
as_completed(fs),则是针对给定的 future 迭代器 fs,在其完成后,返回完成后的迭代器。
exception(timeout=None):捕获程序执行过程中的异常
import concurrent.futures
import requests
def download_one(url):
resp = requests.get(url)
print('Read {} from {}'.format(len(resp.content), url))
def download_all(sites):
with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:
executor.map(download_one, sites)
Python并发编程之Asyncio(……need more)
不同于多线程,Asyncio 是单线程的,但其内部 event loop 的机制,可以让它并发地运行多个不同的任务,并且比多线程享有更大的自主控制权。
所谓 Sync,是指操作一个接一个地执行,下一个操作必须等上一个操作完成后才能执行。而 Async 是指不同操作间可以相互交替执行,如果其中的某个操作被 block 了,程序并不会等待,而是会找出可执行的操作继续执行。
Asyncio 工作原理
对于 Asyncio 来说,它的任务在运行时不会被外部的一些因素打断,因此 Asyncio 内的操作不会出现 race condition 的情况,这样你就不需要担心线程安全的问题了。
Asyncio 用法
Async 和 await 关键字是 Asyncio 的最新写法,表示这个语句 / 函数是 non-block 的
asyncio.run(coro) 是 Asyncio 的 root call,表示拿到 event loop,运行输入的 coro,直到它结束,最后关闭这个 event loop。
import asyncio
import aiohttp
import time
async def download_one(url):
async with aiohttp.ClientSession() as session:
async with session.get(url) as resp:
print('Read {} from {}'.format(resp.content_length, url))
async def download_all(sites):
tasks = [asyncio.create_task(download_one(site)) for site in sites]
await asyncio.gather(*tasks)
def main():
sites = [
'https://en.wikipedia.org/wiki/Portal:Arts',
'https://en.wikipedia.org/wiki/Portal:History',
'https://en.wikipedia.org/wiki/Portal:Society',
'https://en.wikipedia.org/wiki/Portal:Biography',
'https://en.wikipedia.org/wiki/Portal:Mathematics',
'https://en.wikipedia.org/wiki/Portal:Technology',
'https://en.wikipedia.org/wiki/Portal:Geography',
'https://en.wikipedia.org/wiki/Portal:Science',
'https://en.wikipedia.org/wiki/Computer_science',
'https://en.wikipedia.org/wiki/Python_(programming_language)',
'https://en.wikipedia.org/wiki/Java_(programming_language)',
'https://en.wikipedia.org/wiki/PHP',
'https://en.wikipedia.org/wiki/Node.js',
'https://en.wikipedia.org/wiki/The_C_Programming_Language',
'https://en.wikipedia.org/wiki/Go_(programming_language)'
]
start_time = time.perf_counter()
asyncio.run(download_all(sites))
end_time = time.perf_counter()
print('Download {} sites in {} seconds'.format(len(sites), end_time - start_time))
if __name__ == '__main__':
main()
https://docs.python.org/3/library/asyncio-eventloop.html
如果是 I/O bound,并且 I/O 操作很慢,需要很多任务 / 线程协同实现,那么使用 Asyncio 更合适。
如果是 I/O bound,但是 I/O 操作很快,只需要有限数量的任务 / 线程,那么使用多线程就可以了。
如果是 CPU bound,则需要使用多进程来提高程序运行效率。
Python GIL
1、什么是全局解释器锁
在同一个进程中只要有一个线程获取了全局解释器(cpu)的使用权限,那么其他的线程就必须等待该线程的全局解释器(cpu)使用权消失后才能使用全局解释器(cpu),即时多个线程直接不会相互影响在同一个进程下也只有一个线程使用cpu,这样的机制称为全局解释器锁(GIL)。任何时刻仅有 一个线程 在执行,即便在 多核心处理器 上,使用 GIL 的解释器也只允许同一时间执行一个线程。
2、全局解释器锁的好处
1、避免了大量的加锁解锁的好处
2、使数据更加安全,解决多线程间的数据完整性和状态同步
3、全局解释器的缺点
多核处理器退化成单核处理器,只能并发不能并行。
Python垃圾回收机制
- sys.getrefcount() 这个函数,可以查看一个变量的引用次数。不过,getrefcount 本身也会引入一次计数。
- 在函数调用发生的时候,会产生额外的两次引用,一次来自函数栈,另一个是函数参数。
- 只需要先调用 del a 来删除对象的引用;然后强制调用 gc.collect(),清除没有引用的对象,即可手动启动垃圾回收。
1.1引用计数器
当python程序运行时,会根据数据类型的不同找到其对应的结构体,根据结构体中的字段进行创建相关的数据,然后将对象添加到refchain双向链表中。
在C源码中有两个关键的结构体:PyObject、PyVarObject
每个对象中有ob_refcnt就是引用计数器,默认值是1,当有其他对象引用对象时,引用计数器就会发生变化。
- 引用
a = 999
b = a #引用计数增加:1,一个对象分配一个新名称 2,将其放入一个容器中(如列表、元组或字典)
- 删除引用
a = 999
b = a
del b #b变量删除:b对应对象引用计数器-1
del a #a变量删除:a对应对象引用计数器-1
#引用计数减少的情况: 1,使用del语句对对象别名显示的销毁 2,引用超出作用域或被重新赋值
当一个对象的引用计数归零时,它将被垃圾收集机制处理掉。
循环引用问题
引用计数不会归零,对象也不会销毁。(从而导致内存泄露)
1.2标记清除
目的:为了解决引用计数器循环引用的不足。
实现:在python的底层再维护一个链表,链表中专门放那些可能存在循环引用的对象。(list/tuple/dict/set)
在python内部某种情况下触发,去扫描这个链表,检查是否有循环引用,如果有则让双方的引用计数器都-1,如果减为0则垃圾回收。
问题:
什么时候扫描,
扫描代价大
1.3 分代回收
目的:解决标记清除什么时候扫描以及扫描代价大的问题。
实现:将可能存在循环引用的对象维护成3个链表,分别为0、1、2代。
- 0代: 0代中对象个数达到700个扫描一次。
- 1代:0代扫描10次,则1代扫描1次。
- 2代:1代扫描10次,则2代扫描1次。
1.4小结
在python中维护了一个refchain的双向环转链表,这个链表中存储程序创建的所有对象,每种类型的对象中都有一个ob_refcnt引用计数器的值,引用个数+1,-1,最后当引用计数器变为0时会进行垃圾回收。
但是,在python中对于那些可以有多个元素组成的对象可能会存在循环引用的问题,为了解决这个问题python又引入了标记清除和分代回收,在其内部维护了4个链表。
refchain、0代、1代、2代,在源码内部达到各自的阈值时,就会触发扫描链表进行标记清除动作(有循环则各自-1)。
but ,源码内部在上述的流程中提出了优化机制。
1.5.python缓存
1.5.1内存池
Python提供了对内存的垃圾收集机制,但是它将不用的内存放到内存池而不是返回给操作系统。
1,Pymalloc机制。为了加速Python的执行效率,Python引入了一个内存池机制,用于管理对小块内存的申请和释放。
2,Python中所有小于256个字节的对象都使用pymalloc实现的分配器,而大的对象则使用系统的malloc。
3,对于Python对象,如整数,浮点数和List,都有其独立的私有内存池,对象间不共享他们的内存池。也就是说如果你分配又释放了大量的整数,用于缓存这些整数的内存就不能再分配给浮点数。
1.5.2free_list
将不用的对象添加到free_list链表中当缓存,以后再去创建对象时,不再重新开辟内存,而是直接使用free_list。
1.6 调试内存泄漏
objgraph,一个非常好用的可视化引用关系的包。其中 show_refs(),它可以生成清晰的引用关系图;另一个是show_backrefs()。
https://mg.pov.lt/objgraph/
Python Assert断言
Python assert(断言)用于判断一个表达式,在表达式条件为 false 的时候触发异常
。
assert 通常用来对代码进行必要的 self check,表明你很确定这种情况一定发生,或者一定不会发生。需要注意的是,使用 assert 时,一定不要加上括号
,否则无论表达式对与错,assert 检查永远不会 fail。另外,程序中的 assert 语句,可以通过-O等选项被全局 disable。
assert expression [, arguments]
等价于if not expression:
raise AssertionError(arguments)
def func(input):
assert isinstance(input, list), 'input must be type of list'
# 下面的操作都是基于前提:input必须是list
if len(input) == 0:
...
elif len(input) == 1:
...
else:
...
Unittest中的mock、Mock Side Effect、patch
from unittest.mock import patch
@patch('sort')
def test_sort(self, mock_sort):
...
...
import unittest
from unittest.mock import MagicMock
class A(unittest.TestCase):
def m1(self):
val = self.m2()
self.m3(val)
def m2(self):
pass
def m3(self, val):
pass
def test_m1(self):
a = A()
a.m2 = MagicMock(return_value="custom_val")
a.m3 = MagicMock()
a.m1()
self.assertTrue(a.m2.called) #验证m2被call过
a.m3.assert_called_with("custom_val") #验证m3被指定参数call过
if __name__ == '__main__':
unittest.main(argv=['first-arg-is-ignored'], exit=False)
想要测试一下这段代码总的效率以及各个部分的效率。那么,我就只需在开头导入 cProfile 这个模块,并且在最后运行 cProfile.run() 就可以了
ZMQ 的优点主要在轻量、开源和方便易用上
ZMQ 是一个简单好用的传输层,它有三种使用模式:Request - Reply 模式;Publish - Subscribe 模式;Parallel Pipeline 模式。
ORM 框架的优点,是提高了写代码的速度,同时兼容多种数据库系统,如 SQLite、MySQL、PostgreSQL 等这些数据库;而付出的代价,可能就是性能上的一些损失。