當前位置:
首頁 > 知識 > Werkzeug 庫之 local 模塊

Werkzeug 庫之 local 模塊

點擊

上方藍字

,快速關注我們)




來源:fanchunke


fanchunke.me/Flask/Werkzeug庫——local模塊/


如有好文章投稿,請點擊 → 這裡了解詳情




一、簡介



在local模塊中,Werkzeug實現了類似Python標準庫中thread.local的功能。thread.local是線程局部變數,也就是每個線程的私有變數,具有線程隔離性,可以通過線程安全的方式獲取或者改變線程中的變數。參照thread.local,Werkzeug實現了比thread.local更多的功能。Werkzeug官方文檔關於local模塊中對此進行了說明:





The Python standard library comes with a utility called 「thread locals」. A thread local is a global object in which you can put stuff in and get back later in a thread-safe way. That means whenever you set or get an object on a thread local object, the thread local object checks in which thread you are and retrieves the correct value.




This, however, has a few disadvantages. For example, besides threads there are other ways to handle concurrency in Python. A very popular approach is greenlets. Also, whether every request gets its own thread is not guaranteed in WSGI. It could be that a request is reusing a thread from before, and hence data is left in the thread local object.



總結起來: 以上文檔解釋了對於「並發」問題,多線程並不是唯一的方式,在Python中還有「協程」(關於協程的概念和用法可以參考:廖雪峰的博客)。「協程」的一個顯著特點在於是一個線程執行,一個線程可以存在多個協程。也可以理解為:協程會復用線程。對於WSGI應用來說,如果每一個線程處理一個請求,那麼thread.local完全可以處理,但是如果每一個協程處理一個請求,那麼一個線程中就存在多個請求,用thread.local變數處理起來會造成多個請求間數據的相互干擾。




對於上面問題,Werkzeug庫解決的辦法是local模塊。local模塊實現了四個類:






  • Local



  • LocalStack



  • LocalProxy



  • LocalManager




本文重點介紹前兩個類的實現。



二、Local類




Local類能夠用來存儲線程的私有變數。在功能上這個thread.local類似。與之不同的是,Local類支持Python的協程。在Werkzeug庫的local模塊中,Local類實現了一種數據結構,用來保存線程的私有變數,對於其具體形式,可以參考它的構造函數:





class

Local

(

object

)

:


__slots__

=

(

"__storage__"

,

"__ident_func__"

)


def

__init__

(

self

)

:


object

.

__setattr__

(

self

,

"__storage__"

,

{})

object

.

__setattr__

(

self

,

"__ident_func__"

,

get_ident

)




從上面類定義可以看出,Local類具有兩個屬性:__storage__和__ident_func__。從構造函數來看,__storage__是一個字典,而__ident_func__是一個函數,用來識別當前線程或協程。




1. __ident_func__




關於當前線程或協程的識別,local模塊引入get_ident函數。如果支持協程,則從greenlet庫中導入相關函數,否則從thread庫中導入相關函數。調用get_ident將返回一個整數,這個整數可以確定當前線程或者協程。





try

:


from

greenlet

import

getcurrent

as

get_ident


except

ImportError

:


try

:


from

thread

import

get_ident


except

ImportError

:


from

_thread

import

get_ident




2. __storage__




__storage__是一個字典,用來存儲不同的線程/協程,以及這些線程/協程中的變數。以下是一個簡單的多線程的例子,用來說明__storage__的具體結構。





import

threading


from

werkzeug

.

local

import

Local


l

=

Local

()


l

.

__storage__


def

add_arg

(

arg

,

i

)

:


l

.

__setattr__

(

arg

,

i

)


for

i

in

range

(

3

)

:


arg

=

"arg"

+

str

(

i

)


t

=

threading

.

Thread

(

target

=

add_arg

,

args

=

(

arg

,

i

))


t

.

start

()


l

.

__storage__




