1. 程式人生 > 實用技巧 >內建函式,閉包,裝飾器

內建函式,閉包,裝飾器

<div id="page_begin_html">
    <script>loadPageBeginHtml();</script>
</div>
<!--done-->

太白金星

</div><!--end: blogTitle 部落格的標題和副標題 -->
<div id="navigator">
	<div class="blogStats">
		<span id="stats_post_count">隨筆 - 6&nbsp; </span>

文章 - 121
評論 - 239

	</div><!--end: blogStats -->
</div><!--end: navigator 部落格導航欄 -->

內建函式Ⅱ,閉包,裝飾器初識
        </h1>
        <div class="clear"></div>
        <div class="postBody">

1. 匿名函式

匿名函式,顧名思義就是沒有名字的函式,那麼什麼函式沒有名字呢?這個就是我們以後面試或者工作中經常用匿名函式 lambda,也叫一句話函式。

現在有一個需求:你們寫一個函式,此函式接收兩個int引數,返回和值。

def func(a,b):
    return a+b
print(func(3,4))

那麼接下來我們用匿名函式完成上面的需求:

func = lambda a,b: a+b
print(func(3, 4))  # 7

我們分析一下上面的程式碼:

語法:

  函式名 = lambda 引數:返回值

1)此函式不是沒有名字,他是有名字的,他的名字就是你給其設定的變數,比如func.

2)lambda 是定義匿名函式的關鍵字,相當於函式的def.

3)lambda 後面直接加形參,形參加多少都可以,只要用逗號隔開就行。

func = lambda a,b,*args,sex= 'alex',c,**kwargs: kwargs
print(func(3, 4,c=666,name='alex'))  # {'name': 'alex'}
# 所有型別的形參都可以加,但是一般使用匿名函式只是加位置引數,其他的用不到。

4)返回值在冒號之後設定,返回值和正常的函式一樣,可以是任意資料型別。

5)匿名函式不管多複雜.只能寫一行.且邏輯結束後直接返回資料

接下來做幾個匿名函式的小題:

寫匿名函式:接收一個可切片的資料,返回索引為0與2的對應的元素(元組形式)。

func = lambda x:(x[0],x[2])
print(func('afafasd'))
View Code

寫匿名函式:接收兩個int引數,將較大的資料返回。

func = lambda x,y: x if x > y else y
print(func(3,100))
View Code

2. 內建函式Ⅱ

紅色重點講解abs() enumerate() filter() map() max() min() open() range() print() len() list() dict() str() reversed() set() sorted() sum() tuple() type() zip() dir()

昨天,我們已經比較重要的內建函式講完了,那麼今天我們要講的是最最重要的內建函式,這些內建函式是面試與工作中經常用到的,所以,今天的這些內建函式,我們一定要全部記住,並且熟練使用。

print() 螢幕輸出。

''' 原始碼分析
def print(self, *args, sep=' ', end='\n', file=None): # known special case of print
    """
    print(value, ..., sep=' ', end='\n', file=sys.stdout, flush=False)
    file:  預設是輸出到螢幕,如果設定為檔案控制代碼,輸出到檔案
    sep:   列印多個值之間的分隔符,預設為空格
    end:   每一次列印的結尾,預設為換行符
    flush: 立即把內容輸出到流檔案,不作快取
    """
'''print(111,222,333,sep='*')  # 111*222*333
print(111,end='')
print(222)  #兩行的結果 111222
​
f = open('log','w',encoding='utf-8')
print('寫入檔案',fle=f,flush=True)
View Code

int():pass

str():pass

bool():pass

set(): pass

list() 將一個可迭代物件轉換成列表

tuple() 將一個可迭代物件轉換成元組

dict() 通過相應的方式建立字典。

list
l1 = list('abcd')
print(l1)  # ['a', 'b', 'c', 'd']
tu1 = tuple('abcd')
print(tu1)  # ('a', 'b', 'c', 'd')
View Code

abs() 返回絕對值

i = -5
print(abs(i))  # 5
View Code

sum() 求和

print(sum([1,2,3]))
print(sum((1,2,3),100))
View Code

min() 求最小值

