1. 程式人生 > >04 python之函式詳解

04 python之函式詳解

一、函式初識

函式的產生:函式就是封裝一個功能的程式碼片段。

li = ['spring', 'summer', 'autumn', 'winter']
def function():
    count = 0
    for j in li:
        count += 1
    print(count)
function()        # 4
  • def 關鍵字,定義一個函式
  • function 函式名的書寫規則與變數一樣。
  • 括號是用來傳參的。
  • 函式體,就是函式裡面的邏輯程式碼

程式碼從上至下執行,執行到def function() 時, 將function這個變數名載入到臨時記憶體中,但它不執行。

函式的執行:函式名 + () 

 

使用__name__方法獲取函式名 ,使用__doc___方法獲取函式的解釋  

def func1():
    """
    此函式是完成登陸的功能,引數分別是...作用。
    return: 返回值是登陸成功與否(True,False)
    """
    print(666)

func1()
print(func1.__name__)         #獲取函式名
print(func1.__doc__)         #獲取函式名註釋說明 
執行輸出:
666
func1
此函式是完成登陸的功能,引數分別是...作用。
return: 返回值是登陸成功與否(True,False)

這個有什麼用呢?比如日誌功能,需要打印出誰在什麼時間,呼叫了什麼函式,函式是幹啥的,花費了多次時間,這個時候,就需要獲取函式的有用資訊了

 

1. 函式返回值

寫函式,不要在函式中寫print(),  函式是以功能為導向的,除非測試的時候,才可以寫print()

  • 在函式中,遇到return結束函式
def fun():
    print(111)
    return
    print(444)
fun() 
執行輸出:111
  • 將值返回給函式的呼叫者
def fun():
    a = 134
    return a
print(fun()) 
執行輸出:123

1)無 return

def fun():
    pass
print(fun()) 
執行輸出:None

2)return 1個值。該值是什麼,就直接返回給函式的呼叫者,函式名()

def fun():
    return [1,2,3]
print(fun()) 
執行輸出:[1, 2, 3]

3)return 多個值 將多個值放到一個元組裡,返回給函式的呼叫者。

def fun():
    return 1,2,[33,44],'abc'
print(fun()) 
執行輸出:
(1, 2, [33, 44], 'abc')

2. 函式的傳參

(1)實參:在函式執行者裡面的引數叫實參

①位置引數:按順序,一一對應

def func(a,b,c):
    print(a)
    print(b)
    print(c)

func('fdsafdas',3,4) 
執行輸出:
fdsafdas
3
4

如果少一個引數呢?

def func(a,b,c):
    print(a)
    print(b)
    print(c)

func(3,4) 
執行報錯:TypeError: func() missing 1 required positional argument: 'c'

必須是一一對應的。

def compare(x,y):
    ret = x if x > y else y   #三元運算,針對簡單的if else才能使用
    return ret

print(compare(123,122334))        # 122334

②關鍵字引數:可以不按順序,但是必須一一對應

def compare(x,y):
    ret = x if x > y else y
    return ret
print(compare(y=13,x=1))
執行結果:13

③混合引數:關鍵字引數一定要在位置引數後面

def func1(a,b,c,d,e):
    print(a)
    print(b)
    print(c)
    print(d)
    print(e)

func1(1,4,d=2,c=3,e=5) 
執行輸出:
1
4
3
2
5

(2) 形參:

①位置引數:按順序和實參一一對應,位置引數必須傳值

def func(a,b,c):
    print(a)
    print(b)
print(c)

func('fdsafdas',3,4) 
執行輸出:
fdsafdas
3
4

②預設引數:傳參則覆蓋,不傳則預設,預設引數永遠在位置引數後面

例1.

def func(a,b=666):
    print(a,b)
func(1,2) 
執行輸出:1 2

例2.

def func(a,b=666):
    print(a,b)
func(1) 

執行輸出:1 666

舉一個場景:班主任錄入員工資訊表,有2個問題:第一,男生居多;第二,完成函式功能 *****

def  Infor(username,sex='男'):
    with open('name_list',encoding='utf-8',mode='a') as f1:
        f1.write('{}\t{}\n'.format(username,sex))

while True:
    username = input('請輸入姓名(男生以1開頭):').strip()
    if '1' in username:
        username = username[1:]        #去除1
        Infor(username)
    else:
        Infor(username,'女') 

③動態引數:當函式的形引數量不一定時,可以使用動態引數。用*args和**kwargs接收,args是元組型別,接收除鍵值對以外的引數(接收位置引數),kwargs是字典型別,接收鍵值對(關鍵字引數),並儲存在字典中。

def func(*args,**kwargs):
    print(args,type(args))
    print(kwargs,type(kwargs))

func(1,2,3,4,'alex',name = 'alex')
輸出結果是:
(1, 2, 3, 4, 'alex') <class 'tuple'>
{'name': 'alex'} <class 'dict'>

“ * "的魔性作用

(1)在函式定義時:*位置引數和**關鍵字引數代表聚合

將所有實參的位置引數聚合到一個元組中,並將這個元組賦值給args。在關鍵引數前加“ ** ”代表將實參的關鍵字引數聚合到一個字典中,並將這個字典賦值給kwargs。

將2個列表的所有元素賦值給args

def func(*args):
    print(args)

l1 = [1,2,30]
l2 = [1,2,33,21,45,66]
func(*l1)
func(*l1,*l2) 
執行輸出:
(1, 2, 30)
(1, 2, 30, 1, 2, 33, 21, 45, 66)

