python的上下文管理(context)(1)

合集下载
  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

python的上下⽂管理(context)(1)
什么是Python中的上下⽂管理器
怎么使⽤上下⽂管理器
如何创建⾃⼰的上下⽂管理器
关于Python上下⽂库(contextlib)
1. 上下⽂管理器是什么?
举个例⼦,你在写代码的时候经常将⼀系列操作放在⼀个语句块中:
当某条件为真 – 执⾏这个语句块
当某条件为真 – 循环执⾏这个语句块
有时候我们需要在当程序在语句块中运⾏时保持某种状态,并且在离开语句块后结束这种状态。

所以,事实上上下⽂管理器的任务是 – 代码块执⾏前准备,代码块执⾏后收拾。

上下⽂管理器是在Python2.5加⼊的功能,它能够让你的代码可读性更强并且错误更少。

接下来,让我们来看看该如何使⽤。

2. 如何使⽤上下⽂管理器?
看代码是最好的学习⽅式,来看看我们通常是如何打开⼀个⽂件并写⼊”Hello World”?
1 2 3 4 5 6filename = 'my_file.txt'
mode = 'w' # Mode that allows to write to the file writer = open(filename, mode)
writer.write('Hello ')
writer.write('World')
writer.close()
1-2⾏,我们指明⽂件名以及打开⽅式(写⼊)。

第3⾏,打开⽂件,4-5⾏写⼊“Hello world”,第6⾏关闭⽂件。

这样不就⾏了,为什么还需要上下⽂管理器?但是我们忽略了⼀个很⼩但是很重要的细节:如果我们没有机会到达第6⾏关闭⽂件,那会怎样?
举个例⼦,磁盘已满,因此我们在第4⾏尝试写⼊⽂件时就会抛出异常,⽽第6⾏则根本没有机会执⾏。

当然,我们可以使⽤try-finally语句块来进⾏包装:
1 2 3 4 5 6writer = open(filename, mode) try:
writer.write('Hello ') writer.write('World') finally:
writer.close()
finally语句块中的代码⽆论try语句块中发⽣了什么都会执⾏。

因此可以保证⽂件⼀定会关闭。

这么做有什么问题么?当然没有,但当我们进⾏⼀些⽐写⼊“Hello world”更复杂的事情时,try-finally语句就会变得丑陋⽆⽐。

例如我们要打开两个⽂件,⼀个读⼀个写,两个⽂件之间进⾏拷贝操作,那么通过with语句能够保证两者能够同时被关闭。

OK,让我们把事情分解⼀下:
⾸先,创建⼀个名为“writer”的⽂件变量。

然后,对writer执⾏⼀些操作。

最后,关闭writer。

这样是不是优雅多了?
1 2 3with open(filename, mode) as writer: writer.write('Hello ') writer.write('World')
让我们深⼊⼀点,“with”是⼀个新关键词,并且总是伴随着上下⽂管理器出现。

“open(filename, mode)”曾经在之前的代码中出现。

“as”是另⼀个关键词,它指代了从“open”函数返回的内容,并且把它赋值给了⼀个新的变量。

“writer”是⼀个新的变量名。

2-3⾏,缩进开启⼀个新的代码块。

在这个代码块中,我们能够对writer做任意操作。

这样我们就使⽤了“open”上下⽂管理器,它保证我们的代码既优雅⼜安全。

它出⾊的完成了try-finally的任务。

open函数既能够当做⼀个简单的函数使⽤,⼜能够作为上下⽂管理器。

这是因为open函数返回了⼀个⽂件类型(file type)变量,⽽这个⽂件类型实现了我们之前⽤到的write⽅法,但是想要作为上下⽂管理器还必须实现⼀些特殊的⽅法,我会在接下来的⼩节中介绍。

3. ⾃定义上下⽂管理器
让我们来写⼀个“open”上下⽂管理器。

