Flask框架

目录

Flask

1.flask和python web框架介绍

python web框架介绍

本质一样,只要是web框架都会有请求对象响应对象(会有路由、视图函数等东西)

django:大而全,有很多内置和第三方的app让我们使用【主流
flask:小而精,没有那么多内置组件,只完成基本的web框架功能(接收请求返回视图函数执行)。只有借助第三方才能有其他丰富功能【小项目用flask

最近兴起的异步web框架:

fastapi:python的异步web框架,(很多公司在用) https://fastapi.tiangolo.com/zh/
sanic:python的异步web框架,供支持异步高并发请求的web服务(3.6版本后用,但是大多都在用fastapi)

同步框架和异步框架的区别

django3.x之后就支持异步了(但是属于假的异步,并没提升多少效率)

同步框架:一个线程只处理一个请求

异步框架:一个线程可以处理多个请求

以上可以看出异步框架可以显著提高并发量

flask介绍

flask是一个基于Python开发且依赖jinja2模板和Werkzeug(基于WSGI服务的一个微型框架)

jinja2是模板语法,与django的dtl非常像

Werkzeug是基于wsgi的工具包,它封装了Request、Response等(django使用的是wsgiref

  • 用wsgiref写web
from wsgiref.simple_server import make_server


# 2.wsgi规定必须传以下两个参数:http请求的东西、http响应的东西。【下面的就等同于django的框架,只不过django把environ包装成了request】
def mya(environ, start_response):
    # print(environ)
    start_response('200 OK', [('Content-Type', 'text/html')])
    # 如果是/index则打开后读取做成response返回
    if environ.get('PATH_INFO') == '/index':
        with open('index.html', 'rb') as f:
            data = f.read()
    # 否则把Hello做成response返回
    else:
        data = b'<h1>Hello</h1>'
    return [data]  # 做成了response返回data


if __name__ == '__main__':
    # 1.监听地址(不写就是127.0.0.1)、端口、可调用对象  当请求来时会执行mya(等同django中的application)加括号
    myserver = make_server('', 8008, mya)
    print('监听8010')
    myserver.serve_forever()
  • 用Werkzeug写web

需先安装flask,见flask快速使用

from werkzeug.wrappers import Request, Response


@Request.application
def hello(request):
    # 2.因为里面没做路由匹配 所以访问任何地址都会返回Hello
    return Response('Hello')


if __name__ == '__main__':
    # 1.导入一个run_simple
    from werkzeug.serving import run_simple
    # 监听地址、端口、可调用对象
    run_simple('localhost', 4000, hello)

总结:以前用的那些web框架其实就是别人帮我们写的一堆代码,用wsgirefWerkzeug做了服务器

2.flask快速使用

安装flask

pip install flask

flask安装命令会连带安装:MarkupSafe、Werkzeug、 Jinja2、 flask ,所以如果卸载flask不要忘记卸载其他三个

flask有两个版本分别是:1.x、2.x。 两者本质上使用没区别,只是2.x源码上变更了一些东西

创建flask

其实django中帮我们写了flask的基础代码,我们可以直接使用创建

image

不过下面我们研究创建python文件自行写flask代码

from flask import Flask

# 需要传一个字符串,一般大家都是放双下name(反正后面也用不到)
app = Flask(__name__)


# 注册路由>>需用装饰器
@app.route('/index')    # 这里可以写methods=['GET', 'POST']来表示支持GET请求和POST请求
def index():
    return 'index'


@app.route('/')
def home():
    return 'home'


if __name__ == '__main__':
    # 以下用来区分局域网都可以访问还是只有本地才可以访问
    # app.run('127.0.0.1',5000)
    app.run()
    
'此时访问127.0.0.1:5000 访问根路径页面即可显示home,访问127.0.0.1:5000/index 即可显示index'

登录、显示用户信息案例

利用自己创建python文件写flask代码去实现登录、显示用户信息案例

步骤一:创建templates文件夹里面写login.html登录页面,用python文件写登录逻辑

【login.html】登录页面:

<body>
    <form method="post">
        <p>username:<input type="text" name="username"></p>
        <p>password:<input type="password" name="password"></p>
        <!--把要渲染的数据直接插在这里-->
        <input type="submit" value="登录">{{error}}
    </form>
</body>

【登录逻辑】:

注意:flask不同于django,没有request对象,但是并不代表真的没有!【只要是web框架一定有请求对象和响应对象

flask会用一个全局的requestfrom flask import Flask, request

提问:request是全局的,如果下面再写一个视图函数,那request是谁的?
回答:# 每个视图函数中的request用的是自己的request
--------------------------------------
session 同理
from flask import Flask, session

#但是如果要使用session则需要在页设置密钥
app.secret_key = 'xxxxxxxxxxx'
from flask import Flask, request, render_template, redirect, session

app = Flask(__name__)
# 配置session密钥
app.secret_key = 'xxxxxxxxxxx'


@app.route('/login', methods=['GET', 'POST'])
def index():
    # 如果get请求返回login.html登录模板页面
    if request.method == 'GET':
        return render_template('login.html')
    # post请求校验数据
    else:
        # 取出前端传来的用户名和密码,等同于django中的request.POST
        username = request.form.get('username')
        password = request.form.get('password')
        if username == 'zy' and password == '123':
            # 当校验通过保存登录状态到session中
            session['name'] = username
            # 并重定向到根路径
            return redirect('/')
        else:
            # 校验失败则继续返回login.html登录模板页面,并提示密码错误(login.html上写了{{error}})
            return render_template('login.html', error='用户名或密码错误')  # 这里和django的render不同,要模板渲染的数据直接k=v


if __name__ == '__main__':
    app.run()

当用户名或密码错误时则显示渲染出来的错误页面

image

步骤二:写登录成功后的home页面、判断是否登录成功的逻辑

【home.html】:

<body>
		<h1>用户列表</h1>
        <table>
            {% for k,v in user_dict.items() %}
            <tr>
                    <td>{{k}}</td>
                    <td>{{v.name}}</td>
                    <td>{{v['name']}}</td>
                    <td>{{v.get('name')}}</td>
                    <td><a href="/detail/{{k}}">查看详情</a></td>
            </tr>
            {% endfor %}
        </table>
</body>

【校验是否登录】:

USERS = {
    1: {'name': '张三', 'age': 18, 'gender': '男'},
    2: {'name': '李四', 'age': 28, 'gender': '男'},
    3: {'name': '王五', 'age': 18, 'gender': '女'},
}
    
#校验是否登录逻辑
@app.route('/')
def home():
    if session.get('name'):
        # 登录用户可进home页面
        return render_template('home.html',user_dict=USERS)
    else:
        # 否则重定向到登录页面
        return redirect('/login')

如果登录成功,访问首页则进入home页面,否则重定向到登录页面

image

步骤三:写根据id查看用户详细信息

【detail.html】

<body>
    <h1>用户详情</h1>
        <p>姓名:{{user.name}}</p>
        <p>年龄:{{user['age']}}</p>
        <p>性别:{{user.get('gender')}}</p>
</body>

【用户详细信息逻辑】

# 用户详细信息逻辑
@app.route('/detail/<int:pk>')
def detail(pk):
    if session.get('name'):
        # 如果登录了就可以通过点击的pk拿到该用户的详细信息
        user_detail = USERS.get(pk)
        return render_template('detail.html', user=user_detail)
    else:
        return redirect('/login')

登录后即可查看任意用户详细信息

image

步骤四:在页面上显示json格式

from flask import Flask, jsonify

@app.route('/test')
def test:
    return jsonify({'name':'zy', 'age':18})

发给前端json格式数据

image

涉及新知识总结
1.注册路由:
app.route(路径, methods=[请求方式, 请求方式])

2.新手四件套
-render_template    渲染模板
-redirect                  重定向
-return                    返回字符传
-jsonify                   返回json格式字符串  # 和django一样必须是字典或列表

3.请求的reuqest对象是全局的,直接导入使用即可#(每个视图函数中的request用的是自己的request)
-request.method  请求方式
-request.form  获取前端POST请求传来的数据 等同于request.POST  详细点就是:post请求的body体中的内容转成了字典可以.get()取值

4.session 同样也是全局的,且互不干扰。# 但是必须要指定密钥 app.secret_key = '随便写密钥'
-放值:session['xxx'] = 'yyy'
-取值:session.get('xxx')

5.模板的渲染
-兼容django的dtl
-更强大 且 可以加括号 字典可以采用.get   .values()   .item()....
-{% for %}也一样

6.转换器 
@app.route('/detail/<int:pk>')

3.配置文件使用方式

django的配置文件叫settings

flask的配置文件有多种使用方式:

flask的所有配置都放在app中了,直接使用app对象获取配置信息即可

【方式一】:测试用 只能放debug和secret_key

app = Flask(__name__)

app.debug = True  # 调试模式,错误提示信息更详细,修改完代码会自动重启
app.secret_key = 'xxxxxx'  # 密钥

【方式二】:用app.config设置

app.config['DEBUG'] = True
app.config['SECRET_KEY'] = 'xxxxxxx'

打印app.config可以看到里面的配置有哪些

image

【方式三】:新建一个'settings.py'里面写配置 并导入(不常用)

# 【settings.py】
DEBUG = True
SECRET_KEY = 'xxxxxx'
-------------------------------------------
# 导入配置文件使用
app.config.from_pyfile('settings.py')

方式四】:配置文件中用类的方式区分不同配置(常用

同样要新建一个'settings.py',不过里面写上类分别是【测试的】、【上线的】等其他

区别于django,django做不到所以才会建两个配置文件(dev、prod)

#【settings.py】 写一个父类 让两个子类分别继承 做不同修改来区分测试和上线的配置
class BASE(object):
    DEBUG = False
    
class DevelopmentConfig(BASE):
    pass

class ProductionConfig(BASE):
    pass
------------------------------------------
# 直接使用不同的类来区分要用的配置
# app.config.from_object('settings.DevelopmentConfig') 
app.config.from_object('settings.ProductionConfig')

【方式五】:通过环境变量配置

app.config.from_envvar('环境改变量的名字')

【方式六】:json

app.config.from_json('json文件名称')

# JSON文件名称必须是json格式,因为内部会执行json.loads

【方式七】:字典格式

app.config.from_mapping({'DEBUG':True})

额外补充:配置中心

主要在微服务中会使用:一个项目拆成N个服务,每个服务都有自己的配置文件,当把服务开在10台机器上,就需要一台一台去改配置。 用了配置中心统一去改

内置的配置字段

其他可以写自己的,如:redis的连接地址、mysql的连接地址

DEBUG                                                 # 是否开启Debug模式
SECRET_KEY                                         # 密钥
SESSION_COOKIE_NAME                     # cookie过期时间
PERMANENT_SESSION_LIFETIME         # session的名字

4.路由系统

1)路由的本质

django中配置路由 是在urls.py中写path,写在一个列表中

flask大多是基于装饰器来做,很少会有抽取到urls.py里

(1)路由装饰器源码

装饰器其实就是闭包函数
装饰器的作用:不改变程序源代码和调用方式的基础上,为程序增加新功能
装饰器的本质:被装饰后再执行被装饰的函数,其实执行的不是之前的函数了,而是装饰器返回的函数。
装饰器语法糖:python的特殊语法,把被装饰的函数当作参数传给装饰器,并把装饰器的执行结果赋值给被装饰的函数

@app.route('/login')
def index():
    pass
---------点进route------------------------------

    def route(self, rule: str, **options: t.Any) -> t.Callable[[T_route], T_route]:
        # rule是路径
        # 其他参数都给了options
        def decorator(f: T_route) -> T_route:
            endpoint = options.pop("endpoint", None)
            self.add_url_rule(rule, endpoint, f, **options)
            return f
        return decorator
    
---------发现route中返回了decorator---------

所以会把index当作参数传到  def decorator()中,也就是:
def decorator(index):  # f就是index
      endpoint = options.pop("endpoint", None) # 此时endpoint = None
      # 核心:self是实例化flask得到的app对象
      # app对象中有一个方法:add_url_rule,这是【在添加路由】 
      self.add_url_rule(rule, endpoint, f, **options)
      return f
(2)flask路由的本质

就是app对象的add_url_rule来完成路由注册

(3)自己注册路由

现在我们明白route里最重要的是在添加路由,那么我们也可以自己去写注册路由不用装饰器的方式:

def home():
    return '这是自己写的路由'

# 自己写的注册路由
app.add_url_rule('/', endpoint=None, view_func=home, methods=['GET'])

当访问根路径时即可发现成功了

image

仔细看app.add_url_rule('/', endpoint=None, view_func=home, methods=['GET'])会发现它很像django中的path

2)路由的参数add_url_rule

