flask基础

1 请求与响应

1.1 请求对象

复制代码
def index():
    # 请求对象,是全局的,需要导入,这个全局的request,在哪个视图函数中,就是当次的request对象,不会乱
    # request.method  提交的方法
    print(request.method)
    # request.args  get请求提及的数据
    print(request.args)
    print(request.args.get('name'))
    # request.form   post请求提交的数据
    print(request.form)
    # request.values  post和get提交的数据总和
    print(request.values)
    # request.cookies  客户端所带的cookie
    print(request.cookies)
    # request.headers  请求头
    print(request.headers)
    print('------')
    # request.path     不带域名,请求路径
    print(request.path)
    # request.full_path  不带域名,带参数的请求路径
    print(request.full_path)
    # request.script_root
    print('服务端:', request.script_root)
    # request.url           带域名带参数的请求路径
    print(request.url)
    # request.base_url        带域名请求路径
    print(request.base_url)
    # request.url_root      域名
    print(request.url_root)
    # request.host_url        域名
    print(request.host_url)
    # request.host            127.0.0.1:500
    print(request.host)
    # request.files
    print(request.files)
    # obj = request.files['files']
    # obj.save('./xx.jpg')

    print(request.data) # django的body
    return 'hellod'
复制代码

1.2 响应对象

复制代码
@app.route('/',methods=['GET','POST'])
def index():
    # 1四件套
        # -render_template
        # -redirect
        # -jsonify
        # -''
    # 2写入响应头-->没有响应对象,先做出一个响应对象
    # from .wrappers import Response
    res='helloe'
    res=make_response(res)
    # 往Response的对象中,放入响应头
    res.headers['name']='lqz'
    # 3 写入cookie
    # res.set_cookie('xx','xx')
    res.delete_cookie('xx')
    '''
    key, 键
    value=’’, 值
    max_age=None, 超时时间 cookie需要延续的时间(以秒为单位)如果参数是\ None`` ,这个cookie会延续到浏览器关闭为止
    expires=None, 超时时间(IE requires expires, so set it if hasn’t been already.)
    path=’/‘, Cookie生效的路径,/ 表示根路径,特殊的:根路径的cookie可以被任何url的页面访问,浏览器只会把cookie回传给带有该路径的页面,这样可以避免将cookie传给站点中的其他的应用。
    domain=None, Cookie生效的域名 你可用这个参数来构造一个跨站cookie。如, domain=”.example.com”所构造的cookie对下面这些站点都是可读的:www.example.com 、 www2.example.com 和an.other.sub.domain.example.com 。如果该参数设置为 None ,cookie只能由设置它的站点读取
    secure=False, 浏览器将通过HTTPS来回传cookie
    httponly=False 只能http协议传输,无法被JavaScript获取(不是绝对,底层抓包可以获取到也可以被覆盖)
    '''
    return res
复制代码

1.3 前后端分离和混合

# 前后端混合,cookie 是后端写入的
    - res.set_cookie('xx','xx') 混合都是这么写的,这样写了,浏览就会把cookie保存到cookie中
    -本质是后端把cookie放到响应头中,浏览器读到响应头中有cookie,把cookie写入到浏览器中
# 前后端分离后
    -直接把客户端要存到cookie中的数据,放到响应体中
    -前端(浏览器,app,小程序),自己取出来,放到相应的位置
        浏览器使用js自己写入到cookie
        app 自己使用代码写入到某个位置

2 session的使用和原理

 

 2.1 session的使用

# 放值 视图函数中
导入全局的session
session['name']='bxf'

# 取值 视图函数中
导入全局的session
print(session['name'])

 

 

2.2 源码分析

复制代码
# django 的这一套,都在 from django.contrib.sessions.middleware import SessionMiddleware


# flask 在flask源码中
    -请求来了,会执行 app()
    
    
