Day34:描述器
在python中,实现了__get__
、__set__
、__delete__
三个方法中的任意一个都称为描述器;其中仅实现了__get__
方法的称为非数据描述器,实现了__get__
、__set__
方法的称为数据描述器,我们来看一下这三个方法的语句:
object.__get__(self, instance, owner)
object.__set__(self, instance, value)
object.__delete__(self, instance)
- 当非数据描述器是实例的变量时,实例访问非数据描述器不会调用
__get__
方法,只是访问了描述器类的实例;当数据描述器是实例的变量时,实例访问数据描述器会调用描述器的get方法 - 当存在描述器的时候,一个类实例的查找属性顺序为:先查找类或父类中是否有数据描述器属性,如果有那么,先访问数据描述器,如果没有数据描述器 --> 那么就会查找自己实例的dict属性,如果dict属性里面也没有找到 --> 就会在类或父类的非数据描述器中进行查找
Day35:模块化开发
- 模块化是代码的组成的一种方式,Python中的每一个文件就是模块,模块化开发能够将不同的功能组装在一起,实现功能的累加,诸多功能组装在一起,最终形成项目
- 在Python中,文件有三种方式进行组织分别是:Python模块、目录、包;以.py后缀名结尾的文件就是python的一个模块,而包和目录的不同之处在于包内部多一个init.py文件,使得包能够被模块导入,而目录则不能
- 模块的导入分为绝对导入和相对导入
其中绝对导入是从python的搜索路径中导入对应的包中的模块,格式为:from package import module
相对导入只能在导入包中的模块时才能使用,不能在执行文件中用,
格式为from . import module ,如果是一个点表示当前模块,两个点表示上层模块,三个点表示上上层模块
且相对导入的模块无法直接运行,必须使用python -m package.module这样的方式运行,
或者被__main__模块导入之后使用from . import module运行
在模块导入中,模块中所有的对象都能够被导入,没有私有和保护属性的概念,我们在导入时可以指定允许被导入的对象。但是如果使用from 模块名 import *方式导入时,模块内所有对象都将被导入,此时我们可以使用列表
__all__
申明允许被导入的对象,如__all__ = ['add', 'a']
在开发中
__slots__
可以告诉Python不要使用字典,而且只给一个固定集合的属性分配空间,所以我们可以使用__slots__
来加快的属性访问速度和减少内存消耗一个python文件通常有两种使用方法,一种是直接作为脚本直接执行,另一种是导入到其他的 python 脚本中被调用执行,我们可以通过判断语句
if __name__ == '__main__'
来判断模块是直接执行还是被导入的,当条件为真时就是直接被执行,可以继续执行条件语句后面的代码块,条件为假时(被导入)则不会执行后面的代码块
Day36:异常处理
异常就是一个事件,该事件会在程序执行过程中发生,影响了程序的正常执行,一般情况下,在Python无法正常处理程序时就会发生一个异常,当发生异常时我们需要捕获处理它,否则程序会终止执行
异常和错误的区别是异常可以被捕获,而错误有时候无法被捕获,语法错误会被IDE检查到,但是逻辑错误无法被知晓
捕捉异常可以使用try/except语句,包含在try下的所有代码块都会进行异常检测处理 ,如果没有出现异常则正常执行try后面的代码块,如果出现异常通过except来捕获异常信息并处理
try:
需要被捕获异常的代码块
except 异常类型对象:
处理捕获到的异常
except后面可以指定要捕获的异常类型,如BaseException是所有异常的基类,
Exception
表示常规错误的异常等,我们可以自定义异常,但是所有的自定义异常类需要继承Exception
;可以把多个except
语句连接在一起, 处理一个 try 块中可能发生的多种异常我们知道只有try语句块中发生了异常,才会执行
except
,而finally
是不管try中有没有发生异常都会执行的,所以我们可以在finally中关闭文件,或者做一写其他清理工作处理异常除了使用try/except语句,我们还可以通过raise自己触发异常,语法格式为
raise [Exception [, args [, traceback]]]
,语句中 Exception 是异常的类型(例如,NameError)参数标准异常中任一种,args
是自已提供的异常参数,最后一个参数是可选的,如果存在,是跟踪异常对象;注意一旦执行了raise语句,raise之后的语句将不在执行
Day37:模块打包
- 模块打包目的是让自己开发的功能能够实现共享,供给他人使用,包管理索引平台是Python Package Index
- 模块打包有三种格式tar.gz格式、egg格式、whl格式
tar.gz格式是标准压缩格式,里面包含了项目元数据和代码,可以使用Python setup.py sdist命令生成
egg格式也是压缩文件,只是扩展名换了,里面包含了项目元数据以及源代码,
这个格式由setuptools项目引入, 可以通过命令Python setup.py bdist_egg命令生成
whl格式是Wheel包,也是一个压缩文件,只是扩展名换了,里面包含了项目元数据和代码,还支持免安装直接运行,
whl分发包内的元数据和egg包是有些不同的,可以通过命令Python setup.py bdist_wheel生成;
- 怎么打包
我们可以创建需要打包的模块,然后使用命令行将模块打包:python setup.py bdist_wheel;
还需要在PYPI平台注册账号,在邮箱中认证连接
接着在用户的家目录下创建~/.pypirc文件,将文件配置好
然后安装twine的命令行为:pip install twine;
最后可以将打包好的模块发送到PYPI平台:twine upload dist/*;
Day38:插件化开发与GUI开发
- 插件化开发是一种思想,考虑到程序功能的加载时机,用到的时候再加载;
- 插件化开发依赖的技术点有:
反射:判断对象是否存在某种功能
动态import:运行时候,根据用户需求(提供字符串),找到模块的资源动态加载起来
多线程:使用线程完成对应的任务
插件化开发的好处:可以开启一个线程,等待用户输入,从而加载指定名称的模块
在插件化开发中,我们可以使用importlib模块动态导入,它的语法格式为
importlib.import_module(name, package=None)
,name为模块名称字符串,package当相对导入的时候需要传递的包名字符串
- Python拥有大量可用的GUI 工具包,而
Tkinter模块
是 Python 的标准 GUI 开发库,可以快速的创建 GUI 应用程序,因为Tkinter
是内置到 python 的安装包中,所以只要安装好 Python 之后就能直接导入使用import tkinter
Day39:线程(略难可选)
线程有时被称为轻量进程,是程序执行流的最小单元,在Python中,使用threading库来创建线程,创建进程的语法:
threading.Thread(self, group=None, target=None, name=None, args=(), kwargs=None, *, daemon=None)
,它的一些参数:
group
:为线程组,但是Python中没有线程组,所以这是保留参数;
target
:为可调用对象,也就是任务, 可以是函数,如果是实例,那么实例的类必须实现__call__
方法;
name
:指定线程的名称;
args、kwargs
:给线程任务传递参数、关键字参数;
daemon
:指定子线程是否需要主线程等待,主线程是non-daemon线程;Python中的线程没有优先级、线程组、停止、挂起、销毁、恢复的概念;线程退出的方式有两种:
一种是线程任务执行完毕还有一种是线程内部抛出异常,使用线程之前需要先导入threading,语法为import threading
线程的threading模块中的函数
threading.active_count()
表示依然存活的线程数,包括主线程、threading.current_thread()
表示返回当前线程实例对象、threading.enumerate()
表示返回当前存活的线程对象列表(包括主线程,但是不包括终止线程和未启动线程)、threading.main_thread()
表示返回主线程实例对象、threading.get_ident()
表示返回当前线程的ID线程的
thread_obj
有两个方法,分别是run方法
和start方法
,其中start方法会在内存中启动一个新的线程运行任务,而 run方法不会启动新的线程,只是在主线程中执行任务一般在使用多线程的时候,如果需要打印线程的信息,不会使用
print
函数打印,而是使用logging日志模块
打印,因为考虑到print函数在打印过程中可能出现线程的切换,为了确保线程安全通常使用logging模块
,把线程的信息通过日志的形式打印出来线程中有父子的概念,如果在主线程中启动了一个线程,那么主线程就是父线程,启动的这个工作线程就是子线程&;
主线程是non-daemon线程,也就是daemon=False, 如果子线程的daemon=False,
那么主线程会等待子线程执行完毕,主线程才会终止,如果 为True,
那么主线程将不会等待子线程,而是主线程执行完毕后,子线程就会终止运行;
daemon选项必须在启动线程之前设定;
线程中的join方法的语法为:
join(self, timeout=None)
,在当前线程中调用另一个线程的join方法,当前线程会在此处被阻塞,直到被调用的线程结束运行或终止,timeout指定被阻塞的时长,如果没有指定 ,那么就一直阻塞直到调用线程终止;Timer是Thread的一个派生类,用于在指定时间后调用一个方法,如果想实现每隔一段时间就调用一个函数的话,就要在Timer调用的函数中,再次设置Timer;
创建一个定时任务:threading.Timer(self, interval, function, args=None, kwargs=None)
Day40:线程同步与并发(略难可选)
线程之间有很多种通信方式,例如Event(事件)、Critical Section(临界区,一般是通过加锁实现)、Semaphone(信号量)等
Event是事件处理的机制,全局定义了一个内置标志Flag,如果Flag值为 False,那么当程序执行 event.wait方法时就会阻塞,如果Flag值为True, 那么event.wait 方法时便不再阻塞
event实例对象的对象方法有一下几种:
wait(self, timeout=None)
:其中timeout为设置等待的时长,如果超过时长(返回值为False)则不再等待,直接向下执行,如果timeout没有指定则一 直等待,等待的时候是阻塞的没有返回值
set()
:如果执行event.set(),将会设置flag为True,那么wait等待的线程就可以向下执行
clear()
:如果执行event.clear(),将会设置flag标记为Flase, 那么wait等待的线程将再次等待(阻塞);
is_set()
:判断event的flag是否为True,如果为True的话wait等待的线程将向下执行
- 锁是解决临界区资源的问题,保证每一个线程访问临界资源的时候有全部的权利,一旦某个线程获得锁, 其它试图获取锁的线程将被阻塞
加锁
:acquire(blocking=True,timeout=-1),False为不阻塞,timeout为设置时长
释放锁
:释放锁的方法为release(),在完成任务的时候释放锁,让其他的线程获取到临界资源,通过上下文管理器的方式with lock可以默认释放锁,不需要再写release()
方法
pool实例对象有两个非常实用的方法,一个是
submit(self, fn, *args, **kwargs)
用于提交单个任务,还有一个是map(self, fn, *iterables, timeout=None, chunksize=1)
类似高阶函数map,可以提交任务,且传递一个可迭代对象,返回任务处理迭代对象的结果尽管Python完全支持多线程编程, 但是解释器的C语言实现部分在完全并行执行时并不是线程安全的,
实际上,解释器被一个全局解释器锁保护着 ,它确保任何时候都只有一个Python线程执行;
GIL最大的问题就是Python的多线程程序并不能利用多核CPU的优势,
就是因为GIL的存在,使得一个进程的多个线程在执行任务的时候,
一个CPU时 间片内,只有一个线程能够调度到CPU上运行
因此CPU密集型的程序一般不会使用Python实现,可以选择Java,GO等语言;
但是对于非CPU密集型程序,例如IO密集型程序,多数时间都是对网络IO的等待,
因此Python的多线程完全可以胜任
对于全局解释器锁的解决方案有两个,一个是使用
multiprocessing
创建进程池对象,实现多进程并发,这样就能够使用多CPU
计算资源,还可以使用C语言扩展,将计算密集型任务转移给C语言实现去处理,在C代码实现部分可以释放GIL
如果想要同时使用多线程和多进程,最好在程序启动时,创建任何线程之前,先创建一个单例的进程池, 然后线程使用同样的进程池来进行它们的计 算密集型工作,这样类似于线程调用了进程,完成了CPU密集型任务,进程也利用了多CPU的优势
Day41:进程和并发(略难可选)
- python中提供multiprocess模块实现多进程并发,
multiprocessing支持子进程
、通信和共享数据
、执行不同形式的同步
,提供了Process、Queue、Pipe、Lock
等组件
可以通过multiprocessing.Process对象来创建一个进程,该进程可以运行在Python程序内部编写的函数
process模块是一个创建进程的模块,借助这个模块,就可以完成进程的创建,它有start(), run(), join()的方法
concurrent模块能够提供一个future的实例对象,实例对象提供了线程的执行器和进程的执行器
pool的对象方法有三个:
submit()
、map()
、shutdown()
,其中submit()
方法的返回值future对象的方法有如下方法:
submit():返回一个Future对象;
result():查看调用的返回结果;
done():如果任务成功执行或任务取消返回True;
cancel():取消任务;
running():如果任务正在执行返回True;
exception(): 获取执行抛出的异常;
在linux或者unix操作系统中,守护进程(Daemon)是一种运行在后台的特殊进程,它独立于控制终端并且周期 性的执行某种任务或等待处理某些发生的事件
由于在linux中,每个系统与用户进行交流的界面称为终端,每一个从此终端开始运行的进程都会依附于这个终 端,这个终端被称为这些进程的控制终端,当控制终端被关闭的时候,相应的进程都会自动关闭
但是守护进程却能突破这种限制,它脱离于终端并且在后台运行,并且它脱离终端的目的是为了避免进程在运 行的过程中的信息在任何终端中显示并且进程也不会被任何终端所产生的终端信息所打断,它从被执行的时候 开始运转,直到整个系统关闭才退出
守护进程实现的步骤如下:
父进程fork出子进程并exit退出
子进程调用setsid创建新会话
子进程调用系统函数chdir将根目录"/"成为子进程的工作目录
子进程调用系统函数umask将该进程的umask设置为0
子进程关闭从父进程继承的所有不需要的文件描述符
-
os.fork()
返回两个值,一个是在当前父进程返回的,一个是在子进程中返回的,子进程返回的是0,父进程返回的是子进程的pid