with怎么用
with语句是一种与异常处理相关的功能。适用于对资源进行访问的场合,确保不管使用过程中是否发生异常都会执行必要的“清理”操作,比如文件使用后自动关闭、线程中锁的自动获取和释放等。
先举几个例子。比如一般的文件读取,需要这样:
try:
f = open('./with.txt')
print f.readlines()
finally:
f.close()
必须保证打开的文件,不论中间发生了什么异常都要close掉,才能释放文件锁,所以才会将f.close()放在final块里。同样的,对于多线程情况下,关键代码处的互斥,我们经常使用Lock。比如:
import threading
lock = threading.Lock()
count = 0
class MyThread(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
def run(self):
global count
try:
if lock.acquire():
count += 1
print count
finally:
lock.release()
首先判断是否拿到锁,然后在结束后要释放掉。如果发生了异常,而又没有释放锁的话,其他线程都会等在这个锁上。如果使用with,上面这两个例子就会变成这样:
with open('./with.txt') as f:
print f.readlines()
class MyThread2(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
def run(self):
global count
with lock:
count += 1
print count
从上面的例子来看,最直观的感受就是with去掉了finally所需操心的东西。那么为什么open()和lock可以与with一起用呢?with主要做了两件事,第一件就是准备工作,比如打开文件,获取锁;第二件就是结束工作,文件关闭,释放锁。这些都被封装在一个叫做context manager的东西里,谁是context manager?open返回的对象就是,lock自身也是。
Context Manager
关于具体的context manager定义,可以参考https://docs.python.org/release/2.5.2/lib/typecontextmanager.html
一个context manager对象,必然是实现了__enter__()和__exit__()方法。在with进入时,会去调用enter方法,而在with代码块结束时(try finally包起来),会去调用exit方法。如果我们不用with,那么lock的代码可以这样写
class MyThread3(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
def run(self):
global count
lock.__enter__()
try:
count += 1
print count
finally:
lock.__exit__()
手工调用__enter__和__exit__和原本想做的acquire和release是同样的效果,这就是因为lock本身在实现context manager时所定义的__enter__和__exit__方法正是如此。用了with,除了能自动调用__enter__和__exit__之外,还能自动帮我们针对with下面的代码块进行try和finally,在合适的地方调用这两个上下文方法。
同理可以推断,open()所返回的对象,也是同样的实现,在自己的__exit__方法里面实现了对file的close。
自己实现context mananger
从上面的例子不难理解,实现context mananger的一种途径就是定义__enter__和__exit__方法。比如:
class WithAdoptable(object):
def __init__(self):
return
def __enter__(self):
print "Do something with calling with"
def __exit__(self, exc_type, exc_val, exc_tb):
print "Do something when with ends"
with withAdoptable:
print "Do something in the middle"
Do something with calling with o something in the middle o something when with ends
还有一种办法,就是利用contextmanager模块,contextmananger会自动帮被装饰对象加上__enter__和__exit__方法。so上有一个很棒的例子。这个例子其实更精确的说明了流程:
- __enter__的时候直接进入了working_directory方法,切换到目标目录
- yield跳回,相当于enter方法结束
- 外面执行完自己的事情以后,with退出调用__exit__,返回到上次的yield,运行finally部分
也就是说yield之前的语句相当于enter,yield之后的相当于exit。如果想返回一个值来使用with…as…的用法,那么可以直接使用yiled obj的方式返回。
from contextlib import contextmanager
import os
@contextmanager
def working_directory(path):
current_dir = os.getcwd()
os.chdir(path)
try:
yield
finally:
os.chdir(current_dir)
with working_directory("data/stuff"):
# do something within data/stuff
# here I am back again in the original working directory