Python高级特性

一、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锁

  1. GIL( Global Interpreter Lock)全局解释器锁。在Python中 其实并不存在严格意义上的多线程,当系统开始运行两个或两个以上的线程时,会在同一时刻为线程上锁,保证在同一时刻只有一个线程在运行代码,保证线程安全,每个线程在执行之前都要获取GIL锁。
  2. Python语言和GIL锁其实并没有关系。而GIL锁仅仅是因为历史原因在cpython解释器中难以移除的(属于系统漏洞)。
  3. 举个栗子:
    (1)单线程死循环中–>CPU占用率为100%
    (2)多线程死循环
def test():
	while True:
		pass
t1 = Threading.Thread(target=test)
t1.start()
# 主线程死循环
while True:
	pass

查看程序占用CPU占用率,两个中的任何一个CPU都没有占满,而是交替执行。这验证了多线程下每个程序的执行过程都要首先获取GIL,保证同一时刻只有一个线程在运行。

  1. 执行cpu(计算)密集型的程序时(需要进行大量的数值计算、如计算圆周率、对视频进行高清解码等),可选择**换解释器/选用其它语言(语言)/选用多进程(并行)**方案等。
  2. 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)

futureconcurrent.futures模块和asyncio模块的重要组件,两个Future类的实例都表示已完成或者尚未完成的延迟计算,使用并发框架实例化future类。
future表示下一步要执行的程序(即将发生的),确定future的方法就是执行时间已确定。

十一、pdb&cProfile

代码调试和性能分析法宝
方法:

  1. pdb:pdb.set_trace()
  2. cProfile:
import cProfile
# 执行的代码
cProfile.run('fib_seq(30)')

参数说明

  1. ncalls: 相应代码/函数被调用的次数
  2. totime: 相应代码/函数总共执行所需要的时间
  3. cumtime: 相应代码/函数总共运行时间,包含调用的其他代码时间

十二、(超)元类(MetaClass)MCS

元类: 类也是对象(感觉和JAVA的反射机制有异曲同工之妙~)

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值