-rule                                # URL规则
-view_func                      # 视图函数名称
-defaults = None            # 默认值,当URL中无参数,函数需要参数时,使用defaults = {'k':'v'}为函数提供参数
-endpoint = None           # 路径的别名,用于反向解析URL,即url_for('名称')
-methods = None           # 允许的请求方式,如:['GET', 'POST']

-----------以上必须记住---------------------

-strict_slashes = None    # 对URL最后的/符号是否严格要求
-redirect_to = None        # 重定向到指定地址

3)转换器

※ 'default':          UnicodeConverter,   # 不写默认就是default<pk>
    'string':           UnicodeConverter,
    'any':              AnyConverter,
※ 'path':             PathConverter,
※ 'int':              IntegerConverter,   # <int:pk>
    'float':            FloatConverter,
    'uuid':             UUIDConverter,

4)cbv写法

基于类的视图

from flask import Flask, request
from flask.views import MethodView

app = Flask(__name__)
app.debug = True

# 写视图类继承MethodView,类中写和请求方式同名的方法
class IndexView(MethodView):
    def get(self):
        print(request.method)
        return '这是get请求'
    
    def post(self):
        print(request.method)
        return '这是post请求'

# 自己注册路由
app.add_url_rule('/index', endpoint='index', view_func=IndexView.as_view('index'))

if __name__ == '__main__':
    app.run()

5)cbv源码分析

------入口还是as_view------------------------------
IndexView.as_view('index')
---1.点击as_view分析源码------------------------------

def as_view(cls, name, *class_args, **class_kwargs)
    def view(**kwargs):
        # 下面是加了异步,本质是在执行self.dispatch_request
        return current_app.ensure_sync(self.dispatch_request)(**kwargs)
    return view

--------发现IndexView.as_view('index')执行完的结果是函数view的内存地址----------
所以当请求来了会先执行view()>>>也就是执行:self.dispatch_request

