Home > Backend Development > Python Tutorial > 简单介绍Python中的try和finally和with方法

简单介绍Python中的try和finally和with方法

WBOY
Release: 2016-06-06 11:16:01
Original
2094 people have browsed it

用 Python 做一件很平常的事情: 打开文件, 逐行读入, 最后关掉文件; 进一步的需求是, 这也许是程序中一个可选的功能, 如果有任何问题, 比如文件无法打开, 或是读取出错, 那么在函数内需要捕获所有异常, 输出一行警告并退出. 代码可能一开始看起来是这样的
 

def read_file(): 
  try: 
    f = open('yui', 'r') 
    print ''.join(f.readlines()) 
  except: 
    print 'error occurs while reading file'
  finally: 
    f.close()
Copy after login
Copy after login

不过这显然无法运作, 因为 f 是在 try 块中定义的, 而在 finally 中无法引用.

如果将 f 提取到 try 块外部, 如

def read_file(): 
   f = open('azusa', 'r') 
  try: 
    print ''.join(f.readlines()) 
  except: 
    print 'error occurs while reading file'
  finally: 
    f.close()
Copy after login
Copy after login

那么, 问题在于当打开文件失败, 抛出异常将不会被捕获.

挫一点的方法自然是, 再套一层 try 吧

def read_file(): 
   try: 
    f = open('sawako', 'r') 
    try: 
      print ''.join(f.readlines()) 
    except: 
      print 'error occurs while reading file'
    finally: 
      f.close() 
   except: 
     print 'error occurs while reading file'
Copy after login
Copy after login

当然这不仅仅是多一层缩进挫了, 连警告输出都白白多一次呢.

正规一点的方式是, 使用 Python 引入的 with 结构来解决, 如

def readFile(): 
  try: 
     with open('mio', 'r') as f: 
      print ''.join(f.readlines()) 
  except: 
    print 'error occurs while reading file'
Copy after login
Copy after login

当文件打开失败时, 异常自然会被 except 到; 否则, 在 with 块结束之后, 打开的文件将自动关闭.

除了打开文件, 还有其它这样可以用于 with 的东西么? 或者说, 怎么自定义一个什么东西, 让它能用于 with 呢?
直接回答后一个问题吧, 秘密在于 Python 虚拟机在 with 块退出时会去寻找对象的 __exit__ 方法并调用它, 把释放资源的动作放在这个 __exit__ 函数中就可以了; 另外, 对象还需要一个 __enter__ 函数, 当进入 with 块时, 这个函数被调用, 而它的返回值将作为 as 后引用的值. 一个简单的例子是

class Test: 
  def __init__(self): 
    print 'init'
 
  def __enter__(self): 
    print 'enter'
    return self
 
  def __exit__(self, except_type, except_obj, tb): 
    print except_type 
    print except_obj 
    import traceback 
    print ''.join(traceback.format_tb(tb)) 
    print 'exit'
    return True
 
with Test() as t: 
  raise ValueError('kon!')
Copy after login
Copy after login

执行这一段代码, 输出将会是

init 
enter 
<type 'exceptions.ValueError'> 
kon! 
 File "test.py", line 17, in <module> 
  raise ValueError('kon!') 
 
exit
Copy after login
Copy after login

__exit__ 函数接受三个参数, 分别是异常对象类型, 异常对象和调用栈. 如果 with 块正常退出, 那么这些参数将都是 None . 返回 True 表示发生的异常已被处理, 不再继续向外抛出.

简单的介绍到此为止, 详细的情况可以参考 PEP 343 (这数字真不错, 7 3 ).

下面介绍下 with 语句的实例用法 & 高级用法:

Python高端、大气、上档次的with语句

在说with语句之前,先看看一段简单的代码吧

lock = threading.Lock()
...
lock.acquire()
elem = heapq.heappop(heap)
lock.release()
Copy after login
Copy after login

很简单直观,多个线程共用一个优先级队列的时候,首先先用互斥锁lock.acquire()把优先级队列锁上,然后取元素,再然后lock.release()释放这个锁。

虽然看似非常符合逻辑的一个过程,但是里面隐藏着一个巨大的bug:当heap里面没有元素的时候,会抛出一个IndexError异常,再然后堆栈回滚,再然后lock.release()根本不会执行,这个锁就永远得不到释放,因此就发生了喜闻乐见的死锁问题。这个也是很多大神们讨厌异常的原因。经典Java风格的解决方案就是

