深入理解python with 语句

python中with 语句作为try/finally 编码范式的一种替代, 适用于对资源进行访问的场合,确保不管使用过程中是否发生异常都会执行必要的”清理”操作,释放资源,比如文件使用后自动关闭、线程中锁的自动获取和释放等

1. 使用with打开文件

你应该见过下面这种打开文件的方式

with open('data', 'r', encoding='utf-8') as f:

data = f.readlines()

上面的写法,与下面的写法在最终效果上是一致的

f = open('data', 'r', encoding='utf-8')

try:

data = f.readlines()

except:

pass

finally:

f.close()

对比两段代码不难发现,使用with语句时,代码更加简洁,而且不用主动关闭文件,在with语句体退出时,会自动关闭文件,即便with语句体中发生了异常。

2. 上下文管理器和with 语句有关的概念

想要理解with语句,就必须先理解以下几个概念

2.1 上下文管理协议

简单来说,就是实现两个方法,__enter__() 和__exit__()

2.2 上下文管理器

实现了__enter__() 和__exit__()的对象就是上下文管理器

2.3 运行时上下文

由上下文管理器创建,在with语句体代码执行前,通过__enter__()进入,语句体代码执行结束后,通过__exit__()退出

2.4 上下文表达式

在with关键字后面的表达式,表达式返回上下文管理器对象

2.5 语句体

with语句包裹起来的代码

3. 使用with语句控制线程锁的释放

使用with不仅能够自动的关闭打开的文件对象,还可以自动的释放线程锁,这样可以避免死锁的发生,在python多线程---线程锁一文中,为避免多个线程同时对一个变量对象进行修改,在关键语句上加了线程锁

def worker():

time.sleep(1)

global a

for i in range(100000):

m_lock.acquire() # 加锁

a += 1

m_lock.release() # 释放锁

如果你忘记了写m_lock.release() 对锁进行释放,那么这将导致其他线程永远也无法获取到线程锁,这样就形成了死锁,上面的代码在acquire之后,使用release释放所,使用with语句,可以更加优雅的实现加锁和释放锁的操作。

def worker():

time.sleep(1)

global a

for i in range(100000):

with m_lock:

a += 1

4. 同时打开多个文件

许多人都不知道,with语句可以同时打开多个文件,这样做可以减少代码的缩进,让代码的编写更加容易,两个open语句之间用逗号分隔即可。

with open('a1', 'w')as f1, open('a2', 'w')as f2:

f1.write('a')

f2.write('b')

5. 自定义上下文管理器

在调试程序性能时,如果只是想知道某个函数的执行时长,可以使用一个可以统计函数运行时长的装饰器进行处理,但程序往往很复杂,一段代码里,要做很多操作,不只是调用了一个函数,也可能存在循环,因此,单纯的知道某个函数的执行时长,不能帮助我们更好的了解程序的性能。

我们需要针对某个代码段进行时间统计,知道这一段代码的执行时长对我们很有帮助。你可以使用time.time()方法在代码段开始时获取到时间,在结束时再次获取到时间,两个时间做差就可以得到这个代码段的运行时长,这种操作方式写起来很麻烦,如果有多处代码段需要统计,就得写多次,很不方便。

下面是一个可以统计代码段运行时长的上下文管理器

import time

class ProTime(object):

def __init__(self, tag=''):

self.tag = tag

def __enter__(self):

self.start_time = time.time()

def __exit__(self, exc_type, exc_val, exc_tb):

self.end_time = time.time()

time_diff = self.end_time - self.start_time

msg = "代码段{tag}运行时长{time_diff}".format(tag=self.tag, time_diff=time_diff)

print(msg)

with ProTime('first') as pt:

# 这里是你要统计运行时长的代码块

time.sleep(1)

with ProTime('second') as pt:

# 这里是你要统计运行时长的代码块

time.sleep(2)

理解这段代码的关键之处,在with语句所包裹的语句体执行之前,先要执行__enter__方法,语句体执行结束之后,不论是否有异常,都要执行__exit__,在__exit__方法里,三个参数提供了异常的全部信息,如果你想处理异常,可以在这个方法里做处理。

__init__ 方法有一个tag参数,设置这个参数的目的,是为了在输出信息里区分多个代码块,如果不想设置这个tag,可以考虑对这个上下文管理器进行修改,通过调用栈获得调用信息,准确的指出是哪个代码段的执行时长。

修改后的上下文管理器如下

import time

import sys

class ProTime(object):

def __init__(self, tag=''):

frame = sys._getframe()

tag_frame = frame.f_back

self.lineno = tag_frame.f_lineno

self.filename = tag_frame.f_code.co_filename

self.tag = tag

def __enter__(self):

self.start_time = time.time()

def __exit__(self, exc_type, exc_val, exc_tb):

self.end_time = time.time()

time_diff = self.end_time - self.start_time

if self.tag:

msg = "代码段{tag}运行时长{time_diff}".format(tag=self.tag, time_diff=time_diff)

else:

msg = "文件{filename} 第 {lineno} 行代码块执行时长{time_diff}".format(filename=self.filename, lineno=self.lineno, time_diff=time_diff)

print(msg)

with ProTime('first') as pt:

# 这里是你要统计运行时长的代码块

time.sleep(1)