---2.点击dispatch_request分析源码---------------------------
# 注意:我们继承的是MethodView,不是View 不要找错了

    def dispatch_request(self, **kwargs):
        # 反射,self:视图类的对象
        meth = getattr(self, request.method.lower(), None)
        # 下面是加了异步,本质是在执行meth()
        return current_app.ensure_sync(meth)(**kwargs)

image

以上源码读完发现和django没什么区别,大致一样

(1)as_view()必传参

上面我们发现as_view()中必须传一个参数name,这个参数就是别名,即便我们有endpoint别名,这个name也必须传

在as_view中往下看有一个view.__name__ = name 这是在修改函数的名字变成我们传进去的

# 扩展:
'路径中如果不传别名,就会用函数名作为别名'

分析源码可得:
	@app.route('/index')    # 如果没有传endpoint的值那就是None >>调用app.add_url_rule传入None
    
    if endpoint is None:
        #_endpoint_from_view_func 就是返回函数的名字
        endpoint = _endpoint_from_view_func(view_func)

view是as_view内的内层函数:闭包函数

如果不传参数,所有人的别名(endpoint)都是内层函数view,反向解析时会报错

(2)登录认证装饰器和路由装饰器

flask的路由注册会用到装饰器,这个时候我们又写了一个登录认证的装饰器,那谁在上谁在下?

# 登录认证装饰器放在路由装饰器下面,如果放在路由装饰器上面那路由都没匹配成功你还认证个毛啊

但是有个问题:当被登录认证装饰后,视图函数就变成了装饰器的内层函数,如果在多个视图函数上写该装饰器,那所有的__name__都一样了,所以:【#加装饰器,那路由必须指定传endpoint,如果不传会报错】
(3)视图类继承谁

视图类必须继承MethodView,如果继承View,它的dispatch_request没有具体实现(在抛异常)还需重写,为了不那么麻烦所以我们选择继承MethodView

#【View中在抛异常 没有具体实现功能 还需要重写】
def dispatch_request(self) -> ft.ResponseReturnValue:
    raise NotImplementedError()
(4)视图类属性加装饰器

在视图类属性上decorators即可

class IndexView(MethodView):
    decorators = [auth]  # 装饰器
    def get(self):
        print(request.method)
        return 'get请求'

查看decorators源码:

# cls是视图类,里面有decorators
if cls.decorators:
    for decorator in cls.decorators:
        view = decorator(view)  # view = auth(view)

6)cbv源码总结

①as_view执行流程和django中的一样

②如果路径不传别名,那别名就是函数名

③多个视图函数加装饰器,注意和路由的上下顺序且必须传endpoint

④视图类必须继承MethodView,否则就要重写dispatch_request

⑤视图类加装饰器:写类属性decorators = [auth]

5.模板语法

以下仅作了解

模板的路径必须是【templates】,且要和app【同级】

Flask源码中:

    def __init__(
        self,
        ....
        static_folder: t.Optional[t.Union[str, os.PathLike]] = "static",  # 静态文件资源
        template_folder: t.Optional[t.Union[str, os.PathLike]] = "templates",  # 因为这里定义了要是templates,如果我们传参数把它改为abc,那么我们模板的目录名就可以叫abc。一般不做修改 顶多改路径时会用到
    )

【index.html】

<body>
    <hr>
    <h1>模板语法:static</h1>
    <img src="/static/default.png" alt="">

    <hr>
    <h1>模板语法:if</h1>
    {% if name %}
    <h2>Hi{{name}}</h2>
    {% else %}
    <h2>没名字</h2>
    {% endif %}

    <hr>
    <h1>模板语法:标签渲染</h1>
    {{a|safe}}  <!--渲染了标签-->
    {{a}}  <!--出来的是字符串-->
    {{b}}  <!--渲染了标签-->

    <hr>
    <h1>模板语法:执行函数</h1>
    {{add(1,2)}}  <!--django不支持该写法-->
</body>

【py】

from flask import Flask, render_template, Markup

app = Flask(__name__)
app.debug = True


def add(a, b):
    return a + b


@app.route('/')
def index():
    # 标签渲染,方式一:在页面上用a|safe
    a = '<a href="http://www.baidu.com">点击进入百度</a>'  # 不存在xss攻击,处理了
    # 标签渲染,方式二:Markup包起来
    b = Markup(a)
    return render_template('index.html', name='zy', a=a, b=b, add=add)


if __name__ == '__main__':
    app.run()

image

6.请求和响应

请求:全局的request对象,在不同视图函数中大胆用,不会错乱

响应:四件套(响应对象需要用make_response包一下四件套)

from flask import Flask, request, make_response, render_template

app = Flask(__name__)
app.debug = True


@app.route('/', methods=['GET', 'POST'])
def index():
    # 请求
    print(request.method)  # 提交的方法
    print(request.args)  # get请求提及的数据
    print(request.form)  # post请求提交的数据
    print(request.values)  # post和get提交的数据总和
    print(request.cookies)  # 客户端所带的cookie
    print(request.headers)  # 请求头
    print(request.path)  # 不带域名,请求路径
    print(request.full_path)  # 不带域名,带参数的请求路径
    print(request.url)  # 带域名带参数的请求路径
    print(request.base_url)  # 带域名请求路径
    print(request.host_url)  # 域名
    print(request.host)  # 127.0.0.1:500
    # 获取前端传来的文件并保存下来
    obj = request.files['文件名']
    obj.save(obj.filename)

    # 响应(前面的四件套)(新手四件套都用make_response包起来)
    # 1.响应头中写上cookie
    # response = 'hello'
    # res = make_response(response)
    # # print(type(res))  # 发现res变成了响应对象
    # res.set_cookie('xx','xx')  # 往cookie中写值
    # return res
    
    # 2.响应头中写数据
    response = render_template('index.html')
    res = make_response(response)
    print(type(res))
    res.headers['yy'] = 'yy'
    return res


if __name__ == '__main__':
    app.run()

7.session及源码分析

1)session的使用

【login.html】

<body>
    <form method="post">
        <p>账号:<input type="text" name="name"></p>
        <p>密码:<input type="password" name="pwd"></p>
        <p><input type="submit" value="提交"></p>
    </form>
</body>

【py】

from flask import Flask, request, session, render_template, redirect

app = Flask(__name__)
app.debug = True
app.secret_key = 'sdfafdsdf'


@app.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'GET':
        return render_template('login.html')
    else:
        name = request.form.get('name')
        pwd = request.form.get('pwd')
        # 只要登录成功即保存session
        session['name'] = name
        return redirect('/index')


@app.route('/index', methods=['GET', 'POST'])
def index():
    # 如果有则拿名字,没有则拿匿名用户
    return 'hello %s' % session.get('name', '匿名用户')


if __name__ == '__main__':
    app.run()

此时如果在不同浏览器登录成功进入index页面 分别显示的是不同浏览器登录的用户名。证明session也一样自己是自己的不会乱

2)session源码分析

cookie:存在于客户端浏览器上的键值对

session:存在于服务端上的键值对

django:把session存放在django_session表中
flask:把它加密后放在cookie中,如果session发生变化那cookie也会跟着变

【flask中session源码】:

app.session_interface

------1.点进session_interface发现里面配置了一个类的对象,这就是session的执行流程----------
session_interface: SessionInterface = SecureCookieSessionInterface()
    