print(min([1,2,3]))  # 返回此序列最小值
​
ret = min([1,2,-5,],key=abs)  # 按照絕對值的大小,返回此序列最小值
print(ret)
# 加key是可以加函式名,min自動會獲取傳入函式中的引數的每個元素,然後通過你設定的返回值比較大小,返回最小的傳入的那個引數。
print(min(1,2,-5,6,-3,key=lambda x:abs(x)))  # 可以設定很多引數比較大小
dic = {'a':3,'b':2,'c':1}
print(min(dic,key=lambda x:dic[x]))
​
# x為dic的key,lambda的返回值(即dic的值進行比較)返回最小的值對應的鍵
View Code

max() 最大值與最小值用法相同。

reversed() 將一個序列翻轉, 返回翻轉序列的迭代器 reversed 示例:

l = reversed('你好')  # l 獲取到的是一個生成器
print(list(l))
ret = reversed([1, 4, 3, 7, 9])
print(list(ret))  # [9, 7, 3, 4, 1]
View Code

bytes() 把字串轉換成bytes型別

s = '你好太白'
bs = s.encode('utf-8')
print(bs)
結果:b'\xe4\xbd\xa0\xe5\xa5\xbd\xe6\xad\xa6\xe5\xa4\xa7'
​
s1 = bs.decode('utf-8')
print(s1)
結果: 你好太白
​
​
s = '你好'
bs = bytes(s,encoding='utf-8')
print(bs)
# 將字串轉換成位元組
​
bs1 = str(bs,encoding='utf-8')
print(bs1)
# 將位元組轉換成字串
View Code

zip() 拉鍊方法。函式用於將可迭代的物件作為引數,將物件中對應的元素打包成一個個元組,

然後返回由這些元祖組成的內容,如果各個迭代器的元素個數不一致,則按照長度最短的返回,

lst1 = [1,2,3]
​
lst2 = ['a','b','c','d']
​
lst3 = (11,12,13,14,15)
​
for i in zip(lst1,lst2,lst3):
​
    print(i)
​
結果:
​
(1, 'a', 11)
​
(2, 'b', 12)
​
(3, 'c', 13) 
View Code

sorted排序函式

語法:sorted(iterable,key=None,reverse=False)

iterable : 可迭代物件

key: 排序規則(排序函式),在sorted內部會將可迭代物件中的每一個元素傳遞給這個函式的引數.根據函式運算的結果進行排序

reverse :是否是倒敘,True 倒敘 False 正序

lst = [1,3,2,5,4]
lst2
= sorted(lst)
print(lst) #原列表不會改變
print(lst2) #返回的新列表是經過排序的

lst3 = sorted(lst,reverse=True)
print(lst3) #倒敘

結果:
[
1, 3, 2, 5, 4]
[
1, 2, 3, 4, 5]
[
5, 4, 3, 2, 1]

字典使用sorted排序

dic = {1:'a',3:'c',2:'b'}
print(sorted(dic)) # 字典排序返回的就是排序後的key

結果:
[
1,2,3]

和函式組合使用

# 定義一個列表,然後根據一元素的長度排序
lst = ['天龍八部','西遊記','紅樓夢','三國演義']

# 計算字串的長度
def func(s):
return len(s)
print(sorted(lst,key=func))

# 結果:

['西遊記', '紅樓夢', '天龍八部', '三國演義']

和lambda組合使用

lst = ['天龍八部','西遊記','紅樓夢','三國演義']

print(sorted(lst,key=lambda s:len(s)))

結果:
['西遊記', '紅樓夢', '天龍八部', '三國演義']

lst = [{'id':1,'name':'alex','age':18},
{
'id':2,'name':'wusir','age':17},
{
'id':3,'name':'taibai','age':16},]

# 按照年齡對學生資訊進行排序

print(sorted(lst,key=lambda e:e['age']))

結果:
[{'id': 3, 'name': 'taibai', 'age': 16}, {'id': 2, 'name': 'wusir', 'age': 17}, {'id': 1, 'name': 'alex', 'age': 18}]

View Code

filter篩選過濾

語法: filter(function,iterable)

function: 用來篩選的函式,在filter中會自動的把iterable中的元素傳遞給function,然後根據function返回的True或者False來判斷是否保留此項資料

iterable:可迭代物件

lst = [{'id':1,'name':'alex','age':18},
{
'id':1,'name':'wusir','age':17},
{
'id':1,'name':'taibai','age':16},]

ls = filter(lambda e:e['age'] > 16,lst)

print(list(ls))

結果:
[{'id': 1, 'name': 'alex', 'age': 18},
{
'id': 1, 'name': 'wusir', 'age': 17}]

View Code

map

對映函式

語法: map(function,iterable) 可以對可迭代物件中的每一個元素進對映,分別取執行function

計算列表中每個元素的平方,返回新列表

lst = [1,2,3,4,5]

def func(s):

return s*s

mp
= map(func,lst)

print(mp)

print(list(mp))

改寫成lambda

lst = [1,2,3,4,5]

print(list(map(lambda s:s*s,lst)))

計算兩個列表中相同位置的資料的和

lst1 = [1, 2, 3, 4, 5]

lst2
= [2, 4, 6, 8, 10]

print(list(map(lambda x, y: x+y, lst1, lst2)))

結果:

[
3, 6, 9, 12, 15]

View Code

reduce

from functools import reduce
def func(x,y):
    return x + y
​
# reduce 的使用方式:
# reduce(函式名,可迭代物件)  # 這兩個引數必須都要有,缺一個不行
​
ret = reduce(func,[3,4,5,6,7])
print(ret)  # 結果 25
reduce的作用是先把列表中的前倆個元素取出計算出一個值然後臨時儲存著,
接下來用這個臨時儲存的值和列表中第三個元素進行計算,求出一個新的值將最開始
臨時儲存的值覆蓋掉,然後在用這個新的臨時值和列表中第四個元素計算.依次類推
​
注意:我們放進去的可迭代物件沒有更改
以上這個例子我們使用sum就可以完全的實現了.我現在有[1,2,3,4]想讓列表中的數變成1234,就要用到reduce了.
普通函式版
from functools import reduce
​
def func(x,y):
​
    return x * 10 + y
    # 第一次的時候 x是1 y是2  x乘以10就是10,然後加上y也就是2最終結果是12然後臨時儲存起來了
    # 第二次的時候x是臨時儲存的值12 x乘以10就是 120 然後加上y也就是3最終結果是123臨時儲存起來了
    # 第三次的時候x是臨時儲存的值123 x乘以10就是 1230 然後加上y也就是4最終結果是1234然後返回了
​
l = reduce(func,[1,2,3,4])
print(l)
​
​
匿名函式版
l = reduce(lambda x,y:x*10+y,[1,2,3,4])
print(l)

在Python2.x版本中recude是直接 import就可以的, Python3.x版本中需要從functools這個包中匯入

龜叔本打算將 lambda 和 reduce 都從全域性名字空間都移除, 輿論說龜叔不喜歡lambda 和 reduce

最後lambda沒刪除是因為和一個人寫信寫了好多封,進行交流然後把lambda保住了.

View Code

參考資料:

https://www.processon.com/view/link/5b4ee15be4b0edb750de96ac

3. 閉包

由於閉包這個概念比較難以理解,尤其是初學者來說,相對難以掌握,所以我們通過示例去理解學習閉包。

給大家提個需求,然後用函式去實現:完成一個計算不斷增加的系列值的平均值的需求。

例如:整個歷史中的某個商品的平均收盤價。什麼叫平局收盤價呢?就是從這個商品一出現開始,每天記錄當天價格,然後計算他的平均值:平均值要考慮直至目前為止所有的價格。

比如大眾推出了一款新車:小白轎車。

第一天價格為:100000元,平均收盤價:100000元

第二天價格為:110000元,平均收盤價:(100000 + 110000)/2 元

第三天價格為:120000元,平均收盤價:(100000 + 110000 + 120000)/3 元

........

series = []
def make_averager(new_value):
    series.append(new_value)
    total = sum(series)
    return total / len(series)

print(make_averager(100000))
print(make_averager(110000))
print(make_averager(120000))

View Code

從上面的例子可以看出,基本上完成了我們的要求,但是這個程式碼相對來說是不安全的,因為你的這個series列表是一個全域性變數,只要是全域性作用域的任何地方,都可能對這個列表進行改變。

series = []
def make_averager(new_value):
    series.append(new_value)
    total = sum(series)
    return total / len(series)

print(make_averager(100000))
print(make_averager(110000))
series.append(
666) # 如果對資料進行相應改變,那麼你的平均收盤價就會出現很大的問題。
print(make_averager(120000))

View Code

那麼怎麼辦呢?有人說,你把他放在函式中不就行了,這樣不就是區域性變量了麼?資料不就相對安全了麼?

def make_averager(new_value):
    series = []
    series.append(new_value)
    total = sum(series)
    return total / len(series)
​
​
print(make_averager(100000))  # 100000.0
print(make_averager(110000))  # 110000.0
print(make_averager(120000))  # 120000.0
View Code

這樣計算的結果是不正確的,那是因為執行函式,會開啟一個臨時的名稱空間,隨著函式的結束而消失,所以你每次執行函式的時候,都是重新建立這個列表,那麼這怎麼做呢?這種情況下,就需要用到我們講的閉包了,我們用閉包的思想改一下這個程式碼。

def make_averager():
series </span>=<span style="color: rgba(0, 0, 0, 1)"> []
</span><span style="color: rgba(0, 0, 255, 1)">def</span><span style="color: rgba(0, 0, 0, 1)"> averager(new_value):
    series.append(new_value)
    total </span>=<span style="color: rgba(0, 0, 0, 1)"> sum(series)
    </span><span style="color: rgba(0, 0, 255, 1)">return</span> total/<span style="color: rgba(0, 0, 0, 1)">len(series)


return averager

avg
= make_averager()
print(avg(100000))
print(avg(110000))
print(avg(120000))

View Code

大家仔細看一下這個程式碼,我是在函式中嵌套了一個函式。那麼avg 這個變數接收的實際是averager函式名,也就是其對應的記憶體地址,我執行了三次avg 也就是執行了三次averager這個函式。那麼此時你們有什麼問題?

肯定有學生就會問,那麼我的make_averager這個函式只是執行了一次,為什麼series這個列表沒有消失?反而還可以被呼叫三次呢?這個就是最關鍵的地方,也是閉包的精華所在。我給大家說一下這個原理,以圖為證:

上面被紅色方框框起來的區域就是閉包,被藍色圈起來的那個變數應該是make_averager()函式的區域性變數,它應該是隨著make_averager()函式的執行結束之後而消失。但是他沒有,是因為此區域形成了閉包,series變數就變成了一個叫自由變數的東西,averager函式的作用域會延伸到包含自由變數series的繫結。也就是說,每次我呼叫avg對應的averager函式 時,都可以引用到這個自用變數series,這個就是閉包。

閉包的定義:

1. 閉包是巢狀在函式中的函式。

2. 閉包必須是內層函式對外層函式的變數(非全域性變數)的引用。

如何判斷判斷閉包?舉例讓同學回答:

# 例一:
def wrapper():
    a = 1
    def inner():
        print(a)
    return inner
ret = wrapper()
​
# 例二:
a = 2
def wrapper():
    def inner():
        print(a)
    return inner
ret = wrapper()
​
​
# 例三:
def wrapper(a,b):
    def inner():
        print(a)
        print(b)
    return inner
a = 2
b = 3
ret = wrapper(a,b)
View Code

以上三個例子,最難判斷的是第三個,其實第三個也是閉包,如果我們每次去研究程式碼判斷其是不是閉包,有一些不科學,或者過於麻煩了,那麼有一些函式的屬性是可以獲取到此函式是否擁有自由變數的,如果此函式擁有自由變數,那麼就可以側面證明其是否是閉包函數了(瞭解):

def make_averager():
​
    series = []
    def averager(new_value):
        series.append(new_value)
        total = sum(series)
        return total/len(series)
​
    return averager
avg = make_averager()
# 函式名.__code__.co_freevars 檢視函式的自由變數
print(avg.__code__.co_freevars)  # ('series',)
當然還有一些引數,僅供瞭解:

# 函式名.code.co_freevars 檢視函式的自由變數
print(avg.code.co_freevars) # ('series',)

函式名.code.co_varnames 檢視函式的區域性變數

print(avg.code.co_varnames) # ('new_value', 'total')

函式名.closure 獲取具體的自由變數物件,也就是cell物件。

(<cell at 0x0000020070CB7618: int object at 0x000000005CA08090>,)

cell_contents 自由變數具體的值

print(avg.closure[0].cell_contents) # []

View Code

閉包的作用:儲存區域性資訊不被銷燬,保證資料的安全性。

閉包的應用

  1. 可以儲存一些非全域性變數但是不易被銷燬、改變的資料。

  2. 裝飾器。