Python_WEB框架之Flask

前言:

Django:1個重武器,包含了web開發中經常使用的功能、組件的框架;(ORM、Session、Form、Admin、分頁、中間件、信號、緩存、ContenType....);css

Tornado:2大特性就是異步非阻塞、原生支持WebSocket協議;html

Flask:封裝功能不及Django完善,性能不及Tornado,可是Flask的第三方開源組件比豐富;http://flask.pocoo.org/extensions/前端

Bottle:比較簡單;html5

 

總結:python

都不是我寫的!!!不論優劣,不一樣的工具而已;web

小型web應用設計的功能點很少使用Flask;正則表達式

大型web應用設計的功能點比較多使用的組件也會比較多,使用Django(自帶功能多不用去找插件);redis

若是追求性能能夠考慮Tornado;sql

 

Flask的socket是基於Werkzeug 實現的,模板語言依賴jinja2模板,在使用Flask以前須要安裝一下;mongodb

pip3 install flask           #安裝flask
from werkzeug.wrappers import Request, Response  # Flask的socket使用werkzeug實現,因此要導入 werkzeug

@Request.application

def hellow(request):
    return Response('Hello World')

if __name__ == '__main__':
    from werkzeug.serving import run_simple
    run_simple('localhost',400,hellow)

 

Flask簡單使用

from flask import Flask

app=Flask(__name__) #建立1個Flask實例

@app.route('/')      #路由系統生成 視圖對應url,1. decorator=app.route() 2. decorator(first_flask)
def first_flask():    #視圖函數
    return 'Hello World'  #response


if __name__ == '__main__':
    app.run()              #啓動socket

1、配置文件

app=Flask(__name__,template_folder='templates',static_url_path='/static/',static_path='/zhanggen')

模板路徑: template_folder='templates'

靜態文件路徑:static_url_path='/static/'

靜態文件引入別名:static_path='/zhanggen'

設置爲調試環境:app.debug=True (代碼修改自動更新)

設置json編碼格式 若是爲False 就不使用ascii編碼:app.config['JSON_AS_ASCII']=False 

設置響應頭信息Content-Type   app.config['JSONIFY_MIMETYPE'] ="application/json;charset=utf-8"  (注意 ;charset=utf-8)

 

2、路由系統

 

1.動態路由(url傳參)

@app.route('/user/<name>')

from flask import Flask

app=Flask(__name__)

@app.route('/<name>')  #設置url傳參數 http://127.0.0.1:5000/zhanggen
def first_flask(name):  #視圖必須有對應接收參數
    print(name)
    return 'Hello World'  #response


if __name__ == '__main__':
    app.run()
接收字符串類型參數

@app.route('/post/<int:age>')

#接收整型數字參數
app=Flask(__name__)
@app.route('/<int:age>/')  #設置url傳參數 http://127.0.0.1:5000/18/
def first_flask(age):  #視圖必須有對應接收參數
    print(age)
    return 'Hello World'  #response

if __name__ == '__main__':
    app.run()
接收整型數字參數

@app.route('/post/<float:salary>')

#接收浮點型型數字參數
app=Flask(__name__)
@app.route('/<float:salary>/')  #設置url傳參數http://127.0.0.1:5000/2345555.8889/
def first_flask(salary):  #視圖必須有對應接收參數
    print(salary)
    return 'Hello World'  #response

if __name__ == '__main__':
    app.run()
接收浮點型數字參數

@app.route('/post/<path:path>')

# 接收URL連接類型參數
app=Flask(__name__)
@app.route('/<path:url>/')  #設置url傳參數:http://127.0.0.1:5000/http://www.baiu.com/
def first_flask(url):  #視圖必須有對應接收參數
    print(url)
    return 'Hello World'  #response

if __name__ == '__main__':
    app.run()
接收URL連接類型參數

 

二、指定容許的請求方法

@app.route('/login', methods=['GET', 'POST']) 

# 指定容許的請求方法
app=Flask(__name__)
@app.route('/<path:url>/',methods=['get']) #只容許get請求
def first_flask(url):
    print(url)
    return 'Hello World'  #response

if __name__ == '__main__':
    app.run()
指定容許的請求方法

 

三、經過別名反向生成url

#反向生成url
from flask import Flask,url_for
app=Flask(__name__)
@app.route('/<path:url>',endpoint='name1')
def first_flask(url):
    print(url_for('name1',url=url)) #若是設置了url參數,url_for(別名,加參數)
    return 'Hello World'

if __name__ == '__main__':
    app.run()
url_for()反向生成url

 

四、經過app.add_url_rule()調用路由

#方式2經過app.add_url_rule()方法的方式調用路由
app=Flask(__name__)

def first_flask():
    return 'Hello World' 

app.add_url_rule(rule='/index/',endpoint='name1',view_func=first_flask,methods=['GET'])
#app.add_url_rule(rule=訪問的url,endpoint=路由別名,view_func=視圖名稱,methods=[容許訪問的方法])
if __name__ == '__main__':
    app.run()
app.add_url_rule()方法調用路由

 

五、擴展路由功能:正則匹配url

若是須要一些複雜的匹配規則能夠自定義正則匹配url

    from flask import Flask, views, url_for
            from werkzeug.routing import BaseConverter

            app = Flask(import_name=__name__)


            class RegexConverter(BaseConverter):
                """
                自定義URL匹配正則表達式
                """
                def __init__(self, map, regex):
                    super(RegexConverter, self).__init__(map)
                    self.regex = regex

                def to_python(self, value):
                    """
                    路由匹配時,匹配成功後傳遞給視圖函數中參數的值
                    :param value: 
                    :return: 
                    """
                    return int(value)

                def to_url(self, value):
                    """
                    使用url_for反向生成URL時,傳遞的參數通過該方法處理,返回的值用於生成URL中的參數
                    :param value: 
                    :return: 
                    """
                    val = super(RegexConverter, self).to_url(value)
                    return val

            # 添加到flask中
            app.url_map.converters['regex'] = RegexConverter


            @app.route('/index/<regex("\d+"):nid>')
            def index(nid):
                print(url_for('index', nid='888'))
                return 'Index'


            if __name__ == '__main__':
                app.run()