------2.继续点进SecureCookieSessionInterface中查看-----------------------------------------
# 发现里面有两个重要的方法:请求来执行open_session,请求走执行save_session

      # 请求来执行的方法
      def open_session(self, app, request) :
		#1 取出前端传入cookie的value值
        val = request.cookies.get(self.get_cookie_name(app))
        #2 如果没有,则构造一个空session对象
        if not val:
            return self.session_class()
        # 取出过期时间
        max_age = int(app.permanent_session_lifetime.total_seconds())
        try:
            # 如果没有过期,解码做成session对象,后续直接用session即可
            data = s.loads(val, max_age=max_age)
            return self.session_class(data)
        except BadSignature:
            # 如果过期了,也是空session
            return self.session_class()


    # 请求走执行的方法
    def save_session(self, app, session, response) :
        name = self.get_cookie_name(app)
		# 取出过期时间,和把session加密转成字符串,放到cookie中
        expires = self.get_expiration_time(app, session)
        # 把session转成字符串
        val = self.get_signing_serializer(app).dumps(dict(session))
        response.set_cookie(
            name,
            val, 
            expires=expires,
        )

假如想把session放到redis、mysql中怎么做?

只需要写个类,重写open_session,save_session自己写

其实已经有人第三方写好了,后面会讲

3)session执行原理

image

8.闪现

前后端混合时用的多,分离几乎不用

闪现flash:当次请求先把一些数据放在某个位置,下一次请求再把这些数据取出来,取完就没有了

闪现作用:①可以跨请求保存数据 ②当次请求访问出错就重定向到其他地址,到了该地址后可以拿到当时的错误(拿了一次后里面就没有了

django中也有类似的东西:message框架

用法

from flask import Flask, flash, get_flashed_messages

#【设置 闪现】  
        -flash(f'{name}写进的闪现')  # 可以设置多次,放到列表中
    
        -flash('超时错误',category="debug")  # 分类存(可设置一个标签)
        
        
#【获取 闪现】
        -get_flashed_messages()  # 取完就删除
    
        -get_flashed_messages(category_filter=['debug'])  # 分类取(只取该标签的)

本质就是放到session中,如果清空session再获取会发现变成空了。

from flask import Flask, request, flash, get_flashed_messages

app = Flask(__name__)
app.secret_key = 'xadafs'


# 设置闪现
@app.route('/set_flash')
def set_flash():
    name = request.args.get('name')
    flash(f'{name}写进的闪现')
    return '设置闪现'


# 获取闪现
@app.route('/get_flash')
def get_flash():
    res = get_flashed_messages()
    print(res)
    return '获取闪现'


if __name__ == '__main__':
    app.run()

当访问http://127.0.0.1:5000/set_flash?name=zs时,再访问http://127.0.0.1:5000/get_flash就会打印出['zs写进的闪现'],当执行多次set_flash时就会存入多次。取的话是一次全取出。打开两个浏览器写入获取的不通用。

9.请求扩展

请求来、请求走中可以绑定一些函数,到这里时就会执行该函数,类似于django的中间件。flask用请求扩展来代替django中间件

# 请求扩展都有哪些:

    -before_request:         # 请求来了会走,如果返回了四件套就结束了
    -after_request :           # 请求走了会走,一定要返回response对象
    -before_first_request: # 第一次来会走
    -teardown_request:    # 无论是否出异常,都会走
    -errorhandler:             # 监听状态码,404  500
-------' 以下忽略即可'----------
    -template_global:       # 标签
    -template_filter:          # 过滤器

1)before_request请求来

2)after_request请求走

# 请求来执行一个函数(来是从上往下执行)
@app.before_request
def before():
    print('我来了')


# 请求走执行一个函数(走是从下往上执行)
@app.after_request
def after(response):  # 必须返回response
    print('我走了')
    return response

当有多个时 请求1来了>>请求2来了>>请求2走了>>请求1走了

当请求来时返回的不再是None,说明被拦截了就不让走了 请求1来了>>请求2走了>>请求1走了

3)before_first_request第一次请求来

因为没人用,已经被弃用了

# 项目启动后的第一次请求来才执行
@app.before_first_request
def first():
    print('第一次请求才执行')

4)teardown_request无论是否出错都走

常用在记录错误日志

@app.teardown_request
def teardown(e):
    print(e)
    print('不管有没有错我都执行')

5)errorhandler监听响应状态码

监听响应状态码,只有符合监听的状态码才会走

@app.errorhandler(404)
def error_404(arg):
    return '监听到404我才走'

6)template_global标签

@app.template_global()
def add(a1, a2):
    return a1 + a2

7)template_filter过滤器

@app.template_filter()
def db(a1, a2, a3):
    return a1 + a2 + a3

10.蓝图

蓝图(blueprint):对程序进行目录结构划分

1)不用蓝图划分目录

├── no_blueprint_flask    # 项目名
    ├── src                         # 核心代码位置
         ├── __init__.py         # 包:里面实例化得到app对象
         ├── models.py        # 表模型
         └── views.py           # 视图函数
    ├── static                     # 静态文件资源
    ├── template               # 模板
    └── manage.py            # 启动文件

image

蓝图的使用步骤

# 第一步:导入蓝图类
from flask import Blueprint
# 第二步:实例化得到蓝图对象,可以指定static和templates
user_bp=Blueprint('user',__name__)
# 第三步:在不同的views.py中使用蓝图注册路由
@us.route('/user')
def userinfo():
    pass
-----------------------------------------------------
# 第四步:app注册蓝图,可以指定前缀
app.register_blueprint(us)

①蓝图可以有自己的静态文件和模板

②注册蓝图时可以使用前缀,必须以/开头 url_prefix='/前缀'

2)用蓝图划分中小型项目

├── little_blueprint_flask    # 项目名
    ├── src                                # 核心代码
        ├── static                            # 静态文件
            └── 1.png                           # 图片
        ├── templates                     # 模板文件
            └── user.html                     # 模板
        ├── views                            # 视图函数
            └── user.py                         # 用户视图
        ├── __init__.py                     # 包:里面实例化得到app对象
        └── models.py                    # 表模型
    └── manage.py                   # 启动文件

image

views>>user.py

# 1.导入蓝图类
from flask import Blueprint, render_template

# 2.实例化得到蓝图类对象
user_bp = Blueprint('user', __name__)

# 3.使用蓝图注册路由
@user_bp.route('/user')
def userinfo():
    return render_template('user.html', name='zy')

src>>__init__.py

from flask import Flask

app = Flask(__name__)
app.debug = True
app.secret_key = 'sdfaf'

# 4.注册蓝图
from .views.user import user_bp
app.register_blueprint(user_bp)

manage.py启动

from src import app

if __name__ == '__main__':
    app.run()

3)用蓝图划分大型项目

有多个app

├── big_blueprint_flask        # 项目名
    └── src                              # 核心文件
        ├── admin                    # admin的app
            ├── static                  # 静态文件
            ├── templates           # 模板文件目录
                └── admin.html    # 模板文件
            ├── __init__.py           # 包
            ├── models.py           # 表模型
            └── views.py              # 视图函数
        ├── home                      # home的app
        ├── __init__.py               # 包
        └── settings.py              # 配置文件
    └── manage.py                  # 启动文件

image

src>>admin>>__init__.py