# 整个flask,从请求进来,到请求走的整个流程
    def wsgi_app(self, environ, start_response):
        ctx = self.request_context(environ)
        try:
            try:
                ctx.push() # 它的源码
                response = self.full_dispatch_request()
            except Exception as e:
                error = e
                response = self.handle_exception(e)
            except: 
                error = sys.exc_info()[1]
                raise
            return response(environ, start_response)
        finally:
            ctx.pop(error)
            
            
            
   # ctx.push 的 373行左右    
          if self.session is None:
            session_interface = self.app.session_interface
            self.session = session_interface.open_session(self.app, self.request)
            if self.session is None:
                self.session = session_interface.make_null_session(self.app)
                
   # app.session_interface  就是Flask对象中有个session_interface对象
            SecureCookieSessionInterface()
                -open_session:请求来了,从cookie中取出三段串,反序列化解密放到session中
                -save_session:请求走了,把session字典中的值,序列化加密,放到cookie中
            
            
            
  # open_session:请求来了执行
    def open_session(self, app, request) :
        s = self.get_signing_serializer(app)
        if s is None:
            return None
        # val 就是取出的三段:eyJhZ2UiOiIxOSIsIm5hbWUiOiJscXoifQ.Y5ac9g.vOomQFqFuaqXWqRQhvSNyc61UIk
        val = request.cookies.get('session')
        if not val:
            return self.session_class()
        max_age = int(app.permanent_session_lifetime.total_seconds())
        try:
            data = s.loads(val, max_age=max_age)
            return self.session_class(data)
        except BadSignature:
            return self.session_class()
        
        
  # 请求走了,执行save_session
     def save_session(self, app, session, response):
        name = self.get_cookie_name(app)
        domain = self.get_cookie_domain(app)
        path = self.get_cookie_path(app)
        secure = self.get_cookie_secure(app)
        samesite = self.get_cookie_samesite(app)
        httponly = self.get_cookie_httponly(app)

        if not session:  # 如果视图函数放了,不为空 session['name']='lqz'
            if session.modified:  # 
                response.delete_cookie(
                    name,
                    domain=domain,
                    path=path,
                    secure=secure,
                    samesite=samesite,
                    httponly=httponly,
                )

            return

  
        if session.accessed:
            response.vary.add("Cookie")

        if not self.should_set_cookie(app, session):
            return

        expires = self.get_expiration_time(app, session)
        # 序列化---》加密了
        val = self.get_signing_serializer(app).dumps(dict(session))  # type: ignore
        # 三段:
        response.set_cookie(
            name, # session
            val,  # 三段:
            expires=expires,
            httponly=httponly,
            domain=domain,
            path=path,
            secure=secure,
            samesite=samesite,
        )

        
        
        
        
  # 总结:session的执行流程
    1 请求来的时候,会执行open_session--->取出cookie,判断是否为空,如果不为空,把它反序列化,解密---》字典---》转到session对象中----》视图函数
    
    2 请求走的时候,会执行save_session---->把session转成字典----》序列化加密--》三段---》放到cookie中
复制代码

3 闪现

复制代码
# flash 翻译过来叫闪现
# 作用:
    访问a页面,出了错,重定向到了b页面,要在b页面线上a页面的错误信息
    在某个请求中放入值,另一个请求中取出,取出来后就没了
    
# 使用
    设置值:
        flash('不好意思,没有权限看')
        可以用多次
    取值:取出列表
        get_flashed_messages()
        
        
# 使用方式二:分类设置和获取
    设置值:
        flash('钱钱钱',category='lqz')
        flash('666',category='c1')')
        可以用多次
    取值:取出列表
        errors = get_flashed_messages(category_filter=['lqz'])
复制代码

4 补充异步

复制代码
# 异步框架 FastAPi
async def index():
    print('sdfasd')
    a++
    await xxx  # io操作
async def goods():   
    pass
# 框架之前的web框架,开启进程,线程---》一条线程会运行多个协程函数----》协程函数中遇到io,读到await关键字,就会切换到别的协程函数


# 一旦使用了异步,以后所有的模块,都要是异步
    -pymysql :同步的
    -redis :同步
    -aiomysql:异步
    -aioredis:异步
    -在fastapi或sanic中,要操作mysql,redis要使用异步的框架,否则效率更低
    -django 3.x 以后页支持async 关键字
    -没有一个特别好异步的orm框架
        -sqlalchemy在做
        -tortoise-orm
        https://tortoise-orm.readthedocs.io/en/latest/index.html
    
    
# aiomysql
import asyncio
import aiomysql

loop = asyncio.get_event_loop()

async def test_example():
    conn = await aiomysql.connect(host='127.0.0.1', port=3306,
                                       user='root', password='', db='mysql',
                                       loop=loop)

    cur = await conn.cursor()
    await cur.execute("SELECT Host,User FROM user")
    print(cur.description)
    r = await cur.fetchall()
    print(r)
    await cur.close()
    conn.close()

loop.run_until_complete(test_example())


# aioredis
import aioredis
import asyncio
class Redis:
    _redis = None
    async def get_redis_pool(self, *args, **kwargs):
        if not self._redis:
            self._redis = await aioredis.create_redis_pool(*args, **kwargs)
        return self._redis

    async def close(self):
        if self._redis:
            self._redis.close()
            await self._redis.wait_closed()


async def get_value(key):
    redis = Redis()
    r = await redis.get_redis_pool(('127.0.0.1', 6379), db=7, encoding='utf-8')
    value = await r.get(key)
    print(f'{key!r}: {value!r}')
    await redis.close()         

if __name__ == '__main__':
    asyncio.run(get_value('key'))  # need python3.7
复制代码

 

 

5 请求扩展

复制代码
# 在请求进入视图函数之前,执行一些代码
# 请求出了视图函数以后,执行一些代码
# 类似于django的中间件完成的功能
# 7个装饰器

# 1  before_request:在请求进视图函数之前执行
    多个的话,会从上往下,依次执行, django:process_request
    如果返回四件套之一,就直接返回了
    在这里面,正常使用request对象
    
#2  after_request:在请求从视图函数走之后执行
    多个的话,会从下往上,依次执行, django:process_response一样
    要有参数,和返回值,参数就是response对象,返回值也必须是resposne对象
    session,request 照常使用
    向响应头写东西?向cookie中写东西
    
    
    