自定義正則表達式匹配路由

 

4、視圖

 

一、給Flask視圖函數加裝飾器

注意若是要給視圖函數加裝飾器增長新功能,一點要加在路由裝飾器下面,纔會被路由裝飾器裝飾,才能生生成url關係;

#給Flask視圖加裝飾器
#一、定義1個裝飾器

def auth(func):
    print('我在上面')
    def inner(*args,**kwargs):
        return func(*args,**kwargs)
    return inner


app=Flask(__name__)

@app.route('/',methods=['GET'])
@auth #注意若是要給視圖函數加裝飾器,一點要加在路由裝飾器下面,纔會被路由裝飾器裝飾
def first_flask():
    print('ffff')
    return 'Hello World'

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

 

二、request和response

 a.請求相關信息

request.method: 獲取請求方法

request.json

request.json.get("json_key"):獲取json數據 **較經常使用      

request.argsget('name') :獲取get請求參數   

request.form.get('name') :獲取POST請求參數

request.form.getlist('name_list'):獲取POST請求參數列表(多個)

request.values.get('age') :獲取GET和POST請求攜帶的全部參數(GET/POST通用)

request.cookies.get('name'):獲取cookies信息

request.headers.get('Host'):獲取請求頭相關信息

request.path:獲取用戶訪問的url地址,例如(/,/login/,/ index/);

request.full_path:獲取用戶訪問的完整url地址+參數 例如(/login/?age=18)

request.script_root: 抱歉,暫未理解其含義;

request.url:獲取訪問url地址,例如http://127.0.0.1:5000/?age=18;

request.base_url:獲取訪問url地址,例如 http://127.0.0.1:5000/;

request.url_root

request.host_url

request.host:獲取主機地址

 

request.files:獲取用戶上傳的文件

obj = request.files['the_file_name']

obj.save('/var/www/uploads/' + secure_filename(f.filename))  直接保存

 

 b、響應相關信息

return "字符串" :響應字符串

return render_template('html模板路徑',**{}):響應模板

return redirect('/index.html'):跳轉頁面

 

響應json數據

方式1: return jsonify(user_list) 

app.config['JSON_AS_ASCII']=False  #指定json編碼格式 若是爲False 就不使用ascii編碼,
app.config['JSONIFY_MIMETYPE'] ="application/json;charset=utf-8" #指定瀏覽器渲染的文件類型,和解碼格式;
配置

方式2:

return Response(data,mimetype="application/json;charset=utf-8",)

 

 

若是須要設置響應頭就須要藉助make_response()方法

 

from flask import Flask,request,make_response  

 

response = make_response(render_template('index.html'))

response是flask.wrappers.Response類型

response.delete_cookie('key')

response.set_cookie('key', 'value')

response.headers['X-Something'] = 'A value'

return respons

 

3 、Flask之CBV視圖

#CBV視圖
from flask import Flask,url_for,views
#-----------------------------------------------------
app=Flask(__name__)               #裝飾器

def auth(func):
    print('我在上面')
    def inner(*args,**kwargs):
        return func(*args,**kwargs)
    return inner
#--------------------------------------------------------
class IndexView(views.MethodView):  #CBV視圖
    methods=['GET']                #容許的http請求方法(改CBV只容許GET方法)
    decorators = [auth,]            #每次請求過來都加auth裝飾器

    def get(self):
        return 'Index.GET'
    def post(self):
        return 'Index.POST'

app.add_url_rule('/index/',view_func=IndexView.as_view(name='name1')) #(name='name1'反向生成url別名


if __name__ == '__main__':
    app.run()
CBV視圖

 

5、模板語言

Flask使用的是Jinja2模板,因此其語法和Django無差異(Django的模板語言參考Jinja2)

 

1.引用靜態文件

方式1:別名引入

<link rel="stylesheet" href="/zhanggen/commons.css">

方式2:url_for()方法引入

<link rel="stylesheet" href="{{  url_for('static',filename='commons.css')  }}">

 

2.模板語言引用上下文對象

變量

<h1>{{user_list}}</h1> <!--變量 -->
View Code

 

Flask的Jinjia2能夠經過Context 把視圖中的函數傳遞把模板語言中執行,這就是Django中的simple_tag和simple_fifter;

simple_tag(只能傳2個參數,支持for、if)

@app.template_global()   #simple_tag
def foo(arg):
    return '<input type="text">'
視圖
<h1>{{foo(1)|safe}}</h1>                   <!--Flask的模板語言支持simple_tag-->
模板語言

 

simple_fifter(對參數個數無限制,不支持for、if)

@app.template_filter()  #simple_fifter
def foo1(arg1,arg2,arg3):
    return arg1+arg2+arg3
視圖
<h1> {{ 'alex'|foo1('s ','b',) }}  </h1>     <!-- simple_fifter -->
模板語言

 

3.wtform(flask表單驗證插件)

3.0.簡介

 wtformWTForms是一個支持多個web框架的form組件,主要對用戶請求數據 進行表單驗證。

 

3.1. 安裝

pip install wtforms  #安裝wtfroms插件

 

3.2.簡單使用

wtforms和Django自帶的form驗證插件功能相同,使用起來大同小異;

 

用戶登陸頁面驗證

#!/usr/bin/env python
# -*- coding:utf-8 -*-
from flask import Flask, render_template, request, redirect
from wtforms import Form
from wtforms.fields import core
from wtforms.fields import html5
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):

    #不一樣的字段 內部包含正則表達式 html5.EmailField | html5.DateTimeField...
    name=simple.StringField(
        label='用戶名',
        validators=[                                #驗證規則和錯誤提示信息
            validators.DataRequired(message='用戶名不能爲空.'),
            validators.Length(min=6, max=18, message='用戶名長度必須大於%(min)d且小於%(max)d')
        ],
        widget=widgets.TextInput(),                 #前端頁面顯示的插件.TextArea
        render_kw={'class': 'form-control'}      #設置form標籤的class信息

        )

    # 不一樣的字段 內部包含正則表達式  html5.EmailField | html5.DateTimeField...
    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()     #實例化 form驗證類
        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()
