小男孩‘自慰网亚洲一区二区,亚洲一级在线播放毛片,亚洲中文字幕av每天更新,黄aⅴ永久免费无码,91成人午夜在线精品,色网站免费在线观看,亚洲欧洲wwwww在线观看

分享

【Python之路】特別篇

 highoo 2019-03-20
前情提要

1. 作用域

  在python中,函數會創(chuàng)建一個新的作用域。python開發(fā)者可能會說函數有自己的命名空間,差不多一個意思。這意味著在函數內部碰到一個變量的時候函數會優(yōu)先在自己的命名空間里面去尋找。

python中的作用域分4種情況:

  • L:local,局部作用域,即函數中定義的變量;

  • E:enclosing,嵌套的父級函數的局部作用域,即包含此函數的上級函數的局部作用域,但不是全局的;

  • G:globa,全局變量,就是模塊級別定義的變量;

  • B:built-in,系統(tǒng)固定模塊里面的變量,比如int, bytearray等。

搜索變量的優(yōu)先級順序依次是:作用域局部 > 外層作用域 > 當前模塊中的全局 > python內置作用域,也就是LEGB。

當然,local和enclosing是相對的,enclosing變量相對上層來說也是local。

1
2
3
4
5
6
7
8
9
10
11
12
13
x = int(2.9# int built-in
  
g_count = 0  # global
def outer():
    o_count = 1  # enclosing
    def inner():
        i_count = 2  # local
        print(o_count)
    # print(i_count) 找不到
    inner()
outer()
  
# print(o_count) #找不到

在Python中,只有模塊(module),類(class)以及函數(def、lambda)才會引入新的作用域,其它的代碼塊(如if、try、for等)是不會引入新的作用域的。

讓我們寫一個簡單的函數看一下 global 和 local 有什么不同:

1
2
3
4
5
6
7
>>> a_string = "This is a global variable"
>>> def foo():
...     print locals()
>>> print globals()
{..., 'a_string': 'This is a global variable'}
>>> foo() # 2
{}

內置的函數globals返回一個包含所有python解釋器知道的變量名稱的字典。

在#2我調用了函數 foo 把函數內部本地作用域里面的內容打印出來。

我們能夠看到,函數foo有自己獨立的命名空間,雖然暫時命名空間里面什么都還沒有。

global關鍵字 

當內部作用域想修改外部作用域的變量時,就要用到global和nonlocal關鍵字了,當修改的變量是在全局作用域(global作用域)上的,就要使用global先聲明一下,代碼如下:

1
2
3
4
5
6
7
8
9
count = 10
def outer():
    global count
    print(count)
    count = 100
    print(count)
outer()
#10
#100

nonlocal關鍵字 

global關鍵字聲明的變量必須在全局作用域上,不能嵌套作用域上,當要修改嵌套作用域(enclosing作用域,外層非全局作用域)中的變量怎么辦呢,這時就需要nonlocal關鍵字了

1
2
3
4
5
6
7
8
9
10
11
def outer():
    count = 10
    def inner():
        nonlocal count
        count = 20
        print(count)
    inner()
    print(count)
outer()
#20
#20 

2. 變量解析規(guī)則

當然這并不是說我們在函數里面就不能訪問外面的全局變量。

在python的作用域規(guī)則里面,創(chuàng)建變量一定會一定會在當前作用域里創(chuàng)建一個變量,

但是訪問或者修改變量時會先在當前作用域查找變量,沒有找到匹配變量的話會依次向上在閉合的作用域里面進行查看找。

所以如果我們修改函數foo的實現讓它打印全局的作用域里的變量也是可以的:

1
2
3
4
5
>>> a_string = "This is a global variable"
>>> def foo():
...     print a_string # 1
>>> foo()
This is a global variable

在#1處,python解釋器會嘗試查找變量a_string,當然在函數的本地作用域里面是找不到的,所以接著會去上層的作用域里面去查找。

但是另一方面,假如我們在函數內部給全局變量賦值,結果卻和我們想的不一樣:

1
2
3
4
5
6
7
8
>>> a_string = "This is a global variable"
>>> def foo():
...     a_string = "test" # 1
...     print locals()
>>> foo()
{'a_string': 'test'}
>>> a_string # 2
'This is a global variable'

我們能夠看到,全局變量能夠被訪問到(如果是可變數據類型(像list,dict這些)甚至能夠被更改)但是賦值不行。

在函數內部的#1處,我們實際上新創(chuàng)建了一個局部變量,隱藏全局作用域中的同名變量。

我們可以通過打印出局部命名空間中的內容得出這個結論。

我們也能看到在#2處打印出來的變量a_string的值并沒有改變。

3. 變量生存周期

值得注意的一個點是,變量不僅是生存在一個個的命名空間內,他們都有自己的生存周期,請看下面這個例子:

1
2
3
4
5
6
7
>>> def foo():
...     x = 1
>>> foo()
>>> print x # 1
Traceback (most recent call last):
  ...
NameError: name 'x' is not defined

#1處發(fā)生的錯誤不僅僅是因為作用域規(guī)則導致的(盡管這是拋出了NameError的錯誤的原因)

它還和python以及其它很多編程語言中函數調用實現的機制有關。

在這個地方這個執(zhí)行時間點并沒有什么有效的語法讓我們能夠獲取變量x的值,因為它這個時候壓根不存在!

函數foo的命名空間隨著函數調用開始而開始,結束而銷毀。

4. 嵌套函數

Python允許創(chuàng)建嵌套函數。這意味著我們可以在函數里面定義函數而且現有的作用域和變量生存周期依舊適用。

1
2
3
4
5
6
7
8
>>> def outer():
...     x = 1
...     def inner():
...         print x # 1
...     inner() # 2
...
>>> outer()
1

python解釋器需找一個叫x的本地變量,查找失敗之后會繼續(xù)在上層的作用域里面尋找。

這個上層的作用域定義在另外一個函數里面。

對函數outer來說,變量x是一個本地變量,但是如先前提到的一樣,

函數inner可以訪問封閉的作用域(至少可以讀和修改)。

在#2處,我們調用函數inner,非常重要的一點是:

inner也僅僅是一個遵循python變量解析規(guī)則的變量名,python解釋器會優(yōu)先在outer的作用域里面對變量名inner查找匹配的變量.

5. 閉包

我們先不急著定義什么是閉包,先來看看一段代碼:

1
2
3
4
5
6
7
8
>>> def outer():
...     x = 1
...     def inner():
...         print x # 1
...     return inner
>>> foo = outer()
>>> foo.func_closure
(<cell at 0x...: int object at 0x...>,)

inner作為一個函數被outer返回,保存在一個變量foo,并且我們能夠對它進行調用foo()

不過它會正常的運行嗎?我們先來看看作用域規(guī)則。

所有的東西都在python的作用域規(guī)則下進行工作:“x是函數outer里的一個局部變量。

當函數inner在#1處打印x的時候,python解釋器會在inner內部查找相應的變量,當然會找不到,

所以接著會到封閉作用域里面查找,并且會找到匹配。

但是從變量的生存周期來看,該怎么理解呢?

我們的變量x是函數outer的一個本地變量,這意味著只有當函數outer正在運行的時候才會存在。

根據我們已知的python運行模式,我們沒法在函數outer返回之后繼續(xù)調用函數inner,在函數inner被調用的時候,變量x早已不復存在,可能會發(fā)生一個運行時錯誤。

萬萬沒想到,返回的函數inner居然能夠正常工作。

Python支持一個叫做函數閉包的特性,用人話來講就是:

嵌套定義在非全局作用域里面的函數能夠記住它在被定義的時候它所處的封閉命名空間。

這能夠通過查看函數的func_closure屬性得出結論,這個屬性里面包含封閉作用域里面的值

(只會包含被捕捉到的值,比如x,如果在outer里面還定義了其他的值,封閉作用域里面是不會有的)

記住,每次函數outer被調用的時候,函數inner都會被重新定義。

閉包用途:

用途1
用途2

 

 


裝飾器需要掌握知識

  • 作用域知識(LEGB)
  • 函數知識

    • 函數名可以作為參數輸入

    • 函數名可以作為返回值
  • 閉包
    • 在內部函數里,對外部作用域的變量引用,那么內部函數就是一個閉包

寫代碼要遵循開發(fā)封閉原則,雖然在這個原則是用的面向對象開發(fā),但是也適用于函數式編程,簡單來說,它規(guī)定已經實現的功能代碼不允許被修改,但可以被擴展,即:

  • 封閉:已實現的功能代碼塊

  • 開放:對擴展開發(fā)

如果需要在  函數執(zhí)行前 額外執(zhí)行其他功能的話,我們就可以用到裝飾器來實現~

1
2
3
4
5
def index():
    print("Hello !")
    return True
     
index()

如果我們需要在函數 f1執(zhí)行前先輸出一句 Start ,函數執(zhí)行后輸出一句 End  ,那么我們可以這樣做:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def outer(func):
    def inner():
        print("Start")
        result = func()
        print("End")
        return result
    return inner
@outer     # index = outer(index)
def index():
    print("Hello !")
    return True
result = index()
  
# Start
# Hello !
# End

當寫完這段代碼后(函數未被執(zhí)行、未被執(zhí)行、未被執(zhí)行),python解釋器就會從上到下解釋代碼,步驟如下:

1.先把 def outer(func) 函數加載到內存

2.執(zhí)行@outer

執(zhí)行@outer 時 , 先把 index 函數 加載到內存 ! 并且內部會執(zhí)行如下操作: 

1.執(zhí)行 outer 函數,將 index 作為參數傳遞   ( 此時 func = index )

2.將 outer 函數返回值 ( return inner ),重新賦值給 index  (index = inner)

然后執(zhí)行下面的 result = index()  => 相當于 執(zhí)行了 inner() 函數!!!


 

問題:如果被裝飾的函數如果有參數呢?

一個參數
兩個參數

問題:可以裝飾具有處理n個參數的函數的裝飾器嗎?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
def outer(func):
    def inner(*args,**kwargs):
        print("start")
        result = func(*args,**kwargs)
        print("end")
        return result
    return inner
@outer
def index1(a1,):
    print(a1)
    return True
@outer
def index2(a1,a2):
    print(a1,a2)
    return True
@outer
def index3(a1,a2,a3):
    print(a1,a2,a3)
    return True
index1(5)
index2(5,6)
index3(5,6,7)

 

多層裝飾器

問題:一個函數可以被多個裝飾器裝飾嗎?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
def outer_0(func):
    def inner(*args,**kwargs):
        print("0.5")
        result = func(*args,**kwargs)
        return result
    return inner
def outer_1(func):
    def inner(*args,**kwargs):
        print("123")
        result = func(*args,**kwargs)
        print("456")
        return result
    return inner
@outer_0
@outer_1
def index(a1,a2):
    print(a1,a2)
    return True
index(1,2)

1.先把 outer_0 、outer_1 和 index 加載到內存

2.執(zhí)行 @outer_0 時, func = @outer_1 和 index 函數的結合體

3.然后執(zhí)行 @outer_0 的 inner , 其中包含了 @outer_1 和index  =>  所以先執(zhí)行 @outer_1

4.當執(zhí)行 @outer_1 時, func = index , 并執(zhí)行 inner -> 再執(zhí)行 func  即 index 函數

(原理同 只有一個裝飾器一樣 , 可以 把 @outer_1 和 def index 看成一個 結成的函數  => 當作只有一個 裝飾器@outer_0)

 

帶參數的裝飾器

  裝飾器還有更大的靈活性,例如帶參數的裝飾器:在上面的裝飾器調用中,比如@show_time,

  該裝飾器唯一的參數就是執(zhí)行業(yè)務的函數。

  裝飾器的語法允許我們在調用時,提供其它參數,比如@decorator(a)。這樣,就為裝飾器的編寫和使用提供了更大的靈活性。

復制代碼
import time
def time_logger(flag=0):
 
    def show_time(func):
 
            def wrapper(*args,**kwargs):
                start_time=time.time()
                func(*args,**kwargs)
                end_time=time.time()
                print('spend %s'%(end_time-start_time))
 
                if flag:
                    print('將這個操作的時間記錄到日志中')

            return wrapper

    return show_time
 
@time_logger(3)
def add(*args,**kwargs):
    time.sleep(1)
    sum=0
    for i in args:
        sum+=i
    print(sum)
 
add(2,7,5)
復制代碼

  @time_logger(3) 做了兩件事:

 ?。?)time_logger(3):得到閉包函數show_time,里面保存環(huán)境變量flag

 ?。?)@show_time   :add=show_time(add)

  上面的time_logger是允許帶參數的裝飾器。它實際上是對原有裝飾器的一個函數封裝,并返回一個裝飾器(一個含有參數的閉包函數)。

  當我們使用@time_logger(3)調用的時候,Python能夠發(fā)現這一層的封裝,并把參數傳遞到裝飾器的環(huán)境中。

 

類裝飾器

  再來看看類裝飾器,相比函數裝飾器,類裝飾器具有靈活度大、高內聚、封裝性等優(yōu)點。

  使用類裝飾器還可以依靠類內部的__call__方法,當使用 @ 形式將裝飾器附加到函數上時,就會調用此方法。

復制代碼
import time
class Foo(object):
    def __init__(self, func):
        self._func = func

    def __call__(self):
        start_time=time.time()
        self._func()
        end_time=time.time()
        print('spend %s'%(end_time-start_time))

@Foo  #bar=Foo(bar)
def bar():
    print ('bar')
    time.sleep(2)

bar()    #bar=Foo(bar)()>>>>>>>沒有嵌套關系了,直接active Foo的 __call__方法
復制代碼

注意 :

  使用裝飾器極大地復用了代碼,但是他有一個缺點就是原函數的元信息不見了,比如函數的docstring、__name__、參數列表,:

  我們有functools.wraps,wraps本身也是一個裝飾器,它能把原函數的元信息拷貝到裝飾器函數中,這使得裝飾器函數也有和原函數一樣的元信息了。

復制代碼
from functools import wraps
 
def logged(func):
 
    @wraps(func)
    def wrapper(*args, **kwargs):
        print (func.__name__ + " was called")
        return func(*args, **kwargs)
    return wrapper
 
@logged
def cal(x):
   return x + x * x
 
print(cal.__name__)         #cal
復制代碼

 

    本站是提供個人知識管理的網絡存儲空間,所有內容均由用戶發(fā)布,不代表本站觀點。請注意甄別內容中的聯(lián)系方式、誘導購買等信息,謹防詐騙。如發(fā)現有害或侵權內容,請點擊一鍵舉報。
    轉藏 分享 獻花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多