# 3 before_first_request:项目启动后,第一次访问会执行,以后再也不执行了
    可以做一些初始化的操作
    
    
# 4 teardown_request:每一个请求之后绑定一个函数,即使遇到了异常,每个请求走,都会执行,记录错误日志
@app.teardown_request
def tear_down(e):
    print(e)  # 如果有异常,这是异常对象
    print('我执行了')
    
    
#5 errorhandler路径不存在时404,服务器内部错误500
# @app.errorhandler(404)
# def error_404(arg):
#     print('404会执行我')
#     # return "404错误了"
#     return render_template('404.html')

@app.errorhandler(500)  # debug为False请情况下才能看到
def error_500(arg):
    print('500会执行我')
    return "服务器内部错误"



# 6  template_global 标签 ,在模板中用  {{sb(1,2)}}
@app.template_global()
def sb(a1, a2):
    return a1 + a2

# 7 template_filter过滤器  在模板中用  {{10|db(1,2)}}
@app.template_filter()
def db(a1, a2, a3):
    return a1 + a2 + a3
复制代码

6 蓝图

复制代码
# blueprint:对目录进行划分,因为之前所有代码都写在一个py文件中,后期肯定要分到多个文件中
# 蓝图就是为了划分目录的
# 使用步骤:
    -1 在不同的view的py文件中,定义蓝图
    -2 使用app对象,注册蓝图
    -3 使用蓝图,注册路由,注册请求扩展

# 不用蓝图划分目录
    # 目录结构
    flask_blueprint
        -static  # 静态文件存放位置
        -templates # 模板存放位置
            -user.html # 用户html页面
        -views    # 视图函数的py文件
            -__init__.py # 里面定义了Flask的app对象
            goods.py    # 商品相关视图
            user.py    # 用户相关视图
        app.py         #启动文件
        
# 蓝图小型项目
flask_blueprint_little  # 项目名
    -src                # 项目代码所在路径
        -__init__.py   # app对象创建的地方
        -templates     # 模板
            -user.html
        -static       # 静态文件
        -views        # 视图函数存放位置
            -user.py  # 用户相关视图
            -order.py # 订单相关视图
    -manage.py  # 启动文件


# 大型项目
flask_blurprint_big   # 项目名字
    -src   # 项目代码所在位置
        -__init__.py # src的init,falsk,app实例化
        -settings.py # 配置文件
        -admin       # 类似于django的admin app
            -__init__.py # 蓝图初始化
            -template   # 模板
                -backend.html
            -static   # 静态文件
                -xx.jpg
            -views.py  # 视图层
            -models.py # models层,后期咱们表模型
        -api
            -__init__.py
            -template
            -static
            -models.py
            -views.py
    -manage.py  # 启动文件
复制代码

 7 g对象

复制代码
# g :global缩写,是关键字,不能用,就写成了g,对象,是一个全局对象,当此请求过程中,一直有效

# 作用:上下文
    -其实是请求的上下文,从请求进来,就有,到请求走了,一直存在,所以在当次请求过程中,如果调用别的函数,不需要把参数传入,只需要放到g对象中,在别的函数中直接使用g获取即可
    
    
# djagno中有没有这个东西?
    request.context
    
# 为什么不把变量放到request对象中?
    担心把requets原来的属性替换掉
    
    
    
# g对象和session有什么区别
    -g只针对于当次请求
    -session针对于所有请求
复制代码

8 flask-session使用

复制代码
# flask内置的session,把数据加密后保存到浏览器了
# 能不能自己写个session的类,重写open_session和save_session,把数据保存到服务端的redis中

# 第三方:flask-session已经把这事干了,可以放到文件,redis,mongodb,关系型数据库

# 下载:
pip3 install -U flask-session  # 升级安装


# 使用:

#方式一:  以后会保存到redis中
from flask_session.sessions import RedisSessionInterface
from redis import Redis
conn=Redis(host='127.0.0.0',port=6379)
# app.session_interface = RedisSessionInterface(redis=None, key_prefix='lqz')
app.session_interface = RedisSessionInterface(redis=conn, key_prefix='lqz')
# 在视图函数中使用即可
@app.route('/')
def index():
    session['name'] = 'pyy'
    return 'hello'



# 方式二: 通用方案,以后集成第三方插件,大致都按这个流程
    from flask_session import Session
    #配置文件
    app.config.from_pyfile('settings.py')
    # from redis import Redis
    # app.config['SESSION_TYPE'] = 'redis'
    # app.config['SESSION_REDIS'] = Redis(host='127.0.0.1',port='6379')
    Session(app) # 导入一个类,把app传入
    
    
    
 # 方案二,源码分析
    本质在 Session(app)--->就是根据配置文件,生成 RedisSessionInterface 对象,赋值给app.session_interface
    
    
    
    
# 1 如何配置session的过期时间
    配置文件:PERMANENT_SESSION_LIFETIME = timedelta(seconds=10)
    