傳兩個字典給**kwargs

def func(**kwargs):
    print(kwargs)

dic1 = {'name':'jack','age':22}
dic2 = {'name1':'rose','age1':21}
func(**dic1,**dic2) 
執行輸出:
{'name': 'jack', 'age': 22, 'name1': 'rose', 'age1': 21}
def func(*args,**kwargs):
    print(args)
    print(kwargs)

func(*[1,2,3], *[4,5,6], **{'name':'alex'}, **{'age':18})    #相當於func([1,2,3,4,5,6], {'name':'alex','age':18})


(2)在函式的呼叫執行時,打散

   *可迭代物件,代表打散(list,tuple,str,dict(鍵))將元素一一新增到args。

   **字典,代表打散,將所有鍵值對放到一個kwargs字典裡。

def func(*args,**kwargs):
    print(args,kwargs)

dic1 = {'name':'jack','age':22}
dic2 = {'name1':'rose','age1':21}

func(*[1,2,3,4],*'asdk',**dic1,**dic2) 
執行輸出:(1, 2, 3, 4, 'a', 's', 'd', 'k') {'age1': 21, 'name': 'jack', 'age': 22, 'name1': 'rose'}

形參的順序:位置引數 ----> *args ----->關鍵字引數-------->預設引數 ------->**kwargs

*args引數,可以不傳,預設為空(),**kwargs 動態傳參,他將所有的關鍵字引數(未定義的)放到一個字典中

def func(a,b,c,d,*args,e='男',**kwargs):
    print(a,b,c,d,args,e,kwargs)

func(1,2,3,4,5,6,7,v=3,m=7,h=9,e='女') 
執行輸出:1 2 3 4 (5, 6, 7) 女 {'v': 3, 'h': 9, 'm': 7}
def func(a,b,c,**kwargs):
    print(kwargs)
func(1,2,r=4,b1=5,c1=6,c=7) 

執行輸出:{'r': 4, 'c1': 6, 'b1': 5}

執行沒有報錯,是因為函式接收引數後,它會從左邊到右找,最後找到了c,c=7引數,在a,b,c裡面已經定義好了,所以在輸出的字典中,並未出現。因為kwargs返回的是未定義的關鍵字引數。

如果函式含有多個未知引數,一般使用如下格式:

def func1(*args,**kwargs):
    pass
func1() 

二、名稱空間和作用域

  當執行函式的時候,他會在記憶體中開闢一個臨時名稱空間,存放函式體內的所有變數與值的關係,隨著函式的執行完畢,臨時空間自動關閉。

 

函式裡面的變數,在函式外面能直接引用麼?不能

def func1():
    m = 1
    print(m)

print(m)                          # NameError: name 'm' is not defined

上面為什麼會報錯呢?現在我們來分析一下python內部的原理是怎麼樣:

我們首先回憶一下Python程式碼執行的時候遇到函式是怎麼做的,從Python直譯器開始執行之後,就在記憶體中開闢裡一個空間,每當遇到一個變數的時候,就把變數名和值之間對應的關係記錄下來,但是當遇到函式定義的時候,直譯器只是象徵性的將函式名讀入記憶體,表示知道這個函式存在了,至於函式內部的變數和邏輯,直譯器根本不關心。等執行到函式呼叫的時候,Python直譯器會再開闢一塊記憶體來儲存這個函式裡面的內容,這個時候,才關注函式裡面有哪些變數,而函式中的變數會儲存在新開闢出來的記憶體中,函式中的變數只能在函式內部使用,並且會隨著函式執行完畢,這塊記憶體中的所有內容也會被清空。

1. 名稱空間和作用域

名稱空間:存放”名字與值關係的空間“

①全域性名稱空間:程式碼在執行時,建立的儲存”變數名與值的關係“的記憶體空間

②區域性名稱空間:在函式呼叫時臨時開闢出來的空間,會隨著函式的執行完畢而被清空

③內建名稱空間:存放了python直譯器為我們提供的名字:input,print,str,list,tuple...它們都是我們熟悉 的,拿過來就可以用的方法。

 

作用域:就是作用範圍

①全域性作用域:全域性名稱空間、內建名稱空間。在整個檔案的任意位置都能被引用、全域性有效

②區域性作用域:區域性名稱空間,只能在區域性範圍內生效

 

載入順序:

內建名稱空間(程式執行前載入)----->   全域性名稱空間(程式執行中從上至下載入) -----> 區域性名稱空間(程式執行中:呼叫時才載入)

 

取值順序:

  在區域性呼叫:區域性名稱空間->全域性名稱空間->內建名稱空間

  在全域性呼叫:全域性名稱空間->內建名稱空間

 

綜上所述,在找尋變數時,從小範圍,一層一層到大範圍去找尋。取值順序:就近原則

 

區域性變數舉例

name = 'summer'
def func1():
    name = 'spring'
    print(name)
func1()
執行輸出:spring
取值是從內到外
name = 'summer'
def func1():
    print(name)
func1() 

 執行輸出:老男孩

程式碼從上至下依次執行, 呼叫函式:函式裡面從上至下依次執行。

print(111)
def func1():
    print(333)
    func2()
    print(666)
def func2():
    print(444)
def func3():
    print(555)
    func2()

func1()
print(222) 
執行輸出:
111
333
444
666
222
def f1():
    def f2():
        def f3():
            print("in f3")
        print("in f2")
        f3()
    print("in f1")
    f2()
