flask学习

问题汇总

  1. 为什么测试的时候要使用db.session.remove呢??
  2. 什么是sqlalchemy的rollback,需要了解一下

flask注意事项

  1. 这段代码注意一下,传进来的东西(user_id)全部是字符串,如果未加申明的话。可以使用 <int:user_id> 将其声明为一个int

    @main.route('/user_profile/<user_id>/edit', methods=['GET', 'POST'])
    @login_required
    def user_profile_edit(user_id):
            if current_user.id != user_id:
    
  2. 应该通过form.real_name.data去对填充form域的默认值,而非通过placeholder属性去改,后者会导致提交的时候出现空数据

        current_user.real_name = form.real_name.data
        current_user.about_me = form.about_me.data
        db.session.commit()
        flash('your profile has been updated')
        return redirect(url_for('main.user_profile_edit', user_id=user_id))
    
    # 应该通过form.real_name.data去对填充form域的默认值,而非通过placeholder属性去改,后者会导致提交的时候出现空数据
    form.real_name.data = current_user.real_name
    form.about_me.data = current_user.about_me
    return render_template('user_profile_edit.html', form=form)
    
  3. flask的调试可以直接在网页上输入命令进行调试,并且有对应的上下文,非常方便。我可以深挖一下这种调试方法的特性和实现方法(比如为什么能进行调试呢?)

  4. sqlalchemy中的foreignkey既可以直接进行赋值,也可以通过赋值relationship进行赋值,不过对于一般情况而言既然已经用了ORM了就全部用直接用relationship来进行赋值会比较好

  5. 终于明白为什么要使用blueprint了,不使用的话views一定要在app create之后进行加载,这样的话要创建一个test app的话必须附带加载的操作,太麻烦了

  6. 对于统一处理的东西before_request比装饰器要更好,而像注册这种的则应当使用装饰器

  7. 自定义异常的错误码不要与flask中默认错误码一直,不然客户端可能会判断失误

  8. 在程序初始时从蓝图中调用一个函数,这样您就有机会修改应用的参数属性了(就像在在请求处理器前后的调用钩子等)

  9. 你可以引入 WSGI 中间件来包装你的 Flask 实例并在 Flask 应用和 HTTP 服务器之间的中间层引入修正和变更。

  10. rest风格要求每一个资源都用一个唯一的URL来表示,因此最好不要根据current_user来决定展示什么信息,所有的都根据url来(这一点感觉是不能赞同的)

注解

每个资源都要使用唯一的 URL 表示。还是以博客程序为例,一篇博客文章可以使用 URL / api/posts/12345 表示,其中 12345 是这篇文章的唯一标识符,使用文章在数据库中的主键 表示。URL 的格式或内容无关紧要,只要资源的 URL 只表示唯一的一个资源即可。

  1. 表示文件夹的url需要加斜线

注解

Flask 会特殊对待末端带有斜线的路由。如果客户端请求的 URL 的末 端没有斜线,而唯一匹配的路由末端有斜线,Flask 会自动响应一个重定向, 转向末端带斜线的 URL。反之则不会重定向

  1. 对每一个资源所能做的操作都要有对应的请求方法
../_images/F4924150-D47F-406A-A4BC-612678AAB94B.png
  1. REST api对于一个资源请求的response示例
../_images/A0C214C7-E1F9-4E58-A8A8-0C3FF8AB2693.png

返回的有可能是实际的资源,也有可能是url,url能够被用来进行后续的资源请求,客户端只要存储几个顶级的url就可以了

14. 看了flask web开发中测试那一章有了一个想法,所有逻辑模块从views里面能分割的最好分割出去,归并到models里面或者独立于views的辅助类当中,这样更容易进行测试,因此views里面的函数要跑单元测试是需要请求上下文环境的 那么views的路由函数当中应当做什么呢?就是充当一个粘合剂,进行已经封装完毕的函数或方法的调用,最多就是做一下查询 15. flask层面上的性能问题一般会出现在代码和查询两个方面 16. 使用response.get_data(as_text=True)可以简单地获取flask response 当中的数据 17. 使用测试客户端的data参数可以用于填写表单