# 1.导入蓝图类
from flask import Blueprint

# 2.实例化得到蓝图对象,并用自己的模板和静态资源
admin_bp = Blueprint('admin', __name__, template_folder='templates', static_folder='static')

from . import views

src>>admin>>views.py

from flask import render_template
from . import admin_bp

# 3.使用蓝图类注册路由
@admin_bp.route('/admin')
def admin():
    return render_template('admin.html')

src>>__init__.py

from flask import Flask

app = Flask(__name__)
app.config.from_pyfile('settings.py')

# 注册蓝图
from .admin import admin_bp
from .home import home_bp

# 加上url的前缀
app.register_blueprint(admin_bp, url_prefix='/admin')
app.register_blueprint(home_bp, url_prefix='/home')

'加了前缀后访问127.0.0.1:5000/前缀/路由 即可访问'

11.g对象

1)什么是g对象

from flask import g

g对象就是global的缩写,因为它在python中是个关键字,不能以关键字做变量名,所以用了g

g对象就是专门用来存储用户信息的。

g对象:当此请求的全局对象,可以在请求中放值取值

它是全局的,在任意位置都可以导入使用

这个在其他框架中叫context上下文,django用request

2)为何不用request做上下文

# 为什么不学django用request作为上下文?

因为使用request可能会造成request'数据污染',容易不小心改了request的属性而我们察觉不到
所以建议用g,它是空的。放入后在当次请求中全局优先

今后想在当次请求中放一些数据后面再使用,就可以使用g对象

3)g对象和session的区别

g对象只针对'当次请求',这次请求放就有,下次请求不放就没有

session针对'多次请求',这次请求放就有,下次请求不放还有

session对象是可以跨request的,只要session还未失效,不同的request的请求会获取到同一个session

但是g对象不是,g对象不需要管过期时间,请求一次就g对象就改变了一次,或者重新赋值了一次

4)代码操作

from flask import Flask, g, request

app = Flask(__name__)
app.debug = True


def add(a, b):
    print(g.name)
    return a + b


@app.route('/')
def index():
    # 从url?后取出name
    name = request.args.get('name')
    g.name = name
    res = add(1, 2)
    print(res)
    return 'index'


if __name__ == '__main__':
    app.run()

image

12.数据库连接池

django中没有数据库连接池,可以使用第三方

1)pymysql连接数据库

pymysql,在一个视图函数中创建一个连接对象操作,操作完就关闭连接。

连接对象不要用单例,可能会出现数据错乱问题

(1)不用数据库连接池

推导一:在视图函数中创建pymysql的连接操作数据,操作完关闭连接返回给前端

from flask import Flask,jsonify
import pymysql

app = Flask(__name__)
app.debug = True


@app.route('/article')
def article():
    # 从mysql的cnblogs库中的article表中取出数据

    # 连接mysql服务端
    conn = pymysql.connect(
        user='root',
        password="",
        host='127.0.0.1',
        database='cnblogs',
        port=3306,
    )
    # 产生游标对象(数据组成列表套字典格式,不写就是列表套元组)
    cursor = conn.cursor(pymysql.cursors.DictCursor)
    # 发给服务端sql语句:拿两条数据
    cursor.execute('select id,title from article limit 2')
    # 获取sql语句执行结果
    res = cursor.fetchall()
    # print(res)
    return jsonify(res)


if __name__ == '__main__':
    app.run()

当访问http://127.0.0.1:5000/article 即可在前端显示拿到的数据

这样做有问题:来一个请求创建一个连接,请求结束连接关闭(django就是这样做的)

推导二:把连接对象做成全局,在视图函数中使用全局的连接查询、返回给前端

from flask import Flask, jsonify
import pymysql

app = Flask(__name__)
app.debug = True

# 连接mysql服务端
conn = pymysql.connect(
    user='root',
    password="",
    host='127.0.0.1',
    database='cnblogs',
    port=3306,
)


@app.route('/article')
def article():
    # 从mysql的cnblogs库中的article表中取出数据

    # 产生游标对象(数据组成列表套字典格式,不写就是列表套元组)
    cursor = conn.cursor(pymysql.cursors.DictCursor)
    # 发给服务端sql语句:拿两条数据
    cursor.execute('select id,title from article limit 2')
    # 获取sql语句执行结果
    res = cursor.fetchall()
    # print(res)
    return jsonify(res)


if __name__ == '__main__':
    app.run()

当访问http://127.0.0.1:5000/article 也可在前端显示拿到的数据

这样做有问题:会出现数据错乱,如果同时来两个请求分别查两个表,当取的时候请求1可能取得是请求2的数据,请求2拿的时候发现是空的

image

(2)用数据库连接池

为了解决上面两种遇到的问题,采用数据库连接池

第一步:# 创建一个全局的池
第二步:# 每次进入视图函数,从池中取一个连接使用。使用完就放回到池中,只要控制池的大小就能控制mysql的连接数

这里我们采用第三方数据库连接池操作:dbutils

# 安装
pip install dbutils
# 步骤
1.导入,实例化得到一个pool对象
2.pool做成单例以模块导入
3.从pool池中拿到一个连接    'pool.connection()'
4.使用连接获得游标:'cursor',使用游标操作数据库

新建POOL.py

from dbutils.pooled_db import PooledDB
import pymysql

pool = PooledDB(
    creator=pymysql,  # 使用链接数据库的模块
    maxconnections=6,  # 连接池允许的最大连接数,0和None表示不限制连接数
    mincached=2,  # 初始化时,链接池中至少创建的空闲的链接,0表示不创建
    maxcached=5,  # 链接池中最多闲置的链接,0和None不限制
    maxshared=3,
    # 链接池中最多共享的链接数量,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='123456',
    database='s8day127db',
    charset='utf8'
)
from flask import Flask, jsonify
import pymysql

app = Flask(__name__)
app.debug = True

# 导入池
from .POOL import pool


# 使用池
@app.route('/article_pool')
def article_pool():
    # 从池中拿一个链接
    conn = pool.connection()
    # 产生游标对象(数据组成列表套字典格式,不写就是列表套元组)
    cursor = conn.cursor(pymysql.cursors.DictCursor)
    # 发给服务端sql语句:拿两条数据
    cursor.execute('select id,title from article limit 2')
    # 获取sql语句执行结果
    res = cursor.fetchall()
    # print(res)
    return jsonify(res)


if __name__ == '__main__':
    app.run()

2)对两种压力测试

用连接池和不用连接池

from flask import Flask, jsonify
import pymysql

app = Flask(__name__)
app.debug = True

from .POOL import pool
import time
import random


# 使用池
@app.route('/article_pool')
def article_pool():
    # 从池中拿一个链接
    conn = pool.connection()
    # 产生游标对象(数据组成列表套字典格式,不写就是列表套元组)
    cursor = conn.cursor(pymysql.cursors.DictCursor)
    # 发给服务端sql语句:拿两条数据
    cursor.execute('select id,title from article limit 2')
    # 获取sql语句执行结果
    res = cursor.fetchall()
    # print(res)
    return jsonify(res)