f1() 
執行輸出:
in f1
in f2
in f3

2. globals和locals方法

print(globals())         #全域性名稱空間所有變數,字典
print(locals())        #區域性名稱空間所有變數,字典 (當前)

globals()和locals()一般很少用,在函式邏輯比較複雜的情況下,可能會用到。

li = ['spring', 'summer', 'autumn', 'winter']

def func():
    a = 1
    b = 2
    print('func', globals())
    print('func', locals())

    def func1():
        c = 3
        d = 4
        print('func1', globals())
        print('func1', locals())

    func1()

func()

輸出結果

func {'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x011CC410>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': 'C:/Users/Administrator/houseinfo/test.py', '__cached__': None, 'li': ['spring', 'summer', 'autumn', 'winter'], 'func': <function func at 0x03542E40>}
func {'b': 2, 'a': 1}
func1 {'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x011CC410>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': 'C:/Users/Administrator/houseinfo/test.py', '__cached__': None, 'li': ['spring', 'summer', 'autumn', 'winter'], 'func': <function func at 0x03542E40>}
func1 {'d': 4, 'c': 3}

 (1)global:

①在區域性名稱空間宣告全域性變數

def func2():
    global name
    name = 'summer'

func2()
print(name)
執行結果:summer

②在區域性名稱空間對全域性變數進行修改(限於字串,數字)。

count = 1
def func1():
    global count
    count = count + 1
    print(count)
func1()
print(count)
執行結果:
2
2

因為全域性變數count被函式體的global count 覆蓋了

(2)nonlocal

①子函式對父函式的變數進行修改,此變數不能是全域性變數

a = 4
def func1():
    nonlocal a
    a = 5             #修改全域性變數
    #print(name)
func1()
print(a) 
執行輸出:SyntaxError: no binding for nonlocal 'a' found

②在區域性作用域中,對父級作用域的變數進行引用和修改,並且引用的哪層,從那層及以下此變數全部發生改變。

例1

def func1():
    b = 6
    def func2():
        b = 666
        print(b)
    func2()
    print(b)                 #父級不受影響
func1() 
執行輸出:
666
6

例2

def func1():
    b = 6
    def func2():
        nonlocal b          #表示可以影響父級,也就是func1()
        b = 666             #重新賦值
        print(b)
    func2()    
print(b)                 #這個時候,影響了b的值,輸出666
func1() 
執行輸出:
666
666

例3******

def aa():                     #不受ccl影響
    b = 42
    def bb():
        b = 10                 #影響子級函式,b都是10
        print(b)
        def cc():
            nonlocal b             #只能影響父級,也就是bb()
            b = b + 20             #b=10+20 也就是30
            print(b)
        cc()
        print(b)
    bb()
    print(b)
aa()
執行輸出:
10
30
30
42

注意

a = 5
def func1():
    a += 1
    print(a)
func1() 

執行報錯。這裡函式對全域性變數做了改變,是不允許操作的。函式內部可以引用全域性變數,不能修改。如果要修改,必須要global一下

a = 5
def func1():
    global a
    a += 1
    print(a)

func1()     #輸出6

三、裝飾器

1. 函式名應用

函式名是什麼?函式名是函式的名字,本質:變數,特殊的變數。

1)函式名就是函式的記憶體地址,直接列印函式名,就是列印記憶體地址

def func1():
    print(123)
print(func1)         # <function func1 at 0x0000029042E02E18>

2)函式名可以作為變數

def func1():
    print(111)

f = func1
f()                 # f() 就是func1()    

3)函式名可以作為函式的引數

def func1():
    print(111)

def func2(x):
    x()

func2(func1)         #func1作為func2的引數 

4)函式名可以作為函式的返回值

def wrapper():
    def inner():
        print('inner')
    return inner
f = wrapper()
f()

5)函式名可以作為容器類型別的元素

使用for迴圈批量執行函式
def func1():
    print('func1')
def func2():
    print('func2')
def func3():
    print('func3')

l1 = [func1,func2,func3]
for i in l1:
    i()

像上面函式名這種,叫做第一類物件。

 

第一類物件( first-class object)指:

1.可在執行期建立

2.可用作函式引數或返回值

3.可存入變數的實體

*不明白?那就記住一句話,就當普通變數用

 

2. 閉包

1、什麼是閉包:內層函式對外層函式變數(非全域性變數)的引用,並返回內層函式名,就形成了閉包。

2、閉包的作用:爬蟲、裝飾器

當程式執行遇到函式執行時,會在記憶體空間開闢區域性名稱空間,當函式執行完畢,該名稱空間會被銷燬。但是如果這個函 數內部形成閉包,則該記憶體空間不會隨著函式執行完而消失。

3、如何判斷是否是閉包:print(函式名.__closure__) 結果是cell說明是閉包,結果是None說明不是閉包。

 

閉包函式:內部函式包含對外部作用域而非全劇作用域變數的引用,該內部函式稱為閉包函式

 

閉包舉例

def wrapper():
    name = 'summer'
    def inner():
        print(name)
    inner()

wrapper()     # summer

如何判斷它是否是一個閉包函式呢? 內層函式名.__closure__  cell 就是=閉包

例1.

def wrapper():
    name = 'summer'
    def inner():
        print(name)
    inner()
    print(inner.__closure__)

wrapper()     
執行輸出:
summer
(<cell at 0x0000017FC9C90B58: str object at 0x0000017FCA349AD0>,)

例2.