app01.py
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>登陸</h1>
<form method="post" novalidate>
    <!--<input type="text" name="name">-->
    <p>{{form.name.label}} {{form.name}} {{form.name.errors[0] }}</p>

    <!--<input type="password" name="pwd">-->
    <p>{{form.pwd.label}} {{form.pwd}} {{form.pwd.errors[0] }}</p>
    <input type="submit" value="提交">
</form>
</body>
</html>

login.html
login.html

 

用戶註冊頁面驗證 

#用戶註冊
from flask import Flask, render_template, request, redirect
from wtforms import Form
from wtforms.fields import core
from wtforms.fields import html5
from wtforms.fields import simple
from wtforms import validators
from wtforms import widgets

app = Flask(__name__, template_folder='templates')
app.debug = True



class RegisterForm(Form):
    name = simple.StringField(
        label='用戶名',
        validators=[
            validators.DataRequired()
        ],
        widget=widgets.TextInput(),
        render_kw={'class': 'form-control'},
        default='張根'                                             #設置input標籤中默認值
    )

    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="兩次密碼輸入不一致")  #驗證2次輸入的密碼是否一致?
        ],
        widget=widgets.PasswordInput(),
        render_kw={'class': 'form-control'}
    )

    email = html5.EmailField(
        label='郵箱',
        validators=[
            validators.DataRequired(message='郵箱不能爲空.'),
            validators.Email(message='郵箱格式錯誤')
        ],
        widget=widgets.TextInput(input_type='email'),    #生成email input標籤
        render_kw={'class': 'form-control'}
    )

    gender = core.RadioField(
        label='性別',
        choices=(                                        #choice radio選項
            (1, ''),
            (2, ''),
        ),
        coerce=int                                       #講用戶提交過來的 '4' 強制轉成 int 4
    )
    city = core.SelectField(
        label='城市',
        choices=(
            ('bj', '北京'),
            ('sh', '上海'),
        )
    )

    hobby = core.SelectMultipleField(                      #select 下拉框多選框
        label='愛好',
        choices=(
            (1, '籃球'),
            (2, '足球'),
        ),
        coerce=int
    )

    favor = core.SelectMultipleField(
        label='喜愛',
        choices=(
            (1, '籃球'),
            (2, '足球'),
        ),
        widget=widgets.ListWidget(prefix_label=False),        #生成Checkbox 多選框
        option_widget=widgets.CheckboxInput(),
        coerce=int,
        default=[1, 2]
    )

    def __init__(self, *args, **kwargs):                        #重寫form驗證類的__init__方法能夠實時同步數據中數據
        super(RegisterForm, self).__init__(*args, **kwargs)
        self.favor.choices = ((1, '籃球'), (2, '足球'), (3, '羽毛球'))


    def validate_pwd_confirm(self, field):                       #wtforms驗證 鉤子函數
        """
        自定義pwd_confirm字段規則,例:與pwd字段是否一致
        :param field:
        :return:
        """
        # 最開始初始化時,self.data中已經有全部的值

        if field.data != self.data['pwd']:
            # raise validators.ValidationError("密碼不一致") # 繼續後續驗證
            raise validators.StopValidation("密碼不一致")  # 再也不繼續後續驗證


@app.route('/register/', methods=['GET', 'POST'])
def register():
    if request.method == 'GET':
        form = RegisterForm(data={'gender': 1})  #默認值
        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()
app02.py
<!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 item in form %}
    <p>{{item.label}}: {{item}} {{item.errors[0] }}</p>
    {% endfor %}
    <input type="submit" value="提交">
</form>
</body>
</html>
register.html

 

3.2.wtforms源碼 猜測....

A.自動生成html標籤

先來分析一下form驗證類的結構

 

LoginForm類中包含了2個字段: name 和 pwd,而name / pwd字段 = 對象,因此LoginForm 類包含了2個對象;

若是實例化了obj=LoginForm() 就等於 在 這1個對象中嵌套了 2個對象;

 

 

前端使用Form驗證插件:

那若是在前端for循環LoginForm對象,就等於調用LoginForm對象的__iter__方法,把每一個字段(對象)封裝的數據 返回

若是前端{{ obj }}= 直接調用了字段對象的__str__方法;

 

 

class InputText(object):  #插件
    def __str__(self):
        return '<input type="text" />'

class InputPassword(object):
    def __str__(self):
        return '<input type="password" />'

#-----------------------------------------------------------
class StringField(object):  #字段
    def __init__(self,wg):
        self.widget=wg

    def __str__(self):      #調用插件的__str__
        return str(self.widget)

class DateField(object):
    def __init__(self, wg):
        self.widget = wg

    def __str__(self):
        return str(self.widget)


#--------------------------------------------------------------
class LoginForm(object):                #統一 靈活接口 (對象嵌套對象,多層封裝)
    name=StringField(wg=InputText())     #wg=InputText() 對象  StringField(wg=InputText())對象
    pwd=DateField(wg=InputPassword())


l_obj=LoginForm()

print(l_obj.name)
print(l_obj.pwd)
調用關係

 

 

B.數據校驗

後臺定義好正則

用戶發來數據

對數據進行校驗

 

 

 

3.3.源碼流程

生成HTML標籤並顯示

1.驗證類(LogibForm)生成

1.1.因爲 metaclass=FormMeta,因此LoginForm是由FormMeta建立的

'''
class BaseForm():
    pass

class NewBase(BaseForm,metaclass=FormMeta,):
    pass
    
class Form(NewBase):
    pass

class LoginForm(Form):
    pass
    
'''

class Form(with_metaclass(FormMeta,BaseForm)):
class BaseForm

1.2.執行FormMeta 的__init__方法,在LoginForm中添加2個靜態字段

