Python高级核心---第二篇

一、元类编程

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 (动态属性和方法添加)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值