# 2 如何让cookie,关闭浏览器就失效
    # expires 设置为None,就是浏览器关闭cookie就失效了
    res = make_response('hello')
    res.set_cookie('name', 'lqz', expires=None)
    
    #session设置的cookie,关闭浏览器失效
        -使用方式一:app.session_interface = RedisSessionInterface(redis=conn, key_prefix='lqz',permanent=False)
        -使用方式二:配置文件加入
        SESSION_PERMANENT=False
复制代码

9 数据库连接池

9.1 flask中集成mysql

复制代码
import pymysql

app = Flask(__name__)
conn = pymysql.connect(host='127.0.0.1', port=3306, user='root', password='123', database='rbac')
cursor = conn.cursor()
cursor.execute('select * from rbac_prat')
res = cursor.fetchall()
print(res)
cursor.close()
conn.close()
复制代码

9.2 上面代码存在的问题

# 每个请求过来,都打开mysql链接,操作,操作完了,关闭链接
# 如果并发量很高,如果有1w个并发,要开1w mysql的链接,mysql顶不住
    -django就是这么做的

# 解决方案:使用,把连接对象和cursor定义成全局的
    -每个视图函数使用同一个cursor,这样会错乱

 

 

 

9.3 使用数据库连接池

复制代码
# 使用第三方的:DBUtils ,创建数据库连接池

# pip3 install -U DBUtils

# 使用步骤
    #第一步:在py中实例化一个池
    from dbutils.pooled_db import PooledDB
    import pymysql
    # 1 实例化得到对象
    POOL = PooledDB(
        creator=pymysql,   # 使用链接数据库的模块
        maxconnections=6,  # 连接池允许的最大连接数,0和None表示不限制连接数
        mincached=2,       # 初始化时,链接池中至少创建的空闲的链接,0表示不创建
        maxcached=5,       # 链接池中最多闲置的链接,0和None不限制
        maxshared=0,      # 链接池中最多共享的链接数量,0和None表示全部共享。PS: 无用,因为pymysql和MySQLdb等模块的 threadsafety都为1,所有值无论设置为多少,_maxcached永远为0,所以永远是所有链接都共享。
        blocking=True,    # 连接池中如果没有可用连接后,是否阻塞等待。True,等待;False,不等待然后报错
        maxusage=None,    # 一个链接最多被重复使用的次数,None表示无限制
        setsession=[],    # 开始会话前执行的命令列表。如:["set datestyle to ...", "set time zone ..."]
        ping=0,           # ping MySQL服务端,检查是否服务可用。# 如:0 = None = never, 1 = default = whenever it is requested, 2 = when a cursor is created, 4 = when a query is executed, 7 = always
        host='127.0.0.1',
        port=3306,
        user='root',
        password='123',
        database='cnblogs',
        charset='utf8'
    )
    
    # 第二步:在视图函数中使用池
        from db import POOL
        @app.route('/boys')
        def boys():
            # 第一步,从连接池中取一个链接
            conn = POOL.connection()
            cursor = conn.cursor()
            time.sleep(0.01)
            cursor.execute('select * from article')
            res = cursor.fetchall()
            print(res)
            return jsonify(res)
        
        
        
        
  # 总结,
    如果使用池:无论客户端连接数有多大,mysql的连接数,最多就是6个
    如果不使用池:mysql的连接数会过大,把mysql崩掉
    
    
    
    
 # 压力测试:客户端代码
    import requests
       from threading import Thread, get_ident
    def task():
        res = requests.get('http://127.0.0.1:8080/boys')
        print('线程id号为:%s,获取的数据为:' % str(get_ident()), res.json())

    if __name__ == '__main__':
        for i in range(5000):
            t = Thread(target=task)
            t.start()

#链接mysql 查看连接数
    show status like 'Threads%'
复制代码

 

 

 

10 wtfroms(了解)

# 跟 django中学过的forms组件是一个东西
#作用:
    1 校验数据
    2 渲染错误信息
    3 渲染页面
    
#第三方:下载
pip3 install wtfroms

10.1 python代码

复制代码
from flask import Flask, render_template, request, redirect

from wtforms import Form
from wtforms.fields import simple
from wtforms import validators
from wtforms import widgets
from wtforms.fields import choices
app = Flask(__name__, template_folder='templates')

app.debug = True


class LoginForm(Form):
    # 字段(内部包含正则表达式)
    name = simple.StringField(
        label='用户名',
        validators=[
            validators.DataRequired(message='用户名不能为空.'),
            validators.Length(min=6, max=18, message='用户名长度必须大于%(min)d且小于%(max)d')
        ],
        widget=widgets.TextInput(),  # 页面上显示的插件
        render_kw={'class': 'form-control'}

    )
    # 字段(内部包含正则表达式)
    pwd = simple.PasswordField(
        label='密码',
        validators=[
            validators.DataRequired(message='密码不能为空.'),
            validators.Length(min=8, message='用户名长度必须大于%(min)d'),
            validators.Regexp(regex="^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[$@$!%*?&])[A-Za-z\d$@$!%*?&]{8,}",
                              message='密码至少8个字符,至少1个大写字母,1个小写字母,1个数字和1个特殊字符')

        ],
        widget=widgets.PasswordInput(),
        render_kw={'class': 'form-control'}
    )