name = 'summer'
def wrapper():
    def inner():
        print(name)
    inner()
    print(inner.__closure__)

wrapper() 
結果輸出:
summer
None

返回值為None 表示它不是閉包,因為name是一個全域性變數,如果函式呼叫了外層變數而非全域性變數,那麼它就是閉包。

例3.

name = 'summer'
def wrapper2():
    name1 = 'spring'
    def inner():
        print(name)
        print(name1)
    inner()
    print(inner.__closure__)

wrapper2()
結果輸出:
summer
spring
(<cell at 0x030B7310: str object at 0x03043680>,)

只要引用了外層變數至少一次,非全域性的,它就是閉包

例4:判斷下面的函式,是一個閉包嗎?******

name = 'summer'
def wraaper2(n):        #相當於n = 'summer' 
  def inner(): print(n) inner() print(inner.__closure__) wraaper2(name)
結果輸出:
summer
(<cell at 0x03867350: str object at 0x037F3680>,)

它也是一個閉包. 雖然wraaper2傳了一個全域性變數,但是在函式wraaper2內部,inner引用了外層變數,相當於在函式inner外層定義了 n = 'summer',所以inner是一個閉包函式

 

閉包的好處:當函式開始執行時,如果遇到了閉包,他有一個機制,他會永遠開闢一個記憶體空間,將閉包中的變數等值放入其中,不會隨著函式的執行完畢而消失。

 舉一個例子:爬3次,記憶體開了3次,很佔用記憶體

from urllib.request import urlopen
content1 = urlopen('https://www.cnblogs.com/').read().decode('utf-8')
content2 = urlopen('https://www.cnblogs.com/').read().decode('utf-8')
content3 = urlopen('https://www.cnblogs.com/').read().decode('utf-8')

把它封裝成閉包

from urllib.request import urlopen

def index():
    url = "https://www.cnblogs.com/"
    def get():
        return urlopen(url).read()
    return get        #return的是get,就是一個函式名

cnblog = index()
print(cnblog)               # <function index.<locals>.get at 0x02F46978>
content = cnblog()
print(content)              # 頁面原始碼

這個例子,只有第一遍,是從網站抓取的。之後的執行,直接從記憶體中載入,節省記憶體空間

 

3. 裝飾器

3.1 裝飾器初識

裝飾器本質:就是一個python函式,他可以讓其他函式在不需要做任何程式碼變動的前提下,增加額外的功能,裝飾器的返回值也是一個函式物件。

 

裝飾器的應用場景:比如插入日誌,效能測試,事務處理,快取等等場景。

import time
def timmer(f):                           #  2接收引數 f = func1
    def inner():
        start_time = time.time()             # 5.進入inner函式
        f()                             # 6.執行f(),也就是原來的func1函式。雖然func1被覆蓋了,但是之前的值還存在。                            請參考上面a,b賦值的例子
        end_time = time.time()             # 10 獲取當前時間
        print('此函式的執行時間為{}'.format(end_time - start_time))         # 11.輸出差值
    return inner                       # 3.將inner函式返回給函式呼叫者timmer(func1),此時程式結束,繼續執行func1()

def func1():                           # 7.進入函式
    print('in func1')               #  8.列印輸出
    time.sleep(1)                    #  9.等待1秒

func1 = timmer(func1)               # 1.等式計算右邊的,將func1函式傳給timmer函式,此時func1被覆蓋了,原來func1的不存在了。
print(func1)
func1()           # 4.這裡的func1是全新的func1,就是上面的賦值,此時相當於執行 inner函式
輸出結果:
<function timmer.<locals>.inner at 0x03822DF8>
in func1
此函式的執行時間為1.0003533363342285

程式碼從上至下執行

 

語法糖:想測試誰,前面加@裝飾器函式,即可。寫裝飾器,約定俗成,函式名為wrapper

def wrapper(func):
    def inner(*args,**kwargs):
        '''被裝飾函式之前'''
        ret = func(*args,**kwargs)
        '''被裝飾函式之後'''
        return ret
    return inner

@wrapper
def func(*args,**kwargs):
    print(args,kwargs)
    return 666

print(func())
輸出結果:
() {}
666

裝飾器利用return製造了一個假象,func()執行,其實是執行inner(),func()把原來的func()給覆蓋了

3.2 裝飾器傳參

例1:上面裝飾器的例子,func1,要傳2個引數a,b

import time
def timmer(f):
    def inner(a,b):
        start_time = time.time()
        f(a,b)
        end_time = time.time()
        print('此函式的執行時間為{}'.format(end_time - start_time))
    return inner

@timmer
def func1(a,b):
    print('in func1 {}{}'.format(a,b))
    time.sleep(1)  # 模擬程式邏輯

func1(1,2) 
執行輸出:
in func1 12
此函式的執行時間為1.0006024837493896

例2:如果有多個引數呢?改成動態引數

import time
def timmer(f):
    def inner(*args,**kwargs):
        start_time = time.time()
        f(*args,**kwargs)
        end_time = time.time()
        print('此函式的執行時間為{}'.format(end_time - start_time))
    return inner

@timmer
def func1(*args,**kwargs):
    print('in func1 {}{}'.format(args,kwargs))
    time.sleep(1)  # 模擬程式邏輯

func1(1,2,a='3',b=4) 
執行輸出:
in func1 (1, 2){'b': 4, 'a': '3'}
此函式的執行時間為1.000101089477539

函式的執行時,*打散; 函式的定義時,*聚合。