# 不使用池
@app.route('/article')
def article():
    # 连接mysql服务端
    conn = pymysql.connect(
        user='root',
        password="",
        host='127.0.0.1',
        database='bbs',
        port=3306,
    )
    # 产生游标对象(数据组成列表套字典格式,不写就是列表套元组)
    cursor = conn.cursor(pymysql.cursors.DictCursor)
    time.sleep(random.randint(1, 3))
    # 发给服务端sql语句:拿两条数据
    cursor.execute('select id,title from article limit 2')
    # 获取sql语句执行结果
    res = cursor.fetchall()
    # 关闭
    cursor.close()
    conn.close()
    # print(res)
    return jsonify(res)


if __name__ == '__main__':
    app.run()

新建s1.py开启线程测试

from threading import Thread
import requests


def task():
    res = requests.get('http://127.0.0.1:5000/article_pool')
    print(len(res.text))


if __name__ == '__main__':
    # 启动线程
    for i in range(500):
        t = Thread(target=task)
        t.start()

# 查看数据库连接数
show status like 'Threads%'

image

结果
# 使用池的连接数明显【小】
# 不使用池连接数明显很【大】

13.请求上下文分析(源码:request原理)

1)pipreqs导出项目依赖

之前的用法:

pip freeze >requirements.txt  
# 它会把当前解释器环境下所有的第三方依赖导出来,不好用!

现在:用第三方模块精确导出该项目的依赖pipreqs

# 安装
pip install pipreqs

使用pipreqs:

1.使用命令导出依赖:pipreqs ./
    # win由于编码问题可能会出错。建议:pipreqs ./--encoding=utf8
    # mac、linux不会出错
    
2.此时就会在项目根路径下生成:requirements.txt  依赖文件

2)函数和方法

只要会自动传值那就是方法

【函数】:就是普通的函数,有几个参数就要传几个参数
【方法】:绑定给对象的方法、绑定给类的方法,绑定给谁就由谁来调用,会自动把自身当作参数传入

动静态方法:

【绑定给对象的方法】:(类中直接写函数默认是绑定给对象的方法)类也可以调用,但是需要手动把对象当作第一个参数传入
【绑定给类的方法】:(被@classmethod修饰的函数默认是绑定给类的方法)对象也可以调用,会自动把类传入
被@staticmethod修饰的函数会变成普通的函数,无论对象调用还是类调用,有多少参数就传多少参数

MethodType                  # 检查一个对象是不是【方法】
FunctionType                 # 检查一个对象是不是【函数】

isinstance(obj , cls)         # 判断obj对象,是不是cls类的对象
issubclass(cls1,cls)          # 判断一个cls1,是不是cls的子类
from types import MethodType, FunctionType

class Foo(object):
    def objTask(self):
        pass

    @classmethod
    def clsTask(cls):
        pass

    @staticmethod
    def task1():
        pass

"""
对象调用绑定给对象的方法(是方法)
"""
obj = Foo()
print(isinstance(obj.objTask, MethodType))  # true

"""
类调用绑定给对象的方法(会变成普通函数)
"""
print(isinstance(Foo.objTask, MethodType))  # false

"""
类调用绑定给类的方法(是方法)
"""
print(isinstance(Foo.clsTask, MethodType))  # true

"""
对象调用绑定给类的方法(是方法)
"""
print(isinstance(obj.clsTask, MethodType))  # true

"""
对象和类调用被静态方法修饰的方法(是函数)
"""
print(isinstance(obj.task1, FunctionType))  # true
print(isinstance(Foo.task1, FunctionType))  # true

3)threading.local对象

local对象

在并发编程时,多个线程操作同一个变量会出现并发安全问题(你在改我也在改),所以需要加

使用local对象多线程并发时,则不需要加锁,也不会出现数据错乱,这是threading模块给我们提供的local对象

local对象本质原理

多个线程修改同一数据,会复制多份变量给每个线程用并为每个线程开一块空间进行数据存储每个线程操作自己那部分数据

多线程遇到io操作数据错乱问题
from threading import Thread, get_ident
import time

num = -1

def task(arg):
    global num
    num = arg
    time.sleep(2)  # 如果让睡2秒,等cpu再调度该线程执行时会发现num都变成了9,造成了数据错乱
    print('线程id号:%s' % get_ident(), num)

# 开启10个线程
for i in range(10):
    t = Thread(target=task, args=(i,))
    t.start()

image

(1)互斥锁解决并发问题
from threading import Thread, get_ident
from threading import Lock
import time

num = -1
l = Lock()  # 生成锁

def task(arg):
    l.acquire()  # 获得锁
    global num
    num = arg
    time.sleep(2) 
    print('线程id号:%s' % get_ident(), num)
    l.release()  # 释放锁

# 开启10个线程
for i in range(10):
    t = Thread(target=task, args=(i,))
    t.start()

image

这样虽然数据不会错乱 但是加锁的地方变成了串行 本来2秒结束的变成了20秒

(2)local解决并发问题

local不会出现并发数据错乱问题

from threading import Thread, get_ident
from threading import local
import time

num = local()

def task(arg):
    num.arg = arg
    time.sleep(2)
    print('线程id号:%s' % get_ident(), num.arg)

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

image

虽然打印结果窜行了,但是每个线程用的都是自己的数据,没有造成数据错乱全是9的情况

4)偏函数

偏函数的作用就是可以提前传值,比如我函数需要有三个参数,但是我只知道一个另外两个需要一会才能知道,这样就可以用偏函数提前把第一个参数传进去,后面知道了另外两个参数然后再传它们两个

from functools import partial

def add(a, b, c):
    return a + b + c

# print(add(1, 2, 3))  # 如果少传会报错
# 利用偏函数先传一个值进去
add = partial(add, 1)
# 这里可以去执行其他代码 让程序先不报错
# 然后再传另外两个值
print(add(2, 3))