with ProTime() as pt:

# 这里是你要统计运行时长的代码块

time.sleep(2)

def test():

with ProTime() as pt:

# 这里是你要统计运行时长的代码块

time.sleep(1)

test()

python语句解释_深入理解python with 语句相关推荐

  1. python iterable对象_如何理解Python中的iterable对象

    转载请注明出处:https://www.jianshu.com/u/5e6f798c903a [^*] 表示注脚,在文末可以查看对应连接,但简书不支持该语法. 首先,容器和 iterable 间没有必 ...

  2. python参数传递方法_深入理解python中函数传递参数是值传递还是引用传递

    python 的 深入理解python中函数传递参数是值传递还是引用传递 目前网络上大部分博客的结论都是这样的: Python不允许程序员选择采用传值还是传 引用.Python参数传递采用的肯定是&q ...

  3. 完全理解python迭代对象_完全理解Python迭代对象、迭代器、生成器

    1.assert:python assert断言是声明其布尔值必须为真的判定,如果发生异常就说明表达示为假.可以理解assert断言语句为raise-if-not,用来测试表示式,其返回值为假,就会触 ...

  4. 温度转换python代码解释_如何用python代码温度转换?

    如何用python代码温度转换? 用python代码温度转换的方法: 步骤一:分析问题的计算部分 步骤二:确定功能,使用IPO方法进一步分析 输入:华氏或者摄氏温度值.温度标识 处理:温度转化算法 输 ...

  5. 什么是python装饰器_深入理解 Python 装饰器

    作者简介 曾凡伟,携程信息安全部高级安全工程师,2015年加入携程,主要负责安全自动化产品的设计和研发,包括各类扫描器.漏洞管理平台.安全 SaaS 平台等. Python 是一门追求优雅编程的语言, ...

  6. python怎么装饰_如何理解python装饰器

    如何理解装饰器 python 学习遇到的第一个难点是装饰器.装饰器的作用是不大规模改动代码的情况下,增加功能. 作用:为已经存在的对象添加额外的功能 特点:不需要对对象做任何的代码上的变动. 以一个例 ...

  7. 深入理解python异步编程_深入理解Python异步编程

    1 什么是异步编程 1.1 阻塞程序未得到所需计算资源时被挂起的状态. 程序在等待某个操作完成期间,自身无法继续干别的事情,则称该程序在该操作上是阻塞的. 常见的阻塞形式有:网络I/O阻塞.磁盘I/O ...

  8. python赋值语句对错_深入理解Python中变量赋值的问题

    前言 在Python中变量名规则与其他大多数高级语言一样,都是受C语言影响的,另外变量名是大小写敏感的. Python是动态类型语言,也就是说不需要预先声明变量类型,变量的类型和值在赋值那一刻被初始化 ...

  9. python with关键字_完全理解Python关键字with与上下文管理器

    如果你有阅读源码的习惯,可能会看到一些优秀的代码经常出现带有 "with" 关键字的语句,它通常用在什么场景呢?今天就来说说 with 和 上下文管理器. 对于系统资源如文件.数据 ...

最新文章

  1. POJ-2635 The Embarrassed Cryptographer 大数取模
  2. dword类型和十进制有什么区别_微信小商店有几种类型,有什么区别?
  3. 网络部署原理加实验步骤
  4. IPv6实验1_IPv6地址配置
  5. createjs中shape的属性regX和regY
  6. 命令编写注册表文件修改注册表项
  7. mysql数据库进行更新、插入显示中文乱码问题
  8. yum 安装oraclejdk_四、CentOS 7安装Oracle JDK
  9. CVPR2018 目标检测算法总览(最新的目标检测论文)
  10. Android--记录莫名其妙的引用、依赖冲突解决办法
  11. Mac唤醒后无声的一种解决方法
  12. AD18原理图到PCB流程
  13. 搭建一个自己的文件上传服务器。
  14. 课堂笔记-爬虫beautifulsoup模块
  15. php怎样做艺术字体,用ps打造科幻艺术字体
  16. tempfile.mkstemp 详解
  17. java Eclipse 如何打开bin文件目录视图
  18. 二进制转八进制公式计算机,2进制转8进制(二进制转8进制公式)
  19. 论文解读:iDRNA-ITF:基于诱导和转移框架识别蛋白质中的DNA和RNA结合残基
  20. 连续信号、离散信号、模拟信号与数字信号区别

热门文章

  1. matlab mobilenet v2,MobileNetV2-SSDLite代码分析-6 VOC Dataset
  2. php改vue,修改.vue · 柳鑫鹏/1702phpA - Gitee.com
  3. lombok插件_lombok插件,让代码更简洁
  4. php 元素插入数组指定位置,数组任意位置插入元素,删除特定元素的实例
  5. 蚂蚁算法求解tsp问题matlab,蚁群算法解决TSP问题的MATLAB程序
  6. 误落迷宫2(BFS)
  7. 梦幻模拟战更新服务器正在维护,梦幻模拟战魔之启示录更新维护公告
  8. java 拖动图片放大_Android 图片拖拽、放大缩小的自定义控件
  9. 计算机体系结构 -- 第一章3 -- 设计的定量4个原则
  10. 『操作系统』 进程的描述与控制 Part2 进程同步