要实现上下⽂管理器,必须实现两个⽅法 – ⼀个负责进⼊语句块的准备操作,另⼀个负责离开语句块的善后操作。

同时,我们需要两个参数:⽂件名和打开⽅式。

类包含两个特殊的⽅法,分别名为:__enter__以及__exit__(双下划线作为前缀及后缀)。

当⼀个对象被⽤作上下⽂管理器时:
__enter__ ⽅法将在进⼊代码块前被调⽤。

__exit__ ⽅法则在离开代码块之后被调⽤(即使在代码块中遇到了异常)。

下⾯是上下⽂管理器的⼀个例⼦,它分别进⼊和离开代码块时进⾏打印。

class PypixContextManagerDemo:
def __init__(self):
print('init')
def __enter__(self):
print('enter')
def __exit__(self, exc_type, exc_val, exc_tb):
print('exit')
with PypixContextManagerDemo() as f:
print('wcf')
这⾥要说明⼀⼀点,我们常常这么写:
with codecs.open(sourcefile, 'r', encoding='utf-8') as fdialogs:
可是,在codecs⾥⾯,却没有找到__enter__和__exit__。

这不奇怪,因为 with .... as fdialogs⾥⾯,是codecs类的open⽅法返回的类:
通过阅读源代码,可以看到,返回的是:
srw = StreamReaderWriter(file, info.streamreader, info.streamwriter, errors)
# Add attributes to simplify introspection
srw.encoding = encoding
return srw
⽽StreamReaderWriter是有那两个⽅法的:
def __enter__(self):
return self
def __exit__(self, type, value, tb):
self.stream.close()
下⾯继续:
注意⼀些东西:
没有传递任何参数。

在此没有使⽤“as”关键词。

稍后我们将讨论__exit__⽅法的参数设置。

我们如何给⼀个类传递参数?其实在任何类中,都可以使⽤__init__⽅法,在此我们将重写它以接收两个必要参数(filename, mode)。

当我们进⼊语句块时,将会使⽤open函数,正如第⼀个例⼦中那样。

⽽当我们离开语句块时,将关闭⼀切在__enter__函数中打开的东西。

以下是我们的代码:
class PypixOpen:
def __init__(self, filename, mode):
self.filename = filename
self.mode = mode
def __enter__(self):
self.openedFile = open(self.filename, self.mode)
return self.openedFile
def __exit__(self, *unused):
self.openedFile.close()
with PypixOpen(filename, mode) as writer:
writer.write("Hello World from our new Context Manager!")
来看看有哪些变化:
3-5⾏,通过__init__接收了两个参数。

7-9⾏,打开⽂件并返回。

12⾏,当离开语句块时关闭⽂件。

14-15⾏,模仿open使⽤我们⾃⼰的上下⽂管理器。

除此之外,还有⼀些需要强调的事情:
如何处理异常
我们完全忽视了语句块内部可能出现的问题。

如果语句块内部发⽣了异常,__exit__⽅法将被调⽤,⽽异常将会被重新抛出(re-raised)。

当处理⽂件写⼊操作时,⼤部分时间你肯定不希望隐藏这些异常,所以这是可以的。

⽽对于不希望重新抛出的异常,我们可以让__exit__⽅法简单的返回True来忽略语句块中发⽣的所有异常(⼤部分情况下这都不是明智之举)。

我们可以在异常发⽣时了解到更多详细的信息,完备的__exit__函数签名应该是这样的:
def __exit__(self, exc_type, exc_val, exc_tb):
这样__exit__函数就能够拿到关于异常的所有信息(异常类型,异常值以及异常追踪信息),这些信息将帮助异常处理操作。

在这⾥我将不会详细讨论异常处理该如何写,以下是⼀个⽰例,只负责抛出SyntaxErrors异常。

1 2 3 4 5 6 7class RaiseOnlyIfSyntaxError:
def __enter__(self):
pass
def __exit__(self, exc_type, exc_val, exc_tb): return SyntaxError != exc_type。

相关文档
最新文档