class FormMeta(type):
    """
    The metaclass for `Form` and any subclasses of `Form`.

    `FormMeta`'s responsibility is to create the `_unbound_fields` list, which
    is a list of `UnboundField` instances sorted by their order of
    instantiation.  The list is created at the first instantiation of the form.
    If any fields are added/removed from the form, the list is cleared to be
    re-generated on the next instantiation.

    Any properties which begin with an underscore or are not `UnboundField`
    instances are ignored by the metaclass.
    """
    def __init__(cls, name, bases, attrs):
        type.__init__(cls, name, bases, attrs) #繼承type的功能
        cls._unbound_fields = None             #在LoginForm中添加1個靜態字段
        cls._wtforms_meta = None               #在LoginForm中添加1個靜態字段
class FormMeta

 

1.3.開始解釋LoginForm中的 實例化字段對象name=simple.StringField()simple.PasswordField()

StringField/PasswordField開始實例化(提到實例化就應該想到:指定元類的__call__、本身/父類的__new__、__init__):

StringField/PasswordField是默認元類,本身沒有__new__和__init__方法;

但父類Field類中有__new__方法,因此執行父類的__new__(Field.__new__)返回UnboundField對象

 

 def __new__(cls, *args, **kwargs):#執行__new__方法
        if '_form' in kwargs and '_name' in kwargs:
            return super(Field, cls).__new__(cls)
        else:
            #我x 沒想到 ! __new__既然返回了1個 UnboundField()而不是StringField/PasswordField對象;狸貓換了太子 ?
            return UnboundField(cls, *args, **kwargs)
Field.__new__()

 

因爲Field.__new__方法返回了 1個 UnboundField對象,來看 UnboundField的__init__方法

class UnboundField(object):
    _formfield = True
    creation_counter = 0                   #靜態字段 設置計數器

    def __init__(self, field_class, *args, **kwargs): #field_class=.StringField / PasswordField
                                                     #獲取到field_class 的 參數封裝到 UnboundField對象中,而且設置 排序  'creation_counter': 2

        UnboundField.creation_counter += 1              #每實例化1個 UnboundField對象 計數器+1
        self.field_class = field_class
        self.args = args
        self.kwargs = kwargs                #{'label': '用戶名', 'validators': [<wtforms.validators.DataRequired object at 0x00000000038EF080>, <wtforms.validators.Length object at 0x00000000038EF0F0>], 'widget': <wtforms.widgets.core.TextInput object at 0x00000000038EF0B8>, 'render_kw': {'class': 'form-control'}}
        self.creation_counter = UnboundField.creation_counter#

        '''
        print(self.__dict__)  
        {
         'field_class': <class 'wtforms.fields.simple.PasswordField'>,
         'args': (),
         'kwargs': {'label': '密碼', 'validators': [<wtforms.validators.DataRequired object at 0x00000000038EF198>, <wtforms.validators.Length object at 0x00000000038EF1D0>, <wtforms.validators.Regexp object at 0x00000000038EF208>], 'widget': <wtforms.widgets.core.PasswordInput object at 0x00000000038EF2B0>,
         'render_kw': {'class': 'form-control'}},
          'creation_counter': 2
          }
        
        '''
UnboundField.__init__

 

UnboundField的__init__方法在 UnboundField對象中封裝了Field類的參數和計數器,因此如今LoginForml類中封裝數據以下

"""
print(LoginForm.__dict__)
LoginForm ={
    '__module__': '__main__', 
    'name': <1 UnboundField(StringField, (),{'creation_counter': 1, 'label': '用戶名', 'validators': [<wtforms.validators.DataRequired object at 0x00000000037DAEB8>, <wtforms.validators.Length object at 0x000000000382B048>], 'widget': <wtforms.widgets.core.TextInput object at 0x000000000382B080>, 'render_kw': {'class': 'form-control'}  })>, 
    'pwd': <2 UnboundField(PasswordField, (),{'creation_counter': 2,'label': '密碼', 'validators': [<wtforms.validators.DataRequired object at 0x000000000382B0F0>, <wtforms.validators.Length object at 0x000000000382B128>, <wtforms.validators.Regexp object at 0x000000000382B160>], 'widget': <wtforms.widgets.core.PasswordInput object at 0x000000000382B208>, 'render_kw': {'class': 'form-control'}})>, 
    '__doc__': None, 
    '_unbound_fields': None, 
    '_wtforms_meta': None,
}
"""
View Code

 

 

啓發:

不必定要把代碼都寫在當前類中,如過多個類和類之間有同性方法、屬性能夠抽出來集中到父類之中;子類繼承父類因此子類實例化對象以後,繼承享有2者的屬性和方法;因此看源碼遇到繼承一點要注意 觀察父類;

 

每一個對象實例化(在排除MetaClass的狀況下)都會執行 父類的__new__方法,再去執行__init__方法;而__new__實質性決定了實例化出來的對象是神馬?

class Queen(object):
    def __new__(cls, *args, **kwargs):  #類中__new__方法決定了類(),實例化出什麼對象;
        return Cat('狸貓','','太子')

    def __init__(self,name):           #因爲__nwe__方法返回了其餘對象,因此不會執行Queen的__init__方法
        print('ok')
        self.name=name



Prince=Queen('王子')
print(Prince.name)
print(Prince.gender)
print(Prince.identity)
狸貓換太子

 

2.LoginForm實例化

談到類實例化應該先檢查該類是否指定了 Meta類,若是指定了Meta類, 就須要先執行 (指定元類的__call__、本身/父類的__new__、__init__)

 

21.執行FormMeta的__call__方法,賦值LoginForm的_unbound_fields 和 _wtforms_meta屬性;

 

根據unbound對象的creation_counter屬性對 LoginForm中的字段進行排序,並填充到 LoginForm的_unbound_fields屬性中