from functools import wraps
def wrapper(f):                  # f = func1

    @wraps(f)
    def inner(*args,**kwargs):               #聚合,args (1,2,3)
        '''執行函式之前的相關操作'''
        ret = f(*args,**kwargs)               # 打散 1,2,3
        '''執行函式之後的相關操作'''
        return ret
    return inner

@wrapper  # func1 = wrapper(func1)  func1 = inner
def func1(*args):                           #args (1,2,3) 聚合
    print(666)
    return args

print(func1(*[1,2,3]))  
執行輸出:
666
(1, 2, 3)

例3*****

import time                                 #1.載入模組

def timmer(*args,**kwargs):                     #2.載入變數  5.接收引數True,2,3

    def wrapper(f):                             #6.載入變數  8.f = func1

        print(args, kwargs)                     #9.接收timmer函式的值True,2,3

        def inner(*args,**kwargs):                 #10.載入變數. 13.執行函式inner
            if flag:                         #14 flag = True
                start_time = time.time()             #15 獲取當前時間
                ret = f(*args,**kwargs)             #16 執行func1
                time.sleep(0.3)                 #19 等待0.3秒
                end_time = time.time()             #20 獲取當前時間
                print('此函式的執行效率%f' % (end_time-start_time)) #21 列印差值
            else:
                ret = f(*args, **kwargs)

            return ret                         #22 返回給函式呼叫者func1()
        return inner                         #11 返回給函式呼叫者wrapper
    return wrapper                         #7.返回給函式呼叫timmer(flag,2,3)

flag = True                                 #3 載入變數
@timmer(flag,2,3)      # 4.執行函式timmer(flag,2,3) 17.執行函式func1 兩步:1,timmer(flag,2,3) 相當於執行wrapper                                                     2.@wrapper 裝飾器 func1 = wrapper(func1)
def func1(*args,**kwargs):
    return 666                             #18 返回給函式呼叫者f(*args,**kwargs)

print(func1())                             #12 執行函式 

例4:假定現在有100個函式,都加上了裝飾器,增加了顯示函式執行時間的功能,現在需要去掉!直接在裝飾器函式加一個引數即可。

import time
flag = True
def wrapper(f):
    def inner(*args,**kwargs):
        if flag:
            start_time = time.time()
            ret = f(*args,**kwargs)
            time.sleep(0.3)
            end_time = time.time()
            print('此函式的執行效率%f' % (end_time-start_time))
        else:
            ret = f(*args, **kwargs)
        return ret
    return inner

@wrapper

def func1(*args,**kwargs):
    print(args,kwargs)
    return 666

print(func1()) 

執行輸出:
此函式的執行效率0.300431
666
View Code

例5:現在需要關閉顯示執行時間,直接將flag改成false

import time
flag = False
def wrapper(f):
    def inner(*args,**kwargs):
        if flag:
            start_time = time.time()
            ret = f(*args,**kwargs)
            time.sleep(0.3)
            end_time = time.time()
            print('此函式的執行效率%f' % (end_time-start_time))
        else:
            ret = f(*args, **kwargs)
        return ret
    return inner

@wrapper

def func1(*args,**kwargs):
    print(args,kwargs)
    return 666

print(func1()) 

執行輸出:
() {}
666
View Code

這樣,所有呼叫的地方,就全部關閉了,非常方便

寫裝飾器,一般巢狀3層就可以了

3.3 多個裝飾器,裝飾一個函式

def wrapper1(func):                  # func ==  f函式名
    def inner1():
        print('wrapper1 ,before func')          # 2
        func()
        print('wrapper1 ,after func')          # 4
    return inner1

def wrapper2(func):  # func == inner1
    def inner2():
        print('wrapper2 ,before func')          # 1
        func()
        print('wrapper2 ,after func')          # 5
    return inner2

@wrapper2                      #  f = wrapper2(f)  裡面的f==inner1  外面的f == inner2
@wrapper1                      #  f = wrapper1(f)   裡面的f==函式名f  外面的f == inner1

def f():                          # 3
    print('in f')

f()                              # inner2() 
執行輸出:
wrapper2 ,before func
wrapper1 ,before func
in f
wrapper1 ,after func
wrapper2 ,after func

哪個離函式近,哪個先計算。最底下的先執行

執行順序如下圖:

 

 多個裝飾器,都是按照上圖的順序來的

4. 裝飾器的__name__和__doc___

__name__:函式名

__doc___:函式的解釋  

普通函式

def func1():
    """
    此函式是完成登陸的功能,引數分別是...作用。
    return: 返回值是登陸成功與否(True,False)
    """
    print(666)

func1()
print(func1.__name__)         #獲取函式名
print(func1.__doc__)         #獲取函式名註釋說明 

執行輸出:
666
func1
此函式是完成登陸的功能,引數分別是...作用。
return: 返回值是登陸成功與否(True,False)

這個有什麼用呢?比如日誌功能,需要打印出誰在什麼時間,呼叫了什麼函式,函式是幹啥的,花費了多次時間,這個時候,就需要獲取函式的有用資訊了

帶裝飾器的函式

def wrapper(f):      # f = func1

    def inner(*args,**kwargs):             #聚合, args (1,2,3)
        '''執行函式之前的相關操作'''
        ret = f(*args,**kwargs)              # 打散 1,2,3
        '''執行函式之後的相關操作'''
        return ret
    return inner

@wrapper
def func1():
    """
    此函式是完成登陸的功能,引數分別是...作用。
    return: 返回值是登陸成功與否(True,False)
    """
    print(666)
    return True