class RegisterForm(Form):
    name = simple.StringField(
        label='用户名',
        validators=[
            validators.DataRequired()
        ],
        widget=widgets.TextInput(),
        render_kw={'class': 'form-control'},
        default='pyy'
    )

    pwd = simple.PasswordField(
        label='密码',
        validators=[
            validators.DataRequired(message='密码不能为空.')
        ],
        widget=widgets.PasswordInput(),
        render_kw={'class': 'form-control'}
    )

    pwd_confirm = simple.PasswordField(
        label='重复密码',
        validators=[
            validators.DataRequired(message='重复密码不能为空.'),
            validators.EqualTo('pwd', message="两次密码输入不一致")
        ],
        widget=widgets.PasswordInput(),
        render_kw={'class': 'form-control'}
    )

    email = simple.EmailField(
        label='邮箱',
        validators=[
            validators.DataRequired(message='邮箱不能为空.'),
            validators.Email(message='邮箱格式错误')
        ],
        widget=widgets.TextInput(input_type='email'),
        render_kw={'class': 'form-control'}
    )

    gender = choices.RadioField(
        label='性别',
        choices=(
            (1, ''),
            (2, ''),
        ),
        coerce=int # “1” “2”
     )
    city = choices.SelectField(
        label='城市',
        choices=(
            ('bj', '北京'),
            ('sh', '上海'),
        )
    )

    hobby = choices.SelectMultipleField(
        label='爱好',
        choices=(
            (1, '篮球'),
            (2, '足球'),
        ),
        coerce=int
    )

    favor = choices.SelectMultipleField(
        label='喜好',
        choices=(
            (1, '篮球'),
            (2, '足球'),
        ),
        widget=widgets.ListWidget(prefix_label=False),
        option_widget=widgets.CheckboxInput(),
        coerce=int,
        default=[1, 2]
    )

    def __init__(self, *args, **kwargs):
        super(RegisterForm, self).__init__(*args, **kwargs)
        self.favor.choices = ((1, '篮球'), (2, '足球'), (3, '羽毛球'))

    def validate_pwd_confirm(self, field):
        """
        自定义pwd_confirm字段规则,例:与pwd字段是否一致
        :param field:
        :return:
        """
        # 最开始初始化时,self.data中已经有所有的值

        if field.data != self.data['pwd']:
            # raise validators.ValidationError("密码不一致") # 继续后续验证
            raise validators.StopValidation("密码不一致")  # 不再继续后续验证

@app.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'GET':
        form = LoginForm()
        return render_template('login.html', form=form)
    else:
        form = LoginForm(formdata=request.form)
        if form.validate():
            print('用户提交数据通过格式验证,提交的值为:', form.data)
        else:
            print(form.errors)
        return render_template('login.html', form=form)


@app.route('/register', methods=['GET', 'POST'])
def register():
    if request.method == 'GET':
        form = RegisterForm(data={'gender': 2, 'hobby': [1, ]})  # initial
        return render_template('register.html', form=form)
    else:
        form = RegisterForm(formdata=request.form)
        if form.validate():
            print('用户提交数据通过格式验证,提交的值为:', form.data)
        else:
            print(form.errors)
        return render_template('register.html', form=form)


if __name__ == '__main__':
    app.run()
复制代码

10.2 html代码

复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>用户注册</h1>
<form method="post" novalidate style="padding:0  50px">
    {% for field in form %}
    <p>{{field.label}}: {{field}} {{field.errors[0] }}</p>
    {% endfor %}
    <input type="submit" value="提交">
</form>
</body>
</html>
复制代码
复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>登录</h1>
<form method="post" novalidate>
    <p>{{form.name.label}} {{form.name}} {{form.name.errors[0] }}</p>

    <p>{{form.pwd.label}} {{form.pwd}} {{form.pwd.errors[0] }}</p>
    <input type="submit" value="提交">
</form>
</body>
</html>
复制代码

11 信号

复制代码
#  信号和信号量
# 信号量:Semaphore:信号量可以理解为多把锁,同时允许多个线程来更改数据
# 信号:signal:Flask框架中的信号基于blinker,其主要就是让开发者可是在flask请求过程中定制一些用户行为


# 有什么用?
    -代码耦合性低
    -before_render_template :只要模板渲染就会执行,记录日志,login页面被渲染了多少次
    -统计今天用户访问量
    -User表只要删除记录,就干个什么事
    -向banner表存数据,双写一致性问题:定时更新
    
# django得信号:https://www.cnblogs.com/liuqingzheng/articles/9803403.html

