在必须释放连接时,使用try / except / finally的pythonic方法是什么?

时间:2018-09-19 09:26:03

标签: python refactoring

我有一个连接到数据库并获取一些数据并返回此数据的函数。除finally块外,整个过程都围绕着尝试。在finally块中,即使出现错误也释放资源(如果存在)。那么执行以下操作的pythonic方法是什么:

def fetch_data():
    db_conn = None
    try:
        db_conn = DBConnection()
        data = db_conn.get_data()
    except Exception as ex:
        print(f"Exception occurred when fetching data: {ex}")
    finally:
        if db_conn:
            db_conn.close()

db_conn = Nonefinally块的初始化看起来不够优雅或pythonic,我想知道是否有更好的方法可以做到这一点?

1 个答案:

答案 0 :(得分:6)

您要使用上下文管理器。上下文管理器和with语句已添加到专门用于处理这种模式的语言中。

代码将变为:

with DBConnection() as db_conn:
    data = db_conn.get_data()

请参见With Statement Context ManagersDBConnection的实现需要提供__enter____exit__方法来处理:

class DBConnection:
    def __init__(self):
        # .. initialisation of the instance. **Not** in the context manager

    def close(self):
        # ...

    def get_data(self):
        # ...
        return data_loaded

    def __enter__(self):
        # context is entered, `try:` 'opens' *after* this point.
        # Perhaps you want to actually connect to the database here
        # whatever is returned here is assignable via `with ... as name`
        # this can be a new object or self
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        # The context is exiting, equivalent of except ... and finally ...
        self.close()
        if exc_type:
            print(f"Exception occurred when fetching data: {exc_value}")
        # returning None will allow exceptions to propagate, returning
        # True (or other true value) will clear the exception.
        return True  # exception is cleared

我在return self处理程序中使用了__enter__,因为这是用于上下文管理器的良好模式,非常适合您的特定示例,但是您还可以返回其他内容。例如,某些数据库适配器会在该点返回事务对象或游标。

请注意,__enter__ 中引发的异常不是上下文的一部分,也不会在那里处理!如果在打开不想传播的数据库时需要处理异常,则必须将连接推迟到第一次get_data()调用之前。

上下文管理器封装 try: ... except ...: finally: ...模式。另请参见将其添加到Python的PEP 343 -- The *with* statement提案:

  

此PEP在Python语言中添加了一个新的“ with”语句,从而可以排除try / finally语句的标准用法。

请注意,处理异常的位置取决于您的用例。对于某些使用__exit__方法的上下文管理器,对于其他情况,仅清理上下文而不抑制异常是有用的。例如,文件是上下文管理器,但它们不会清除异常。此时,您需要添加一个try: .. except SpecificException:来处理此类情况:

try:
    with open(filename) as fobj:
        # ...
except IOError:
    # oops, file failed to open

try:
    open_file = open(filename)
except IOError:
    # oops, file failed to open
else:
    with open_file as fobj:
        # ...

上下文管理器的作用是确保文件对象(如果已打开)处于关闭状态,而没有其他情况。