flask核心

  1. flask相当于在WSGI接口的基础上又加了一层封装,本来WSGI接口构造接受请求和回复请求之间的逻辑,而由于请求类型多样,需要做多重判断,一个函数去做逻辑非常混乱,而flask则是将这本来一个函数去做的事情,通过装饰器加函数拆分为多个函数去面对不同的请求,函数还是只是处理逻辑部分,而装饰器去识别请求类型,把逻辑交给对应的函数去进行处理,这样的话整体结构上就清晰很多
  2. 开头的 app = Flask(__name__) 中的 __name__ 直接指代该模块在上下文中的名字,对于 __init__ 模块来说,其实就是包名,这个语句相当于利用Falsk类的生成函数建立一个Flask的类实例,传入包名作为参数,传入包名flask才能够知道应该去哪里寻找模板和静态文件
  3. 关于为什么__init__中对于Flask的实例化要在import views的前面,因为views里面import了这个实例化的app对象,如果import在实例化之前,这个import就会找不到对象。从这里也看出了__init__的作用,首先是集合代码,把各个模块中的全部import进来,这样所有模块都可以从包中引用,其次是定义一些全局变量,这些变量是所有模块都可以用到的,这就避免了模块之间引用来引用去的现象,而是将__init__模块作为核心,所有共有的东西都由__init__来进行定义,其他模块只需要从它地方引用就可以了。还有一个细节就是__init__模块既然引用了其他模块,为什么其他模块无法使用__init__中的变量,这是因为python的引用并非简单的代码合并,被引用的一方是有自己的域的,其不能获取自己的域之外的变量(关于 __init__.py 的正确使用方式还有待进一步的探讨)
  4. 为什么说flask这类框架是MTV而非MVC,这是因为C这个控制部分(负责请求转发等)已经被flask吸收进框架内部了,开发者使用的时候很少需要用到这部分逻辑
  5. 学了一下发现url_for还是很好用的,要明白的是其永远指向端点函数,后面的可变参数是根据端点函数的可变url来配置的,如果端点函数url定死,自然无需配置可变参数,参数可以配置多个,函数、装饰器、url_for共用参数名),url_for的参数不仅限于动态路由中的参数,还能将额外参数添加到查询字符串中
  6. 路由:处理URL与 响应 之间关系的程序
  7. Flask相当于是通过装饰器把函数注册为路由,在python当中装饰器的惯常用法就是将函数注册为事件的处理程序
  8. Flask会把某些对象变成全局可访问,比如request对象 python - 程序上下文与请求上下文的区别? - SegmentFault

注解

其实所谓的上下文就是保存所需要的资源(变量/函数...)的位置。

  1. flask支持4种请求钩子,能够在请求被分发到视图函数之前或之后调用,使用上下文全局变量g可以在请求钩子函数和视图函数之间共享数据,相当于是一个临时的盒子,每次请求都会重设
  2. abort函数感觉起来很像是break,会将控制权交换给web服务器,其本质就是触发一个异常
  3. 可以使用errorhandler装饰器来自定义错误页面
  4. 能用url_for的地方尽量使用url_for,能确保修改路由名字后依然可用
  5. 如果路由地址是动态的,那么调用路由函数的时候会根据动态的部分的名称去函数中寻找对应名称的参数,相当于可以支持多个参数,从模板到url到函数参数,都是一致的,并且以url上的命名为核心,模板上的会根据根据端点函数找到url,再根据url来决定生成的url是什么样的,而url会根据参数向函数传递指定参数名的参数值
  6. 了解jinja2模板继承的用法了,子模板会自动继承父模板的非block区域,如果想要继承block区域需要加上 {{ super() }} ,子模板里的block顺序怎么放都无所谓,会自动根据父模板中的位置进行摆放,只要block的name相同就行
  7. 重点,通过query获取对象后要将对象的值copy到一个dict,避免直接操作对象,这样会直接导致隐式的update,如果保证不对对象进行操作的话倒是可以用一下,毕竟方便很多(一般情况下都不会改变对象本身,直接传进模板就行了)
  8. 对于使用blueprint可以将app对象的生成和路由函数的注册相分离,而且便于在一个项目中对多个子项目(比如运营后台和前端页面)的管理
  9. 注意flash函数的描述

注解

Flashes a message to the next request.

  1. flask的调试无论如何都要先把应用上下文给压栈,因为很多函数都有对current_app的引用,如果不压栈的话根本没法用
  2. before_request用于请求刚进来还没有路由的时候
  3. after_request用于路由函数已经走完,response已经生成的时候,其接受一个response,并要求返回一个response对象

flask插件

flask_sqlalchemy

  1. 运用flask_sqlalchemy的时候有几个注意点,首先查询不需要使用session,直接调用数据库类的query属性就可以了(其实差不太多),会返回针对这个数据库类的一个查询对象,再调用其all()方法,就能得到一个记录对象的列表,例如 Blog.query.all() ,当然使用session应该也是可以的 db.session.query(Blog) 与之前那个是等价的,前者的话貌似可以使用一些方法
  2. 可以对column对象调用desc方法来使其倒序排列