func1()
print(func1.__name__)
print(func1.__doc__) 
執行輸出:
666
inner
執行函式之前的相關操作

咦?為什麼輸出了inner,我要的是func1啊。因為函式裝飾之後,相當於執行了inner函式,所以輸出inner

為了解決這個問題,需要呼叫一個模組wraps

wraps將 被修飾的函式(wrapped) 的一些屬性值賦值給修飾器函式(wrapper) ,最終讓屬性的顯示更符合我們的直覺

from functools import wraps

def wrapper(f):                  # f = func1
    @wraps(f)                 #f是被裝飾的函式
    def inner(*args,**kwargs):         #聚合args (1,2,3)
        '''執行函式之前的相關操作'''
        ret = f(*args,**kwargs)          # 打散 1,2,3
        '''執行函式之後的相關操作'''
        return ret
    return inner

@wrapper
def func1():
    """
    此函式是完成登陸的功能,引數分別是...作用。
    return: 返回值是登陸成功與否(True,False)
    """
    print(666)
    return True

func1()
print(func1.__name__)
print(func1.__doc__) 
執行輸出:
666
func1
此函式是完成登陸的功能,引數分別是...作用。
return: 返回值是登陸成功與否(True,False)

四、迭代器

python 一切皆物件, 能被for迴圈的物件就是可迭代物件。

迭代器: f1檔案控制代碼

 

dir列印該物件的所有操作方法

s = 'python'
print(dir(s))

執行輸出:

['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__iter__', '__le__', '__len__', '__lt__', '__mod__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmod__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'capitalize', 'casefold', 'center', 'count', 'encode', 'endswith', 'expandtabs', 'find', 'format', 'format_map', 'index', 'isalnum', 'isalpha', 'isdecimal', 'isdigit', 'isidentifier', 'islower', 'isnumeric', 'isprintable', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'maketrans', 'partition', 'replace', 'rfind', 'rindex', 'rjust', 'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill']

1.可迭代物件

物件內部含有__iter__方法就是可迭代物件,例如 str、list、dict、tuple、set、range()

 檢視某個物件是否是可迭代物件,有2種方式

  •   print('__iter__' in dir(物件))

 

  •   from collections import Iterable

         print(isinstance(物件,Interable))

 

第一種:

s = 'python'
print('__iter__' in dir(s))
執行輸出:True

第二種:

from collections import Iterable

l = [1, 2, 3, 4]
print(isinstance(l, Iterable))           # True

2. 迭代器

物件內部含有__iter__和__next__方法就是迭代器,檔案控制代碼就是迭代器。

 

(1)可迭代物件轉化成迭代器:可迭代物件.__iter__() --->迭代器

l1 = [1,2,3]
l1_obj = l1.__iter__()                                   # 迭代器
print(l1_obj) <list_iterator object at 0x000001987D5EB668>            # 表示它是一個列表迭代器物件

(2)判斷方法是否是迭代器的方法

①print('__iter__' in dir(物件))

  print('__next__' in dir(物件))

兩個都必須是True, 這種方法居多

 

② from collection import Iterator

    print(isinstance(物件,Iterator))

 

方法1:

with open('a', encoding='utf-8') as f1:
    print('__iter__'in dir(f1))
    print('__next__'in dir(f1))

方法2:

from collections import Iterator
print(isinstance(f1,Iterator))         # True

例:

l1 = [1,2,3]
l1_obj = l1.__iter__()              # 轉換為迭代器
print('__iter__' in  dir(l1_obj))          #是否含有__iter__方法
print('__next__' in  dir(l1))          #是否含有__next__方法
print('__next__' in  dir(l1_obj)) 
執行輸出:從結果中,可以看出l1_obj是同時含有__iter__和__next__的物件,所以它是迭代器
True
False
True 

(3)迭代器使用__next__獲取一個值

l1 = [1,2,3]
l1_obj = l1.__iter__()          # 迭代器
print(l1_obj.__next__())         # 獲取一個元素
print(l1_obj.__next__())
print(l1_obj.__next__())
print(l1_obj.__next__())

多取了一個,就會報錯,因為列表只有3個元素

s1 = 'sadda'
ite1 = iter(s1)
while 1:
    try:
        print(ite1.__next__())

    except StopIteration:
        break

使用while迴圈,指定用__next__方法遍歷列表

l2 = [1, 2, 3, 4, 5, 6, 7, 8]
l2_obj = l2.__iter__()                     #1.將可迭代物件轉化成迭代器
while True:
    try:
        i = l2_obj.__next__()                 #內部使用__next__方法取值
        print(i)

    except Exception:                     #運用了異常處理去處理報錯
        break 

try裡面的程式碼,出現報錯,不會提示紅色文字。Exception 可以接收所有報錯,表示報錯的時候,該怎麼處理,這裡直接使用breck跳出迴圈

面試題:使用whlie迴圈去遍歷一個有限物件,直接使用上述程式碼即可。

 

for迴圈的內部執行機制:

① 將可迭代物件轉化為迭代器

② 呼叫__next__方法取值

③ 利用異常處理機制停止報錯

l1 = [1,2,3]
l1_obj = l1.__iter__()  # 轉換為迭代器
for i in l1_obj:
    print(i) 

 

執行輸出:for迴圈的內部機制,就是用__next__方法執行的。為什麼沒有報錯呢?它內部有異常處理機制
1
2
3

3.迭代器的好處

1)節省記憶體空間。迭代器最大的好處好的程式設計師,會在記憶體優化方面考慮,比如迭代器。