lock = threading.Lock()
...
lock.acquire()
try:
  elem = heapq.heappop(heap)
finally:
  lock.release()
Copy after login
Copy after login

这个虽然可以,但是怎么看怎么dirty,和Python优雅、简单的风格出入很大。其实,自从Python2.5开始引入了with语句,一切就变得非常简单:

lock = threading.Lock()
...
with lock:
  elem = heapq.heappop(heap)
Copy after login
Copy after login

在此无论以何种方式离开with语句的代码块,锁都会被释放。
with语句的设计目的就是为了使得之前需要通过try...finally解决的清理资源问题变得简单、清晰,它的的用法是

with expression [as variable]:
  with-block
Copy after login
Copy after login

其中expression返回一个叫做「context manager」的对象,然后这个对象被赋给variable(如果有的话)。「context manager」对象有两个方法,分别是__enter__()和__exit__(),很明显一个在进入with-block时调用,一个离开with-block的时候调用。

这样的对象不需要自己去实现,在Python标准库里面很多API都是已经实现了这两个方法,最常见的一个例子就是读写文件的open语句。

with open('1.txt', encoding = 'utf-8') as fp:
  lines = fp.readlines()
Copy after login
Copy after login

无论是正常离开还是因为异常原因离开with语句块,打开的文件资源总是会释放。
接下去讨论一下with语句配合contextlib库的一些比较实用的方法,比如需要同时打开两个文件,一个读一个写,这个时候就可以这样写:

from contextlib import nested
...
with nested(open('in.txt'), open('out.txt', 'w')) as (fp_in, fp_out):
  ...
Copy after login
Copy after login

这样就可以省掉两个with的语句的嵌套了,另外如果遇到一些还没有支持「context manager」的API呢?比如urllib.request.urlopen(),这个返回的对象因为不是「context manager」,结束的时候还需要自己去调用close方法。
类似这种API,contextlib提供了一个叫做closing方法,它会在离开with语句的时候,自动调用对象的close方法,因此urlopen也可以这样写:

from contextlib import closing
...
with closing(urllib.request.urlopen('http://www.yahoo.com')) as f:
  for line in f:
    sys.stdout.write(line)
Copy after login
Copy after login

用 Python 做一件很平常的事情: 打开文件, 逐行读入, 最后关掉文件; 进一步的需求是, 这也许是程序中一个可选的功能, 如果有任何问题, 比如文件无法打开, 或是读取出错, 那么在函数内需要捕获所有异常, 输出一行警告并退出. 代码可能一开始看起来是这样的

def read_file(): 
  try: 
    f = open('yui', 'r') 
    print ''.join(f.readlines()) 
  except: 
    print 'error occurs while reading file'
  finally: 
    f.close()
Copy after login
Copy after login

不过这显然无法运作, 因为 f 是在 try 块中定义的, 而在 finally 中无法引用.

如果将 f 提取到 try 块外部, 如

def read_file(): 
   f = open('azusa', 'r') 
  try: 
    print ''.join(f.readlines()) 
  except: 
    print 'error occurs while reading file'
  finally: 
    f.close()
Copy after login
Copy after login

那么, 问题在于当打开文件失败, 抛出异常将不会被捕获.

挫一点的方法自然是, 再套一层 try 吧

def read_file(): 
   try: 
    f = open('sawako', 'r') 
    try: 
      print ''.join(f.readlines()) 
    except: 
      print 'error occurs while reading file'
    finally: 
      f.close() 
   except: 
     print 'error occurs while reading file'
Copy after login
Copy after login

当然这不仅仅是多一层缩进挫了, 连警告输出都白白多一次呢.

正规一点的方式是, 使用 Python 引入的 with 结构来解决, 如

def readFile(): 
  try: 
     with open('mio', 'r') as f: 
      print ''.join(f.readlines()) 
  except: 
    print 'error occurs while reading file'
Copy after login
Copy after login

当文件打开失败时, 异常自然会被 except 到; 否则, 在 with 块结束之后, 打开的文件将自动关闭.

除了打开文件, 还有其它这样可以用于 with 的东西么? 或者说, 怎么自定义一个什么东西, 让它能用于 with 呢?
直接回答后一个问题吧, 秘密在于 Python 虚拟机在 with 块退出时会去寻找对象的 __exit__ 方法并调用它, 把释放资源的动作放在这个 __exit__ 函数中就可以了; 另外, 对象还需要一个 __enter__ 函数, 当进入 with 块时, 这个函数被调用, 而它的返回值将作为 as 后引用的值. 一个简单的例子是