根據 LoginForm的__mro__繼承順序:獲取當前類(FormLogin)全部父類,並在每一個父類中 提取Meta屬性添加到列表,轉成元組,最後建立Meta類讓其繼承,賦值LoginForm._wtforms_meta屬性

 def __call__(cls, *args, **kwargs):
        if cls._unbound_fields is None:      #在建立類時 已經設置LoginForm的_unbound_fields爲空
            fields = []
                                                # 獲取LoginForm類中,中全部屬性的key:[ '_get_translations', '_unbound_fields', '_wtforms_met,'name', 'populate_obj', 'process', 'pwd', 'validate'..... ]
            for name in dir(cls):
                if not name.startswith('_'):   #排除__下劃線的私有屬性 name. pwd
                    unbound_field = getattr(cls, name) #cls =LoginForm類 #根據key 獲取unbound_field 對象
                    if hasattr(unbound_field, '_formfield'): #檢查unbound_field 對象是否包含_formfield = True
                        fields.append((name, unbound_field))
            # '''
            # fields = [
            #     (name,name的unbound對象),
            #     (pwd,pwd的unbound對象),
            #             ]
            # '''
            #對fields 按照定義順序 進行排序
            fields.sort(key=lambda x: (x[1].creation_counter, x[0]))  #根據unbound對象的creation_counter進行字段排序
            cls._unbound_fields = fields

        if cls._wtforms_meta is None:
            bases = [] #bases = [DefaultMeta],
            # 按照繼承順序:獲取當前類(FormLogin)全部父類
            for mro_class in cls.__mro__:
                if 'Meta' in mro_class.__dict__:  #去每一個父類(mro_class)獲取  Meta = DefaultMeta


                    bases.append(mro_class.Meta)    #bases = [DefaultMeta],

                    '''
                    class Meta(DefaultMeta):
                        pass 
                '''
            cls._wtforms_meta = type('Meta', tuple(bases), {}) #cls._wtforms_meta=Meta(DefaultMeta)類:

        return type.__call__(cls, *args, **kwargs)
FormMeta.__call__

 

 執行完了指定元類 FormMeta.__call__()方法以後的LoginForm類中封裝的數據