# 内置信号
    request_started = _signals.signal('request-started')                # 请求到来前执行
    request_finished = _signals.signal('request-finished')              # 请求结束后执行

    before_render_template = _signals.signal('before-render-template')  # 模板渲染前执行
    template_rendered = _signals.signal('template-rendered')            # 模板渲染后执行

    got_request_exception = _signals.signal('got-request-exception')    # 请求执行出现异常时执行

    request_tearing_down = _signals.signal('request-tearing-down')      # 请求执行完毕后自动执行(无论成功与否)
    message_flashed = _signals.signal('message-flashed')                # 调用flashed在其中添加数据时,自动触发
    appcontext_tearing_down = _signals.signal("appcontext-tearing-down")
    appcontext_pushed = _signals.signal("appcontext-pushed")
    appcontext_popped = _signals.signal("appcontext-popped")

   # 使用步骤:
    1 定义函数
    2 跟内置信号绑定:signals.before_render_template.connect(before_render)
    3 等待信号被触发
    
# 自定义信号
    使用步骤:
        1 定义一个自定义信号
        2 定义一个函数
        3 函数跟自定义信号绑定
        4 某种情况下触发信号的执行
        
        
        
        
# 自定义信号使用:
### 自定义信号
# 第一步:定义要给信号
lqz = _signals.signal('lqz')

# 第二步:定义函数
def test(*args, **kwargs):
    print(args)
    print(kwargs)
    print('我执行了')

# 第三步:绑定自定义的信号
lqz.connect(test)

# 第四步:触发自定义的信号
复制代码

 

 

12 多app应用(了解)

复制代码
from flask import Flask
from werkzeug.serving import run_simple
from werkzeug.middleware.dispatcher import DispatcherMiddleware

app01 = Flask('app01')
app02 = Flask('app02')


@app01.route('/index')
def index():
    return "app01"


@app02.route('/index2')
def index2():
    return "app2"


dm = DispatcherMiddleware(app01, {'/sec': app02, })
if __name__ == '__main__':
    run_simple('localhost', 5000, dm)


# 内部如何执行:
'''
1 请求来了,会执行dm(environ,start_response)
2 dm的__call__ 根据请求的地址,拿到不同的app,执行app(environ,start_response)--->Flask的__call__

'''
复制代码

13 flask-script

复制代码
# flask 的一个第三方插件,完成像django 的  python manage.py runserver 命令操作


# 下载:
pip3 install flask-script

Flask==2.2.2
Flask_Script==2.0.3

# django 中自定义命令
    -第一步:在app中新建包:management
    -第二步:在management下新建包:commands
    -第三步:commands新建py文件,py文件名就是命令名 init.py
    -第四步:init.py写入
    from django.core.management.base import BaseCommand, CommandError
    class Command(BaseCommand):
        def add_arguments(self, parser):
            parser.add_argument('--name', type=str) # 指令接受的参数列表,参数名左边有两个减号,可以添加类型限制

        def handle(self, *args, **options):
            name = options['name'] 
            # 执行这个命令的逻辑是什么
# python manage.py init --name=lqz
复制代码
复制代码
from flask import Flask
from flask_script import Manager

app = Flask('app01')
# 使用第三方模块
manager = Manager(app)


# 自定制命令

@manager.command
def custom(arg):
    """
    自定义命令
    python manage.py custom 123
    """
    print(arg)


@manager.option('-n', '--name', dest='name')
@manager.option('-u', '--url', dest='url')
def cmd(name, url):
    """
    自定义命令(-n也可以写成--name)
    执行: python manage.py  cmd -n lqz -u http://www.oldboyedu.com
    执行: python manage.py  cmd --name lqz --url http://www.oldboyedu.com
    """
    print(name, url)

# 后期可以自己定制一些命令

@app.route('/index')
def index():
    return "app01"


if __name__ == '__main__':
    manager.run()
复制代码

14 threading.local

复制代码
# threading.local 对象
# 多个线程操作同一个变量,如果不加锁,会出现数据错乱问题

# 作用:
线程变量,意思是threading.local中填充的变量属于当前线程,该变量对其他线程而言是隔离的,也就是说该变量是当前线程独有的变量。threading.local为变量在每个线程中都创建了一个副本,
那么每个线程可以访问自己内部的副本变量
# 但是 多个线程同时操作 threading.local 对象 就不会出现数据错乱 -java:ThreadLocal -python:threading.local
复制代码
复制代码
###不使用local对象
# 不用local,会出现数据错乱问题,除非加锁解决
# from threading import Thread
# import time
# from threading import Lock
#
# lqz = -1
# lock = Lock()
#
# def task():
#     lock.acquire()
#     global lqz
#     tem = lqz
#     time.sleep(0.0001)
#     lqz = tem + 1
#     print('---', lqz)
#
#     lock.release()
#
#
# for i in range(10):
#     t = Thread(target=task)
#     t.start()
#
# print(lqz)


####演示2
# from threading import Thread
# import time
# from threading import Lock
# lock = Lock()
# lqz = -1
# def task(arg):
#     # lock.acquire()
#     global lqz
#     lqz = arg
#     time.sleep(0.01)
#     print(lqz)
#     # lock.release()
#
# for i in range(10):
#     t = Thread(target=task,args=(i,))
#     t.start()



# 使用local
from threading import Thread,get_ident
from threading import local
import time


# 特殊的对象
lqz = local()