5)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
请求上下文执行流程(ctx):
		-0 flask项目一启动,有6个全局变量
			-_request_ctx_stack:LocalStack对象
			-_app_ctx_stack :LocalStack对象
			-request : LocalProxy对象
			-session : LocalProxy对象
		-1 请求来了 app.__call__()---->内部执行:self.wsgi_app(environ, start_response)
		-2 wsgi_app()
			-2.1 执行:ctx = self.request_context(environ):返回一个RequestContext对象,并且封装了request(当次请求的request对象),session,flash,当前app对象
			-2.2 执行: ctx.push():RequestContext对象的push方法
				-2.2.1 push方法中中间位置有:_request_ctx_stack.push(self),self是ctx对象
				-2.2.2 去_request_ctx_stack对象的类中找push方法(LocalStack中找push方法)
				-2.2.3 push方法源码:
				    def push(self, obj):
						#通过反射找self._local,在init实例化的时候生成的:self._local = Local()
						#Local(),flask封装的支持线程和协程的local对象
						# 一开始取不到stack,返回None
						rv = getattr(self._local, "stack", None)
						if rv is None:
							#走到这,self._local.stack=[],rv=self._local.stack
							self._local.stack = rv = []
						# 把ctx放到了列表中
						#self._local={'线程id1':{'stack':[ctx,]},'线程id2':{'stack':[ctx,]},'线程id3':{'stack':[ctx,]}}
						rv.append(obj)
						return rv
		-3 如果在视图函数中使用request对象,比如:print(request)
			-3.1 会调用request对象的__str__方法,request类是:LocalProxy
			-3.2 LocalProxy中的__str__方法:lambda x: str(x._get_current_object())
				-3.2.1 内部执行self._get_current_object()
				-3.2.2 _get_current_object()方法的源码如下:
				    def _get_current_object(self):
						if not hasattr(self.__local, "__release_local__"):
							#self.__local()  在init的时候,实例化的,在init中:object.__setattr__(self, "_LocalProxy__local", local)
							# 用了隐藏属性
							#self.__local 实例化该类的时候传入的local(偏函数的内存地址:partial(_lookup_req_object, "request"))
							#加括号返回,就会执行偏函数,也就是执行_lookup_req_object,不需要传参数了
							#这个地方的返回值就是request对象(当此请求的request,没有乱)
							return self.__local()
						try:
							return getattr(self.__local, self.__name__)
						except AttributeError:
							raise RuntimeError("no object bound to %s" % self.__name__)
				-3.2.3 _lookup_req_object函数源码如下:
					def _lookup_req_object(name):
						#name是'request'字符串
						#top方法是把第二步中放入的ctx取出来,因为都在一个线程内,当前取到的就是当次请求的ctx对象
						top = _request_ctx_stack.top
						if top is None:
							raise RuntimeError(_request_ctx_err_msg)
						#通过反射,去ctx中把request对象返回
						return getattr(top, name)
				-3.2.4 所以:print(request) 实质上是在打印当此请求的request对象的__str__
		-4 如果在视图函数中使用request对象,比如:print(request.method):实质上是取到当次请求的reuquest对象的method属性
		
		-5 最终,请求结束执行: ctx.auto_pop(error),把ctx移除掉
		
	其他的东西:
		-session:
			-请求来了opensession
				-ctx.push()---->也就是RequestContext类的push方法的最后的地方:
					if self.session is None:
						#self是ctx,ctx中有个app就是flask对象,   self.app.session_interface也就是它:SecureCookieSessionInterface()
						session_interface = self.app.session_interface
						self.session = session_interface.open_session(self.app, self.request)
						if self.session is None:
							#经过上面还是None的话,生成了个空session
							self.session = session_interface.make_null_session(self.app)
			-请求走了savesession
				-response = self.full_dispatch_request() 方法内部:执行了before_first_request,before_request,视图函数,after_request,savesession
				-self.full_dispatch_request()---->执行:self.finalize_request(rv)-----》self.process_response(response)----》最后:self.session_interface.save_session(self, ctx.session, response)
		-请求扩展相关
			before_first_request,before_request,after_request依次执行
		-flask有一个请求上下文,一个应用上下文
			-ctx:
				-是:RequestContext对象:封装了request和session
				-调用了:_request_ctx_stack.push(self)就是把:ctx放到了那个位置
			-app_ctx:
				-是:AppContext(self) 对象:封装了当前的app和g
				-调用 _app_ctx_stack.push(self) 就是把:app_ctx放到了那个位置
	-g是个什么鬼?
		专门用来存储用户信息的g对象,g的全称的为global 
		g对象在一次请求中的所有的代码的地方,都是可以使用的 
		
		
	-代理模式
		-request和session就是代理对象,用的就是代理模式

14.wtforms(了解)

django中有forms组件:生成前端模板、校验数据、渲染错误信息

flask中用第三方wtforms可实现和django的forms一样的功能

由于以后都是前后端分离,所以这里仅作了解

# 安装
pip install wtforms
1.导入,定义一个类继承forms
2.模板中, for循环生成模板
3.视图函数中,使用form校验数据

py

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

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'}
    )

@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)

if __name__ == '__main__':
    app.run()

templates>>login.html

<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>

补充

1)django、flask项目高并发部署

django和flask是同步框架,部署时使用uwsgi部署(uwsgi是进程线程架构,并发量不高。可以通过uwsgi+gevent部署成异步

2)uwsgi启动python的web项目中不要用全局变量

用了全局变量后:假如有两个进程在执行,第一次进的视图函数会进第一个进程中去修改全局变量的值,再进一个视图函数会进第二个进程中去修改全局变量的值,最后这个全局变量会乱掉

15.信号

信号:signial翻译过来的,和并发编程中的信号量(Semaphore)不是一回事。信号的作用主要是:让开发者可以在flask/django请求过程中定制一些用户的行为(中间写了就会执行某个函数,不写就不执行)

信号量类似多把锁 只有每次只可以有三条线程获得锁。
互斥锁只能产生一把锁

信号其实就是观察者模式,又叫发布-订阅(Publish/Subscribe),是23种设计模式之一,只要我观察的东西有变化,那就能触发我的某个东西的执行

信号能干嘛?

假如在django中项目已经写完了,又要添加一个功能:用户每新增一条记录就记录一下日志。那我们还得去所有代码中搜user.object.create在它下面写记录日志的函数。这样做代码耦合性、侵入性太强,假如好不容易改好了 老大又说要去掉,难道还要一个一个去删吗??

这个时候如果用信号,两句话解决:写一个函数,绑定内置信号,只要程序执行到这,就会执行这个函数

1)flask的信号

Flask框架中的信号基于blinker

安装

pip install blinker
(1)内置信号
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')                # 调用flask在其中添加数据时,自动触发

--------以下忽略-------------

appcontext_tearing_down = _signals.signal('appcontext-tearing-down')# 应用上下文执行完毕后自动执行(无论成功与否)
appcontext_pushed = _signals.signal('appcontext-pushed')            # 应用上下文push时执行
appcontext_popped = _signals.signal('appcontext-popped')            # 应用上下文pop时执行
使用步骤
1.写一个函数
2.绑定内置信号
3.等待被触发
from flask import Flask, render_template, signals

app = Flask(__name__)

# 1.写一个函数
def test(*args, **kwargs):
    print(args)  # 当前app对象
    print(kwargs)  # template、context、请求对象、session都传进来了
    print('执行了函数')

# 2.绑定内置信号(signals.内置信号.connect(要执行的函数))
signals.before_render_template.connect(test)

# 3.当符合触发条件时就会触发信号
@app.route('/index')
def index():
    return render_template('index.html', name='zy')

if __name__ == '__main__':
    app.run()
(2)自定义信号
使用步骤
1.自定义出信号

2.写一个函数
3.绑定自定义信号
4.等待被触发

5.触发函数执行   
# 内置信号不需要触发函数执行是因为源码中帮我们去执行了
from flask import Flask, session
from flask.signals import _signals

app = Flask(__name__)
app.secret_key='zdsfaf'

# 1.自定义出信号
session_set = _signals.signal('session_set')

# 2.写一个函数
def test(*args, **kwargs):
    print(args)
    print('设置session时触发的函数')

# 3.绑定自定义信号
session_set.connect(test)

# 4.等待被触发
@app.route('/')
def home():
    session['name'] = 'zy'
    # 5.触发信号执行(当传了值后会被函数的args和kwargs接收)
    session_set.send('传值')
    return 'home'

if __name__ == '__main__':
    app.run()

image

2)django的信号

https://www.cnblogs.com/liuqingzheng/articles/9803403.html

(1)内置信号
'Model signals(和表模型相关)''
    pre_init                    # django的model执行其构造方法前,自动触发
    post_init                   # django的model执行其构造方法后,自动触发
    pre_save                    # django的model对象保存前,自动触发
    post_save                   # django的model对象保存后,自动触发
    pre_delete                  # django的model对象删除前,自动触发
    post_delete                 # django的model对象删除后,自动触发
    m2m_changed                 # django的model中使用ManyToMany字段操作第三张表(add,remove,clear)前后,自动触发
    class_prepared              # 程序启动时,检测已注册的app中modal类,对于每一个类,自动触发
    
