《Python编程与道:Python视频课程语言先进 《Python编程与道:Python视频课程链接:https://edu.csdn.net/course/detail/28618
上下文管理器 (Context Manager)
管理资源:广泛应用于任何编程语言,如文件操作或数据库连接。 但这些资源是有限的。 因此,这些资源应在使用后释放。 如果不释放它们,则将导致资源泄漏,并可能导致系统变慢或崩溃。 如果用户有自动设置和释放资源的机制,这将非常有帮助。在Python它可以通过使用上下文管理器来实现,这有助于正确处理资源。
最常见的执行文件操作方法是使用with
关键字如下:
# Python program showing # a use of with keyword with open("./files/test1.txt") as f: data = f.read()
以文件管理为例。 打开文件时,使用文件描述符(file descriptor),这是一种有限的资源。 一次只能打开一定数量的文件。 下面的程序演示它。
file_descriptors = [] for x in range(100000): file_descriptors.append(open('./files/test1.txt', 'w'))
一条错误的消息指出打开了太多的文件。 上面的例子是文件描述符的泄漏。 这是因为打开的文件太多,没有关闭。 程序员可能会忘记关闭打开的文件。
使用上下文管理器管理资源:
假设代码块引起异常,或者如果代码块具有复杂的算法,包含多个返回路径,则在所有位置关闭文件将变得非常麻烦。
使用文件try-except-finally
文件资源即使有异常也可以在使用后关闭。Python管理资源的方法很简单:上下文管理器。 使用with
关键字,将建立上下文管理的对象。
可以使用类别或函数(带装饰器)来编写上下文管理器。
用类创建上下文管理器:
在使用类创建上下文管理器时,用户需要确保该类有以下方法:__enter__()
和__exit__()
。__enter__()
返回需要管理的资源,__exit__()
清理操作不返回任何内容。
首先,让我们创建一个名字ContextManager了解上下文管理器的基本结构,如下所示:
# Python program creating a # context manager class ContextManager(): def __init__(self): print('init method called') def __enter__(self): print('enter method called') return self def __exit__(self, exc_type, exc_value, exc_traceback): print('exit method called') with ContextManager() as manager:
print('with statement block')
这种情况下了将创建一个ContextManager对象。 这是在as关键字之后, 即manager,赋值给变量的。 在运行上述程序时,将依次执行以下操作:
__init__()
__enter__()
- statement body (code inside the with block)
__exit__()
[the parameters in this method are used to manage exceptions]
使用上下文管理器进行文件管理:
让我们应用以上概念创建一个有助于文件资源管理的类。FileManager类有助于打开文件,写入/读取内容然后关闭它。
# Python program showing
# file management using
# context manager
class FileManager():
def __init__(self, filename, mode):
self.filename = filename
self.mode = mode
self.file = None
def __enter__(self):
self.file = open(self.filename, self.mode)
return self.file
def __exit__(self, exc_type, exc_value, exc_traceback):
self.file.close()
# loading a file
with FileManager('./files/test1.txt', 'w') as f:
f.write('Test')
print(f.closed)
True
使用上下文管理器和with语句进行文件管理:
在执行with块时,将依次执行以下操作:
- 当执行
__init__
方法时,将使用test1.txt作为文件名并使用w(write)作为模式创建FileManager对象。 __enter__
方法以写模式(设置操作)打开test1.txt文件,并将FileManager对象返回到变量f。- 文字‘Test’ 写入了文件中。
__exit__方
法负责在退出with块时关闭文件。 运行print(f.closed)时,输出为True,因为FileManager已经负责关闭文件,否则需要显式完成。
使用上下文管理器进行数据库连接管理:
让我们创建一个简单的数据库连接管理系统。 一次可以打开的数据库连接的数量也受到限制(就像文件描述符一样)。 上下文管理器有助于管理与数据库的连接,因为程序员可能会忘记关闭连接。
# Python program shows the
# connection management
# for MongoDB
from pymongo import MongoClient
class MongoDBConnectionManager():
def __init__(self, hostname, port):
self.hostname = hostname
self.port = port
self.connection = None
def __enter__(self):
self.connection = MongoClient(self.hostname, self.port)
return self
def __exit__(self, exc_type, exc_value, exc_traceback):
self.connection.close()
# connecting with a localhost
with MongoDBConnectionManager('localhost', 27017) as mongo:
collection = mongo.connection.SampleDb.test
data = collection.find({
'_id': 1})
for element in data:
print(element.name)
---------------------------------------------------------------------------
ServerSelectionTimeoutError Traceback (most recent call last)
<ipython-input-4-99b2d8cb3efa> in <module>
22 collection = mongo.connection.SampleDb.test
23 data = collection.find({'_id': 1})
---> 24 for element in data:
25 print(element.name)
C:\ProgramData\Anaconda3\lib\site-packages\pymongo\cursor.py in next(self)
1154 if self.__empty:
1155 raise StopIteration
-> 1156 if len(self.__data) or self._refresh():
1157 if self.__manipulate:
1158 _db = self.__collection.database
C:\ProgramData\Anaconda3\lib\site-packages\pymongo\cursor.py in _refresh(self)
1048
1049 if not self.__session:
-> 1050 self.__session = self.__collection.database.client._ensure_session()
1051
1052 if self.__id is None: # Query
C:\ProgramData\Anaconda3\lib\site-packages\pymongo\mongo_client.py in _ensure_session(self, session)
1808 # Don't make implicit sessions causally consistent. Applications
1809 # should always opt-in.
-> 1810 return self.__start_session(True, causal_consistency=False)
1811 except (ConfigurationError, InvalidOperation):
1812 # Sessions not supported, or multiple users authenticated.
C:\ProgramData\Anaconda3\lib\site-packages\pymongo\mongo_client.py in __start_session(self, implicit, **kwargs)
1761
1762 # Raises ConfigurationError if sessions are not supported.
-> 1763 server_session = self._get_server_session()
1764 opts = client_session.SessionOptions(**kwargs)
1765 return client_session.ClientSession(
C:\ProgramData\Anaconda3\lib\site-packages\pymongo\mongo_client.py in _get_server_session(self)
1794 def _get_server_session(self):
1795 """Internal: start or resume a _ServerSession."""
-> 1796 return self._topology.get_server_session()
1797
1798 def _return_server_session(self, server_session, lock):
C:\ProgramData\Anaconda3\lib\site-packages\pymongo\topology.py in get_server_session(self)
483 any_server_selector,
484 self._settings.server_selection_timeout,
--> 485 None)
486 elif not self._description.readable_servers:
487 self._select_servers_loop(
C:\ProgramData\Anaconda3\lib\site-packages\pymongo\topology.py in _select_servers_loop(self, selector, timeout, address)
207 if timeout == 0 or now > end_time:
208 raise ServerSelectionTimeoutError(
--> 209 self._error_message(selector))
210
211 self._ensure_opened()
ServerSelectionTimeoutError: localhost:27017: [WinError 10061] 由于目标计算机积极拒绝,无法连接。
使用上下文管理器和with语句进行数据库连接管理:
在执行with块时,将依次执行以下操作:
- 当执行
__init__
方法时,将创建一个MongoDBConnectionManager对象,其中localhost为主机名,端口27017为端口。 __enter__
方法打开mongodb连接,并将MongoDBConnectionManager对象返回到变量mongo。- 访问SampleDb数据库中的测试集合,并检索
_id = 1
的文档。 打印文档的名称字段。 __exit__
方法负责在退出with块时关闭连接。
使用装饰器创建上下文管理器:
上下文管理器是如此有用,Python拥有专门用于它们的整个标准库模块contextlib
,包含用于创建和使用上下文管理器的工具。 从类创建上下文管理器的一个不错的捷径是使用@contextmanager
装饰器。 要使用它,装饰一个生成器函数,该函数只需调用一次yield
即可。 调用yield
之前的所有内容都被视为__enter__()
的代码。 yield
后面的所有内容都是__exit__()
的代码。 让我们使用装饰器方法重写文件上下文管理器:
from contextlib import contextmanager
@contextmanager
def open_file(path, mode):
the_file = open(path, mode)
yield the_file
the_file.close()
files = []
for x in range(100000):
with open_file('foo.txt', 'w') as infile:
files.append(infile)
for f in files:
if not f.closed:
print('not closed')