pagination = blog_query.order_by(Blog.gmt_create.desc()).paginate(
page,
per_page=20,
error_out=False
)
  1. db.session.query()这个query当中用来放你要查什么,诸如 count(*) 什么的都是放在这个里面的,filter()相当于where,会返回一个Query对象的拷贝,而Query对象根据文档的意思来看可以视作一个“查询”,提供all()方法可以返回一个对应的列表
  2. 我们从数据库查询到的变量的类型是按照我们定义的model来的,所以各种方法都适用
  3. model定义当中的relationship对象与数据库是无关的,是在高层次上建立两个model之间的关系,对于一对多的情况,一般在一那里定义,“一”那里存储了一个“多”的对象的列表,定义backref字段可以在“多”那里建立对应的实例变量,这个变量存储的是一个”一“的对象,但还是有一点关联的,由于只在一边定义了关系, 关系需要通过在另一个model中寻找对应的外键才能知道具体的关系是什么样的,通常情况下自己就可以找到,所以相当于是外键的定义构造了潜在的关系,relationship对象只是用来声明这个关系,relationship的好处就是可以直接获取对象,使用起来非常方便,这也是ORM的一个好处
  4. 创建model实例的时候relationship对象也可以使用,可以替代外键的定义
  5. session的作用在于其操作的原子性,进行commit的时候要么全部成功,要么全部不成功,这样就不会使得多个关联操作进行的时候出现完成了一半的尴尬情况,这样看来commit这个东西还是很有必要的
  6. 现在来看,Query对象要调用all()等方法才会实际执行简直不可思议,猜想可能是对遍历所得对象调用属性的时候自动进行了query的执行,就像查询关系那样,调试的结果能够说明这个问题,调试后发现遍历的这个语句当中直接执行了query,要么是遍历这个操作导致执行,要么是首次的属性调用导致执行,倾向于前者,因为执行的是整个query。又找了一个query对象测试了一下,发现query本身就具有__iter__方法,因此是一个可迭代对象,怀疑是__iter__方法中会执行query的查询
  7. 查询关系的时候可以直接使用调用实例变量,但是这里其实隐含了一个查询,并且直接调用了all方法,设置关系的时候将lazy参数设置一下可以不调用all方法,就返回一个query对象
  8. filter比filter_by好的一点在于可以支持sql表达式,而filter只能支持x值等于y值这种
  9. 实验了一下实例化sqlalchemy的过程其实就是直接用实例变量覆盖所有类变量,发现就算没有指定的类变量也会被,这是什么原理呢?
  10. 在query当中使用column+first()只是会是返回的结果只有那一列,返回的对象是一个result,要在其中重新取值,但是如果是直接用model的话,那么返回结果就是一个model对象,两者是不一样的,所以还是使用后者更加直观一些(没看懂我之前写的是什么,以后这种东西直接代码贴出来,免得以后看不懂)
  11. sqlalchemy的column对象的构造当中,default参数可以接受一个函数对象,如果是函数对象的话,会在Model类每次实例化的时候生成默认值,而不是在Column类实例化的时候

flask_login

运用flask_login模块的时候,在views当中要定义一个回调函数,为什么说这是回调函数呢?因为系统运行的时候通过装饰器调用它然后会改变系统内部的一个变量user_callback,这个变量会直接影响到current_user这个变量,调用current_user时候会通过这个回调函数去加载current_user,current_user本质上就是一个代理

@login_manager.user_loader
def load_user(user_id):
    return db.session.query(User).filter_by(id=user_id).first()

对于基础的理论有了更深理解以后的解释。首先这个装饰器有什么鸟用呢?进去一看首先是把load_user这个函数注册进LoginManager对象的user_callback属性,然后这个函数什么时候调用呢?看了下,首先调用current_user(通过local_proxy创建的)这个全局变量的时候会调用,使用login_required的时候也会调用(本质上还是使用current_user)

(1)flask_login当中开头定义的 LoginManager.user_loader ,其作用其实就是根据login_user当中获取到的user_id寻找到一个User对象,如果返回None说明没有找到这个对象
(2)current_user的默认值是一个匿名用户,因此需要通过 is_authenticate 进行判断 (还存在一个问题,这个所谓的匿名用户究竟是什么)
(3)可以通过设置login_manager对象的login_view属性来将所有的login_request页面重定向到指定的login页面,并且还会有一个flash(很有意思,不知道是怎么实现的)