def task(arg):
    # 对象.val = 1/2/3/4/5
    lqz.value = arg
    time.sleep(0.1)
    print('第:%s条线程的值为:%s'%(get_ident(),lqz.value))


for i in range(10):
    t = Thread(target=task, args=(i,))
    t.start()
复制代码

15 自定义local支持线程和协程

复制代码
# flask 的request,和session 都是全局的,但是我们在不同的视图函数中使用的 是正对于当前这次请求的对象,它的底层就是基于local写的
# flask部署支持多进程线程架构,也支持协程架构,flask内部重写了local,让它支持线程和协程


# local的本质是如何实现的
    -变量对其他线程而言是隔离的
    -local: {'线程id号':{}}
    -设置值:
        -线程1:local.val='lqz'   ---> {'线程1id号':{val:lqz},}
        -线程2:local.val='pyy'   ---> {'线程1id号':{val:lqz},'线程2id号':{val:pyy},}
        
    -取值:
        -线程1:print(local.val)  ---->l={'线程1id号':{val:lqz},'线程2id号':{val:pyy},}--》先当前线程的id号:get_ident()   l[get_ident(线程1)]['val']
        -线程2:print(local.val)  ---->l={'线程1id号':{val:lqz},'线程2id号':{val:pyy},}--》先当前线程的id号:get_ident()   l[get_ident(线程2)]['val']
复制代码
复制代码
# 1 通过字典自定义threading.local
# from threading import get_ident, Thread
# import time
#
# storage = {}
#
#
# def set(k, v):
#     ident = get_ident()  # 当前线程id号
#     if ident in storage: #如果当前线程id号在字典中,表示修改值,直接改即可
#         storage[ident][k] = v #
#     else:  #新增
#         storage[ident] = {k: v}
#
#
# def get(k):
#     ident = get_ident()
#     return storage[ident][k]
#
#
# def task(arg):
#     set('val', arg)
#     v = get('val')
#     time.sleep(0.01)
#     print(v)
#
#
# for i in range(10):
#     t = Thread(target=task, args=(i,))
#     t.start()


# # 2 面向对象版
# from threading import get_ident, Thread
# import time
#
#
# class Local(object):
#     storage = {}
#
#     def set(self, k, v):
#         ident = get_ident()
#         if ident in Local.storage:
#             Local.storage[ident][k] = v
#         else:
#             Local.storage[ident] = {k: v}
#
#     def get(self, k):
#         ident = get_ident()
#         return Local.storage[ident][k]
#
#
# obj = Local()
#
#
# def task(arg):
#     obj.set('val', arg)
#     v = obj.get('val')
#     time.sleep(0.01)
#     print(v)
#
#
# for i in range(10):
#     t = Thread(target=task, args=(i,))
#     t.start()


# 3 重写类的 __setattr__  __getattr
# from threading import get_ident, Thread
# import time
#
#
# class Local(object):
#     storage = {}
#
#     def __setattr__(self, k, v):
#         ident = get_ident()
#         if ident in Local.storage:
#             Local.storage[ident][k] = v
#         else:
#             Local.storage[ident] = {k: v}
#
#     def __getattr__(self, k):
#         ident = get_ident()
#         return Local.storage[ident][k]
#
#
# obj = Local()  # 多个local对象公用一个storage
#
# def task(arg):
#     obj.val = arg
#     v = obj.val
#     time.sleep(0.01)
#     print(v)
#
#
# for i in range(10):
#     t = Thread(target=task, args=(i,))
#     t.start()


# 4 每个对象有自己的存储空间(字典)
# 取值:对象.属性,如果没有属性会触发 __getattr__
# 设置值:对象.属性='值',如果属性不存在,会触发 __setattr__
# 如果属性有,直接就拿回来了
# from threading import get_ident, Thread
# import time
#
#
# class Local(object):
#     def __init__(self):
#         # self.storage = {}   # 只要self.属性,就会调用 __setattr__,内部又掉了self.storage--->递归了
#         #类来调用对象的绑定方法__setattr__,这个方法就会变成函数,有几个值就要传几个值
#         # 本质就是完成 self.storage = {} 要完成的事,但是不会触发递归调用
#         object.__setattr__(self, 'storage', {})
#         # setattr(self,'storage', {})  # 反射的方式设置值,也会触发递归
#
#     def __setattr__(self, k, v):
#         ident = get_ident()
#         if ident in self.storage:
#             self.storage[ident][k] = v
#         else:
#             self.storage[ident] = {k: v}
#
#     def __getattr__(self, k):
#         ident = get_ident()
#         return self.storage[ident][k]
#
#
# obj = Local()  # 每个local对象,用自己的字典
#
#
# def task(arg):
#     obj.val = arg
#     v = obj.val
#     time.sleep(0.01)
#     print(v)
#
#
# for i in range(10):
#     t = Thread(target=task, args=(i,))
#     t.start()


# 6 兼容线程和协程
try:
    from greenlet import getcurrent as get_ident
except Exception as e:
    from threading import get_ident
from threading import Thread
import time