class Test: 
  def __init__(self): 
    print 'init'
 
  def __enter__(self): 
    print 'enter'
    return self
 
  def __exit__(self, except_type, except_obj, tb): 
    print except_type 
    print except_obj 
    import traceback 
    print ''.join(traceback.format_tb(tb)) 
    print 'exit'
    return True
 
with Test() as t: 
  raise ValueError('kon!')
Copy after login
Copy after login

执行这一段代码, 输出将会是

init 
enter 
<type 'exceptions.ValueError'> 
kon! 
 File "test.py", line 17, in <module> 
  raise ValueError('kon!') 
 
exit
Copy after login
Copy after login

__exit__ 函数接受三个参数, 分别是异常对象类型, 异常对象和调用栈. 如果 with 块正常退出, 那么这些参数将都是 None . 返回 True 表示发生的异常已被处理, 不再继续向外抛出.

简单的介绍到此为止, 详细的情况可以参考 PEP 343 (这数字真不错, 7 3 ).

下面介绍下 with 语句的实例用法 & 高级用法:

Python高端、大气、上档次的with语句

在说with语句之前,先看看一段简单的代码吧

lock = threading.Lock()
...
lock.acquire()
elem = heapq.heappop(heap)
lock.release()
Copy after login
Copy after login

很简单直观,多个线程共用一个优先级队列的时候,首先先用互斥锁lock.acquire()把优先级队列锁上,然后取元素,再然后lock.release()释放这个锁。

虽然看似非常符合逻辑的一个过程,但是里面隐藏着一个巨大的bug:当heap里面没有元素的时候,会抛出一个IndexError异常,再然后堆栈回滚,再然后lock.release()根本不会执行,这个锁就永远得不到释放,因此就发生了喜闻乐见的死锁问题。这个也是很多大神们讨厌异常的原因。经典Java风格的解决方案就是

lock = threading.Lock()
...
lock.acquire()
try:
  elem = heapq.heappop(heap)
finally:
  lock.release()
Copy after login
Copy after login

这个虽然可以,但是怎么看怎么dirty,和Python优雅、简单的风格出入很大。其实,自从Python2.5开始引入了with语句,一切就变得非常简单:

lock = threading.Lock()
...
with lock:
  elem = heapq.heappop(heap)
Copy after login
Copy after login

在此无论以何种方式离开with语句的代码块,锁都会被释放。
with语句的设计目的就是为了使得之前需要通过try...finally解决的清理资源问题变得简单、清晰,它的的用法是

with expression [as variable]:
  with-block
Copy after login
Copy after login

其中expression返回一个叫做「context manager」的对象,然后这个对象被赋给variable(如果有的话)。「context manager」对象有两个方法,分别是__enter__()和__exit__(),很明显一个在进入with-block时调用,一个离开with-block的时候调用。

这样的对象不需要自己去实现,在Python标准库里面很多API都是已经实现了这两个方法,最常见的一个例子就是读写文件的open语句。

with open('1.txt', encoding = 'utf-8') as fp:
  lines = fp.readlines()
Copy after login
Copy after login

无论是正常离开还是因为异常原因离开with语句块,打开的文件资源总是会释放。
接下去讨论一下with语句配合contextlib库的一些比较实用的方法,比如需要同时打开两个文件,一个读一个写,这个时候就可以这样写:

from contextlib import nested
...
with nested(open('in.txt'), open('out.txt', 'w')) as (fp_in, fp_out):
  ...
Copy after login
Copy after login

这样就可以省掉两个with的语句的嵌套了,另外如果遇到一些还没有支持「context manager」的API呢?比如urllib.request.urlopen(),这个返回的对象因为不是「context manager」,结束的时候还需要自己去调用close方法。
类似这种API,contextlib提供了一个叫做closing方法,它会在离开with语句的时候,自动调用对象的close方法,因此urlopen也可以这样写:

from contextlib import closing
...
with closing(urllib.request.urlopen('http://www.yahoo.com')) as f:
  for line in f:
    sys.stdout.write(line)
Copy after login
Copy after login


Related labels:
source:php.cn
Statement of this Website
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn
Popular Tutorials
More>
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template