一、Python GIL
1、全局解释器锁
2、产生原因
3、说明
4、解决方案
二、迭代器和生成器
1、迭代器
2、生成器
3、区别
三、值传递和引用传递
1、什么情况值传递
2、什么情况引用传递
四、装饰器decorator
1、装饰器功能
2、装饰器分类
五、类方法@classmethod|静态方法@staticmethod
1、类方法和静态方法
2、区别
六、Python垃圾回收机制
1、回收策略
2、引用计数优点
3、引用计数缺点
4、标记消除和分代回收机制
5、标记消除机制
6、分代回收机制
七、lambda匿名函数与容器
1、使用场景
八、assert断言
1、assert概念
2、assert实例
Python的高级特性
一、GIL锁
- GIL( Global Interpreter Lock)全局解释器锁。在Python中 其实并不存在严格意义上的多线程,当系统开始运行两个或两个以上的线程时,会在同一时刻为线程上锁,保证在同一时刻只有一个线程在运行代码,保证线程安全,每个线程在执行之前都要获取GIL锁。
- Python语言和GIL锁其实并没有关系。而GIL锁仅仅是因为历史原因在cpython解释器中难以移除的(属于系统漏洞)。
- 举个栗子:
(1)单线程死循环中–>CPU占用率为100%
(2)多线程死循环
def test():
while True:
pass
t1 = Threading.Thread(target=test)
t1.start()
# 主线程死循环
while True:
pass
查看程序占用CPU占用率,两个中的任何一个CPU都没有占满,而是交替执行。这验证了多线程下每个程序的执行过程都要首先获取GIL,保证同一时刻只有一个线程在运行。
- 当执行cpu(计算)密集型的程序时(需要进行大量的数值计算、如计算圆周率、对视频进行高清解码等),可选择**换解释器/选用其它语言(语言)/选用多进程(并行)**方案等。
- Python3.x使用计时器,(执行时间达到阈值,当前线程释放GIL。当tickets计数达到100,线程之间开放竞争GIL锁)。多线程中遇到IO阻塞会自动释放GIL锁,所以不会存在同一时刻只有一个线程运行的问题。
二、iterator迭代器&generator生成器
1、迭代器:
li = [list(range(n) for i in range(n)]
def li
2、生成器:
ge = (tuple(range(n) for i in range(n))
del ge
3、区别
(1)迭代器会将算法(容器)中所有内容一次性执行出来,消耗资源。
(2)生成器只是封装了算法,每次要调用时才会去调用算法,这样可以大大节省内存资源。
(3)当yield n时,就是一个生成器了,当调用函数时,内部代码不会被执行,只有当yield中的next方法被调用时才执行,而for循环会自动执行next方法。
三、值传递和引用传递
1、(1)值传递:调用函数时将实参复制传递
(2)引用传递:调用函数时将实参地址传递,修改参数,会影响到实参
2、JAVA中的基本类型都是值传递,只有当形参是引用类型时,才是引用传递。
Python中,当形参是可变类型(列表List|字典dict|集合set)时,**按照引用传递。创建了新的引用对象,分配新的内存地址。**当形参是不可变类型(字符串string|元组tuple|整数int)时,按照值传递,值是共享的。
四、装饰器decorator
装饰器是特殊的闭包,主要可以用于**处理计时逻辑,重复计数逻辑,给函数加入事务的能力**等。
example 计时逻辑:
def run_time(func):
def wrapper(*args,**kwargs):
start_time = time.time()
result = func(*args,**kwargs)
end_time = time.time()
print("spend time",end_time - start_time)
return result
return wrapper
装饰器分类
1、单个装饰器,不带参数
def (装饰器名)run (func):
def deco/wrapper(*args,**kwargs):
# ...(执行代码)
func(*args,**kwargs)
# ...(执行代码)
'''
这里一定不能带括号(),否则返回的是指,而不会是函数
装饰器在被装饰器函数return值时生效
'''
return deco/wrapper
2、单个装饰器,带参数
def (装饰器名)run (a):
def deco_real(func):
def method(*args):
#...(执行代码)
result = a + func(*args)
#...(执行代码)
return method
3、单参数的嵌套函数装饰器
def (装饰器名)repeat(num):
def deco(func):
def wrapper(*args, **kwargs):
for i in range(num):
print("wrapper of deco")
func(xxx)
return wrapper
return deco
#引用装饰器
@repeat(4)
def greet(msg:str):
print(msg)
greet("Hello Deco")
五、类方法@classmethod|静态方法@staticmethod
两个方法都很类似,多数情况下,**staticmethod可以用classmethod替代**。使用时,可以不需要实例化,直接用来调用。如果需要直接使用该类,***将该类作为参数传递***,且希望在实例化该前就提供功能,就是用方法、符合OOP、多态的意义。
区别:
1、staticmethod唯一的好处是调用时返回一个真正的函数,且每次调用时返回一个实例。
2、staticmethod中的函数只能静态调用,不能实例化了,classmethod可调用类的属性和方法,还能实例化对象。
3、@staticmethod不需要self自身对象和cls自身类参数,@classmethod需要cls参数并且是第一个。
六、Python垃圾回收机制
1、Python同Java采取一样的GC机制,不过不一样的是Python采用引用计数机制为主,标记-消除和分代回收两种机制为辅的策略。
2、引用计数优点:
(1)简单,通过sys.getrefConunt()查看技术次数,计数为0时被回收。
(2)实时性,一旦没有引用,内存就直接释放,不必额外处理回收内存。
3、引用计数缺点:
(1)引用计数消耗资源。
(2)循环计用,创建两个对象obj1和obj2,若两个对象互相引用obj.appen(obj2),obj.append(obj1),则引用计数为1,占用内存永远不会被回收,致命的内存泄露。
4、针对引用计数缺点,增加了标记消除和分代回收机制。
5、标记消除:当程序不再使用这些节点对象,希望Python的GC可以智能地释放对象并回收它们占用的内存空间。
(1)标价消除为两个阶段。第一阶段,GC把所有"活动对象"打上标记。第二阶段,没有标记的"非活动对象"进行回收。
(2)标记消除主要处理容器对象,如list,dict,tuple等。
(3)从根节点开始(全局变量、调用栈、容器),可达对象(1,2,3)为活动对象,不可达对象(4,5)为非活动对象。
(4)解决循环引用问题
6、分代回收
总共三个代(第0代、第1代、第2代),一个代就是一个链表,属于同一"代"的内存块都链接在了同一链表中。
7、避免、解决内存泄露
(1)编写安全的代码
在编写循环引用的代码时,在最后接触循环引用,避免内存泄露。
obj1.append() = None
obj2.append() = None
(2)弱引用
提供了weakref模块,weakref不会在引用计数中计数,但list和dict不支持弱引用。
obj1.child() = weakref.proxy(obj2)
obj2.child() = weakref.proxy(obj1)
七、lambda匿名函数与容器
lambda x:x+1
lambda通常与容器,map、reduce、filter方法一起使用
List = list(filter(lambda n:n%2==1, [i for i in range(11)]))
八、Python3 assert断言
用于判断一个表达式,**为false时触发异常**。
assert expression --> if not expression:
raise expression
实例:
1、
# 该代码只能在linux环境下运行
assert ('linux' in sys.platform)
2、
# a不是数字
assert type(a)==int
九、Python协程
协程是轻量级的进程,协程也可以理解为轻量级线程也就是**微线程**。
协程的作用:执行函数A时**可以随时中断**去执行函数B,然后**又可以中断函数B去执行函数A**(自由切换),看似是多线程,但整个过程中**只有一个线程在执行**。协程相当于**运行在单线程中的"并发"**。
协程的优势:相比较多线程,协程的一大优势就是**省去了多线程之间的切换开销,以获得更大的运行效率**。与多线程相比,**线程的数量越多,协程性能的优势就越明显**;协程**不需要多线程的锁机制,也不存在写变量冲突**。
注:
- Python 2.X 协程:
# 通过生成器实现生产者-消费者模型
yield + send gevent
# yield --->一次挂起/中断
- Python 3.X 协程:
# Python 3.4+
asyncio + yield
# Python 3.5+
asuncio + async/await
- 异步协程
import asyncio
# 方法调用另一个生成器
r = yield from asyncio.sleep(1)
# asyncio.sleep()也是一个coroutine(协同程序),线程不会等待sleep(),而是中断执行下一个循环,可以看成一个IO操作,实现并发执行
if __name__ == '__main__':
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))
loop.close()
- asyncio + async.await
为了简化并且更好地标识异步IO,Python3.5开始引入了新的async和await
async def test(i):
print(xxx)
# 挂起操作,挂起当前协程,执行别的协程。这里相当于并发操作
await asyncio.sleep(1)
xxx
- Gevent:是基于Greenlet实现的网络库,通过Greenlet实现协程,由于IO操作十分耗时,经常会使程序处于等待状态,有了gevent为我们来自动切换协程,可以保证Greenlet总是在运行,而不是等待IO操作。
gevent.getcurrent(). gevent.spawn(). gevent.sleep()
不添加sleep()时,greenlet依次执行。 - 在实际代码中,并不会用gevent.sleep()去切换协程,而是执行到IO操作时gevent自动完成,实现这一功能可通过第三方库monkey patch (猴子补丁)完成阻塞式调用变为协作式运行
from gevent import monkey;monkey.patch_all()
# 给所有耗时操作打上补丁
gevent.joinall()
#完成了并发的执行,并且都是在一个线程之内完成
多线程的切换需要操作系统来完成,而协程是在一个线程内切换,切换过程可以由我们自己来控制,因此开销很小。
- asyncio.gather(*aws,bop=None,return_exceptions=False)
在aws中等待的是协程,将会自动调度为任务。
for i in range(2, number+1):
print(f"Task.{name}:Computer factorial({i})...")
await asyncio.sleep(1)
f *= i
print(f"Task.{name}:factorial({number})={f}")
async def main():
results = await asyncio.gather(
xxx
xxx
)
print(xxx)
# 协程的嵌套执行,Python3.7以上新特性
asyncio.run(main())
-
总结
要运行协程,主要提供了三种运行机制。
1、asyncio.run()。 运行最高层级的入口点,asyncio程序的主入口点。
2、await。等待一个协程,也可以启动一个协程,可以同步执行,取决于协程上一次运行的状态。
3、asyncio.create_task()。 用来并发执行作为asyncio任务的多个协程。
4、可等待对象:一个对象可以在await语句中使用,即为可等待对象。主要有三种类型:协程 await,任务 asyncio.create_task(),Future。十、Python并发编程future
future表示未发生/即将发生的事情,确定某件事情会发生的方法是其执行时间已经确定。
-
并发 concurrency: 多线程/协程。同一时刻,只能运行一个线/协程,通过不断切换实现并发 -->IO密集型。
-
并行 parallelism: 多进程。多个CPU同时运行 -->CPU密集型。
import concurrent.futures
with concurrent.futures.ThreadPoolExceutor(max_worker=4) as e:
e.map(down,sites)
future是concurrent.futures模块和asyncio模块的重要组件,两个Future类的实例都表示已完成或者尚未完成的延迟计算,使用并发框架实例化future类。
future表示下一步要执行的程序(即将发生的),确定future的方法就是执行时间已确定。
十一、pdb&cProfile
代码调试和性能分析法宝
方法:
- pdb:pdb.set_trace()
- cProfile:
import cProfile
# 执行的代码
cProfile.run('fib_seq(30)')
参数说明
- ncalls: 相应代码/函数被调用的次数
- totime: 相应代码/函数总共执行所需要的时间
- cumtime: 相应代码/函数总共运行时间,包含调用的其他代码时间
十二、(超)元类(MetaClass)MCS
元类: 类也是对象(感觉和JAVA的反射机制有异曲同工之妙~)