class Local(object):
    def __init__(self):
        # self.storage = {}   # 只要self.属性,就会调用 __setattr__,内部又掉了self.storage--->递归了
        # 类来调用对象的绑定方法__setattr__,这个方法就会变成函数,有几个值就要传几个值
        # 本质就是完成 self.storage = {} 要完成的事,但是不会触发递归调用
        object.__setattr__(self, 'storage', {})
        # setattr(self,'storage', {})  # 反射的方式设置值,也会触发递归

    def __setattr__(self, k, v):
        ident = get_ident()
        if ident in self.storage:
            self.storage[ident][k] = v
        else:
            self.storage[ident] = {k: v}

    def __getattr__(self, k):
        ident = get_ident() # 在协程中,gevent中是获取协程id号,如果在线程中,获取的是线程id号
        return self.storage[ident][k]


obj = Local()  # 每个local对象,用自己的字典


def task(arg):
    obj.val = arg
    v = obj.val
    time.sleep(0.01)
    print(v)


for i in range(10):
    t = Thread(target=task, args=(i,))
    t.start()
复制代码

15.2 flask 自定义的local,支持线程和协程

复制代码
class Local(object):

    def __init__(self):
        object.__setattr__(self, "__storage__", {})
        object.__setattr__(self, "__ident_func__", get_ident)


    def __getattr__(self, name):
        try:
            return self.__storage__[self.__ident_func__()][name]
        except KeyError:
            raise AttributeError(name)

    def __setattr__(self, name, value):
        ident = self.__ident_func__()
        storage = self.__storage__
        try:
            storage[ident][name] = value
        except KeyError:
            storage[ident] = {name: value}
复制代码

16 flask请求上下文分析

复制代码
# 请求来了---》app()----->Flask.__call__--->self.wsgi_app(environ, start_response)
    def wsgi_app(self, environ, start_response):
        # environ:http请求拆成了字典
        # ctx对象:RequestContext类的对象,对象里有:当次的requets对象,app对象,session对象
        ctx = self.request_context(environ)
        error = None
        try:
            try:
                #ctx RequestContext类 push方法
                ctx.push()
                # 匹配成路由后,执行视图函数
                response = self.full_dispatch_request()
            except Exception as e:
                error = e
                response = self.handle_exception(e)
            except:
                error = sys.exc_info()[1]
                raise
            return response(environ, start_response)
        finally:
            if self.should_ignore_error(error):
                error = None
            ctx.auto_pop(error)
            
            
            
            
            
  # RequestContext :ctx.push
 def push(self):
        # _request_ctx_stack = LocalStack() ---》push(ctx对象)--》ctx:request,session,app
        _request_ctx_stack.push(self)
        #session相关的
        if self.session is None:
            session_interface = self.app.session_interface
            self.session = session_interface.open_session(self.app, self.request)

            if self.session is None:
                self.session = session_interface.make_null_session(self.app)
        # 路由匹配相关的
        if self.url_adapter is not None:
            self.match_request()
            
            
            
# LocalStack()  push --->obj 是ctx对象
    def push(self, obj):
        #self._local  _local 就是咱们刚刚自己写的Local的对象---》LocalStack的init初始化的_local---》self._local = Local()---》Local对象可以根据线程区分数据 
        rv = getattr(self._local, "stack", None)
        if rv is None:
            rv = []
            self._local.stack = rv  # self._local.stack 根据不同线程用的是自己的数据
        rv.append(obj)  # self._local.stack.append(obj)
        # {'线程id号':{stack:[ctx]},'线程id号2':{stack:[ctx]}}
        return rv
    
    
    
 # 再往后执行,就会进入到路由匹配,执行视图函数
    # request = LocalProxy(partial(_lookup_req_object, "request"))
    # LocalProxy 代理类---》method---》代理类去当前线程的stack取出ctx,取出当时放进去的request
    视图函数中:print(request.method)
    
    
# print(request) 执行LocalProxy类的__str__方法
# request.method 执行LocalProxy类的__getattr__
    def __getattr__(self, name): #name 是method
        # self._get_current_object() 就是当次请求的request
        return getattr(self._get_current_object(), name)
    
    
 # LocalProxy类的方法_get_current_object
   def _get_current_object(self):
        if not hasattr(self.__local, "__release_local__"):
            return self.__local()
        try:
            return getattr(self.__local, self.__name__)
        except AttributeError:
            raise RuntimeError("no object bound to %s" % self.__name__)
            
            
            
 # self.__local 是在 LocalProxy 类实例化的时候传入的local

# 在这里实例化的:request = LocalProxy(partial(_lookup_req_object, "request"))
# local 是 partial(_lookup_req_object, "request")

#_lookup_req_object ,name=request
def _lookup_req_object(name):
    top = _request_ctx_stack.top  # 取出了ctx,是当前线程的ctx
    if top is None:
        raise RuntimeError(_request_ctx_err_msg)
    return getattr(top, name)  #从ctx中反射出request,当次请求的request
复制代码

 

posted @   shangxin_bai  阅读(57)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示