上面的例子,具體分析為:






  • 首先,代碼創建了一個Local的實例l,並且訪問它的__storage__屬性。由於目前還沒有數據,所以l.__storage__的結果為{};



  • 代碼創建了3個線程,每個線程均運行add_arg(arg, i)函數。這個函數會為每個線程創建一個變數,並對其賦值;



  • 最後,再次訪問l.__storage__。這次,l實例中將包含3個線程的信息。其結果為:





{20212: {"arg0": 0}, 20404: {"arg1": 1}, 21512: {"arg2": 2}}




從以上結果可以看出,__storage__這個字典的鍵表示不同的線程(通過get_ident函數獲得線程標識數值),而值表示對應線程中的變數。這種結構將不同的線程分離開來。當某個線程要訪問該線程的變數時,便可以通過get_ident函數獲得線程標識數值,進而可以在字典中獲得該鍵對應的值信息了。




三、LocalStack類




LocalStack類和Local類類似,但是它實現了棧數據結構。




在LocalStack類初始化的時候,便會創建一個Local實例,這個實例用於存儲線程/協程的變數。與此同時,LocalStack類還實現了push、pop、top等方法或屬性。調用這些屬性或者方法時,該類會根據當前線程或協程的標識數值,在Local實例中對相應的數值進行操作。以下還是以一個多線程的例子進行說明:





from

werkzeug

.

local

import

LocalStack

,

LocalProxy


import

logging

,

random

,

threading

,

time


# 定義logging配置


logging

.

basicConfig

(

level

=

logging

.

DEBUG

,


format

=

"(%(threadName)-10s) %(message)s"

,


)


# 生成一個LocalStack實例_stack


_stack

=

LocalStack

()


# 定義一個RequestConetxt類,它包含一個上下文環境。


# 當調用這個類的實例時,它會將這個上下文對象放入


# _stack棧中去。當退出該上下文環境時,棧會pop其中


# 的上下文對象。


class

RequestConetxt

(

object

)

:


def

__init__

(

self

,

a

,

b

,

c

)

:


self

.

a

=

a


self

.

b

=

b


self

.

c

=

c


def

__enter__

(

self

)

:


_stack

.

push

(

self

)


def

__exit__

(

self

,

exc_type

,

exc_val

,

exc_tb

)

:


if

exc_tb

is

None

:


_stack

.

pop

()


def

__repr__

(

self

)

:


return

"%s, %s, %s"

%

(

self

.

a

,

self

.

b

,

self

.

c

)


# 定義一個可供不同線程調用的方法。當不同線程調用該


# 方法時,首先會生成一個RequestConetxt實例,並在這


# 個上下文環境中先將該線程休眠一定時間,之後列印出


# 目前_stack中的信息,以及當前線程中的變數信息。


# 以上過程會循環兩次。


def

worker

(

i

)

:


with

request_context

(

i

)

:


for

j

in

range

(

2

)

:


pause

=

random

.

random

()


logging

.

debug

(

"Sleeping %0.02f"

,

pause

)


time

.

sleep

(

pause

)


logging

.

debug

(

"stack: %s"

%

_stack

.

_local

.

__storage__

.

items

())


logging

.

debug

(

"ident_func(): %d"

%

_stack

.

__ident_func__

())


logging

.

debug

(

"a=%s; b=%s; c=%s"

%


(

LocalProxy

(

lambda

:

_stack

.

top

.

a

),


LocalProxy

(

lambda

:

_stack

.

top

.

b

),


LocalProxy

(

lambda

:

_stack

.

top

.

c

)))


logging

.

debug

(

"Done"

)


# 調用該函數生成一個RequestConetxt對象


def

request_context

(

i

)

:


i

=

str

(

i

+

1

)


return

RequestConetxt

(

"a"

+

i

,

"b"

+

i

,

"c"

+

i

)


# 在程序最開始顯示_stack的最初狀態


logging

.

debug

(

"Stack Initial State: %s"

%

_stack

.

_local

.

__storage__

.

items

())


# 產生兩個線程,分別調用worker函數


for

i

in

range

(

2

)

:


t

=

threading

.

Thread

(

target

=

worker

,

args

=

(

i

,))


