1. 利用上下问管理器处理数据库写入操作
user = User()
user.email = form.email.data
user.nickname = form.nickname.data
user.password = form.password.data
try:
db.session.add(user)
db.session.commit()
return redirect(url_for('web.index'))
except Exception as e:
# 这种写法目前是有问题的,需要加入回滚,否则一旦提交失败,会造成阻塞到其它提交 !! rollback
db.session.rollback()
print("出错了:!" + traceback.format_exc())
raise e
上面是操作用户表的一个例子,用try except 将可能出mysql错误的代码包裹住。
但是我们在一个项目中,有至少几十张表,需要处理读写错误的地方很多,那么,每个地方都用try except 包裹住么,这样太麻烦了。
因此,需要利用上下文管理器,只需要在业务侧保留1-2行代码
db.session.add(user)
而开启事务和回滚操作都交给框架完成是最优解。
这时候就需要用到了,上下文管理器。
2.什么是上下文管理器
class MyResource():
def __enter__(self):
print("i am enter")
return self
def __exit__(self, exc_type, exc_val, exc_tb):
print("i am exit")
if exc_type:
print(exc_type, exc_val, exc_tb)
print(traceback.format_exc())
raise exc_val
def query(self, data):
print("i am query data")
with MyResource() as r:
r.query(data=[])
i am enter
i am query data
i am exit
在enter中处理开启try
在exit中处理except的部分
注意这个mysource中的enter函数,return了self本身。
3. 用contextmanager简化
from contextlib import contextmanager
def make_myresource():
print("connect to resource!")
return MyResource()
print("close resource connection!")
try:
with make_myresource() as r:
try:
r.query([])
except Exception as e:
print(traceback.format_exc())
except Exception as e:
print(e)
报错,我承认,我是故意让他出错的,需要将return 改成yield,yield 很熟悉的
connect to resource!
'MyResource' object is not an iterato
输出正常了:
connect to resource!
i am query data
close resource connection!
with 语句 会将yield后面的表达式返回r,也就是执行到yield后面,等query执行完成后,再去返回yield的位置继续向下执行。
return的意思是返回,返回了就不能再回到原来的位置继续执行了,yield是让渡,让渡出去,等完成了,还能再回来
yield 是生成器,自行学习一下。
4.一个小应用
from contextlib import contextmanager
@contextmanager
def book_mark():
print("《",end="")
yield
print(">>",end="")
with book_mark():
print("且将生活一饮而尽")
输出了:《且将生活一饮而尽》
案例4就和我们的要求极为相近了,yield 不需要返回什么,
with 也不需要as搭配。
做一下类比:
try == 《
except == 》
yield 也不需要返回
def auto_commit(db):
try:
yield
db.session.commit()
except Exception as e:
db.session.rollback()
raise e
或者也可以用对象的方式:
class C:
@contextmanager
def ccc(self):
print("1111")
yield
print("33333")
c = C()
with c.ccc():
print("2222")
输出:
1111
2222
33333
5.终极修改方案
class SQLAlchemy(_SQLAlchemy):
@contextmanager
def auto_commit(self):
try:
yield
self.session.commit()
except Exception as e:
self.session.rollback()
raise e
db = SQLAlchemy()
重写sqlalchemy
with db.auto_commit():
user = User()
user.email = form.email.data
user.nickname = form.nickname.data
user.password = form.password.data
db.session.add(user)
return redirect(url_for('web.index'))
6.拓展
上下文管理器其实是一种面向切面的编程方式,比如数据库操作,进入函数和出去函数的时候记录日志【尤其是微服务之前相互调用】,可以在最底层的http请求的函数加上下午管理器,自动的将入参和返回值记录下来。返回值有的需要记录全量,有的则需要记录状态码【全量数据太多的话】,自行处理吧。