'Management signals(和migrate相关)'
    pre_migrate                 # 执行migrate命令前,自动触发
    post_migrate                # 执行migrate命令后,自动触发
    
'Request/response signals(和请求相关)'
    request_started             # 请求到来前,自动触发
    request_finished            # 请求结束后,自动触发
    got_request_exception       # 请求异常后,自动触发
    
'Database Wrappers(和数据库相关)'
    connection_created          # 创建数据库连接时,自动触发
使用步骤
1.写一个函数
2.绑定内置信号
3.等待被触发
1.写一个函数
def callBack(*args, **kwargs):
    print(args)
    print(kwargs)
    
2.绑定信号
"""方式一:"""
post_save.connect(callBack)  # model对象保存后自动触发
    
3.等待触发
(2)自定义信号

和flask差不多 详细看https://www.cnblogs.com/liuqingzheng/articles/9803403.html

16.微服务

https://www.cnblogs.com/liuqingzheng/p/16271897.html

17.flask-script

在django中启动项目命令:python manage.py runserver

在flask中也想用命令启动就需要借助第三方flask-script去实现

安装flask-script

pip install flask-script

版本问题

# 执行可能会报错 是版本问题,需改为以下版本
Flask==2.2.2
Flask_Script==2.0.3
----------------------
pip install flask==2.2.2
pip install flask-script==2.0.3

1)基本使用步骤

1.修改代码

from flask_script import Manager

manager=Manager(app)
manager.run()

2.命令启动

python manage.py runserver

代码

from flask import Flask
from flask_script import Manager

app = Flask(__name__)
# 1.把app包起来
manager = Manager(app)

@app.route('/')
def home():
    return 'home'

if __name__ == '__main__':
    # app.run()
    # 2.用manage.run()
    manager.run()

此时右键运行会报错,提示我们要用命令runserver启动

image

命令启动:python manage.py runserver

注:这个manage.py取决去文件名叫什么 如果叫run 就是 python run.py runserver

2)自定制命令

django也可以自订制命令,自行百度

简单的自定制命令

from flask import Flask
from flask_script import Manager

app = Flask(__name__)
# 1.把app包起来
manager = Manager(app)

# 2. 简单自定制命令
@manager.command
def custom(arg):
    # 可以写要执行命令的代码,比如:初始化数据库, 有个excel表格,使用命令导入到mysql中
    print(arg)
    """
    执行命令:python manage.py custom zy
    就会打印出来:zy
    """
    
if __name__ == '__main__':
    # app.run()
    manager.run()

复杂的自定制命令

from flask import Flask
from flask_script import Manager

app = Flask(__name__)
# 1.把app包起来
manager = Manager(app)

# 2.复杂一些的自定制命令
@manager.option('-n', '--name', dest='name')
@manager.option('-u', '--url', dest='url')
def cmd(name, url):
    print(name, url)
    """
    执行命令:python manage.py cmd -n zy -u xxx
    执行命令:python manage.py cmd --name zy --url xxx
    就会打印出来:zy xxx
    """
    
if __name__ == '__main__':
    # app.run()
    manager.run()

image

18.sqlalchemy

1)介绍

flask中没有orm框架,需要使用对象关系映射来方便我们快速操作数据库

在 flask 和 fastapi 中用sqlalchemy居多

sqlalchemy是一个基于python实现orm的框架,和其他web框架没关系,可以独立使用。该框架建立在DB API之上,使用关系对象映射进行数据库操作,简单说就是将类和对象转换成SQL,然后使用数据API执行SQL并获取执行结果

安装

pip install sqlalchemy

了解

# SQLAlchemy本身无法操作数据库,其必须以pymsql等第三方插件来操作
pymysql
    mysql+pymysql://<username>:<password>@<host>/<dbname>[?<options>]
    
cx_Oracle
    oracle+cx_oracle://user:pass@host:port/dbname[?key=value&key=value...]
    
# 更多官方文档:http://docs.sqlalchemy.org/en/latest/dialects/index.html

2)快速使用

(1)原生sql使用

但是没什么用 我们用sqlalchemy目的是为了做对象关系映射,能把数据库中的表创建出来,插一条记录可以用对象.save()保存起来,查出来也是个对象.所以这里仅了解即可 重点在下面

# 1.导入
from sqlalchemy import create_engine

# 2.生成引擎对象
engine = create_engine(
    'mysql+pymysql://root:123@127.0.0.1:3306/luffy',
    max_overflow=0,  # 超过连接池大小外最多创建的连接
    pool_size=5,  # 连接池大小
    pool_timeout=30,  # 池中没有线程最多等待的时间,否则报错
    pool_recycle=-1  # 多久之后对线程池中的线程进行一次连接的回收(重置)
)
# 3.使用引擎获取连接操作数据库
conn = engine.raw_connection()
cursor = conn.cursor()
cursor.execute('select * from luffy_teacher')
print(cursor.fetchall())

image

(2)快速创建表、删除表

新建>>models.py

# 第一步:导入
from sqlalchemy import create_engine
import datetime
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String, Text, ForeignKey, DateTime, UniqueConstraint, Index

# 第二步:执行declarative_base得到一个基类
Base = declarative_base()

# 第三步:继承生成的Base类
class User(Base):
    # 第四步:写字段,所有字段都叫Column
    id = Column(Integer, primary_key=True)  # id列,类型是Integer,主键
    name = Column(String(32), index=True, nullable=False)  # name列,类型是String,varchar32,索引,不可为空
    email = Column(String(32), unique=True)
    # datetime.datetime.now不能加括号,加了括号以后永远是当前时间
    ctime = Column(DateTime, default=datetime.datetime.now)
    extra = Column(Text, nullable=True)  # 允许为空

    # 第五步:写表名(不写就以类名为表名)
    __tablename__ = 'users'  # 数据库的名字

    # 第六步:建立联合唯一、联合索引
    __table_args__ = (
        UniqueConstraint('id', 'name', name='uix_id_name'),  # 联合唯一
        Index('ix_id_name', 'name', 'email'),  # 联合索引
    )

class Book(Base):
    id = Column(Integer, primary_key=True)
    name = Column(String(32))
    __tablename__ = 'books' 


# 第七步:把表同步到数据库中 需先创建engine对象
# 它不会创建库 只会创建表,所以需自行创建一个aaa库出来
engine = create_engine(
    'mysql+pymysql://root:123@127.0.0.1:3306/aaa',
    max_overflow=0,  # 超过连接池大小外最多创建的连接
    pool_size=5,  # 连接池大小
    pool_timeout=30,  # 池中没有线程最多等待的时间,否则报错
    pool_recycle=-1  # 多久之后对线程池中的线程进行一次连接的回收(重置)
)
# 把表同步到数据库(把被Base管理的所有表都创建到数据库中)
Base.metadata.create_all(engine)

# 把所有表删除
# Base.metadata.drop_all(engine)

image

注意:原生的sqlalchemy是不能修改字段的增加、删除都不可以,比如上面的代码,创建出来表后我再把某个字段注释掉运行,发现数据库中该字段还在!要么表删了重新建,要么用第三方。

posted @ 2023-04-07 21:54  oreox  阅读(43)  评论(0编辑  收藏  举报