几个注意点:

  1. current_user只能用在请求过程当中,因为其依赖请求上下文

flask_uploads

模板当中如果要在表单中上传文件的话需要用 enctype="multipart/form-data" ,而在views当中要取这个数据的话可以使用flask_wtf的form.file.data,但这里取出来的数据其实是一个werkzeug定义的FileStorage类型

flask-script

(1)使用shell参数可以在flask应用上下文中运行python shell,感觉是一种很好的调试手段,可以尝试一下 (2)使用 python manage.py runserver -h 能够指定host (3)使用flask-script的shell命令可以自动帮你push app_context

flask-bootstrap

等bootstrap熟悉了之后再进行研究

flask-wtf

(1)使用quick_form()函数可以在模板中按照form类所定义的form快速创建一个form,基本上来说是没啥用,肯定要自定义样式什么的 (2)提交form表单的时候尽量使用重定向,使得最后一个请求为GET请求,这样可以在用户进行刷新的时候不弹出警告提示,但是重定向有个问题就是不能再POST的response函数当中保留局部变量,可以用的方法是将该变量存储在session(和request,也是请求上下文中的变量,是一个字典,开启请求上下文即可使用)当中,直接给session[key]赋值就能进行存储,在所有请求生命周期当中和request一样能够全局使用(所以说重定向并非直接在服务器当中处理的,还是返回给用户让用户自己去进行请求,可以看下重定向响应的整个response) (3)要注意一个点,session是针对cookie的,不同cookie的用户有不同的session

flask-migrate

(1)如果使用工厂函数生成app的话,那么需要结合flask-script使用 (2)在创建migrate脚本的过程当中,程序会比较两个点,第一个是是否已经update到最新版本,没有的话报错,下一个是当前的model和数据库的实际model相比是否有变化,如果没变化的话就不执行(有可能是自己把数据库给改了,这个应该就反滚不了了,不是很确定),有变化的话就生成脚本。所以综合起来看不要手动去修改数据库,防止反滚不了(drop的时候应该会出现没有这个字段的现象)。也不要轻易反滚,肯定会丢失数据。 (3)使用flask db command会无视virtualenv调用系统自带的python,所以还是直接用python run.py db command比较好 (4)flask-migrate要搭配flask-script使用,否则一个系统只能使用一个app,非常麻烦,具体是否真的只能使用一个不是很确定,不过还是使用flask-script更好一些

Werkzeug

  1. Response对象其实相当于一个WSGI应用,为什么这么说呢?因为可以直接call并传入environ和start_response ,这个是WSGI应用的定义啊,它的内部实现了对于start_response的调用,以及对于response body部分的return,这样来看flask不就成了一个中间件了,感觉也不像

示例:

from werkzeug.wrappers import Response

def application(environ, start_response):
        response = Response('Hello World!', mimetype='text/plain')
        return response(environ, start_response)
  1. 所谓中间件其实就是对于服务器扮演的是应用角色,对于application则扮演的是服务器角色(在上面的代码当中,application相当于一个中间件,而response则是真正的应用(真滴嘛),对于服务器来说,它向application函数传入了environ和start_response回调函数,而对于response而言,它被application传入了environ和start_response回调函数)尽管如此,我觉得还是把中间件看做是和应用相关联比较合适,毕竟前面所说的“实际上的应用“只是做了response而已,要怎么response其实还是在它外面处理的。
  2. 中间件所承担的功能有:
(1)重写environ变量后,根据目标URL,将请求消息路由到不同应用对象
(2)允许在一个进程中同时运行多个应用程序或应用框架
(3)负载均衡和远程处理,通过在网络上转发请求和响应消息
(4)进行内容后处理,例如应用XSLT样式表

从这个角度来看,flask并非中间件

  1. 如果是多线程服务器的话,那么在一个请求还在处理的时候,能另开一个线程来处理另一个请求,因此为了保证两个线程的request是不一样的,flask使用werkzeug工具包中的某个和thread.local很像的local模块来保证线程安全,local模块能进一步解决协程的安全问题 Werkzeug库——local模块 | Learn Python 要注意开线程的是服务器而非flask 关于flask线程安全的简单研究 - JamesPei - 博客园 文中这样说是因为他把flask的测试服务器也囊括进flask当中了,其实app.run是调用了werkzeug的run_simple函数

所谓线程安全就是:

注解

就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染。 线程不安全就是不提供数据访问保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据