一、元类编程
1、@property 和 @属性名.setter的使用:
前者放在等价于get_attrname()的方法前,后者放在等价于set_attrname()的方法前,用来控制属性,可以添加约束等;两个方法的名字和@属性名.setter中的属性名必须一致,调用时只需要 obj.属性名
2、__getattribute__和__getattr__两个方法的区别:
与对象名.属性名的调用有关,访问时如果有的话先执行前者,最好不要覆盖这个方法;
只有当访问时属性名不存在时才执行后者(前提也是后者方法覆盖的话),可以返回处理后的值,错误反馈或者引导正确的属性名等;
3、属性描述符(本质是一个类,关键是__get__(),__set__(),__delete__()三个魔法方法)
1)作用:@property每次只能控制一个属性,当多个属性约束相同时,为了复用代码从而引出了属性描述符对象的概念;
2)分类:
(1)数据属性描述符:定义的类中包含__get__,和__set__、__delete__()(两者中至少一个)
(2)非数据属性描述符:定义的类中只包含了__get__()方法
3)用法:
定义属性描述符,即定义一个类,涉及的三个魔法方法中给定的参数有self,instance,以及value;读取一般使用self.属性名=value,instance虽然表示实例,但是不需instance.当前属性名
4、属性查找过程( . 的用法):首先执行__getattribute__(),若找到返回,找不到抛出异常后执行__getattr__();其中属性查找的逻辑主要在__getattribute__中,如不重写,object中默认的逻辑如下:
- 若属性出现在类或基类的__dict__中,而且为数据描述符,则调用其__get__()
- 若属性出现在对象的__dict__中,则直接调用 __dict__[属性名];(为类的数据描述符优先级会高于对象的__dict__)
- 若属性出现在类或基类的__dict__中,如果为非数据描述符,则调用__get__(),否则直接调用__dict__[属性名]
- 此时__getattribute__的逻辑执行完毕,如果仍为找到,则抛出AttributeError异常;若定义了__getattr__,则继续执行这个方法,否则抛出异常;
(虽然步骤有些多,但是多顺几遍就很容易记忆了,在没有__getattribute__和__getattr__时,我们只考虑了第2和第3后半部分的情况
5、动态属性和方法的添加:由于Python的一切皆对象,类和对象都是可以动态添加属性和方法的;属性比较简单,只需要.运算即可,方法(静态、类和实例)的话需要遵守类中定义的格式,其中前两种方法可以使用.运算,但是实例方法需要 使用类|对象.方法名=types.Method(类|对象,方法名)格式;
注意事项:对象可以调用三种方法,但是动态添加时只能添加实例方法;类可以调用前两种方法,但是动态添加时可以添加三种,而且对象也可以访问类动态添加的任意一种方法;
(具体内容可以看参考给出的链接
6、元类编程
(1)__new__()和__init__()的区别:
前者第一个参数为cls,控制对象的生成过程,开辟内存空间给实例化对象,然后return给init; !!!一定要有return,否则不会执行init
后者第一个参数为self, 自定义对象的初始化过程
先执行__new__ 再执行__init__
(2)元类:可以定义类的类,内置的元类为type,也可以自定义元类但是要继承type类
①type动态创建类:type(“类名",(基类1,基类2,... ,),{属性名:属性值,方法名:已定义的方法名,...}) 其中引用已定义的方法必须是遵守类中定义的格式
②自定义元类创建类:自定义元类要继承type类,创建类时通过 metaclass=元类名来指定元类,一般元类中用来执行__new__
二、Python的socket编程(import socket
1、socket:由os提供,可以直接与传输层交流,从而实现自己应用层的协议;
2、socket实现C-S通信流程:
server: socket -> bind(协议,地址,端口号) -> listen() -> accept(->sock,addr) ->阻塞等待连接请求 (sock开始工作) -> recv() -> send()(也可以返回到阻塞请求) ->close()
client: socket --------------------------------------------------------------------------------------->connect(三次握手实现连接) -> send() --> recv() ---------------------->close()
三、多线程、多进程
1、GIL(global interpret lock):
python中的一个线程对应C语言的一个线程;gil会使同一个时刻只有一个线程在cpu上执行字节码,无法将多个线程映射到多个CPU上;GIL会根据执行的字节码行数以及时间片释放,且遇到IO操作时也会主动释放;
2、多进程和多线程:前者适合耗CPU的(GIL的存在),后者适合IO操作频繁的(线程切换代价更小)
3、多线程编程:
1)线程编程方法:线程常用到的方法有- start(), join()(等子线程结束主线程才能继续运行), setDaemon()(参数为True表示为守护线程,主线程结束其线程也结束)
(1)直接通过threading.Thread类实例化: thread1=threading.Thread(target=函数名,args=(参数列表,)获得一个线程对象;适合代码量小,内部逻辑简单的情况;
(2)通过继承Thread类然后实例化得到线程对象,需要重载run(self)方法
注:主线程为当前运行的程序,由程序代码定义的线程为子线程
2)线程间通信
(1)共享变量:可以使用全局变量,也可以将涉及的共享变量放入一个文件中,然后import即可;这个由于不是严格同步可能会出现异常结果,不是安全的;
(2)queue.Queue :这个是线程安全的,以线程间同步的方式共享;常涉及的方法有- put() get() join() taskdone()
3)线程间同步
(1)threading.Lock: 影响性能;可能会引起死锁;不可以以各种形式对对一个锁进行嵌套使用
用法: lock=Lock() ; lock.acquire() 代码段1 lock.release();lock.acquire() 代码段2 lock.release();在同一个锁的acquire和release之间的代码段同一时间只有一个在执行
(2)threading.RLock:是一种可重入的锁,可以在同一个线程中连续调用多次,但是要注意acquire和release的配对
(3)threading.Condition:条件变量,用于复杂的线程间同步,本质上利用了RLock实现的,且实现了__enter__(执行了acquire)和__exit__(执行了release),所以可以结合with使用
用法:con=Condition() ; with con: con.wait() con.notify()
注意:wait和notify的顺序很重要;先with(或者acquire和release)才可以使用wait和notify;实际上有两层锁,一把锁在线程调用了wait时释放,另一把锁在每一次调用wait时分配一把,并放入到条件变量的等待队列中,等待notify的唤醒
(4)threading.Semaphore:信息量,用于控制进入资源数量的锁,本质是借助condition
用法: sema=Semaphore(n)(最多指定n个) sema.acquire() sema.release()
4)线程池 (from concurrent.futures import ThreadPoolExecutor)
(1)作用:控制线程数量,主线程还可以获得其他线程的状态或某一个任务的状态以及返回值,当一个线程完成时可让主线程立即知道;threading.futures统一了线程和进程编码接口
(2)用法:executor=ThreadPoolExecutor(max_workers=n);task=executor.submit(函数名,(参数列表))->返回一个Future对象;task.done() cancel() result()
统计完成的线程:①concurrent.futures.as_completed(线程序列):已完成的顺序返回一个可迭代的线程序列
②executor.map(函数名,参数列表的序列):按照指定参数的序列返回完成的线程序列
concurrent.futures.wait() :令主线程等待所有子线程完成才接着运行,也可以设置return_when参数来设置接着运行的条件
4、多进程编程(Windows下的if __name__=="__main__"编写代码)
1)进程编写方法:(from multiprocessing import Process)
(1)直接使用Process类定义: process1=Processs(target=函数名,args=(参列,))获得一个进程
(2)继承Process类然后实例化对象得到进程,需要重载run(self)
方法:is_alive()、join([timeout])、run()、start()、terminate()。其中,Process以start()启动某个进程。
属性:authkey、daemon(要通过start()设置)、exitcode(进程在运行时为None、如果为–N,表示被信号N结束)、name、pid。其中daemon是父进程终止后自动终止,且自己不能产生新进程,必须在start()之前设置。
2)进程池
(1) from concurrent.futures import ProcessPoolExecutor: 与线程池用法基本一致
(2) import multiprocessing.Pool:比(1)更加底层,常用的方法如下: pool=Pool(n)
- apply_async(func[, args[, kwds[, callback]]]) 它是非阻塞,子进程优先,apply(func[, args[, kwds]])是阻塞的,主进程优先
- close() 关闭pool,使其不在接受新的任务。
- terminate() 结束工作进程,不在处理未完成的任务。
- join() 主进程阻塞,等待子进程的退出, join方法要在close或terminate之后使用
- imap(函数名,参数列表的序列),imap_unordered()
3)进程间通信:
(1)multiprocessing.Queue:与线程的queue.Queue类似,只适合非进程池的进程之间的通信
(2)multiprocessing.Pipe:j简化版的Queue,仅适合两个进程之间的通信; recevice_pipe, send_pipe=Pipe(),send() recevice()
(3)multiprocessing.Manager: 实现了内存共享,提供了各种常见的数据类型比如dict()等,但要注意同步;其中Queue()常用来进程池之间进程的通信
注:全局变量不能通信,因为进程的变量是隔离的
四、协程与异步IO
1、基本概念:
(1)并发:同一时间段多个程序同时运行,但同一个时刻只有一个程序在CPU上运行
并行:同一时刻有多个程序在多个cpu上运行
(2)同步:指代码调用IO操作时,必须等待IO操作完成后才返回的调用方式
异步:指代码调用IO操作时,不需要等待IO操作完成便继续向下运行
(3)阻塞:指调用函数时当前线程被挂起
非阻塞:指调用函数时当前线程不会被挂起,而是立即返回
同步异步是消息的机制;阻塞与非阻塞是程序的调用方式
2、Unix的五种IO类型:
(1)阻塞式IO:进程阻塞于recvfrom(等待数据+拷贝数据)的调用
(2)非阻塞式IO:进程反复调用recvfrom(等待数据+拷贝数据)等待返回成功指示
(3)IO多路复用:进程阻塞与select(等待数据)调用,等待可能多个描述符中任意一个可读,然后调用recvfrom(拷贝数据);(监听多个任务的状态
(4)信号驱动式IO:等待程序时进程不阻塞,拷贝数据时进程阻塞
(5)异步IO:等待和拷贝时进程都在执行
3、IO多路复用:select、poll和epoll三种机制
(1)IO多路复用的使用:使用select完成HTTP请求
import select ---(包装)--> from selectors import DefaultSelector(根平台自动选择机制,并额外提供了注册的机制)
#http请求:connect-> send-> recv (client.setblocking(False)设置非阻塞IO)
selector=DefaultSelector()
selector.register(文件描述符,事件类型,回调函数) #事件类型EVENT_WRITE EVENT_READ
selector.unregister(文件描述符)
#事件循环,注意select本身不支持register模式,需要由程序员完成回调
while True:
ready=selector.select()
for key,mask in ready:
call_back=key.data
call_back(key)
4、协程:
(1)定义:不是计算机提供的,由程序员人为创造,也称“微线程”;用户态上下文切换的技术;简而言之就是通过一个线程实现代码块相互切换执行(异步方式的编程)
(2)实现:
- greenlet,早期的第三方模块(手动切换):from greenlet import greenlet; gr=greenlet(函数名);在函数中使用gr即可实现手动切换;
- yield , yield from关键字(手动切换):通过委托方的yield from 在调用方和子生成器中建立双向通道
- asyncio模块的装饰器(3.4版本及以后,可以自动切换):将函数利用@asyncio.coroutine和yield from 变为协程函数,而yield from后的内容遇到IO会自动切换到其他任务
- async,await关键字(3.5版本及以后):同上一个类似,async等价于@,await等价于yield from
(3)基本理解:协程函数是指定义前有async或@asyncio.coroutine装饰;协程对象就是协程函数得到的为~; result=function(),其中result为协程对象,function为协程函数
协程函数在func()调用时内部代码不会执行,必须将对象提交结合事件循环来处理才可以;在py3.7以前,loop=asyncio.get_event_loop(); loop.run_until_complete(协程对象)
在py3.7及以后, 可直接 asyncio.run(协程对象)
(4)基本语法:
await+可等待的对象(比如协程对象,Future和Task对象)
Task对象:
①创建方式有三种--asyncio.create_task()(3.7及以后) ;asyncio.ensure_future()(低层级的,3.7版本前使用);loop.create_task() ; 另:可以指定name参数
②调用:await task ;done,pending= await asyncio.wait(task_list[,timeout=n|None]) done为已完成任务集合,pending为超时集合
Future对象:Task继承与Future,这个区别于concurrent.futures.Future,这个是线程池进程池异步时使用到的对象;两种Future可以交叉使用,比如当第三方模块不支持协程时,那么只需在涉及第三方模块采用线程池或进程池,关键代码:loop.run_in_executor(None|pool,普通函数名)#本质是调用池的submit,然后包装返回来的futures.Future为asyncio.Future
异步迭代器:实现了__anext__()和__aiter__(),其中前者必须返回一个可等待的对象;在协程函数中与async for 一起使用;
异步上下文:实现了__aenter__()和__aexit__(),在协程函数中与async with联合使用
uvloop:是asyncio的事件循环替代方案,为第三方库,但是效率更高
五、异步编程
参考:https://blog.youkuaiyun.com/u014294083/article/details/109823700 (属性查找过程)
https://blog.youkuaiyun.com/qq_30295213/article/details/81483727 (动态属性和方法添加)