flask之路【第五篇】扩充知识点
十二、扩充知识点
12.1内容补充
- getitem、setitem、setattr、getattr
class Foo:
#
obj = Foo()
obj() # __call__
obj[x1] = 123 # __setitem__
obj[x2] # __getitem__
obj.x1 = 123 # __setattr__
obj.x2 # __getattr__
- 从看flask源码你学到了什么?
- 新的编程思路。
- django、drf数据是通过传递。
- flask,存储在某个地方,以后用的时候去拿。
哪种好?两个不同的实现机制,没有好坏之分。
django好,疑问如果是一个初学者对于django的机制比较好理解,flask学习代价比较大(了解上下文管理机制之后才能更好的理解)。
- 技术点
- 单利模式的应用场景
- LocalProxy
- 装饰器不注意functools
-
在flask的Local对象中为什么要通过线程ID进行区分?
因为在flask中可以开启多线程的模式,当开启多线程模式进行处理用户请求时,需要将线程之间的数据进行隔离,以防止数据混乱。 -
在flask的Local对象中为什么要维持成一个栈?
{
111:{stack:[ctx, ]},
}
在web runtime 时,栈永远只有1个对象。
{
111:{stack:[ctx, ]}
112:{stack:[ctx, ]}
}
{
111:{stack:[app_ctx, ]}
112:{stack:[app_ctx, ]}
}
在写离线脚本时,才会用在栈中放多个对象。(创建一个py文件本地运行)
from flask import current_app, g
from pro_excel import create_app
app1 = create_app()
with app1.app_context(): ApplicationContext对象(app, g) -> local对象
print(current_app.config) app1
app2 = create_app()
with app2.app_context(): ApplicationContext对象(app, g) -> local对象
print(current_app.config)
print(current_app.config)
写离线脚本且多个上下文嵌套时,才会在栈中添加多个对象。
注意:在flask中很少出现嵌套的脚本。
12.2 信号
信号里边的函数都没有返回值,但是可以将要传递的值放在g中,等到有地方返回的时候再返回
- 信号
信号,是在flask框架中为我们预留的钩子,让我们可以进行一些自定义操作。
pip install blinker
根据flask项目的请求流程来进行设置扩展点
- 中间件
from flask import Flask, render_template
app = Flask(__name__)
@app.route('/index')
def index():
return render_template('index.html')
@app.route('/order')
def order():
return render_template('order.html')
class MyMiddleware(object):
def __init__(self, old_app):
self.wsgi_app = old_app.wsgi_app
def __call__(self, *args, kwargs):
print('123')
result = self.wsgi_app(*args, kwargs)
print('456')
return result
app.wsgi_app = MyMiddleware(app)
if __name__ == '__main__':
app.run()
-
当app_ctx被push到local中栈之后,会触发appcontext_pushed信号,之前注册在这个信号中的方法,就会被执行。
from flask import Flask, render_template from flask import signals app = Flask(__name__) @signals.appcontext_pushed.connect def f1(arg): print('appcontext_pushed信号f1被触发', arg) @signals.appcontext_pushed.connect def f2(arg): print('appcontext_pushed信号f2被触发', arg) @app.route('/index') def index(): return render_template('index.html') @app.route('/order') def order(): return render_template('order.html') if __name__ == '__main__': app.run() app.__call__
-
执行before_first_request扩展
from flask import Flask, render_template
app = Flask(__name__)
@app.before_first_request
def f2():
print('before_first_requestf2被触发')
@app.route('/index')
def index():
return render_template('index.html')
@app.route('/order')
def order():
return render_template('order.html')
if __name__ == '__main__':
app.run()
- request_started信号
from flask import Flask, render_template
from flask import signals
app = Flask(__name__)
@signals.request_started.connect
def f3(arg):
print('request_started信号被触发', arg)
@app.route('/index')
def index():
return render_template('index.html')
@app.route('/order')
def order():
return render_template('order.html')
if __name__ == '__main__':
app.run()
-
url_value_processor
from flask import Flask, render_template, g from flask import signals app = Flask(__name__) @app.url_value_preprocessor def f5(endpoint, args): print('f5') @app.route('/index/') def index(): print('index') return render_template('index.html') @app.route('/order') def order(): print('order') return render_template('order.html') if __name__ == '__main__': app.run()
-
before_request
from flask import Flask, render_template, g from flask import signals app = Flask(__name__) @app.before_request def f6(): g.xx = 123 print('f6') @app.route('/index/') def index(): print('index') return render_template('index.html') @app.route('/order') def order(): print('order') return render_template('order.html') if __name__ == '__main__': app.run()
-
视图函数
-
before_render_template / rendered_template
from flask import Flask, render_template, g
from flask import signals
app = Flask(__name__)
@signals.before_render_template.connect
def f7(app, template, context):
print('f7')
@signals.template_rendered.connect
def f8(app, template, context):
print('f8')
@app.route('/index/')
def index():
return render_template('index.html')
@app.route('/order')
def order():
print('order')
return render_template('order.html')
if __name__ == '__main__':
app.run()
-
after_request
from flask import Flask, render_template, g from flask import signals app = Flask(__name__) @app.after_request def f9(response): print('f9') return response @app.route('/index/') def index(): return render_template('index.html') @app.route('/order') def order(): print('order') return render_template('order.html') if __name__ == '__main__': app.run()
-
request_finished
from flask import Flask, render_template, g from flask import signals app = Flask(__name__) @signals.request_finished.connect def f10(app, response): print('f10') @app.route('/index/') def index(): return render_template('index.html') @app.route('/order') def order(): print('order') return render_template('order.html') if __name__ == '__main__': app.run()
-
got_request_exception
from flask import Flask, render_template, g
from flask import signals
app = Flask(__name__)
@app.before_first_request
def test():
int('asdf')
@signals.got_request_exception.connect
def f11(app, exception):
print('f11')
@app.route('/index/')
def index():
return render_template('index.html')
@app.route('/order')
def order():
print('order')
return render_template('order.html')
if __name__ == '__main__':
app.run()
- teardown_request
from flask import Flask, render_template, g
from flask import signals
app = Flask(__name__)
@app.teardown_request
def f12(exc):
print('f12')
@app.route('/index/')
def index():
return render_template('index.html')
@app.route('/order')
def order():
print('order')
return render_template('order.html')
if __name__ == '__main__':
app.run()
- request_tearing_down
from flask import Flask, render_template, g
from flask import signals
app = Flask(__name__)
@signals.request_tearing_down.connect
def f13(app, exc):
print('f13')
@app.route('/index/')
def index():
return render_template('index.html')
@app.route('/order')
def order():
print('order')
return render_template('order.html')
if __name__ == '__main__':
app.run()
- appcontext_popped
from flask import Flask, render_template, g
from flask import signals
app = Flask(__name__)
@signals.appcontext_popped.connect
def f14(app):
print('f14')
@app.route('/index/')
def index():
return render_template('index.html')
@app.route('/order')
def order():
print('order')
return render_template('order.html')
if __name__ == '__main__':
app.run()
总结:关于flask内部共有14+个扩展点用于我们对flask框架内部进行定制,其中有:9个是信号。
template_rendered = _signals.signal("template-rendered")
before_render_template = _signals.signal("before-render-template")
request_started = _signals.signal("request-started")
request_finished = _signals.signal("request-finished")
request_tearing_down = _signals.signal("request-tearing-down")
got_request_exception = _signals.signal("got-request-exception")
appcontext_tearing_down = _signals.signal("appcontext-tearing-down")
appcontext_pushed = _signals.signal("appcontext-pushed")
appcontext_popped = _signals.signal("appcontext-popped")
###### message_flashed 给flash赋值会触发这个信号。
message_flashed = _signals.signal("message-flashed")
12.3 扩展:flash
(flash存值之后只能取一次)其实这个值是存在session中的,用session一样可以实现。
from flask import Flask, render_template, flash, get_flashed_messages, session
from flask import signals
app = Flask(__name__)
app.secret_key = 'iuknsoiuwknlskjdf'
@app.route('/index/')
def index():
# flash('123')
session['k1'] = 123
return render_template('index.html')
@app.route('/order')
def order():
# messages = get_flashed_messages()
# print(messages)
val = session['k1']
del session['k1']
print(val)
return render_template('order.html')
if __name__ == '__main__':
app.run()
💎💎flash的显示,弹窗**
后端:flash('密码已成功重置!请使用新密码登录。', 'success')
前端:
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
<script>
{% for category, message in messages %}
alert("{{ message }}"); // 使用 JavaScript 弹窗
{% endfor %}
</script>
{% endif %}
{% endwith %}
12.4 flask-script
Flask-Script 是一个用于处理命令行脚本的扩展,高版本已经弃用
高版本的 Flask 弃用了 Flask-Script,主要是为了降低维护成本、避免功能重复,并简化生态系统。这使得 Flask 项目更加轻量和高效,同时也为开发者提供了更好的开发体验。
社区中出现了更加成熟和功能丰富的命令行扩展,如 Flask-Cli
,这些扩展提供了更多的功能和更好的用户体验。
💎推荐使用argparse
模块
12.5 蓝图划分
目录结构的划分
- 分功能蓝图:
- 分结构蓝图:bigblue
十三、SqlHelper的实现
以下两种实现都可以用,两种不同的机制单例模式(借助threadinglocal实现)和多例模式:
- 方式一(单例 参见project:Practice0701)sqlhelper.py
import pymysql
from dbutils.pooled_db import PooledDB
from pymysql import cursors
import threading
'''
storage = {
1111: {'stack': []}
}
'''
class SqlHelper(object):
def __init__(self):
self.pool = PooledDB(
creator=pymysql,
maxconnection=6, # 最大连接数,0和None表示不限制
mincached=2, # 初始化最少创建的链接,0表示不创建
blocking=True, # 链接池中如果没有可用,等待
ping=0, # ping mysql服务,检查服务是否可用 # 0,代表不去检查, 1当表当请求是检查
host='127.0.01',
port=3306,
user='root',
password='My@20241103',
db='db_name',
charset='utf8mb4',
)
self.local = threading.local()
def open(self):
conn = self.pool.connection()
cursor = conn.cursor()
return conn, cursor
def close(self, cursor, conn):
cursor.close()
conn.close()
def fetchall(self, sql, *args):
""" 获取所有数据 """
conn, cursor = self.open()
cursor.execute(sql, args)
result = cursor.fetchall()
self.close(conn, cursor)
return result
def fetchone(self, sql, *args):
""" 获取所有数据 """
conn, cursor = self.open()
cursor.execute(sql, args)
result = cursor.fetchone()
self.close(conn, cursor)
return result
def __enter__(self):
conn, cursor = self.open()
rv = getattr(self.local, 'stack', None)
if not rv:
self.local.stack = [(conn, cursor)]
else:
rv.append((conn, cursor))
self.local.stack = rv
return cursor
def __exit__(self, exc_type, exc_val, exc_tb):
rv = getattr(self.local, 'stack', None)
if not rv:
# del self.local.stack
return
conn, cursor = self.local.stack.pop()
cursor.close()
conn.close()
db = SqlHelper()
调用:
from sqlhelper import db
# db.fetchall(...)
# db.fetchone(...)
with db as c1:
c1.execute('select 1')
with db as c2:
c1.execute('select 2')
print(123)
- 方式二
'''
通过全局变量的形式。实现连接,关闭连接是通过每个实例的close关闭。
上下文管理器简化了数据库操作,确保了连接的正确释放,同时连接池机制优化了资源利用率。
不能用单例模式
'''
import pymysql
from dbutils.pooled_db import PooledDB
from pymysql import cursors
POOL = PooledDB(
creator=pymysql,
maxconnection=6, # 最大连接数,0和None表示不限制
mincached=2, # 初始化最少创建的链接,0表示不创建
blocking=True, # 链接池中如果没有可用,等待
ping=0, # ping mysql服务,检查服务是否可用 # 0,代表不去检查, 1当表当请求是检查
host='127.0.0.1',
port=3306,
user='root',
password='My@20241103',
db='db_name',
charset='utf8mb4',
)
class SqlHelper(object):
def __init__(self):
self.conn = None
self.cursor = None
def open(self):
conn = POOL.connection()
cursor = conn.cursor()
return conn, cursor
def close(self):
self.cursor.close()
self.conn.close()
def __enter__(self):
self.conn, self.cursor = self.open()
return self.cursor
def __exit__(self, exc_type, exc_val, exc_tb):
self.close()
# ######## 使用 #########
with SqlHelper() as c1: #,with SqlHelper()会执行里边的__enter__方法并将返回值会给c1。with块代码执行完以后会执行SqlHelper()的__exit__方法。
c1.execute('select 1')
with SqlHelper() as c2:
c2.execute('select 2')
print(666)
with SqlHelper() as cursor:
cursor.execute('select 1')
with SqlHelper() as cursor:
cursor.execute('select 1')
其他python知识点:类的特殊方法
💎python对象小知识点__call__
方法:
在Python中,如果类的实例对象后面加上括号(即对象()
), 这会触发执行该类中的__call__
方法
class Foo:
def __init__(self, name):
self.name = name
def __call__(self):
print(f"Calling {self.name} with __call__ method")
obj = Foo("test")
obj() # 输出:Calling test with __call__ method
# 在这个例子中,当调用obj()时,会执行Foo类中的__call__方法,并打印相应的消息
💎python对象小知识点__init__
方法
一个特殊的方法,通常被称为构造函数。它在创建类的实例时自动调用,并用于初始化对象的状态。通过 __init__
方法,你可以定义一个对象被创建时应该拥有的属性。
class Dog:
def __init__(self, name, age):
self.name = name # 初始化name属性
self.age = age # 初始化age属性
def bark(self):
return f"{self.name} says woof!"
# 创建Dog类的一个实例
my_dog = Dog("Buddy", 3)
# 访问实例的属性和方法
print(my_dog.name) # 输出: Buddy
print(my_dog.age) # 输出: 3
print(my_dog.bark()) # 输出: Buddy says woof!
在这个例子中,Dog
类有一个 __init__
方法,它接受两个参数 name
和 age
,并在创建 Dog
的实例时将这些值赋给实例的 name
和 age
属性。这样每个 Dog
实例都会有自己的 name
和 age
属性。
💎__init__
和__call__
区别:
在创建类的实例时,只有 __init__
方法会被自动调用,而 __call__
方法则是在实例被当作函数调用时才会被调用。
参数
__init__
方法:通常接受一个或多个参数,用于初始化对象的属性。第一个参数通常是self
,代表实例本身5。__call__
方法:可以接受任意数量的参数,这些参数是在调用实例时传递的。
返回值
__init__
方法:通常不返回任何值,因为它主要用于初始化对象。__call__
方法:可以返回一个值,就像普通函数一样。
class MyClass:
def __init__(self, value):
self.value = value # 初始化实例属性
def __call__(self, arg):
return self.value + arg # 在调用实例时执行的操作
# 创建实例
my_instance = MyClass(10) # __init__ 被调用
# 调用实例
result = my_instance(5) # __call__ 被调用,返回 15
其他python知识点: 读取excel 内容放入数据库
(xlrd模块,只能处理.xls文档)
import xlrd
@od_bp.route('/upload', methods=["GET", "POST"])
def upload():
if 'excel_file' not in request.files:
return "没有选择文件", 400
file = request.files['excel_file']
if file.filename == '':
return "没有选择文件", 400
if file:
if '.' in file.filename and \
file.filename.rsplit('.', 1)[1].lower() in {'xls', 'xlsx'}:
try:
# 直接处理上传的文件内容
file_data = file.read()
workbook = xlrd.open_workbook(file_contents=file_data)
sheet = workbook.sheet_by_index(0)
for row_idx in range(1, sheet.nrows):
row_data = sheet.row_values(row_idx)
author = row_data[2]
title = row_data[0]
price = row_data[1]
db.insert_one(
"INSERT INTO `book` (author, title, price, author_id) VALUES (%s, %s, %s, 2)",
(author, title, price)
)
return redirect("/index")
except Exception as e:
return f"处理失败: {str(e)}", 500
else:
return "无效的文件类型", 400
else:
return "无效的文件类型", 400
<form action="/upload" method="post" enctype="multipart/form-data">
<input type="file" name="excel_file" accept=".xls,.xlsx" required>
<input type="submit" class="btn btn-primary" value="导入书籍">
</form>
总结一下xlrd用法:
work_book =xlrd.open_workbook(file_path) #传入file_path或者文件对象,获得工作簿对象
sheet1 = work_book.sheet_by_index(0) #获取工作表
for row_idx in range(1,sheet1.nrows)
row_data = sheet1.row_values(row_idx) # 获取某一行的值,是一个列表