# 4.4 flask上下文与with语句

我们上一小节通过手动将app推入栈，弹出栈的方式，解决了working outside application context的问题。实际上更经典的做法是使用with语句来完成。

首先使用with语句替换之前的代码

```python
app = Flask(__name__)

with app.app_context():
    a = current_app
    b = current_app.config["DEBUG"]
```

## 什么时候可以使用with语句：

> 1.实现了上下文协议的对象，可以使用with语句 2.对于实现了上下文协议的对象，我们通常称为上下文管理员 3.通过实现\_\_enter\_\_和\_\_exit\_\_来实现上下文协议 4.上下文表达式必须返回一个上下文管理器

对于上面一段代码来说，AppContext就是上下文管理器；`app.app_context()`就是上下文表达式。\_\_enter\_\_中做了push操作，\_\_exit\_\_中做了pop操作。 所以只要进入with语句，current\_app就是有值的，一旦离开了with语句，current\_app 就会弹出，然后就又没有值了（又变成了unbound）。

```python
    def __enter__(self):
        self.push()
        return self

    def __exit__(self, exc_type, exc_value, tb):
        self.pop(exc_value)

        if BROKEN_PYPY_CTXMGR_EXIT and exc_type is not None:
            reraise(exc_type, exc_value, tb)
```

## 通过数据库的链接和释放来理解with语句的具体含义

连接数据库的操作步骤： 1.连接数据库 2.sql或者其他的业务逻辑 3.释放资源

如果上面的第二部分出错，那么第三部分的释放资源就不会被执行，资源就会一直被占用。 解决这个问题的通常做法是使用`try-except-finally` 但是在finally中更优雅的方式就是使用with语句中。我们可以把连接数据库的操作写在上下文管理器的\_\_enter\_\_方法里面，把业务代码写在with语句的代码块里面，把释放资源的语句写在\_\_exit\_\_里面。

## 读写文件的具体例子

一般的写法

```python
try:
  f = open(r'/Users/test.txt')
  print(f.read())
finally:
  f.close()
```

使用with语句的写法:

```python
with open(r'/Users/test.txt') as f:
  print(f.read())
```

**注意上面的with语句后面的as 返回的并不是上下文管理器，他实际上是\_\_enter\_\_方法返回的一个值**,

![image.png](https://upload-images.jianshu.io/upload_images/7220971-706a6a0afd66dc83.png?imageMogr2/auto-orient/strip|imageView2/2/w/1240) 上面一段代码我们在\_\_enter\_\_中返回了一个a，所以下面as 后的obj\_A就是1

## exit方法详解

注意我们编写的测试代码，运行时会报错的，错误原因是exit方法接受的参数数量不够。 exit方法的作用不只是释放资源，还有处理异常，所以exit方法还要多接受exc\_type,exc\_value,tb三个参数。这三个参数在没有异常发生的时候回传控制，如果有异常的话，这三个参数分别是异常类型，异常消息，和详细的异常堆栈信息

exit方法还需要返回一个boolean类型的值，如果返回True，那么外部就不会抛出异常，如果返回False，那么还会在外部抛出异常，如果什么都不返回，按照False处理。Flask提供了一种非常灵活的方式，可以让我们选择在with语句内部还是外部处理异常

![image.png](https://upload-images.jianshu.io/upload_images/7220971-100050602e8cca30.png?imageMogr2/auto-orient/strip|imageView2/2/w/1240)
