python3-開發進階Flask的基礎(4)


今日內容:

  1. 上下文管理:LocalProxy對象
  2. 上下文管理:  請求上下文: request/session   app上下文:app/g
  3. 第三方組件:wtforms       1、使用     2、原理   

 

一、LocalProxy

首先我們一看一段自己寫的代碼:

#test.py
DATA={
    'request':{
        'method':'GET',
        'form':{}
    },
    'session':{
        'user':'duoduo',
        'age':'20'
    }
}

class LocalProxy(object):   #

    def __init__(self,key):
        self.key=key


    def get_dict(self):
        return DATA[self.key]


    def __str__(self):
        return 'duoduo'

    def __getattr__(self, item):
        data_dict=self.get_dict()
        return data_dict[item]

    def __getitem__(self, item):
        data_dict = self.get_dict()
        return data_dict[item]

    def __add__(self, other):
        return other+1


request=LocalProxy('request')
session=LocalProxy('session')
LocalProxy的類就相當於一個代理

下面我們來看看源碼:先來個最簡單flask

from flask import Flask
duo=Flask(__name__)

@duo.route('/index/') 
def index():
    return "hello world"

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

我們把flask分為二個階段,

第一階段:

請求到來  先執行app.__call__ --->wsgi_app--->實例化RequestContext對象賦值給ctx

在ctx中 ctx.request=Request(environ),ctx.session=None

(具體描述:將request和Session 相關數據封裝到 ctx=RequestContext對象中)

ctx.push()------->_request_ctx_stack.push(self)   這個self就是包含了request,session的ctx

_request_ctx_stack=LocalStack()  單例,全局變量,只有一個

(具體描述:再通過LocalStack將ctx添加到Local中)

存儲的格式: __storage__={唯一字段:{’stack:[ctx(request,session)]‘}}  

 

第二階段:視圖函數中獲取request或session

  方式一:直接找LocalStack獲取

from flask.globals import  _request_ctx_stack
#先這句寫在視圖函數中
print(_request_ctx_stack.top.request.method)

  方式二:通過代理LocalProxy獲取

from flask import Flask,request
print(request.method)

他是怎么實現的,導入的request是在哪放着

我們點開導入的request,看看是什么:

我們猜想request.method有值,那么LocalProxy有method 或者是__getattr__方法,點開源碼看看里邊到底有沒有

沒有method,那么就是另一這種可能,__getattr__

 

 

 調用偏函數

 

 

 

二、上下文管理:  請求上下文: request/session   app上下文:app/g

1、程序啟動:

  我們創建兩個Local:還有兩個LocalStack,

  _request_ctx_stack

  _app_ctx_stack

2、請求到來

  對數據進行封裝

  ctx=RequestContext(request,session)

  app_ctx=AppContext(app,g)

  保存數據

  將包含了(app,g)數據的app_ctx對象,利用_app_ctx_stack=LocalStack()將app_ctx添加到Local中

    storage={'唯一標識:{stack:[app_ctx(app,g),]}'}

  將包含了request,session數據的ctx對象,利用_request_ctx_stack=LocakStack(),將ctx添加到Local中

    storage={'唯一標識:{stack:[ctx(request,session),]}'}

3、視圖函數處理:

直接導入,然后相應的獲取值,一個是請求上下文取值,一個是app上下文取值

4、結束

_app_ctx_stack.pop

_request_ctx_stack.pop

這下值就去取走了

 

我們來看源碼:

from flask import globals  #點globals

他們都在最開始就創建了local對象

 

請求進來,執行__call__---》wsgi_app--->生成ctx對象----》ctx.push,再看這里的源碼

 

再來看看  AppContext

才會去看看 app_ctx.push()

 

現在我們提出一個問題:

1、flask中g的生命周期?

在請求來的時候g就創建了,當請求走的時候g的生命周期一結束,

2、g和session一樣嗎?

和session 是不一樣的,session的值還存在cookie中,還是可以取值

3、g和全局變量一樣?

不一樣,全局變量程序啟動就一直在內存中

4、上面的演示都是單線程的,多線程的時候會變嗎?

他們存值時,都是以線程的唯一標志存值,不影響的

 

三、flask 第三方組件 wtforms

作用:1、生成html標簽  2、form表單驗證

安裝:

pip3 install wtforms

實例:

import pymysql
from DBUtils.PooledDB import PooledDB, SharedDBConnection
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='庫名',
        charset='utf8'
    )

def connect(type):
    conn = POOL.connection()
    cursor = conn.cursor(cursor=type)
    return conn,cursor


def connect_close(conn,cursor):
    cursor.close()
    conn.close()

def fetch_all(sql,args,type=pymysql.cursors.DictCursor):
    conn,cursor = connect(type)

    cursor.execute(sql, args)
    record_list = cursor.fetchall()
    connect_close(conn,cursor)

    return record_list


def fetch_one(sql, args):
    conn, cursor = connect()
    cursor.execute(sql, args)
    result = cursor.fetchone()
    connect_close(conn, cursor)

    return result


def insert(sql, args):
    conn, cursor = connect()
    row = cursor.execute(sql, args)
    conn.commit()
    connect_close(conn, cursor)
    return row
helper.py
from flask import Flask,request,render_template,session,current_app,g,redirect
from wtforms import Form
from wtforms.fields import simple
from wtforms.fields import html5
from wtforms.fields import core