2)滿足惰性機制。

3)不能反覆取值,不可逆。不可逆,表示,已經取過的值,不能再次取,它只能取下一個。

 

僅含有__iter__方法的,就是可迭代物件; 包含__iter__和__next__方法的,就是迭代器

 

總結:

1.什麼是可迭代物件,什麼是迭代器

內部含有__iter__方法的物件就叫做可迭代物件

內部必須有__iter__方法和__next__方法的物件,叫做迭代器

 

2.可迭代物件如何轉化成迭代器

轉化成迭代器:可迭代物件.__iter__() 

例如

l1 = [1,2,3]

l1_obj = l1.__iter__()

 

3.迭代器如何取值 

迭代器使用__next__()方法

五、 生成器

生成器本質上是迭代器,包含__iter__和__next__功能

 

生成器的產生方式:

1,生成器函式構造。

2,生成器推導式構造。

3,資料型別的轉化。

 

通過構造生成器函式,就是將函式中的return變為yield

def func2(x):
    x += 1
    print(111)
    yield x

    x += 1
    print(222)
    yield x

func2(2)                #函式不會執行
g_obj  =func2(3)          #將func2(3)賦值給g_obj,g_obj是個迭代器
g_obj.__next__()          #輸出結果是111,一個next對一個一個yield,只有遇到next,函式才會執行
print(g_obj.__next__())    #輸出結果是222   \n    5
print(g_obj.__next__())    #超出,將報錯
def func1():
    for i in range(10):
        yield i

g=func1()
for i in range(10):
    print(g.__next__())         #列印0-9,一行一個

return 與 yield的區別

① 自定製的區別

② 記憶體級別的區別

     迭代器是需要可迭代物件進行轉化,可迭代物件非常佔記憶體

     生成器是直接轉化,從本質上節省記憶體

 

 next 和send 功能一樣,都是執行一次

send 與 next 的區別

① send 與 next 一樣,也是對生成器進行取值

② send 可以給上一個yield 傳值

③ 第一次取值只能用next

④ 最後一個yield永遠得不到send傳的值

例1

def func1():
    print(1)
    count = yield 1
    print(count)
    print(2)
    count2 = yield 2
    print(count2)
    print(3)
    count3 = yield 3

g=func1()            # g稱作生成器物件。
g.__next__()
g.send('alex')
g.send('hello')
輸出結果:
1
alex
2
hello
3

例2

def generator():
    print(123)
    content = yield 1
    print('=======',content)
    print(456)
    yield

g = generator()
ret = g.__next__()            # 123
print('***',ret)            # *** 1
ret = g.send('hello')           #send的效果和next一樣   ======= hello        \n    456
print('***',ret)            # *** None
執行輸出:
123
*** 1
======= hello
456
*** None

send 獲取下一個值的效果和next基本一致, 只是在獲取下一個值時,給上一yield的位置傳遞一個數據

 

使用send的注意事項:第一次使用生成器的時候 是用next獲取下一個值。最後一個yield不能接受外部的值

例:比如生產10000套服裝

 一個廠商直接生產出10000套了

def func1():
    for i in range(1,10001):
        print('ARMAIN服裝%d套' % i)
func1() 
執行輸出:
...

ARMAIN服裝9998套
ARMAIN服裝9999套
ARMAIN服裝10000套

第二個廠商,先生產出50套,給老闆看

def func1():
    for i in range(1,10001):
        yield 'ARMAIN服裝%d套' % i

g = func1()
for i in range(1,51):
    print(g.__next__()) 
執行輸出:
...
ARMAIN服裝48套
ARMAIN服裝49套
ARMAIN服裝50套

最終老闆只要200套,先50套,再150套

def func1():
    for i in range(1,10001):
        yield 'ARMAIN服裝%d套' % i

g = func1()
for i in range(1,51):
    print(g.__next__())

#再執行150次,注意,它是從51開始的
for j in range(150):
    print(g.__next__()) 
  • 對於列表而言,for迴圈是從頭開始
  • 對於生成器而言,它是有指標的,__next__一次,指標向前一次。它不能從頭開始。必須依次執行

 

生成器和迭代器的區別
  迭代器: 有內建方法
  生成器: 開發者自定義

 

問題:什麼是生成器?如何寫一個生成器?生成器怎麼取值?

生成器,即生成一個容器。在Python中,一邊迴圈,一邊計算的機制,稱為生成器。

生成器示例

def fun1():
    yield 1

生成器使用__next__()方法取值,或者for迴圈

 

六、三元運算

三元運算子就是在賦值變數的時候,可以直接加判斷,然後賦值

格式:[on_true] if [expression] else [on_false]

三元運算只適用於簡單的if else判斷,再多一層if判斷就不適用了。

 

舉例說明:比大小,大者返回

寫一個函式

def max_min(a,b):
    if int(a) > int(b):
        return a
    else:
        return b
print(max_min(1,3)) 

三元運算

def max_min(a,b):
    z = a if a > b else b
    return z 

再進一步簡寫

def max_min(a,b):
    return a if a > b else b 

七、列表生成式

優點:一行程式碼幾乎可以搞定所需要的任何列表

缺點:容易著迷,不易排錯,不能超過三個迴圈

 

  • 用列表推導式能構建的任何列表,用別的都可以構建,比如for迴圈
  • 列表推導式,最多不超過3個for迴圈。判斷只能用一個

 

1. 迴圈模式:模板:[經過加工的i for i in 可迭代物件]

使用for迴圈方法

li = []
for i in range(1,4):
    li.append('還珠格格第'+str(i)+'部')

print(li)                   #['還珠格格第1部', '還珠格格第2部', '還珠格格第3部']

第二種寫法

li = []
for i in range(1,4):
    li.append('還珠格格第%s部' % i)

print(li)

上面的程式碼,可以一行搞定。用列表推導式就可以了

li = ['還珠格格第%s部' %i for i in range(1,4)]
print(li)

例:求1~10平方結果

li = [i ** 2 for i in range(1,11)]
print(li) 
執行輸出:[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

2. 篩選模式:[經過加工的i for i in 可迭代物件 if 條件 篩選] 

30以內所有能被3整除的數
l3 = [i for i in range(1,31) if i % 3 == 0]
print(l3)                                 # [3, 6, 9, 12, 15, 18, 21, 24, 27, 30]
30以內所有能被3整除的數的平方
li = [i**2 for i in range(1,31) if i % 3 == 0]
print(li)                                 # [9, 36, 81, 144, 225, 324, 441, 576, 729, 900]
找到巢狀列表中名字含有兩個'e'的所有名字
names = [['Tom', 'Billy', 'Jefferson', 'Andrew', 'Wesley', 'Steven', 'Joe'],['Alice', 'Jill', 'Ana', 'Wendy', 'Jennifer', 'Sherry']]
l4 = [name for i in names for name in i if name.count('e') == 2 ]
print(l4)                                 # ['Jefferson', 'Wesley', 'Steven', 'Jennifer']

常用的是列表推導式

 

字典推導式

將一個字典的key和value對調

dict = {'a': 10, 'b': 34}
dict_frequency = {dict[k]: k for k in mcase}
print(dict_frequency) 
執行輸出:{10: 'a', 34: 'b'}

相當於

dict = {'a': 10, 'b': 34}
dict_frequency = {}
for k in dict:
    dict_frequency[k]=dict[k]

print(dict_frequency) 
如果Key和value是一樣的,不適合上面的程式碼

集合推導式

計算列表中每個值的平方,自帶去重功能

squared = {x**2 for x in [1, -1, 2]}
print(squared)
執行輸出:{1, 4}
a = {1,1,4}
print(type(a)) 
執行輸出:<class 'set'>  結果是一個集合,它也是用{}表示的。

集合和字典是有區別的:

  有鍵值對的,是字典,比如{'k1':1,'k1':2}

  沒有鍵值對的,是集合,比如{1,2,3,4}

 

八、生成器表示式

與列表推導式相同,只是將[ ]改為( )

l_obj = ('還珠格格第%s部' % i for i in range(1,4))
print(l_obj)
結果是一個生成器物件:<generator object <genexpr> at 0x000002DDBEBADE60>

取值使用__next__方法

l_obj = ('還珠格格第%s部' % i for i in range(1,4))

print(l_obj.__next__())
print(l_obj.__next__())
print(l_obj.__next__())
輸出結果:
還珠格格第1部
還珠格格第2部
還珠格格第3部

列表推導式:一目瞭然,佔記憶體

生成器表示式: 不便看出,節省記憶體。

九、遞迴函式

遞迴函式:在一個函式中,呼叫這個函式本身。遞迴的預設最大深度為998。

 

它是執行到多少次時,報錯呢加一個計數器。預設遞迴深度為998

count = 0
def func1():
    global count
    count += 1
    print(count)
    func1()

func1()

遞迴深度是可以改的

import sys
sys.setrecursionlimit(100000)                #更改預設遞迴深度
count = 0
def func1():
    global count
    count += 1
    print(count)
    func1() 

func1()

問年齡

def func(n):
    if n == 1:
        return 18
    else:
        return func(n-1) +2

print(func(4))                    # 24

十、匿名函式

匿名函式:lambda 表示式。普通函式有且只有返回值的函式才能用匿名函式進行簡化成一行函式。

匿名函式不單獨使用,一般和內建函式結合使用。內建函式中,可加入函式的有min、max、sorted、map、filter

 

關於匿名函式格式的說明:

  函式名 = lambda 引數 :返回值

  引數可以有多個,用逗號隔開

  匿名函式不管邏輯多複雜,只能寫一行,且邏輯執行結束後的內容就是返回值

  返回值和正常的函式一樣可以是任意資料型別

 

 

 1.簡單使用

返回一個數的平方

使用函式方式
def func1(x):
    return x ** 2 

使用匿名函式一行搞定
func = lambda x:x ** 2
print(func(5))             # 25 

x+y

使用函式方式
def func2(x,y):
    return x + y 

改成匿名函式
fun = lambda x,y:x+y
print(fun(1,3))             #  4

 

2. lambda 函式與內建函式的結合。sorted,map,fiter,max,min,reversed

比較字典值的大小,並輸出key的值

dic={'k1': 10, 'k2': 100, 'k3': 30}
print(max(dic, key=lambda x: dic[x]))         # k2

X2

res = map(lambda x:x**2,[1,5,7,4,8])
for i in res:
    print(i) 

打印出大於10的元素

l1 = [1,2,3,11,12,40,20,50,79]
ret = filter(lambda x:x > 10,l1)

for i in ret:
    print(i) 

如果l1列表的資料,有上百萬,不能使用列表推導式,非常佔用記憶體。建議使用lamdba,它只佔用一行

&n