t

.

start

()


main_thread

=

threading

.

currentThread

()


for

t

in

threading

.

enumerate

()

:


if

t

is

not

main_thread

:


t

.

join

()


# 在程序最後顯示_stack的最終狀態


logging

.

debug

(

"Stack Finally State: %s"

%

_stack

.

_local

.

__storage__

.

items

())




以上例子的具體分析過程如下:






  • 首先,先創建一個LocalStack實例_stack,這個實例將存儲線程/協程的變數信息;



  • 在程序開始運行時,先檢查_stack中包含的信息;



  • 之後創建兩個線程,分別執行worker函數;



  • worker函數首先會產生一個上下文對象,這個上下文對象會放入_stack中。在這個上下文環境中,程序執行一些操作,列印一些數據。當退出上下文環境時,_stack會pop該上下文對象。



  • 在程序結束時,再次檢查_stack中包含的信息。




運行上面的測試例子,產生結果如下:





(

MainThread

)

Stack Initial

State

:

[]


(

Thread

-

1

)

Sleeping

0.31


(

Thread

-

2

)

Sleeping

0.02


(

Thread

-

2

)

stack

:

[(

880

,

{

"stack"

:

[

a1

,

b1

,

c1

]}),

(

13232

,

{

"stack"

:

[

a2

,

b2

,

c2

]})]


(

Thread

-

2

)

ident_func

()

:

13232


(

Thread

-

2

)

a

=

a2

;

b

=

b2

;

c

=

c2


(

Thread

-

2

)

Sleeping

0.49


(

Thread

-

1

)

stack

:

[(

880

,

{

"stack"

:

[

a1

,

b1

,

c1

]}),

(

13232

,

{

"stack"

:

[

a2

,

b2

,

c2

]})]


(

Thread

-

1

)

ident_func

()

:

880


(

Thread

-

1

)

a

=

a1

;

b

=

b1

;

c

=

c1


(

Thread

-

1

)

Sleeping

0.27


(

Thread

-

2

)

stack

:

[(

880

,

{

"stack"

:

[

a1

,

b1

,

c1

]}),

(

13232

,

{

"stack"

:

[

a2

,

b2

,

c2

]})]


(

Thread

-

2

)

ident_func

()

:

13232


(

Thread

-

2

)

a

=

a2

;

b

=

b2

;

c

=

c2


(

Thread

-

2

)

Done


(

Thread

-

1

)

stack

:

[(

880

,

{

"stack"

:

[

a1

,

b1

,

c1

]})]


(

Thread

-

1

)

ident_func

()

:

880


(

Thread

-

1

)

a

=

a1

;

b

=

b1

;

c

=

c1


(

Thread

-

1

)

Done


(

MainThread

)

Stack

Finally

State

:

[]




注意到:






  • 當兩個線程在運行時,_stack中會存儲這兩個線程的信息,每個線程的信息都保存在類似{"stack": [a1, b1, c1]}的結構中(註:stack鍵對應的是放入該棧中的對象,此處為了方便列印了該對象的一些屬性)。



  • 當線程在休眠和運行中切換時,通過線程的標識數值進行區分不同線程,線程1運行時它通過標識數值只會對屬於該線程的數值進行操作,而不會和線程2的數值混淆,這樣便起到線程隔離的效果(而不是通過鎖的方式)。



  • 由於是在一個上下文環境中運行,當線程執行完畢時,_stack會將該線程存儲的信息刪除掉。在上面的運行結果中可以看出,當線程2運行結束後,_stack中只包含線程1的相關信息。當所有線程都運行結束,_stack的最終狀態將為空。




看完本文有收穫?請轉

發分享給更多人


關注「Python開發者」,提升Python技能


喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 Python開發者 的精彩文章:

PyPy v5.8 發布,有這些新亮點
量化投資重磅上線,橫掃金融界就靠你啦!
Werkzeug 庫以及 wrapper 模塊
這裡有 10 個省時間的 PyCharm 技巧

TAG:Python開發者 |