print(LoginForm.__dict__)
LoginForm ={
    '__module__': '__main__', 
    'name': <1 UnboundField(StringField, (),{'creation_counter': 1, 'label': '用戶名', 'validators': [<wtforms.validators.DataRequired object at 0x00000000037DAEB8>, <wtforms.validators.Length object at 0x000000000382B048>], 'widget': <wtforms.widgets.core.TextInput object at 0x000000000382B080>, 'render_kw': {'class': 'form-control'}  })>, 
    'pwd': <2 UnboundField(PasswordField, (),{'creation_counter': 2,'label': '密碼', 'validators': [<wtforms.validators.DataRequired object at 0x000000000382B0F0>, <wtforms.validators.Length object at 0x000000000382B128>, <wtforms.validators.Regexp object at 0x000000000382B160>], 'widget': <wtforms.widgets.core.PasswordInput object at 0x000000000382B208>, 'render_kw': {'class': 'form-control'}})>, 
    '__doc__': None, 
    
    '_unbound_fields': [
                (name, UnboundField對象(1,simple.StringField,參數)),
                (pwd, UnboundField對象(2,simple.PasswordField,參數)),
            ],, 
    '_wtforms_meta': Meta(DefaultMeta)類,
}
"""

 

啓發:

#sort排序
v1=[
    (11,'Martin11',18),
    (121,'Martin121',19),
    (311,'Martin311',25),
    (311, 'Martin311', 26)  #按元素1排序,若是元素1相同按照 元素3排序
]

v1.sort(key=lambda x:(x[0],x[2])) #列表的sort方法,根據 列表中的元組元素 進行排序
print(v1)
'''

[(11, 'Martin11', 18), (121, 'Martin121', 19), (311, 'Martin311', 25), (311, 'Martin311', 26)]
'''
列表的 sort()
class F1(object):
    pass
class F2(object):
    pass
class F3(F1):
    pass

class F4(F2,F3):
    pass

print(F4.__mro__) #打印F4 的繼承關係

'''
(
   <class '__main__.F4'>,
    <class '__main__.F2'>,
    <class '__main__.F3'>,
    <class '__main__.F1'>,
    <class 'object'>)


'''
__mro__獲取當前對象的繼承順序

 

 

 

2.2.執行LoginForm的__new__方法

沒有__new__方法 pass

 

 

2.3.執行LoginForm的__init__方法實例化form對象

    def __init__(self, formdata=None, obj=None, prefix='', data=None, meta=None, **kwargs):

        # 實例化LoginForm中封裝的 Meta類進行實例化,之後用於生成CSRF Tocken 標籤
        meta_obj = self._wtforms_meta()
        #meta是 form = LoginForm(meta={'csrf':'true'})傳過來的參數,封裝到meta_obj中
        if meta is not None and isinstance(meta, dict):
            meta_obj.update_values(meta)

        #執行父類的構造方法,參數
        # self._unbound_fields
        '''
            '_unbound_fields'=[
                (name, UnboundField對象(1,simple.StringField,參數)),
                (pwd, UnboundField對象(2,simple.PasswordField,參數)),
            ],
        '''
        # meta_ob=Meta(DefaultMeta)對象

        super(Form, self).__init__(self._unbound_fields, meta=meta_obj, prefix=prefix)
        #給 form對象 中的_fields字段賦值以下;
        '''
        _fields: {
                name: StringField對象(),
                pwd: PasswordField對象(),
            }
        name:  StringField對象(widget=widgets.TextInput()),
        pwd:  PasswordField對象(widget=widgets.PasswordInput())
    
        '''
        #循環form對象 中的_fields字段(字典),給form對象賦值 form.name/form.pwd
        for name, field in iteritems(self._fields):
            setattr(self, name, field)
        '''
         _fields: {
            name: StringField對象(),
            pwd: PasswordField對象(),
                }
         name:  StringField對象(widget=widgets.TextInput()),
         
         pwd:  PasswordField對象(widget=widgets.PasswordInput())

           '''
        self.process(formdata, obj, data=data, **kwargs)
Form.__init__
 form = {
        _fields: {
                name: StringField對象(),
                pwd: PasswordField對象(),
            }

 


循環form對象 中的_fields字段(字典),分別賦值到form對象,這樣就能夠經過form.name/form.pwd直接獲取到Field對象了
,無需form._fields['name'] / form._fields['name']

代碼:
for name, field in iteritems(self._fields):
    setattr(self, name, field)

form對象封裝數據就變成如下內容嘍
form = {
        _fields: {
                name: StringField對象(),
                pwd: PasswordField對象(),
            }
        name:  StringField對象(widget=widgets.TextInput()),
        pwd:  PasswordField對象(widget=widgets.PasswordInput())
    
        }

 

 

3. 當form對象生成以後 print(form.name) = 執行StringField對象的__str__方法;

StringField類中沒有__str__方法,因此去執行基類Field的,Field.__str__方法返回了:  self()  =  StringFieldObj.__call__()

 def __str__(self):
        return self() #執行LoginForm的__call__方法
Field.__str__方法

 

StringField沒有__call__因此執行其基類Field.__call__方法,調用了self.meta.render_field(self, kwargs)

    def __call__(self, **kwargs):            # self=StringField對象
        return self.meta.render_field(self, kwargs) #把StringField對象傳傳入meta.render_field方法

 

下面來看self.meta.render_field(self, kwargs)作了什麼?

  def render_field(self, field, render_kw):

        other_kw = getattr(field, 'render_kw', None)
        if other_kw is not None:
            render_kw = dict(other_kw, **render_kw)
        # StringField對象.widget(field, **render_kw)
        #插件.__call__()
        '''
        #field =StringField對象
           StringField對象.widget對象()=調用widget對象的.__call__方法
        '''
        return field.widget(field, **render_kw)

 

來看widget對象=TextInput()的__call__方法,最終打印了obj.name的結果

  def __call__(self, field, **kwargs):
        kwargs.setdefault('id', field.id)
        kwargs.setdefault('type', self.input_type)
        if 'value' not in kwargs:
            kwargs['value'] = field._value()
        if 'required' not in kwargs and 'required' in getattr(field, 'flags', []):
            kwargs['required'] = True
        return HTMLString('<input %s>' % self.html_params(name=field.name, **kwargs))
   """
        0. Form.__iter__: 返回全部字段對象
            1. StringField對象.__str__
            2. StringField對象.__call__
            3. meta.render_field(StringField對象,)
            4. StringField對象.widget(field, **render_kw)
            5. 插件.__call__()
        """

 

 

4.執行for iteam in form對象的執行流程

執行form對象基類BaseForm的__inter__方法,變量self._fields字典中的內容

def __iter__(self):
        """Iterate form fields in creation order."""
        return iter(itervalues(self._fields))
_fields: {
                name: StringField對象(),
                pwd: PasswordField對象(),
            }

 

用戶輸入數據的校驗驗證流程form = LoginForm(formdata=request.form)

        # 請求發過來的值
        form = LoginForm(formdata=request.form) # 值.getlist('name')

        # 實例:編輯
        # # 從數據庫對象
        # form = LoginForm(obj='值') # 值.name/值.pwd
        #
        # # 字典 {}
        # form = LoginForm(data=request.form) # 值['name']

        # 1. 循環全部的字段
        # 2. 獲取每一個字段的鉤子函數
        # 3. 爲每一個字段執行他的驗證流程 字段.validate(鉤子函數+內置驗證規則)

 

 

 

 

6、session功能

 

1. Flask自帶的session功能

from flask import session
import json
app=Flask(__name__,template_folder='templates',static_path='/static/',static_url_path='/static/')
app.debug=True
app.secret_key='sjehfjeefrjewth43u'  #設置session加密
app.config['JSON_AS_ASCII']=False  #指定json編碼格式 若是爲False 就不使用ascii編碼,
app.config['JSONIFY_MIMETYPE'] ="application/json;charset=utf-8" #指定瀏覽器渲染的文件類型,和解碼格式;

@app.route('/login/',methods=['GET','POST'])
def login():
    msg = ''
    if request.method=='POST':
        name=request.values.get('user')
        pwd=request.values.get('pwd')
        if name =='zhanggen' and pwd=='123.com':
            session['user']=name  #設置session的key value
            return redirect('/index/')
        else:
            msg='用戶名或者密碼錯誤'
    return render_template('login.html',msg=msg)

@app.route('/index/',methods=['GET','POST'])
def index():
    user_list = ['張根', 'egon', 'eric']
    user=session.get('user')           #獲取session
    if user:
        user=['alex','egon','eric']
        return jsonify(user_list)
    else:
        return redirect('/login/')

if __name__ == '__main__':
    app.run()
Flask自帶session功能

 

2.第三方session組件(Session)

安裝 pip install flask-session

from flask import session, Flask,request,make_response,render_template,redirect,jsonify,Response

from flask.ext.session import Session #引入第三方session
import json
app=Flask(__name__,template_folder='templates',static_path='/static/',static_url_path='/static/')
app.debug=True
app.secret_key='sjehfjeefrjewth43u'  #設置session加密
app.config['JSON_AS_ASCII']=False  #指定json編碼格式 若是爲False 就不使用ascii編碼,
app.config['JSONIFY_MIMETYPE'] ="application/json;charset=utf-8" #指定瀏覽器渲染的文件類型,和解碼格式;

app.config['SESSION_TYPE']='redis'

from redis import Redis        #引入鏈接 redis模塊
app.config['SESSION_REDIS']=Redis(host='192.168.0.94',port=6379) #鏈接redis
Session(app)

@app.route('/login/',methods=['GET','POST'])
def login():
    msg = ''
    if request.method=='POST':
        name=request.values.get('user')
        pwd=request.values.get('pwd')
        if name =='zhanggen' and pwd=='123.com':
            session['user']=name  #設置session的key value
            return redirect('/index/')
        else:
            msg='用戶名或者密碼錯誤'
    return render_template('login.html',msg=msg)

@app.route('/index/',methods=['GET','POST'])
def index():
    user_list = ['張根', 'egon', 'eric']
    user=session.get('user')           #獲取session
    if user:
        user=['alex','egon','eric']
        return jsonify(user_list)
    else:
        return redirect('/login/')

if __name__ == '__main__':
    app.run()
把session存到redis

不只能夠把session存放到redis還可放到文件、內存、memcache...

    def _get_interface(self, app):
        config = app.config.copy()
        config.setdefault('SESSION_TYPE', 'null')
        config.setdefault('SESSION_PERMANENT', True)
        config.setdefault('SESSION_USE_SIGNER', False)
        config.setdefault('SESSION_KEY_PREFIX', 'session:')
        config.setdefault('SESSION_REDIS', None)
        config.setdefault('SESSION_MEMCACHED', None)
        config.setdefault('SESSION_FILE_DIR',
                          os.path.join(os.getcwd(), 'flask_session'))
        config.setdefault('SESSION_FILE_THRESHOLD', 500)
        config.setdefault('SESSION_FILE_MODE', 384)
        config.setdefault('SESSION_MONGODB', None)
        config.setdefault('SESSION_MONGODB_DB', 'flask_session')
        config.setdefault('SESSION_MONGODB_COLLECT', 'sessions')
        config.setdefault('SESSION_SQLALCHEMY', None)
        config.setdefault('SESSION_SQLALCHEMY_TABLE', 'sessions')

        if config['SESSION_TYPE'] == 'redis':
            session_interface = RedisSessionInterface(
                config['SESSION_REDIS'], config['SESSION_KEY_PREFIX'],
                config['SESSION_USE_SIGNER'], config['SESSION_PERMANENT'])
        elif config['SESSION_TYPE'] == 'memcached':
            session_interface = MemcachedSessionInterface(
                config['SESSION_MEMCACHED'], config['SESSION_KEY_PREFIX'],
                config['SESSION_USE_SIGNER'], config['SESSION_PERMANENT'])
        elif config['SESSION_TYPE'] == 'filesystem':
            session_interface = FileSystemSessionInterface(
                config['SESSION_FILE_DIR'], config['SESSION_FILE_THRESHOLD'],
                config['SESSION_FILE_MODE'], config['SESSION_KEY_PREFIX'],
                config['SESSION_USE_SIGNER'], config['SESSION_PERMANENT'])
        elif config['SESSION_TYPE'] == 'mongodb':
            session_interface = MongoDBSessionInterface(
                config['SESSION_MONGODB'], config['SESSION_MONGODB_DB'],
                config['SESSION_MONGODB_COLLECT'],
                config['SESSION_KEY_PREFIX'], config['SESSION_USE_SIGNER'],
                config['SESSION_PERMANENT'])
        elif config['SESSION_TYPE'] == 'sqlalchemy':
            session_interface = SqlAlchemySessionInterface(
                app, config['SESSION_SQLALCHEMY'],
                config['SESSION_SQLALCHEMY_TABLE'],
                config['SESSION_KEY_PREFIX'], config['SESSION_USE_SIGNER'],
                config['SESSION_PERMANENT'])
        else:
            session_interface = NullSessionInterface()

        return session_interface
View Code

 

3.自定義session組件

#!/usr/bin/env python
# -*- coding:utf-8 -*-
import uuid
import json
from flask.sessions import SessionInterface
from flask.sessions import SessionMixin
from itsdangerous import Signer, BadSignature, want_bytes


class MySession(dict, SessionMixin):
    def __init__(self, initial=None, sid=None):
        self.sid = sid
        self.initial = initial
        super(MySession, self).__init__(initial or ())

    def __setitem__(self, key, value):
        super(MySession, self).__setitem__(key, value)

    def __getitem__(self, item):
        return super(MySession, self).__getitem__(item)

    def __delitem__(self, key):
        super(MySession, self).__delitem__(key)


class MySessionInterface(SessionInterface):
    session_class = MySession
    container = {}

    def __init__(self):
        import redis
        self.redis = redis.Redis()

    def _generate_sid(self):
        return str(uuid.uuid4())

    def _get_signer(self, app):
        if not app.secret_key:
            return None
        return Signer(app.secret_key, salt='flask-session',
                      key_derivation='hmac')

    def open_session(self, app, request):
        """
        程序剛啓動時執行,須要返回一個session對象
        """
        sid = request.cookies.get(app.session_cookie_name)
        if not sid:
            sid = self._generate_sid()
            return self.session_class(sid=sid)

        signer = self._get_signer(app)
        try:
            sid_as_bytes = signer.unsign(sid)
            sid = sid_as_bytes.decode()
        except BadSignature:
            sid = self._generate_sid()
            return self.session_class(sid=sid)

        # session保存在redis中
        # val = self.redis.get(sid)
        # session保存在內存中
        val = self.container.get(sid)

        if val is not None:
            try:
                data = json.loads(val)
                return self.session_class(data, sid=sid)
            except:
                return self.session_class(sid=sid)
        return self.session_class(sid=sid)

    def save_session(self, app, session, response):
        """
        程序結束前執行,能夠保存session中全部的值
        如:
            保存到resit
            寫入到用戶cookie
        """
        domain = self.get_cookie_domain(app)
        path = self.get_cookie_path(app)
        httponly = self.get_cookie_httponly(app)
        secure = self.get_cookie_secure(app)
        expires = self.get_expiration_time(app, session)

        val = json.dumps(dict(session))

        # session保存在redis中
        # self.redis.setex(name=session.sid, value=val, time=app.permanent_session_lifetime)
        # session保存在內存中
        self.container.setdefault(session.sid, val)

        session_id = self._get_signer(app).sign(want_bytes(session.sid))

        response.set_cookie(app.session_cookie_name, session_id,
                            expires=expires, httponly=httponly,
                            domain=domain, path=path, secure=secure)
組件
from flask import Flask
from flask import session
from my_session import MySessionInterface

app = Flask(__name__)

app.secret_key = 'A0Zr98j/3yX R~XHH!jmN]LWX/,?RT'
app.session_interface = MySessionInterface()


@app.route('/login/', methods=['GET', "POST"])
def login():
    print(session)
    session['user1'] = 'alex'
    session['user2'] = 'alex'
    del session['user2']

    return "內容"


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

 

 

 7、藍圖

 

使用Flask自帶Blueprintmuk模塊,幫助咱們作代碼目錄結構的歸類

 

import luffy   #導入luffy包就會執行luffy包中__init__.py文件

luffy.app.run()
app.py
from flask import Flask

app=Flask(__name__,template_folder='templates',static_path='/static/',static_url_path='/static/')

app.debug=True


from .views import login
from .views import index

#把文件中藍圖對象註冊到app裏
app.register_blueprint(login.login,url_prefix='/login') #訪問login藍圖必須以url_prefix開頭
app.register_blueprint(index.index,url_prefix='/index')

if __name__ == '__main__':
    app.run()
luffy包的__init__.py
from flask import Blueprint     #導入藍圖
login=Blueprint('login',__name__) #在本模塊實例化1個藍圖

@login.route('/login/',methods=['GET','POST'])
def login1():
    return '登陸頁面'
login
from flask import Blueprint
index=Blueprint('index',__name__)
@index.route('/index/',methods=['GET','POST'])
def index1():
    return '首頁'
index

 

 

8、message (閃現)

 

message是一個基於Session實現的用於保存數據的集合,其特色是:一次性。

特色:和labada匿名函數同樣不長期佔用內存

from flask import Flask,request,flash,get_flashed_messages

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


@app.route('/set/')
def index2():
    flash('Disposable')    #在message中設置1個個值
    return 'ok'

#---------------------------------------------------------------------------------

@app.route('/')
def index1():
    messages = get_flashed_messages() #獲取message中設置的值,只能獲取1次。(1次性)
    print(messages)
    return "Index1"






if __name__ == "__main__":
    app.run()
flask_message

 

 

9、中間件

 

flask也有中間件功能和Django相似,不一樣的是使用的是使用3個裝飾器來實現的;

1.@app.before_first_request :請求第1次到來執行1次,以後都不執行;

2.@app.before_request:請求到達視圖以前執行;(改函數不能有返回值,不然直接在當前返回)

3.@app.after_request:請求 通過視圖以後執行;(最下面的先執行)

 

#!/usr/bin/env python
# -*- coding:utf-8 -*-
from flask import Flask, Request, render_template

app = Flask(__name__, template_folder='templates')
app.debug = True


@app.before_first_request  #第1個請求到來執行
def before_first_request1():
    print('before_first_request1')


@app.before_request #中間件2
def before_request1():
    Request.nnn = 123
    print('before_request1')  #不能有返回值,一旦有返回值在當前返回


@app.before_request
def before_request2():
    print('before_request2')



@app.errorhandler(404)
def page_not_found(error):
    return 'This page does not exist', 404



@app.route('/')
def hello_world():
    return "Hello World"





@app.after_request #中間件 執行視圖以後
def after_request1(response):
    print('after_request1', response)
    return response


@app.after_request #中間件 執行視圖以後 先執行 after_request2
def after_request2(response):
    print('after_request2', response)
    return response

if __name__ == '__main__':
    app.run()
中間件

 

 

10、Flask相關組件

一、flask-sqlchemy

二、flask-script組件

flask-script組件:用於經過腳本的形式,啓動 flask;(實現相似Django的python manager.py runserver 0.0.0.0:8001)

pip install flask-script        #安裝
#!/usr/bin/env python
# -*- coding:utf-8 -*-

from sansa import create_app
from flask_script import Manager #導入
app = create_app()

manager=Manager(app)              #實例化Manager對象

if __name__ == '__main__':
    manager.run()
run.py
python run.py runserver -h 0.0.0.0 -p 8001

* Running on http://0.0.0.0:8001/ (Press CTRL+C to quit)

 

3.flask-migrate組件

在線修改、遷移數據庫(Django的 migrate 。

#!/usr/bin/env python
# -*- coding:utf-8 -*-

from sansa import create_app,db
from flask_script import Manager #導入
from flask_migrate import  Migrate,MigrateCommand

app = create_app()
manager=Manager(app)              #實例化Manager對象
migrate=Migrate(app,db)

manager.add_command('db',MigrateCommand) #註冊命令

if __name__ == '__main__':
    manager.run()
run.py
pip install flask-migrate       #安裝

3.1.初始化數據庫:python run.py db init

3.2.遷移數據:       python run.py db migrate

3.3.生成表:           python run.py db upgrade  

ps:修改表結構 first 直接註釋靜態字段代碼,second 執行 python run.py db upgrade.

D:\Flask練習\sansa>python run.py db init
Creating directory D:\Flask練習\sansa\migrations ... done
Creating directory D:\Flask練習\sansa\migrations\versions ... done
Generating D:\Flask練習\sansa\migrations\alembic.ini ... done
Generating D:\Flask練習\sansa\migrations\env.py ... done
Generating D:\Flask練習\sansa\migrations\README ... done
Generating D:\Flask練習\sansa\migrations\script.py.mako ... done
Please edit configuration/connection/logging settings in 'D:\\Flask練習\\sansa\\migrations\\alembic.ini' before proceeding.

D:\Flask練習\sansa>python run.py db migrate
INFO  [alembic.runtime.migration] Context impl MySQLImpl.
INFO  [alembic.runtime.migration] Will assume non-transactional DDL.
INFO  [alembic.autogenerate.compare] Detected added table 'users666'
Generating D:\Flask練習\sansa\migrations\versions\a7f412a8146f_.py ... done

D:\Flask練習\sansa>python run.py db upgrade
INFO  [alembic.runtime.migration] Context impl MySQLImpl.
INFO  [alembic.runtime.migration] Will assume non-transactional DDL.
INFO  [alembic.runtime.migration] Running upgrade  -> a7f412a8146f, empty message

D:\Flask練習\sansa>

 

 

 

 

 

 

 

 

 

文檔:       http://docs.jinkan.org/docs/flask/quickstart.html

銀角大王:http://www.cnblogs.com/wupeiqi/articles/7552008.html

銀角大王:http://www.cnblogs.com/wupeiqi/articles/5713330.html(執行原生SQL模塊 pymsql ,ORM框架 SQLAchemy)

相關文章
相關標籤/搜索