小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。
with 语句
with
语句适用于对资源进行访问的场合,确保不管使用过程中是否发生异常都会执行必要的清理操作,释放资源。底层通过 __enter__
,__exit__
,来实现上下文管理,释放资源。
file = './a.txt'
with open(file) as f:
file_data = file.read()
print(file_data)
详情请看:Python with关键字原理详解 https://juejin.cn/post/6959886107496415246
闭包、装饰器
闭包
闭包概念:在一个内部函数中,对外部作用域的变量进行了引用, (并且一般外部函数的返回值为内部函数),那么将这个内部函数以及用到的一些变量称之为闭包
(colsure)
。
def func(number):
# 在函数内部再定义一个函数,并且这个函数用到了外部函数的变量,
# 那么将这个函数以及用到的一些变量称之为闭包
def func_in(number_in):
print("in func_in 函数, number_in is %d" % number_in)
return number + number_in
# 这里返回的就是闭包
return func_in
利用闭包的特性和函数引用的传递,就可以实现 装饰器。
更多详情请看:深入浅出Python闭包 https://juejin.cn/post/6960487978703519775
装饰器
装饰器就是
python
中用于扩展原来函数功能的函数。通过 @函数名 它可以将被装饰的函数当做参数传递给装饰器函数。@函数名 是Python
的一种语法糖。装饰器的有很多应用场景,例如插入日志,计算程序运行时间,登录认证,事务处理等。很好的提升了代码的复用性,并且不会破坏函数内部结构。非常适合 面向切面编程
AOP
import time
def calc_time(func):
"""
计算函数运行时间
"""
def wapper():
start_time = time.time()
func() # 调用传递过来的函数
use_time = start_time - time.time()
print(use_time)
return wapper
@calc_time
def demo():
for i in range(100000):
print(i)
@calc_time
的作用就是会让 Python解释器 执行 demo = calc_time(demo)
面试题:编写一个带参数的装饰器
带参数的装饰器,可能函数的嵌套有点多,只要理解它,其实和普通的装饰器没什么差别。
大概的思路就是:通过带参函数调用然后返回内部的装饰器,就实现了带参数的装饰器。
# 1、定义一个带参数的函数
def router(name):
# 2、函数内部定义装饰器
def _router(func):
def wapper():
pass
return wapper
# 3、返回装饰器
return _router
# 1、定义一个带参数的函数
def router(path):
# 2、函数内部定义装饰器
def _router(func):
def wapper():
print('path:', path) # 使用函数传递过来的参数
func()
return wapper
# 3、返回装饰器
return _router
# 使用router装饰器
@router('/index')
def index():
print('首页')
index()
# 结果如下
path: /index
首页
上面程序的执行流程如下
- 首先执行
router('/index')
的函数调用,返回了_router
装饰器 - 然后就是执行Python的语法糖
@_router
,把index
的函数引用传递给_router(func)
index = _router(index)
- 最后就是调用
index()
函数
迭代器、生成器
迭代器
迭代是访问集合元素的一种方式。迭代器是一个可以记住遍历的位置的对象。迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束。迭代器只能往前不会后退。
通过
__iter__()
来实现获取一个迭代器对象,然后利用__next__()
来迭代元素。__iter__(), __next__()
都是具体的实现细节,通常都是用iter()
函数来获取可迭代对象的迭代器,然后next()
函数用于遍历迭代。
来看看构造一个斐波那契数列迭代器
class FibIterator(object):
"""斐波那契数列迭代器"""
def __init__(self, n):
"""
:param n: int, 指明生成数列的前n个数
"""
self.n = n
# current用来保存当前生成到数列中的第几个数了
self.current = 0
# num1用来保存前前一个数,初始值为数列中的第一个数0
self.num1 = 0
# num2用来保存前一个数,初始值为数列中的第二个数1
self.num2 = 1
def __next__(self):
"""被next()函数调用来获取下一个数"""
if self.current < self.n:
num = self.num1
self.num1, self.num2 = self.num2, self.num1+self.num2
self.current += 1
return num
else:
raise StopIteration
def __iter__(self):
"""迭代器的__iter__返回自身即可"""
return self
if __name__ == '__main__':
fib = FibIterator(10)
for num in fib:
print(num, end=" ")
# 结果如下
0 1 1 2 3 5 8 13 21 34
更多细节请移步到:Python 迭代器 https://juejin.cn/post/6948437239286202375
生成器
利用迭代器,我们可以在每次迭代获取数据(通过 next()
方法)时按照特定的规律进行生成。但是我们在实现一个迭代器时,关于当前迭代到的状态需要我们自己记录,进而才能根据当前状态生成下一个数据。为了达到记录当前状态(保存上下文环境),并配合 next()
函数进行迭代使用,我们可以采用更简便的语法,即 生成器(generator)。
生成器的创建方式
把列表推导式 []
换成 ()
就是生成器。
In [21]: L = [i for i in range(10)]
In [22]: G = (i for i in range(10))
In [23]: type(L)
Out[23]: list
In [24]: type(G)
Out[24]: generator
另一种方式就是利用 yiled
关键字
用生成器来实现斐波那契数列
def fib(n):
cur = 0
num1 = 0
num2 = 1
while cur < n:
yield num1
num1, num2 = num2, num1 + num2
cur += 1
In [11]: f = fib(5)
In [12]: type(f)
Out[12]: generator
In [13]: next(f)
Out[13]: 0
In [14]: next(f)
Out[14]: 1
In [15]: next(f)
Out[15]: 1
In [16]: next(f)
Out[16]: 2
In [17]: next(f)
Out[17]: 3
In [18]: next(f)
---------------------------------------------------------------------------
StopIteration Traceback (most recent call last)
<ipython-input-18-aff1dd02a623> in <module>
----> 1 next(f)
StopIteration:
In [19]: for i in fib(10):
...: print(i, end=' ')
...:
0 1 1 2 3 5 8 13 21 34
In [20]:
通过列表推导式,可以直接创建一个列表。但是,受到内存限制,列表容量肯定是有限的。而且,创建一个包含百万元素的列表,不仅是占用很大的内存空间,如:我们只需要访问前面的几个元素,后面大部分元素所占的空间都是浪费的。因此,没有必要创建完整的列表。在Python中,我们可以采用生成器:边循环,边计算的机制—> generator
。可以节省大量内存空间。
import sys
import time
def calc_time(func):
"""
计算函数运行时间装饰器
"""
def wapper(*args, **kwargs):
start_time = time.time()
f = func(*args, **kwargs)
use_time = time.time() - start_time
print(use_time, 's')
return f
return wapper
@calc_time
def get_list(n):
"""列表推导式生成列表数据"""
return [i for i in range(n)]
@calc_time
def generate_list(n):
"""通过生成器生成列表数据"""
return (i for i in range(n))
n = 1_0000_0000
li = get_list(n)
g_li = generate_list(n)
print('li size:', sys.getsizeof(li))
print('g_li size', sys.getsizeof(g_li))
for i in range(5):
print('li', li[i], 'g_li', next(g_li))
运行结果
4.615425109863281s # 列表推导式所花费的时间
0.0s # 生成器所花费的时间
li size: 859724472 # 列表推导式产生对象大小
g_li size 120 # 生成器产生对象的大小
li 0 g_li 0
li 1 g_li 1
li 2 g_li 2
li 3 g_li 3
可以看出列表推导式在生成很多数据耗时久且占用内存大,但是通过 生成器 几乎不需要花费时间生成,因为他是运行时动态生成数据,因此生成器也不用保存所有数据,只需保存一些上下文环境所用的变量,但生成器不方便操作,不支持切片,也没有列表这么丰富的方法。各有优缺点。
面试题:文件中有 1000w
条数据,内存无法一次全部存储,该如何读取?
def read_file(file):
with open(file, mode='r') as f:
# f.read() 加载所有数据到内存中
# f.readline() 每次读取文件中的一行
# f.readlines() 返回的是每行组成的列表
for row in f.readlines():
yield row # 通过 yield 返回每行数据
file = 'aaa.txt'
for row in read_file(file):
print(row)
赋值与深浅拷贝
- 直接赋值: 其实就是指向对象的引用(别名)。
- 浅拷贝(copy): 拷贝父对象,不会拷贝对象的内部的子对象。但对于不可变数据类型,不会拷贝,仅仅是指向
- 深拷贝(deepcopy):
copy
模块的deepcopy
方法,完全(递归)拷贝了父对象及其子对象。
内建模块 copy
可以帮我们实现 浅拷贝 (copy),深拷贝 (deepcopy)
来看个例子拷贝 c = [ [1, 2], [3, 4] ]
# 直接赋值
In [54]: import copy
In [55]: c = [ [1, 2], [3, 4] ]
In [56]: d = c
In [57]: id(d), id(c)
Out[57]: (2365035580680, 2365035580680)
d = c
赋值引用,c
和 d
都指向同一个对象,地址相同。
In [58]: # 浅拷贝
In [59]: e = copy.copy(c)
In [60]: id(e), id(c)
Out[60]: (2365034915848, 2365035580680)
In [61]: e
Out[61]: [[1, 2], [3, 4]]
In [62]: c
Out[62]: [[1, 2], [3, 4]]
In [63]: e[0].append(5)
In [64]: e
Out[64]: [[1, 2, 5], [3, 4]]
In [65]: c
Out[65]: [[1, 2, 5], [3, 4]]
e = copy.copy(c)
浅拷贝,c
和 e
是一个 独立的对象(地址不同),但他们的 子对象还是指向统一对象即引用。因此往e[0]
添加数据 5,会影响到 c
。
In [66]: # 深拷贝
In [67]: c=[ [1, 2], [3, 4] ]
In [68]: f = copy.deepcopy(c)
In [69]: id(c), id(f)
Out[69]: (2365035892552, 2365035662792)
In [70]: c
Out[70]: [[1, 2], [3, 4]]
In [71]: f
Out[71]: [[1, 2], [3, 4]]
In [72]: f.append(5)
In [73]: f[0].append(6)
In [74]: f[1].append(7)
In [75]: c
Out[75]: [[1, 2], [3, 4]]
In [76]: f
Out[76]: [[1, 2, 6], [3, 4, 7], 5]
f = copy.deepcopy(c)
深度拷贝, f
完全拷贝了 c
的父对象及其子对象,两者是完全独立的。f
做任何的修改都不会影响到 c
。
注意事项:
copy.copy()
对于可变类型,会进行浅拷贝。copy.copy()
对于不可变类型,不会拷贝,仅仅是指向。copy.deepcopy()
深拷贝对可变、不可变类型都一样递归拷贝所有,对象完全独立。
对于 可变数据类型、不可变数据类型的详情和拷贝的细节请查看:
深度解析Python的赋值、浅拷贝、深拷贝 https://juejin.cn/post/6955354098988220423/
GIL锁
GIL 的全称为
Global Interpreter Lock
(全局解释器锁),由于这个锁的存在,CPython
在多线程并发的环境下 同一时刻 只有一个线程在运行,无法充分利用多核CPU
的性能。虽然很鸡肋,但是遇到IO
堵塞的情况下,全局解释器锁会释放,让其他线程工作。例如:在文件读取,网络请求(IO密集的场景下),还是能有效的提升性能。
解决 GIL
锁的问题
1、使用多进程
原理: 每个进程分配不同的解释器,有单独的 GIL
,内建模块 multiprocessing
可以开启多进程。
缺点: 资源消耗大,额外产生数据序列化与通信的开销。
2、使用C语言扩展模块
原理: C语言扩展程序的执行保持与Python解释器隔离,在C代码中释放GIL,例如 numpy, pandas
等库。
缺点: 调用C函数时GIL会被锁定,若阻塞,解释器无法释放GIL。
使用方法: 在C代码中插入特殊的宏或是使用其他工具来访问C代码,如 ctypes
库或者 Cython
。(ctypes
默认会在调用C代码时自动释放GIL)
3、选用其他没有 GIL 的解释器代替 CPython
原理: 使用没有GIL的解释器实现。
缺点: 不完全兼容。
使用方法: 目前 Jython
和 IronPython
没有GIL。
进程,线程,协程
- 进程 Process 是操作系统资源分配的基本单位
- 在 Python 中使用
multiprocessing
内建模块可以创建进程。
- 在 Python 中使用
- 线程 Thread 是CPU调度的最小执行单元。一个进程内可以有多个子线程。
- 在 Python 中使用
threading
内建模块可以创建线程。
- 在 Python 中使用
- 协程 CoRoutine 是一种轻量级的用户态 微线程,实现了上下文环境的保存与切换。
- 简单来说,线程的调度是由操作系统负责,线程的睡眠、等待、唤醒的时机是由操作系统控制,开发者无法决定。使用协程,开发者可以自行控制程序切换的时机,可以在一个函数执行到一半的时候中断执行,让出
CPU
,在需要的时候再回到中断点继续执行。切换的时机是由开发者来决定。 - 在 Python 中可以使用
yield
关键字来实现 协程,或者使用第三方库greenlet, gevent
。 - Python 3.5版本之后新增了
async/await
关键字配合asyncio
内建模块可以实现协程和异步编程。
- 简单来说,线程的调度是由操作系统负责,线程的睡眠、等待、唤醒的时机是由操作系统控制,开发者无法决定。使用协程,开发者可以自行控制程序切换的时机,可以在一个函数执行到一半的时候中断执行,让出
Python 中进程、线程、协程的使用请查阅如下链接:
asyncio 异步编程
asyncio
是Python 3.4
版本引入到标准库,Python3.5
又加入了async/await
特性,能很方便的创建协程以及控制其进行上下文切换。
在定义函数前面添加 aysnc
关键字,调用函数时返回的是 <class 'coroutine'>
,Python 内部封装好的协程类的实例对象。
In [93]: async def task():
...:
...: i = 0
...: while i < 5:
...: print(i)
...:
In [94]: c = task()
In [95]: c
Out[95]: <coroutine object task at 0x00000226A71505C8>
In [96]: type(c)
Out[96]: coroutine
import asyncio
async def task1():
i = 0
while i < 5:
print('task1', i)
i += 1
await asyncio.sleep(0.1) # await 让耗时任务自动挂起
async def task2():
i = 0
while i < 5:
print('task2', i)
i += 1
await asyncio.sleep(0.1)
def main():
loop = asyncio.get_event_loop()
tasks = [
task1(),
task2()
]
# 等待所有任务执行完成
loop.run_until_complete(asyncio.wait(tasks))
print('end')
if __name__ == '__main__':
main()
结果如下:
task2 0
task1 0
task2 1
task1 1
task2 2
task1 2
task2 3
task1 3
task2 4
task1 4
end
单例模式的实现
1、重写类的 __new__()
方法
class Singleton(object):
def __init__(self, name):
self.name = name
def __new__(cls, *args, **kwargs):
if not hasattr(cls, '_instance'):
cls._instance = super().__new__(cls)
return cls._instance
s1 = Singleton('hui')
s2 = Singleton('jun')
print(id(s1), id(s2))
print(s1.name, s2.name)
# 结果如下
3097281233736 3097281233736 # 地址一样
jun jun # 因此操作的是同一个对象
2、使用装饰器
def singleton(cls):
_instance = {}
def _singleton(*args, **kwargs):
if cls not in _instance:
_instance[cls] = cls(*args, **kwargs)
return _instance[cls]
return _singleton
@singleton
class Demo(object):
def __init__(self, name, age=18):
self.name = name
self.age = age
d1 = Demo('hui', age=18)
d2 = Demo('jun', age=21)
print(id(d1), id(d2))
print(d1.name, d1.age)
print(d2.name, d2.age)
# 结果如下
2107174554696 2107174554696
hui 18
hui 18
分析如下
def singleton(cls):
print(type(cls))
_instance = {}
def _singleton(*args, **kwargs):
print(args)
print(kwargs)
if cls not in _instance:
print(cls.__dict__)
instance = cls(*args, **kwargs)
print(type(instance))
_instance[cls] = instance
return _instance[cls]
return _singleton
@singleton
class Demo(object):
def __init__(self, name, age=18):
self.name = name
self.age = age
d1 = Demo('hui', age=18)
d2 = Demo('jun', age=21)
print(id(d1), id(d2))
print(d1.name, d1.age)
print(d2.name, d2.age)
运行结果与分析:
<class 'type'> # Demo类对象(cls)的类型
('hui',) # Demo 初始化时的位置参数
{'age': 18} # Demo 初始化时的关键字参数
# Demo类对象的__dict__属性
{'__module__': '__main__',
'__init__': <function Demo.__init__ at 0x00000239C8784798>,
'__dict__': <attribute '__dict__' of 'Demo' objects>,
'__weakref__': <attribute '__weakref__' of 'Demo' objects>,
'__doc__': None
}
# 通过类对象构造出的实例对象
<class '__main__.Demo'>
('jun',)
{'age': 21}
2447199767752 2447199767752 # 地址相同
# 因为第一次构建好了实例对象,下次就不会创建,直接返回第一次
# 所以后面创建对象所传递的初始化参数没有效果
hui 18
hui 18
更多创建单例的单例方法请查看:www.cnblogs.com/huchong/p/8…
元类
CGI,WSGI
CGI
全称为Common Gateway Interface
(通用网关接口),是一种定义程序和服务器交互方式的标准协议。
WSGI
的全称为Python Web Server Gateway Interface
(Web 服务网关接口),它是Web服务器和Web应用程序通信的接口规范,用来支持Web服务器和Web应用程序交互。
Python 内存管理与垃圾回收机制
Python中的内存管理由Python私有堆空间管理。所有Python对象和数据结构都位于私有堆中。程序员无权访问此私有堆。Python解释器负责处理这个问题。
Python 内存管理机制:引用计数、垃圾回收、内存池。
垃圾回收机制:主要以引用计数器为主,标记清除和分代回收为辅进行垃圾回收。