1.三程
1.1 进程
进程的定义
- 进程是资源分配最小单位
- 一个运行起来的程序就是一个进程
什么是程序(程序是我们存储在硬盘里的代码、文件)
当我们双击图标,打开程序的时候,实际上就是通过I/O操作(读写)内存条里面
内存条就是我们所指的资源 - 进程之间内存独立,不能相互访问
进程定义拓展回答内容:
- 程序并不能单独运行,只有将程序装载到内存中,系统为它分配资源才能运行,这种执行的程序就称之为进程
- 程序和进程的区别就在于:程序是指令的集合,它是进程运行的静态描述文本;进程是程序的一次执行活动,属于动态概念
- 在多道编程中,我们允许多个程序同时加载到内存中,在操作系统的调度下,可以实现并发地执行。
- 进程的出现让每个用户感觉到自己独享CPU,因此,进程就是为了在CPU上实现多道编程而提出的。
- 进程之间有自己独立的内存,各进程之间不能相互访问
- 创建一个新线程很简单,创建新进程需要对父进程进行复制
进程和程序的区别
- 程序只是一个普通文件,是一个机器代码指令和数据的集合,所以,程序是一个静态的实体
- 而进程是程序运行在数据集上的动态过程,进程是一个动态实体,它应创建而产生,应调度执行因等待资源或事件而被处于等待状态,因完成任务而被撤消
- 进程是系统进行资源分配和调度的一个独立单位
- 一个程序对应多个进程,一个进程为多个程序服务(两者之间是多对多的关系)
- 一个程序执行在不同的数据集上就成为不同的进程,进程可以控制 块 来唯一标识每个程序
多道编程概念:
- 多道编程: 在计算机内存中同时存放几道相互独立的程序,他们共享系统资源,相互穿插运行
- 单道编程: 计算机内存中只允许一个的程序运行
进程具有独立的内存空间,所以没有办法相互通信
进程通信:
python提供了多种进程通信的方式,主要Queue和Pipe这两种方式,Queue用 于多个进程间实现通信,Pipe是两个进程的通信。
- Queue有两个方法:
\1. Put方法:以插入数据到队列中
\2. Get方法:从队列读取并且删除一个元素 - Pipe常用于两个进程,两个进程分别位于管道的两端
Pipe方法返回(conn1,conn2)代表一个管道的两个端,Pipe方法有duplex参数,默认为True,即全双工模式,若为FALSE,conn1只负责接收信息,conn2负责发送, - managers
- RabbitMQ、redis等
进程间互相访问数据的四种方法:
注:不同进程间内存是不共享的,所以互相之间不能访问对方数据
- 利用Queues实现父进程到子进程(或子进程间)的数据传递
- 使用管道pipe实现两个进程间数据传递
- Managers实现很多进程间数据共享
- 借助redis中间件进行数据共享
进程池:
为什么需要进程池?
- 一次性开启指定数量的进程
- 如果有十个进程,有一百个任务,一次可以处理多少个(一次性只能处理十个)
- 防止进程开启数量过多导致服务器压力过大
- 开进程池是为了效率,进程直接的切换是属于IO调度,每个进程的内存空间都有自己的寄存器,堆栈和文件。
from multiprocessing import Process,Pool
import time,os
def foo(i):
time.sleep(2)
print("in the process",os.getpid()) #打印子进程的pid
return i+100
def call(arg):
print('-->exec done:',arg,os.getpid())
if __name__ == '__main__':
pool = Pool(3) #进程池最多允许5个进程放入进程池
print("主进程pid:",os.getpid()) #打印父进程的pid
for i in range(10):
#用法1 callback作用是指定只有当Foo运行结束后就执行callback调用的函数,父进程调用的callback函数
pool.apply_async(func=foo, args=(i,),callback=call)
#用法2 串行 启动进程不在用Process而是直接用pool.apply()
# pool.apply(func=foo, args=(i,))
print('end')
pool.close() #关闭pool
pool.join() #进程池中进程执行完毕后再关闭,如果注释,那么程序直接关闭。
进程池优点:
不仅仅减少了IO而且还减少了内存。
下面的例子便可以区分 其他语言的进程池还可以根据服务器的压力来增减,有着上限和下限。
12
建议:超过五个进程就用进程池
有了进程为什么还要线程?
- 进程优点:
提供了多道编程,让我们感觉我们每个人都拥有自己的CPU和其他资源,可以提高计算机的利用率 - 进程的两个重要缺点
a. 第一点:进程只能在一个时间干一件事,如果想同时干两件事或多件事,进程就无能为力了。
b. 第二点:进程在执行的过程中如果阻塞,即使进程中有些工作不依赖于输入的数据,也将无法执行(例如等待输入,整个进程就会挂起)。
c. 例如,我们在使用qq聊天, qq做为一个独立进程如果同一时间只能干一件事,那他如何实现在同一时刻 即能监听键盘输入、又能监听其它人给你发的消息
d. 你会说,操作系统不是有分时么?分时是指在不同进程间的分时呀
e. 即操作系统处理一会你的qq任务,又切换到word文档任务上了,每个cpu时间片分给你的qq程序时,你的qq还是只能同时干一件事呀
1.2 线程
线程的定义:
- 线程是系统调度的最小单位
- 同进程下线程资源共享
- 进程无法自己执行,只有通过线程操作CPU,内存
- 为了保证数据安全,必须使用线程锁
线程定义拓展回答内容:
- 线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位
- 一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务
- 无论你启多少个线程,你有多少个cpu, Python在执行的时候会淡定的在同一时刻只允许一个线程运行
- 进程本身是无法自己执行的,要操作cpu,必须创建一个线程,线程是一系列指令的集合
- 所有在同一个进程里的线程是共享同一块内存空间的,不同进程间内存空间不同
- 同一个进程中的各线程可以相互访问资源,线程可以操作同进程中的其他线程,但进程仅能操作子进程
- 两个进程想通信,必须要通过一个中间代理
- 对主线程的修改可能回影响其他子线程,对主进程修改不会影响其他进程因为进程间内存相互独立,但是同一进程下的线程共享内存
进程和线程的区别:
- 进程包含线程
- 线程共享内存空间
- 进程内存是独立的(不可互相访问)
- 进程可以生成子进程,子进程之间互相不能互相访问(相当于在父级进程克隆两个子进程)
- 在一个进程里面线程之间可以交流。两个进程想通信,必须通过一个中间代理来实现
- 创建新线程很简单,创建新进程需要对其父进程进行克隆。
- 一个线程可以控制或操作同一个进程里面的其它线程。但进程只能操作子进程。
- 父进程可以修改不影响子进程,但不能修改。
- 线程可以帮助应用程序同时做几件事
for循环同时启动多个线程:
import threading
import time
def sayhi(num): #定义每个线程要运行的函数
print("running on number:%s" %num)
time.sleep(3)
for i in range(50):
t = threading.Thread(target=sayhi,args=('t-%s'%i,))
t.start()
t.join(): 实现所有线程都执行结束后再执行主线程:
import threading
import time
start_time = time.time()
def sayhi(num): #定义每个线程要运行的函数
print("running on number:%s" %num)
time.sleep(3)
t_objs = [] #将进程实例对象存储在这个列表中
for i in range(50):
t = threading.Thread(target=sayhi,args=('t-%s'%i,))
t.start() #启动一个线程,程序不会阻塞
t_objs.append(t)
print(threading.active_count()) #打印当前活跃进程数量
for t in t_objs: #利用for循环等待上面50个进程全部结束
t.join() #阻塞某个程序
print(threading.current_thread()) #打印执行这个命令进程
print("----------------all threads has finished.....")
print(threading.active_count())
print('cost time:',time.time() - start_time)
setDaemon(): 守护线程,主线程退出时,需要子线程随主线程退出:
import threading
import time
start_time = time.time()
def sayhi(num): #定义每个线程要运行的函数
print("running on number:%s" %num)
time.sleep(3)
for i in range(50):
t = threading.Thread(target=sayhi,args=('t-%s'%i,))
t.setDaemon(True) #把当前线程变成守护线程,必须在t.start()前设置
t.start() #启动一个线程,程序不会阻塞
print('cost time:',time.time() - start_time)
GIL全局解释器锁:保证同一时间仅有一个线程对资源有操作权限:
作用:在一个进程内,同一时刻只能有一个线程执行`
`说明:python多线程中GIL锁只是在CPU操作时(如:计算)才是串行的,其他都是并行的,所以比串行快很多
- 为了解决不同线程同时访问同一资源时,数据保护问题,而产生了GIL
- GIL在解释器的层面限制了程序在同一时间只有一个线程被CPU实际执行,而不管你的程序里实际开了多少条线程
- CPython自己定义了一个全局解释器锁,同一时间仅仅有一个线程可以拿到这个数据
- python之所以会产生这种不好的状况是因为python启用一个线程是调用操作系统原生线程,就是C接口
- 但是这仅仅是CPython这个版本的问题,在PyPy,中就没有这种缺陷
线程锁:
- 当一个线程对某个资源进行CPU计算的操作时加一个线程锁,只有当前线程计算完成主动释放锁,其他线程才能对其操作
- 这样就可以防止还未计算完成,释放GIL锁后其他线程对这个资源操作导致混乱问题
- 线程锁本质把线程中的数据加了一把互斥锁
有了GIL全局解释器锁为什么还需要线程锁
因为cpu是分时使用的
GIL是限制同一个进程中只有一个线程进入Python解释器。。。。。
而线程锁是由于在线程进行数据操作时保证数据操作的安全性(同一个进程中线程之间可以共用信息,如果同时对数据进行操作,则会出现公共数据错误)
其实线程锁完全可以替代GIL,但是Python的后续功能模块都是加在GIL基础上的,所以无法更改或去掉GIL,这就是Python语言最大的bug…只能用多进程或协程改善,或者直接用其他语言写这部分
死锁定义
两个以上的进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去
用户锁:
import time
import threading
lock = threading.Lock() #1 生成全局锁
def addNum():
global num #2 在每个线程中都获取这个全局变量
print('--get num:',num )
time.sleep(1)
lock.acquire() #3 修改数据前加锁
num -= 1 #4 对此公共变量进行-1操作
lock.release() #5 修改后释放
Semaphore(信号量):
- 互斥锁 同时只允许一个线程更改数据,而Semaphore是同时允许一定数量的线程更改数据
- 比如厕所有3个坑,那最多只允许3个人上厕所,后面的人只能等里面有人出来了才能再进去
- 作用就是同一时刻允许运行的线程数量
多线程
-
GIL锁:
全局解释锁,每次只能一个线程获得cpu的使用权:为了线程安全,也就是为了解决多线程之间的数据完整性和状态同步而加的锁,因为我们知道线程之间的数据是共享的。
-
join()作用:
在进程中可以阻塞主进程的执行, 直到等待子线程全部完成之后, 才继续运行主线程后面的代码
-
setDaemon():
将该线程标记为守护线程或用户线程
线程池
- 使用以下模块创建线程池:
- 使用threadpool模块,这是个python的第三方模块,支持python2和python3
- 使用concurrent.futures模块,这个模块是python3中自带的模块,但是,python2.7以上版本也可以安装使用
-
线程池实现并发:
python import requests from concurrent.futures import ThreadPoolExecutor def fetch_request(url): result = requests.get(url) print(result.text) url_list = [ 'https://www.baidu.com', 'https://www.google.com/', #google页面会卡住,知道页面超时后这个进程才结束 'http://dig.chouti.com/', #chouti页面内容会直接返回,不会等待Google页面的返回 ] pool = ThreadPoolExecutor(10) # 创建一个线程池,最多开10个线程 for url in url_list: pool.submit(fetch_request,url) # 去线程池中获取一个线程,线程去执行fetch_request方法 pool.shutdown(True) # 主线程自己关闭,让子线程自己拿任务执行
1.3协程
什么是协程(进入上一次调用的状态)
- 协程,又称微线程,纤程,协程是一种用户态的轻量级线程。
- 线程的切换会保存到CPU的栈里,协程拥有自己的寄存器上下文和栈,
- 协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈
- 协程能保留上一次调用时的状态(即所有局部状态的一个特定组合),每次过程重入时,就相当于进入上一次调用的状态
- 协程最主要的作用是在单线程的条件下实现并发的效果,但实际上还是串行的(像yield一样)
- 协程能在单线程处理高并发
协程的定义:
- 协程在单线程下实现并发效果
- 协程遇IO自动切换
- 协程保留上一次调用状态
协程的优点:
- 无需线程上下文切换的开销
- 无需原子操作锁定及同步的开销,因为协程是串行的
- 方便切换控制流,简化编程模型
- 高并发,高扩展,低成本,一个cpu支持上万个协程没有问题,所以非常适合高并发处理
协程的缺点:
- 无法利用多核的优势,但是协程和进程配合就可以使协程运行在不同的cpu上,就可以利用 多核的优势,但是在现实中,大部分场景都没有这个需要
- 只要一个协程阻塞(Blocking),就会阻塞整个协程,因为协程是串行的,这个问题必须要解决,才能让协程大范围应用
- 解决方法:
如果遇到io操作,则进行协程切换,去执行其他的协程,可以用gevent来实现,具体的实现是这样的,
比如协程1通过os去读一个file,这个时候就是一个 io操作,在调用os的接口前,就会有一个列表,协议1的这个操作就会被注册到这个列表中,然后就切换到其他协程去处理;等待os拿到要读file后,也会把这个文件句柄放在这个列表中,然后等待在切换到
协程1的时候,协程1就可以直接从列表中拿到数据,这样就可以实现不阻塞了
协程处理并发:
- Gevent
遇IO自动切换
- Gevent 是一个第三方库,可以轻松通过gevent实现并发同步或异步编程
- 协程之所以快是因为遇到I/O操作就切换(最后只有CPU运算)
- 其实Gevent模块仅仅是对greenlet的再封装,将I/O间的手动切换变成自动切换
- Greenlet
遇IO手动切换
- Greenlet全部运行在主程序操作系统进程的内部,但它们被协作式地调度。
使用协程处理并发
注:Gevent只用起一个线程,当请求发出去后gevent就不管,永远就只有一个线程工作,谁先回来先处理
import gevent
import requests
from gevent import monkey
monkey.patch_all()
# 这些请求谁先回来就先处理谁
def fetch_async(method, url, req_kwargs):
response = requests.request(method=method, url=url, **req_kwargs)
print(response.url, response.content)
##### 发送请求 #####
gevent.joinall([
gevent.spawn(fetch_async, method='get', url='https://www.python.org/', req_kwargs={
}),
gevent.spawn(fetch_async, method='get', url='https://www.google.com/', req_kwargs={
}),
gevent.spawn(fetch_async, method='get', url='https://github.com/', req_kwargs={
}),
])
select、poll、epoll(重点)
:
I/O的实质是什么?
I/O的实质是将硬盘中的数据,或收到的数据实现从内核态 copy到 用户态的过程
本文讨论的背景是Linux环境下的network IO。
比如微信读取本地硬盘的过程
微信进程会发送一个读取硬盘的请求----》操作系统
只有内核才能够读取硬盘中的数据—》数据返回给微信程序(看上去就好像是微信直接读取)
用户态 & 内核态
系统空间分为两个部分,一部分是内核态,一部分是用户态的部分
内核态:内核态的空间资源只有操作系统能够访问
用户态:我们写的普通程序使用的空间
- select (能监控数量有限,不能告诉用户程序具体哪个连接有数据)
单个进程就可以同时处理多个网络连接的io请求(同时阻塞多个io操作)。基本原理就是程序呼叫select,然后整个程序就阻塞状态,这时候,kernel内核就会轮询检查所有select负责的文件描述符fd,当找到其中那个的数据准备好了文件描述符,会返回给select,select通知系统调用,将数据从kernel内核复制到进程缓冲区(用户空间)。
- poll(和select一样,仅仅去除了最大监控数量)
- poll和select在本质上没有多大差别,但是poll没有最大文件描述符数量的限制
差别如下:
描述fd集合的方式不同,poll使用 pollfd 结构而不是select结构fd_set结构,所以poll是链式的,没有最大连接数的限制
poll有一个特点是水平触发,也就是通知程序fd就绪后,这次没有被处理,那么下次poll的时候会再次通知同个fd已经就绪。
- epoll (不仅没有最大监控数量限制,还能告诉用户程序哪个连接有活跃)
注:epoll被认为是linux下性能最好的多路io就绪通知方法
- epoll直到Linux2.6(centos6以后)才出现了由内核直接支持
- Epoll没有最大文件描述符数量限制
- epoll最重要的优点是他可以直接告诉用户程序哪一个,比如现在用epoll去监控10000个socket链接,交给内核去监测,现在有一个连接有数据了,在有有一个连接有数据了,epoll会直接高数用户程序哪个连接有数据了
epoll是select和poll的改进方案,在 linux 上可以取代 select 和 poll,可以处理大量连接的性能问题
- epoll能实现高并发原理
-
epoll() 中内核则维护一个链表,epoll_wait 直接检查链表是不是空就知道是否有文件描述符准备好了。
-
在内核实现中 epoll 是根据每个 sockfd 上面的与设备驱动程序建立起来的回调函数实现的。
-
某个 sockfd 上的事件发生时,与它对应的回调函数就会被调用,把这个 sockfd 加入链表。
-
epoll上面链表中获取文件描述,这里使用内存映射(mmap)技术, 避免了复制大量文件描述符带来的开销
内存映射(mmap):内存映射文件,是由一个文件到一块内存的映射,将不必再对文件执行I/O操作epoll有4个动作:创建,注册,等待,取消注册,很显然我们用不着
-
epoll和select,poll还有一个本质的区别的就是:
select 和 poll 只有在下次在循环回来,再去操作系统获取文件描述符
epoll 会直接告诉程序,我们这里已经就绪了,你可以接受数据了,等下一次协程去调用 epoll_wait 的时候就可以直接拿到就绪的文件描述符
猴子补丁
即在运行时对方法 / 类 / 属性 / 功能进行修改,把新的代码作为解决方案代替原有的程序,也就是为其打上补丁。
在使用gevent模块的使用会遇到猴子补丁
import gevent.monkey
gevent.monkey.patch_all()
注解:使用猴子补丁的方式,gevent能够修改标准库里面大部分的阻塞式系统调用,包括socket、ssl、threading和 select等模块,而变为协作式运行。也就是通过猴子补丁的monkey.patch_xxx()来将python标准库中模块或函数改成gevent中的响应的具有协程的协作式对象。这样在不改变原有代码的情况下,将应用的阻塞式方法,变成协程式的。
2.三器
2.1装饰器
介绍
装饰器(Decorators)是 Python 的一个重要部分。
简单地说:他们是修改其他函数的功能的函数。他们有助于让我们的代码更简短,也更Pythonic(Python范儿)。
装饰器的概念:
1. 装饰器实际上就是一个函数
2. 有2个特别之处,参数是一个函数。返回值是一个参数
12
装饰器的简单理解:
实际上就是为了给一个程序添加功能,但是该程序已经上线或者已被使用,
那么就不能大批量的修改源码,这样不现实,因此就产生了装饰器。
注意点:
1. 不能修改被装饰的函数的源代码
2. 不能修改被装饰的函数的调用方式
12345
装饰器组成方式:
函数+实参高阶函数+返回值高阶函数+嵌套函数+语法糖 = 装饰器
1
-
有关高阶函数的理解:
- 把一个函数名当作实参传给另外一个函数(”实参高阶函数“)
- 返回值中包含函数名(”返回值高阶函数“)
-
嵌套函数的理解:
嵌套函数指的是在函数内部定义一个函数,而不是调用。
-
语法糖:
写法:@xx ,一般写在函数的上方
装饰器实例
-
使用高阶函数模拟装饰器:
import time def timer(func): start_time = time.time() func() print '函数执行时间为', time.time() - start_time def test(): print '开始执行test' time.sleep(3) print 'test执行结束' timer(test) ''' 开始执行test test执行结束 函数执行时间为 3.00332999229 ''' 123456789101112131415
-
计算运行时间装饰器:
import time def timer(func): #timer(test1) func=test1 def deco(*args,**kwargs): start_time = time.time() func(*args,**kwargs) #run test1 stop_time = time.time() print("running time is %s"%(stop_time-start_time)) return deco @timer # test1=timer(test1) def test1(): time.sleep(3) print("in the test1") test1() 12345678910111213
-
装饰无参函数,示例代码如下:
#装饰器装饰的函数无参数 def timer(func): #func其实指的就是test def deco(): start = time.time() func() #这里其实是对test的调用 stop = time.time() print (stop-start) return deco @timer #test函数使用装饰器 def test(): time.sleep(2) print ("test is running") test() 打印结果: test is running 2.003510952 12345678910111213141516171819
-
装饰有参函数,示例代码如下:
#装饰器装饰的函数有参数 def timer(func): def deco(*args,**kwargs): #添加可变参数*args和**kwargs start = time.time() func(*args,**kwargs) #这里也是一样,添加可变参数*args和**kwargs stop = time.time() print (stop-start) return deco @timer def test(value): #test函数有个参数value,正因为装饰器timer装饰的函数test有参数value,因此在timer中的有了可变参数 time.sleep(2) print ("test is running %s" %value) test("22") 打印结果: test is running 22 2.00424408913 12345678910111213141516171819
-
带参数的装饰器,示例代码如下:
#装饰器带参数 def timer(parameter): def out_wapper(func): def wapper(*wargs,**kwargs): if parameter == "task1": start = time.time() func(*wargs,**kwargs) stop = time.time() print ("the task1 is run:",stop-start) elif parameter == "task2": func(*wargs, **kwargs) print ("the task2 is run:") return wapper return out_wapper @timer(parameter = "task1") def task1(): time.sleep(2) print "in the task1" @timer(parameter = "task2") def task2(): time.sleep(2) print "in the task2" task1() task2() 打印结果: in the task1 ('the task1 is run:', 2.002906084060669) in the task2 the task2 is run: 123456789101112131415161718192021222324252627282930313233
装饰器使用场景
授权:装饰器能有助于检查某个人是否被授权去使用一个web应用的端点(endpoint)。它们被大量使用于Flask和Django web框架中
日志:在记录日志的地方添加装饰器
缓存:通过装饰器获取缓存中的值
闭包
-
定义:
如果在一个函数的内部定义了另一个函数,外部的我们叫他外函数,内部的我们叫他内函数。那闭包就是,在一个外函数中定义了一个内函数,内函数里运用了外函数的临时变量,并且外函数的返回值是内函数的引用。这样就构成了一个闭包
2.2迭代器
定义:
- 迭代是Python最强大的功能之一,是访问集合元素的一种方式。
- 迭代器是一个可以记住遍历的位置的对象。
- 迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束。迭代器只能往前不会后退。
- 迭代器有两个基本的方法:iter() 和 next()。
- 字符串,列表或元组对象都可用于创建迭代器:
可迭代对象:
在Python世界里,一切皆对象。对象根据定义的维度,又可以分为各种不同的类型,比如:文件对象,字符串对象,列表对象。。。等等。
一句话:“实现了__inter__方法的对象就叫做可迭代对象”,__inter__方法的作用就是返回一个迭代器对象。
直观理解就是能用for循环进行迭代的对象就是可迭代对象。比如:字符串,列表,元祖,字典,集合等等,都是可迭代对象。
next()与iter():
next()返回迭代器的下一个项目
next语法:
next(iterator[,dafault])
iterator -- 可迭代对象
default -- 可选,用于设置在没有下一个元素时返回该默认值,如果不设置,又没有下一个元素则会触发 StopIteration 异常。
iter():
iter()函数用来生成迭代器
iter语法:
12345678910
迭代器实现斐波那契
class Fib():
def __init__(self, n):
self.a = 0
self.b = 1
self.n = n
self.count = 0
def __iter__(self):
return self
def next(self):
res = self.a
self.a, self.b = self.b, self.a + self.b
if self.count > self.n:
raise StopIteration
self.count += 1
return res
print(list(Fib(5)))
print(list(Fib(10)))
-
生成器和迭代器之间的区别
在使用生成器时,我们创建一个函数;在使用迭代器时,我们使用内置函数iter()和next()。 在生成器中,我们使用关键字‘yield’来每次生成/返回一个对象。 生成器中有多少‘yield’语句,你可以自定义。 每次‘yield’暂停循环时,生成器会保存本地变量的状态。而迭代器并不会使用局部变量,它只需要一个可迭代对象进行迭代。 使用类可以实现你自己的迭代器,但无法实现生成器。 生成器运行速度快,语法简洁,更简单。 迭代器更能节约内存。
2.3生成器
-
生成器定义、简介
在python中,生成器是根据某种算法边循环边计算的一种机制。主要就是用于操作大量数据的时候,
一般我们会将操作的数据读入内存中处理,可以计算机的内存是比较宝贵的资源,我认为的当要处理的数据超过内存四分之一的大小时就应该使用生成器。 -
生成器的作用
-
通过列表生成式,我们可以直接创建一个列表,但是,受到内存限制,列表容量肯定是有限的。
-
而且,创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。
-
所以,如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?
-
这样就不必创建完整的list,从而节省大量的空间。在Python中,这种一边循环一边计算的机制,称为生成器:generator。
- 特点
-
和传统的容器相比,生成器更节省内存。
-
延迟计算,在我们需要结果时就调用一下生成器的next()方法即可。
-
可迭代,你可以像遍历list一样,遍历生成器
- 生成器工作原理
-
生成器是这样一个函数,它记住上一次返回时在函数体中的位置。
-
对生成器函数的第二次(或第 n 次)调用跳转至该函数中间,而上次调用的所有局部变量都保持不变。
-
生成器不仅“记住”了它数据状态;生成器还“记住”了它在流控制构造中的位置。
-
生成器是一个函数,而且函数的参数都会保留。
-
迭代到下一次的调用时,所使用的参数都是第一次所保留下的,即是说,在整个所有函数调用的参数都是第一次所调用时保留的,而不是新创建的
在python中有两种方式创建生成器:生成器表达式 和 生成器函数。
- 生成器 和 普通函数的区别 ?
生成式函数和普通函数只有一个区别,普通函数使用return返回结果,而生成器函 数使用yield返回结果。
yield的特点在于,它并不是结束函数,而是在返回结果后将函数处于一种挂起状态,等待再次next函数的调用,然后从上次挂起的地方(yield)继续执行。
- 可迭代的数据类型
列表、元组、字典和集合都是可迭代的对象,可以从其中获得迭代器。
所有这些对象都可用iter()方法获取迭代器:
-
yield运行机制
:在Python中,yield就是这样的一个生成器。
- 当你问生成器要一个数时,生成器会执行,直至出现 yield 语句,生成器把yield 的参数给你,之后生成器就不会往下继续运行。
- 当你问他要下一个数时,他会从上次的状态开始运行,直至出现yield语句,把参数给你,之后停下。如此反复
- 在python中,当你定义一个函数,使用了yield关键字时,这个函数就是一个生成器
- 它的执行会和其他普通的函数有很多不同,函数返回的是一个对象,而不是你平常所用return语句那样,能得到结果值。如果想取得值,那得调用next()函数
- 每当调用一次迭代器的next函数,生成器函数运行到yield之处,返回yield后面的值且在这个地方暂停,所有的状态都会被保持住,直到下次next函数被调用,或者碰到异常循环退出。
def fib(max_num):
a,b = 1,1
while a < max_num:
yield b
a,b=b,a+b
g = fib(10) #生成一个生成器:[1,2, 3, 5, 8, 13]
print(g.__next__()) #第一次调用返回:1
print(list(g)) #把剩下元素变成列表:[2, 3, 5, 8, 13]
每次执行send()或next()只是返回了对应yield表达式的参数值,其实对应表达式并未执行,直到下次再执行send()或next()才会执行上次返回参数的yield表达式,所谓的执行yield表达式就是给其赋值,并返回下一个yield表达式的参数值!
yield机制详细地址
3. 面向对象
简介:
面向对象编程(Object Oriented Programming-OOP) 是一种解决软件复用的设计和编程方法。 这种方法把软件系统中相近相似的操作逻辑和操作 应用数据、状态,以类的型式描述出来,以对象实例的形式在软件系统中复用,以达到提高软件开发效率的作用。
其实面向对象也很简单,却也很难,熟能生巧。你需要了解类和对象,要学会定义类,创建对象。
特点:
- 类(Class): 用来描述具有相同的属性和方法的对象的集合。它定义了该集合中每个对象所共有的属性和方法。对象是类的实例。
- 方法:类中定义的函数。
- 类变量:类变量在整个实例化的对象中是公用的。类变量定义在类中且在函数体之外。类变量通常不作为实例变量使用。
- 数据成员:类变量或者实例变量用于处理类及其实例对象的相关的数据。
- 方法重写:如果从父类继承的方法不能满足子类的需求,可以对其进行改写,这个过程叫方法的覆盖(override),也称为方法的重写。
- 局部变量:定义在方法中的变量,只作用于当前实例的类。
- 实例变量:在类的声明中,属性是用变量来表示的,这种变量就称为实例变量,实例变量就是一个用 self 修饰的变量。
- 继承:即一个派生类(derived class)继承基类(base class)的字段和方法。继承也允许把一个派生类的对象作为一个基类对象对待。
- 实例化:创建一个类的实例,类的具体对象。
- 对象:通过类定义的数据结构实例。对象包括两个数据成员(类变量和实例变量)和方法。
下面详细介绍:
3.1 方法
1.1 静态方法:
- 定义:使用装饰器@staticmethod。参数随意,没有“self”和“cls”参数,但是方法体中不能使用类或实例的任何属性和方法;
- 调用:实例对象和类对象都可以调用。
- 特性: 静态方法只是名义上归类管理,实际上在静态方法里访问不了类或则实例中的任何属性
- 作用:静态方法可以更好的组织代码,防止代码变大后变得比较混乱。
- 静态方法使用场景:
- 我们要写一个只在类中运行而不在实例中运行的方法.
- 经常有一些跟类有关系的功能但在运行时又不需要实例和类参与的情况下需要用到静态方法.
- 比如更改环境变量或者修改其他类的属性等能用到静态方法.
- 这种情况可以直接用函数解决, 但这样同样会扩散类内部的代码,造成维护困难.
class Dog(object):
def __init__(self,name):
self.name = name
@staticmethod
def eat():
print("I am a static method")
d = Dog("ChenRonghua")
d.eat() #方法1:使用实例调用
Dog.eat() #方法2:使用类直接调用
1.2 类方法:
- 定义:使用装饰器@classmethod。第一个参数必须是当前类对象,该参数名一般约定为“cls”,通过它来传递类的属性和方法(不能传实例的属性和方法);
- 调用:实例对象和类对象都可以调用。
- 作用:无需实例化直接被类调用
- 类方法使用场景: 当我们还未创建实例,但是需要调用类中的方法
class Dog(object):
name = '类变量' #在这里如果不定义类变量仅定义实例变量依然报错
def __init__(self,name):
self.name = '实例变量'
self.name = name
@classmethod
def eat(self,food):
print("%s is eating %s"%(self.name,food))
Dog.eat('baozi') #方法1:使用类直接调用
d = Dog("ChenRonghua")
d.eat("包子") #方法2:使用实例d调用
1.3 实例方法:
- 定义:第一个参数必须是实例对象,该参数名一般约定为“self”,通过它来传递实例的属性和方法(也可以传类的属性和方法);
- 调用:只能由实例对象调用。
class Dog(object):
def __init__(self, name):
self.name = name
@property
def eat(self):
print(" %s is eating" % self.name)
d = Dog("ChenRonghua")
d.eat()
# 调用会出以下错误, 说NoneType is not callable, 因为eat此时已经变成一个静态属性了,
# 不是方法了, 想调用已经不需要加()号了,直接d.eat就可以了
1.4 魔法方法:
我们在调用python类中的某个方法时,通常会看到某些特殊的方法,它们总被双下划线所包围,像这种格式:“方法名”,这些方法很强大,充满魔力,可以让你实现很多功能。,如果你的对象实现(重载)了这些方法中的某一个,那么这个方法就会在特殊的情况下被 Python 所调用,你可以定义自己想要的行为,而这一切都是自动发生的。因此了解这类方法的作用及用户很有必要,以下对基本魔法方法做出总结,请看:魔法方法表格
type生成类调用顺序:
new : 先于__init__方法,每生成一个实例执行一次,new 类方法创建实例对象
init : __init__方法每生成一个实例就会执行一次,初始化实例对象
call : 后与__init__方法,C()() 使用类再加一个括号调用, C为类名称
del : 析构方法,删除无用的内存对象(当程序结束会自动自行析构方法)
类实例化时魔法方法调用顺序
class Student(object):
def __new__(cls, *args, **kwargs):
print('__new__')
return object.__new__(cls) # 必须返回父类的__new__方法,否则不不执行__init__方法,无法创建实例
def __init__(self,name):
print('__init__')
self.name = name
def __str__(self): # 作用:打印实例时显示指定字符串,而不是内存地址
print('__str__')
return self.name
def __call__(self, *args, **kwargs): # 当执行C()(*args) 或者 s1(*args) 就会执行__call__
print('__call__',*args)
def __del__(self): # 作用:清除无用的实例对内存的暂用
print('__del__')
#1、实例化时机会执行__new__、__init__
s1 = Student('tom')
#2、执行 实例() 就会执行__call__ 方法,并将参数传递给__call__函数
s1('call01')
#3、当打印实例时就会执行 __str__ 方法下返回的字符串(默认返回的实例地址)
print(s1)
#4、析构方法:当删除实例时就会调用 __del__ 方法
del s1
# 析构方法作用:在程序结束后会自动执行析构方法删除所有实例
# 但是在程序运行时有很多实例是无用的,但是python内存回收机制却不会自动删除他们,这样就浪费内存
# 我们可以执行 del s1 ,那么在程序运行时,python内存回收机制会检测到这些实例时无用的,才会删除
# 其实我们执行del s1,并没有回收内存,只不过是摘除门牌号,python内存回收机制发现没有门牌号后会自动回收内存
new & __init__详解:
- new 至少要有一个参数cls,代表要实例化的类,此参数在实例化时由Python解释器自动 提供
- new 必须要有返回值,返回实例化出来的实例,这点在自己实现 new 时要特别注 意,可以return父类 new 出来的实例,或者直接是object的 new 出来的实例
- init 有一个参数self,就是这个 new 返回的实例, init 在 \new 的基础上 可以完成一些其它初始化的动作, init 不需要返回值 我们可以将类比作制造商, new 方法就是前期的原材料购买环节, init 方法就是在 有原材料的基础上,加工,初始化商品环节。
1.5 单例模式:
__new__方法书写:
class A(object):
def __init__(self):
print(self)
print("这是 init 方法")
def __new__(cls):
print(id(cls))
print("这是 __new__ 方法")
ret = object.__new__(cls)
print(res)
return ret
print(id(A))
-->: 12345678987654321
a = A()
-->: 12345678987654321
这是 new 方法
-->: <__main__.A object at 0x105b96ac8>
-->: <__main__.A object at 0x105b96ac8>
线程安全的单例:
import threading
"""
线程安全的单利模式
紧跟with后面的语句被求值后,返回对象的 __enter__() 方法被调用,这个方法的返回值将被赋值给as后面的变量。
当with后面的代码块全部被执行完之后,将调用前面返回对象的 __exit__()方法
"""
def synchronized(func):
func.__lock__ = threading.Lock()
def lock_func(*args, **kwargs):
with func.__lock__:
return func(*args, **kwargs)
return lock_func
class Singleton(object):
instance = None
@synchronized
def __new__(cls):
# 关键在于这,每一次实例化的时候,我们都只会返回这同一个instance对象
if not cls.instance:
cls.instance = super(Singleton, cls).__new__(cls)
return cls.instance
先看类,可以看出这里我们先定义了一个类属性instance,接着我们重写了父类的__new__方法,这个方法就是我们在实例化一个对象时最先调用的一个方法。和其他静态语言不一样,其他静态语言,直接调用了构造方法,一般情况下初始化的程序也写在构造方法之中。而python实例化一个对象和初始化是分开的。__new__是类方法,__init__是实例方法,也就是说,__init__是在对象已经创建完成之后,才执行。
在python3中,调用父类的方法是用super()来调用。所以我们这里的思路就是,还是用父类的方法去创造,但是我们要加一个判断,就是说,当这个对象也就是类属性并不为空的时候,我们就不在实例化,而是返回一个已经实例化的类属性。
线程不安全的单例
class Singleton(object):
__instance = None
def __new__(cls, name, age):
# 如果类属性__instance的值为None,那么就创建一个对象
if not cls.__instance:
cls.__instance = object.__new__(cls)
# 如果已经有实例存在,直接返回
return cls.__instance
a = Singleton("Zhangsan", 18)
b = Singleton("lisi", 20)
print(id(a))
print(id(b))
a.age = 30 # 给a指向的对象添加一个属性
print(b.age) # 获取b指向的对象的age属性
del
Python 通过调用 init() 方法构造当前类的实例化对象,而 del() 方法,功能正好和 init() 相反,其用来销毁实例化对象。
事实上在编写程序时,如果之前创建的类实例化对象后续不再使用,最好在适当位置手动将其销毁,释放其占用的内存空间(整个过程称为垃圾回收(简称GC))。
大多数情况下,Python 开发者不需要手动进行垃圾回收,因为 Python 有自动的垃圾回收机制,能自动将不需要使用的实例对象进行销毁。
无论是手动销毁,还是 Python 自动帮我们销毁,都会调用 del() 方法。
3.2 特性
面向对象三大特性: 封装,继承,多态
3.2.1 封装:
- 在类中对数据的赋值、内部调用对外部用户是透明的
- 这使类变成了一个胶囊或容器,里面包含着类的数据和方法
- 作用:
- 防止数据被随意修改
- 使外部程序不需要关注对象内部的构造,只需要通过对外提供的接口进行直接访问
继承的种类
- 单继承:一个类继承单个基类
- 多继承:一个类继承多个基类
- 多级继承:一个类继承自单个基类,后者继承自另一个基类
- 分层继承:多个类继承自单个基类
- 混合继承:两种或多种类型继承的混合
封装的好处:
- 将变化隔离
- 便于使用
- 提高复用性
- 提高安全性
封装:将数据进行封装到对象中,以供其他函数进行调用
3.2.2 Inheritance 继承(代码重用:
- 一个类可以派生出子类,在这个父类里定义的属性、方法自动被子类继承
- 比如CS中的警察和恐怖分子,可以将两个角色的相同点写到一个父类中,然后同时去继承它
- 使用经典类: Person.init(self, name, age) 并重写写父类Person的构造方法,实现,先覆盖,再继承,再重构
继承的优点:
- 节省代码,减少代码的重复性
- 增强耦合性(也就是增强代码可读性)
- 使代码更加规范化
- 子类可以调用父类的所有属性
class D:
def talk(self):
print('D')
class B(D):
pass
# def talk(self):
# print('B')
class C(D):
pass
def talk(self):
print('C')
class A(B,C):
pass
# def talk(self):
# print('A')
a = A()
a.talk()
# 黑人,白人都继承父类Person就可以都有父类的属性和方法了
class Person(object):
def __init__(self,name,age): #执行Person.__init__(self,name,age)时就会将传入的参数执行一遍
self.name = name #所以在BlackPerson中不仅有name,age而且还有sex
self.age = age
self.sex = "normal"
def talk(self):
print("person is talking....")
class WhitePerson(Person):
pass
class BlackPerson(Person):
def __init__(self,name,age,strength): #先覆盖,再继承,再重构
#先覆盖父类的__init__方法,再继承父类__init__,再加自己的参数
Person.__init__(self,name,age) #先继承父类Person,这里self就是BlackPerson本身
#先将name,age传给子类BlackPerson,然后调用Person.__init__构造方法将参数出入父类()
self.strength = strength #然后再重构自己的方法,即写自己的参数
print(self.name,self.age,self.sex)
print(self.strength)
def talk(self):
print("black balabla")
def walk(self):
print("is walking....")
b = BlackPerson("wei er smith",22,"Strong")
b.talk()
b.walk()
# 运行结果:
# wei er smith 22 normal
# Strong
# black balabla
# is walking....
# person is talking....
新式类经典类区别:
Python 2.x中默认都是经典类,只有显式继承了object才是新式类
Python 3.x中默认都是新式类,不必显式的继承object
当类是经典类时,多继承情况下,会按照深度优先方式查找
当类是新式类时,多继承情况下,会按照广度优先方式查找
3.2.3 Polymorphism 多态(接口重用)
- 多态是面向对象的重要特性,简单点说:“一个接口,多种实现”
- 指一个基类中派生出了不同的子类,且每个子类在继承同样的方法名的同时又对父类的方法做了不同的实现
- 这就是同一种事物表现出的多种形态
- 比如黄种人继承了人talk这个功能,但是他说的是中文,而美国人的talk是英文,但是他们是同样的talk
作用:简单的讲就是允许父类调用子类的方法
很多人喜欢将多态与多态性二者混为一谈,然后百思不得其解,其实只要分开看,就会很明朗。
- 多态指的是一类事物有多种形态,(一个抽象类有多个子类,因而多态的概念依赖于继承)
- 多态性是指具有不同功能的函数可以使用相同的函数名,这样就可以用一个函数名调用不同内容的函数。在面向对象方法中一般是这样表述多态性:向不同的对象发送同一条消息,不同的对象在接收时会产生不同的行为(即方法)。也就是说,每个对象可以用自己的方式去响应共同的消息。所谓消息,就是调用函数,不同的行为就是指不同的实现,即执行不同的函数。
# 多态举例
class Animal:
def __init__(self, name): # Constructor of the class
self.name = name
def talk(self): # Abstract method, defined by convention only
raise NotImplementedError("Subclass must implement abstract method")
class Cat(Animal):
def talk(self):
return 'Meow!'
class Dog(Animal):
def talk(self):
return 'Woof! Woof!'
animals = [Cat('Missy'),
Dog('Lassie')]
for animal in animals:
print(animal.name + ': ' + animal.talk())
# 运行结果:
# Missy: Meow!
# Lassie: Woof! Woof!
Python中多态的特点
- 只关心对象的实例方法是否同名,不关心对象所属的类型;
- 对象所属的类之间,继承关系可有可无;
- 多态的好处可以增加代码的外部调用灵活度,让代码更加通用,兼容性比较强;
- 多态是调用方法的技巧,不会影响到类的内部设计。
3.3 属性
- 类的公有属性
public_attrs:能在类的外部被使用或直接访问。在类内部的方法中使用时 public_attrs_attrs,在类的外部class_name.public_attrs。 - 类的私有属性
__private_attrs:两个下划线开头,声明该属性为私有,不能在类的外部被使用或直接访问。在类内部的方法中使用时 self.__private_attrs。 - 类的(公有)方法
在类的内部,使用 def 关键字来定义一个方法,与一般函数定义不同,类方法必须包含参数 self,且为第一个参数,self 代表的是类的实例。
self 的名字并不是规定死的,也可以使用 this,但是最好还是按照约定是用 self。 - 类的私有方法
__private_method:两个下划线开头,声明该方法为私有方法,只能在类的内部调用 ,不能在类的外部调用。self.__private_methods。
3.4 反射: hasattr、getattr、setattr 和 delattr
在做程序开发中,我们常常会遇到这样的需求:需要执行对象里的某个方法,或需要调用对象中的某个变量,但是由于种种原因我们无法确定这个方法或变量是否存在,这是我们需要用一个特殊的方法或机制要访问和操作这个未知的方法或变量,这中机制就称之为反射。
反射就是通过字符串的形式,导入模块;通过字符串的形式,去模块寻找指定函数,并执行。利用字符串的形式去对象(模块)中操作(查找/获取/删除/添加)成员,一种基于字符串的事件驱动!
–反射详解–
四大属性:
-
hasattr(ogj,name_str) 判断一个对象里是否有对应的字符串方法
class Dog(object): def eat(self,food): print("eat method!!!") d = Dog() #hasattr判断对象d是否有eat方法,有返回True,没有返回False print(hasattr(d,'eat')) #True print(hasattr(d,'cat')) #False 12345678
-
getattr(obj,name_str) 根据字符串去获取obj对象里的对应的方法的内存地址
class Dog(object): def eat(self): print("eat method!!!") d = Dog() if hasattr(d,'eat'): # hasattr判断实例是否有eat方法 func = getattr(d, 'eat') # getattr获取实例d的eat方法内存地址 func() # 执行实例d的eat方法 #运行结果: eat method!!! 123456789
-
使用stattr给类实例对象动态添加一个新的方法
def abc(self): print("%s正在交谈"%self.name) class Person(object): def __init__(self,name): self.name = name p = Person("汇森") setattr(p,"talk",abc) # 将abc函数添加到对象中p中,并命名为talk p.talk(p) # 调用talk方法,因为这是额外添加的方法,需手动传入对象 # 打印结果 汇森正在交谈 setattr(p,"age",30) # 添加一个变量age,复制为30 print(p.age) # 打印结果:30 12345678910111213141516
-
delattr删除对象中的变量。注意:不能用于删除方法
class Person(object): def __init__(self,name): self.name = name def talk(self): print("%s正在交谈"%self.name) p = Person("汇森") delattr(p,"name") # 删除name变量 print(p.name) # 此时将报错 12345678910
Python基础
1. 深拷贝浅拷贝
1.1 预备知识一——python的变量及其存储
- python的一切变量都是对象,变量的存储,采用了引用语义的方式,存储的只是一个变量的值所在的内存地址,而不是这个变量的只本身
- 不管多么复杂的数据结构,浅拷贝都只会copy一层。
理解:两个人公用一张桌子,只要桌子不变,桌子上的菜发生了变化两个人是共同感受的。
1.2 浅copy与deepcopy
- 浅copy: 不管多么复杂的数据结构,浅拷贝都只会copy一层
- deepcopy : 深拷贝会完全复制原变量相关的所有数据,在内存中生成一套完全一样的内容,我们对这两个变量中任意一个修改都不会影响其他变量
import copy
sourceList = [1,2,3,[4,5,6]]
copyList = copy.copy(sourceList)
deepcopyList = copy.deepcopy(sourceList)
sourceList[3][0]=100
print(sourceList) # [1, 2, 3, [100, 5, 6]]
print(copyList) # [1, 2, 3, [100, 5, 6]]
print(deepcopyList) # [1, 2, 3, [4, 5, 6]]
2. python垃圾回收机制
2.1 引用计数:
- 当一个对象的引用被创建或者复制时,对象的引用计数加1;当一个对象的引用被销毁时,对象的引用计数减1.
- 当对象的引用计数减少为0时,就意味着对象已经再没有被使用了,可以将其内存释放掉。
2.2 标记-清除:
- 它分为两个阶段:第一阶段是标记阶段,GC会把所有的活动对象打上标记,第二阶段是把那些没有标记的对象非活动对象进行回收。
- 对象之间通过引用(指针)连在一起,构成一个有向图
- 从根对象(root object)出发,沿着有向边遍历对象,可达的(reachable)对象标记为活动对象,不可达的对象就是要被清除的非活动对象,根对象就是全局变量、调用栈、寄存器。
注:像是PyIntObject、PyStringObject这些不可变对象是不可能产生循环引用的,因为它们内部不可能持有其它对象的引用。
- 在上图中,可以从程序变量直接访问块1,并且可以间接访问块2和3,程序无法访问块4和5
- 第一步将标记块1,并记住块2和3以供稍后处理。
- 第二步将标记块2,第三步将标记块3,但不记得块2,因为它已被标记。
- 扫描阶段将忽略块1,2和3,因为它们已被标记,但会回收块4和5。
2.3 分代回收:
- 分代回收是建立在标记清除技术基础之上的,是一种以空间换时间的操作方式。
- Python将内存分为了3“代”,分别为年轻代(第0代)、中年代(第1代)、老年代(第2代)
- 他们对应的是3个链表,它们的垃圾收集频率与对象的存活时间的增大而减小。
- 新创建的对象都会分配在年轻代,年轻代链表的总数达到上限时,Python垃圾收集机制就会被触发
- 把那些可以被回收的对象回收掉,而那些不会回收的对象就会被移到中年代去,依此类推
- 老年代中的对象是存活时间最久的对象,甚至是存活于整个系统的生命周期内。
3 上下文管理
3.1 什么是with语句
- with是一种上下文管理协议,目的在于从流程图中把 try,except 和finally 关键字和资源分配释放相关代码统统去掉,简化try….except….finlally的处理流程。
- 所以使用with处理的对象必须有enter()和exit()这两个方法
1. with通过enter方法初始化(enter方法在语句体执行之前进入运行)
2. 然后在exit中做善后以及处理异常(exit()方法在语句体执行完毕退出后运行)
3.2 with语句使用场景
- with 语句适用于对资源进行访问的场合,确保不管使用过程中是否发生异常都会执行必要的“清理”操作,释放资源
- 比如文件使用后自动关闭、线程中锁的自动获取和释放等。
3.3 with处理文件操作的实例
with open('/etc/passwd') as f:
for line in f:
print(line)
# 这段代码的作用:打开一个文件,如果一切正常,把文件对象赋值给f,然后用迭代器遍历文件中每一行,当完成时,关闭文件;
# 而无论在这段代码的任何地方,如果发生异常,此时文件仍会被关闭。
4 高阶函数
4.1 lambda基本使用
- lambda只是一个表达式,函数体比def简单很多。
- lambda的主体是一个表达式,而不是一个代码块。仅仅能在lambda表达式中封装有限的逻辑进去。
- lambda表达式是起到一个函数速写的作用。允许在代码内嵌入一个函数的定义。
- 格式:lambda的一般形式是关键字lambda后面跟一个或多个参数,紧跟一个冒号,之后是一个表达式。
f = lambda x,y,z:x+y+z
print(f(1,2,3)) # 6
my_lambda = lambda arg : arg + 1
print(my_lambda(10)) # 11
12345
4.2 三元运算:
- 三元运算格式: result=值1 if x<y else 值2 if条件成立result=1,否则result=2
- 作用:三元运算,又称三目运算,主要作用是减少代码量,是对简单的条件语句的缩写
name = 'Tom' if 1 == 1 else 'fly'
print(name)
# 运行结果: Tom
f = lambda x:x if x % 2 != 0 else x + 100
print(f(10)) # 110
map()函数用法:
map(function, iterable, …)
功能:
- 将第一个参数 function 依次作用在参数可迭代对象中的每一个元素上,返回包含每次 function 函数返回值的新迭代器
- map() 会根据提供的函数对指定序列做映射。(映射及对应)
- 第一个参数 function 以参数序列中的每一个元素调用 function 函数,返回包含每次 function 函数返回值的新列表。
参数:
function – 函数,有两个参数
iterable – 一个或多个可迭代对象(如:序列)
返回值:
Python 3.x 返回迭代器
# demo
def func(x):
return x*x
ret = map(func, [1, 2, 3, 4, 5, 6, 7, 8, 9])
print(list(ret))
# 运行结果:
[1, 4, 9, 16, 25, 36, 49, 64, 81]
reduce()函数语法
reduce(function, iterable[, initializer])
功能:
- 函数将一个数据集合(链表,元组等)中的所有数据进行下列操作:用传给 reduce 中的函数 function(有两个参数)先对集合中的第 1、2 个元素进行操作,得到的结果再与第三个数据用 function 函数运算,最后得到一个结果。
其效果类似:reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4) - reduce() 函数会对参数序列中元素进行累积。
- 函数将一个数据集合(链表,元组等)中的所有数据进行下列操作:用传给 reduce 中的函数 function(有两个参数)先对集合中的第 1、2 个元素进行操作,得到的结果再与第三个数据用 function 函数运算,最后得到一个结果。
参数:
function – 函数,有两个参数
iterable – 可迭代对象
initializer – 可选,初始参数
返回值:
返回函数计算结果。
# demo:
from functools import reduce
def add(x, y):
return x + y
r = reduce(add, [1, 3, 5, 7, 9])
print(r)
# 1. 运行结果:
25
filter()函数:
filter(function, iterable)
功能
- 该接收两个参数,第一个为函数,第二个为序列,序列的每个元素作为参数传递给函数进行判断,然后返回 True 或 False,最后将返回 True 的元素放到新迭代器对象中
- filter() 函数用于过滤序列,过滤掉不符合条件的元素,返回由符合条件元素组成的新列表。
参数:
function – 判断函数
iterable – 可迭代对象(如:序列)
返回值:
返回一个迭代器对象
# demo:
def is_odd(n):
return n % 2 == 1
tmplist = filter(is_odd, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
newlist = list(tmplist)
print(newlist)
# 运行结果:
[1, 3, 5, 7, 9]
sorted()函数
sorted(iterable, key=abs, reverse=False)
功能:
- 对所有可迭代的对象进行排序操作
参数:
iterable – 可迭代对象。
key – key指定的函数将作用于可迭代对象上的每一个元素,并根据key函数返回的结果进行排序
reverse – 排序规则,reverse = True 降序 , reverse = False 升序(默认)
返回值:
返回重新排序的列表
# demo:
print(sorted([36, 5, -12, 9, -21]))
运行结果:[-21, -12, 5, 9, 36]
print(sorted([36, 5, -12, 9, -21], key=abs))
#abs 匿名函数
运行结果:[5, 9, -12, -21, 36]
返回函数:
高阶函数除了可以接受函数作为参数外,还可以把函数作为结果值返回。
def lazy_sum(*args):
def sum():
ax = 0
for n in args:
ax = ax + n
return ax
return sum #将定义的函数sum()作为结果值返回
f = lazy_sum(1, 3, 5, 7, 9)
f()
4.3 filter()函数可以对序列做过滤处理
利用 filter、lambda表达式 获取l1中元素小于33的所有元素 l1 = [11, 22, 33, 44, 55]
l1= [11,22,33,44,55]
a = filter(lambda x: x<33, l1)
print(list(a))
4.4 Map是对序列根据设定条件进行操作后返回他设置的是操作方法
利用map,lambda表达式将所有偶数元素加100
l1= [11,22,33,44,55]
ret = map(lambda x:x if x % 2 != 0 else x + 100,l1)
print(list(ret))
# 运行结果: [11, 122, 33, 144, 55]
4.5 reduce函数
使用reduce进行求和运算
- reduce()函数即为化简函数,它的执行过程为:每一次迭代,都将上一次的迭代结果与下一个元素一同传入二元func函数中去执行。
- 在reduce()函数中,init是可选的,如果指定,则作为第一次迭代的第一个元素使用,如果没有指定,就取seq中的第一个元素。
from functools import reduce
def f(x, y):
return x + y
print(reduce(f, [1, 3, 5, 7, 9])) # 25
# 1、先计算头两个元素:f(1, 3),结果为4;
# 2、再把结果和第3个元素计算:f(4, 5),结果为9;
# 3、再把结果和第4个元素计算:f(9, 7),结果为16;
# 4、再把结果和第5个元素计算:f(16, 9),结果为25;
# 5、由于没有更多的元素了,计算结束,返回结果25。
print( reduce(lambda x, y: x + y, [1, 3, 5, 7, 9]) ) # 25
4.6 sorted函数
sorted对字典排序
d = {
'k1':1, 'k3': 3, 'k2':2}
# d.items() = [('k1', 1), ('k3', 3), ('k2', 2)]
a = sorted(d.items(), key=lambda x: x[1])
print(a) # [('k1', 1), ('k2', 2), ('k3', 3)]
subprocess模块
subprocess是Python 2.4中新增的一个模块,它允许你生成新的进程,连接到它们的 input/output/error 管道,并获取它们的返回(状态)码。这个模块的目的在于替换几个旧的模块和方法,如:
- os.system
- os.spawn*
subprocess模块中的常用函数
- subprocess.run()
Python 3.5中新增的函数。执行指定的命令,等待命令执行完成后返回一个包含执行结果的CompletedProcess类的实例。 - subprocess.call()
执行指定的命令,返回命令执行状态,其功能类似于os.system(cmd)。 - subprocess.check_call()
Python 2.5中新增的函数。 执行指定的命令,如果执行成功则返回状态码,否则抛出异常。其功能等价于subprocess.run(…, check=True)。 - subprocess.check_output()
Python 2.7中新增的的函数。执行指定的命令,如果执行状态码为0则返回命令执行结果,否则抛出异常。 - subprocess.getoutput(cmd)
接收字符串格式的命令,执行命令并返回执行结果,其功能类似于
os.popen(cmd).read()和commands.getoutput(cmd)。 - subprocess.getstatusoutput(cmd)
执行cmd命令,返回一个元组(命令执行状态, 命令执行结果输出),其功能类似于commands.getstatusoutput()。
paramiko模块
paramiko是一个用于做远程控制的模块,使用该模块可以对远程服务器进行命令或文件操作,paramiko是用python语言写的一个模块,遵循SSH2协议,支持以加密和认证的方式,进行远程服务器的连接。
由于使用的是python这样的能够跨平台运行的语言,所以所有python支持的平台,如Linux, Solaris, BSD, MacOS X, Windows等,paramiko都可以支持,因此,如果需要使用SSH从一个平台连接到另外一个平台,进行一系列的操作时,paramiko是最佳工具之一。
5.python2和python3的区别:
- python2 解释器默认编码:ascii python3 解释器默认编码:utf-8
- range在Python2中返回列表,而在Python3中返回range可迭代对象。
- 在Python2中有两个不等运算符!=和<>,在Python3中去掉了<>,只有!=符号表示不等
- 在Python2中long是比int取值范围更大的整数,Python3中取消了long类型,int的取值范围扩大到之前的long类型范围。
- python2 的代码混乱,重复较多,冗余。python3源码规范、清晰、简单优美。
- python3x:unicode 默认是4个字节表示一个字符、python2x :unicode 默认2个字节表示一个字符