from wtforms import widgets
from wtforms import validators

app = Flask(__name__)


class LoginForm(Form):
    name = simple.StringField(
        validators=[
            validators.DataRequired(message='用戶名不能為空.'),
            # validators.Length(min=6, max=18, message='用戶名長度必須大於%(min)d且小於%(max)d')
        ],
        widget=widgets.TextInput(),
        render_kw={'placeholder':'請輸入用戶名'}
    )
    pwd = simple.PasswordField(
        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個特殊字符')

        ],
        render_kw={'placeholder':'請輸入密碼'}
    )


@app.route('/login',methods=['GET','POST'])
def login():
    if request.method == "GET":
        form = LoginForm()
        # print(form.name,type(form.name)) # form.name是StringField()對象, StringField().__str__
        # print(form.pwd,type(form.pwd))   # form.pwd是PasswordField()對象,PasswordField().__str__
        return render_template('login.html',form=form)

    form = LoginForm(formdata=request.form)
    if form.validate():
        print(form.data)
        return redirect('https://www.luffycity.com/home')
    else:
        # print(form.errors)
        return render_template('login.html', form=form)


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

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

    pwd_confirm = simple.PasswordField(
        label='重復密碼',
        validators=[
            validators.DataRequired(message='重復密碼不能為空.'),
            validators.EqualTo('pwd', message="兩次密碼輸入不一致")
        ],
        widget=widgets.PasswordInput(),
        render_kw={'class': 'form-control'}
    )

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

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

    hobby = core.SelectMultipleField(
        label='愛好',
        choices=(
            (1, '籃球'),
            (2, '足球'),
        ),
        coerce=int
    )

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



@app.route('/register', methods=['GET', 'POST'])
def register():
    if request.method == 'GET':
        form = RegisterForm()
        return render_template('register.html', form=form)

    form = RegisterForm(formdata=request.form)
    if form.validate():
        print(form.data)
        return redirect('https://www.baidu.com')

    return render_template('register.html', form=form)

import helper
class UserForm(Form):
    city = core.SelectField(
        label='城市',
        choices=(),
        coerce=int
    )
    name = simple.StringField(label='姓名')

    def __init__(self,*args,**kwargs):
        super(UserForm,self).__init__(*args,**kwargs)

        self.city.choices=helper.fetch_all('select id,name from tb1',[],type=None)


@app.route('/user')
def user():
    if request.method == "GET":
        #form = UserForm(data={'name':'duoduo','city':3})
        form = UserForm()
        return render_template('user.html',form=form)

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

先思考幾個問題:

1、form 對象為什么可以for循環?

變成一個可迭代對象,類中含有__iter__,並且返回一個迭代器

class Foo(object):
    # def __iter__(self):
    #     return iter([11,22,33])   #值是11,22,33

    def __iter__(self):
        yield 1
        yield 2
        yield 3
obj=Foo()

for item in obj:
    print(item)

 

2、__new__方法返回值決定對象到底是什么

class Bar(object):
    def __init__(self,cls):
        self.cls=cls

class Foo(object):
    def __new__(cls, *args, **kwargs):
        #return super(Foo,cls).__new__(cls,*args,**kwargs)  <__main__.Foo object at 0x0000023741EEAC88>
        return  Bar(cls)    #<__main__.Bar object at 0x0000013FC1ABA4E0>

obj=Foo()
print(obj)

 

3、metaclass   

  1. 創建類時,先執行type的__init__方法,當一個類實例化時,
  2. 執行type的__call__方法, 先執行類的__new__,創建對象再執行類的__init__,初始化
  3. __call__方法的返回值就是實例化的對象
#類創建的兩種方式      類是type創建的

#1、
# class Foo(object):
#     a1=123
#     def func(self):
#         return 666

# Foo=type('Foo',(object,),{'a1':123,'func':lambda self:666})

#2、自定義type
#
# class MyType(type):
#     pass
#
# class Foo(object,metaclass=MyType):  #指定當前類由誰創建
#     a1=123
#     def func(self):
#         return 666

# Foo=MyType('Foo',(object,),{'a1':123,'func':lambda self:666})

#3、metaclass 作用
#指定當前類由誰來創建


class MyType(type):
    def __init__(self,*args,**kwargs):
        super(MyType,self).__init__(*args,**kwargs)

    def __call__(cls, *args, **kwargs):
        obj=cls.__new__(cls)
        cls.__init__(obj,*args, **kwargs)
        return obj

class Foo(object,metaclass=MyType):
    a1=123

    def __init__(self):
        pass

    def __new__(cls, *args, **kwargs):
        return object.__new__(cls)

    def func(self):
        return 666

#Foo是類
#Foo是MyType的一個對象

obj=Foo()

 

4、wtforms的實現

from wtforms import Form  #先點開Form

 

 

 

 我們創建的類沒有metaclass但他繼承的父類,有指定metaclass,就按照父類的來創建類

看看FormMeta 中的__init__方法做了什么事?

 實例化時先執行__new__

 

 

 

 UnboundField類是做什么的?

 

 

做完自己的操作,再執行父類的__call__,然后type會調用自己__new__方法,__init__方法

 下面Form 的序列化

 

源碼:

類的創建  type.__init__

對象的創建  type.__call__   1、類.__new__  2、類.__init__


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM