1. 程式人生 > >記錄我的 python 學習歷程-Day13 匿名函式、內建函式 II、閉包

記錄我的 python 學習歷程-Day13 匿名函式、內建函式 II、閉包

一、匿名函式

以後面試或者工作中經常用匿名函式 lambda,也叫一句話函式。

課上練習:

# 正常函式:
def func(a, b):
    return a + b

print(func(4, 6))   # 10

# 匿名函式:
func = lambda a, b: a + b

print(func(11, 33)) # 44

# 寫匿名函式:接收一個可切片的資料,返回索引為0與2的對應的元素(元組形式)。
func = lambda x: (x[0], x[2])
print(func('Dylan'))    # ('D', 'l')

# 寫匿名函式:接收兩個int引數,將較大的資料返回。
func1 = lambda a, b: a if a > b else b
print(func1(11, 88))    # 88

語法:
函式名 = lambda 引數:返回值

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

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

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

    func = lambda a,b,*args,sex= 'alex',c,**kwargs: kwargs
    print(func(3, 4,c=666,name='alex'))  # {'name': 'alex'}
    # 所有型別的形參都可以加,但是一般使用匿名函式只是加位置引數,其他的用不到。
  • 返回值在冒號之後設定,返回值和正常的函式一樣,可以是任意資料型別。

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

二、內建函式 ii

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

# python 提供了68個內建函式。
# eval()    剝去字串的外衣,運算裡面的程式碼,有返回值。**
s = '1 + 3'
print(s)    # 1 + 3
print(eval(s))  # 4


s1 = "{'name': 'Dylan'}"
print(s1, type(s1))     # {'name': 'Dylan'} <class 'str'>
# print(dict(s1))     # 不能轉換成字典,報錯。
print(eval(s1), type(eval(s1))) # {'name': 'Dylan'} <class 'dict'> 轉換成字典了。
# 網路傳輸的 str input 輸入的時候,sql 注入等,絕對不能使用 eval()。

# exec()與 eval()幾科一樣,程式碼流。
msg = '''
for i in range(10):
    print(i)
'''
# print(msg)
exec(msg)

# hash() 獲取一個物件(可雜湊物件:int,str,Bool,tuple)的雜湊值。
print(hash('Dylan'))    # -2239819904114377323

# help() 幫助 **
s2 = 'Dylan'
print(help(str.upper))
s1 = s2.upper()
print(s1)

# callable():用於檢查一個物件是否是可呼叫的。***
s1 = 'Dylan'
def func():
    pass


print(callable(s1))     # False
print(callable(func))   # True

# int() 用於將一個字串或數字轉換為整型。
print(int())        # 0
print(int('12'))    # 12
print(int(3.6))     # 3
print(int('0100', base=2))  # 將2進位制的 0100 轉化成十進位制。結果為 4

# float() 用於將整數和字串轉換成浮點數。
print(float(3)) # 3.0
print(float())  # 0.0
print(float("12.33"))   # 12.33

# complex() 用於建立一個值為 real + imag * j 的複數或者轉化一個字串或數為複數。
# 如果第一個引數為字串,則不需要指定第二個引數。
print(complex(1, 2))    # (1+2j)

# bin:將十進位制轉換成二進位制並返回。**
# oct:將十進位制轉化成八進位制字串並返回。**
# hex:將十進位制轉化成十六進位制字串並返回。**
print(bin(10), type(bin(10)))   # 0b1010 <class 'str'>
print(oct(10), type(oct(10)))   # 0o12 <class 'str'>
print(hex(10), type(hex(10)))   # 0xa <class 'str'>

# divmod() 計算除數與被除數的結果,返回一個包含商和餘數的元組(a // b, a % b)。**
# round() 保留浮點數的小數位數,預設保留整數。**
# pow() 求x**y次冪。(三個引數為x**y的結果對z取餘)**
print(divmod(8, 3))     # (2, 2)
print(round(7/3, 2))    # 2.33
print(round(7/3))       # 2
print(round(3.32353, 3))    # 3.323
print(pow(2, 3))    # 兩個引數為2**3次冪
print(pow(2, 3, 3)) # 三個引數為2**3次冪,對3取餘。結果 2。

# bytes() 用於不同編碼之間的轉化。***
s = 'Dylan'
bs = s.encode('utf-8')
print(bs)   # b'Dylan'

bs = bytes(s, encoding='utf-8')
print(bs)   # b'Dylan'

# ord:輸入字元找該字元編碼的位置
# chr:輸入位置數字找出其對應的字元
print(ord('a'))     # 97
print(ord('中'))    # 20013

print(chr(97))      # a
print(chr(20013))   # 中

# repr:返回一個物件的string形式(原形畢露)。
print(repr('Dylan'))    # 'Dylan'
print('Dylan')  # Dylan

name = 'Dylan'
print('我叫%r' % name)    # 我叫'Dylan'

# all:可迭代物件中,全都是True才是True
# any:可迭代物件中,有一個True 就是True
print(all([1, 2, True, 0])) # False
print(any([1, '', 0]))      # True

# #################### 非常重要 ######################## #
# 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(11, 22, 33, sep='|')  # 11|22|33
print(111, end='')
print(222)  # 兩行結果為 111222

# list() 將一個可迭代物件轉換成列表
# tuple() 將一個可迭代物件轉換成元組
# dict() 通過相應的方式建立字典。

l1 = list('Dylang')
print(l1)   # ['D', 'y', 'l', 'a', 'n', 'g']
tu = tuple('Dylan')
print(tu)   # ('D', 'y', 'l', 'a', 'n')
# dict 建立字典的幾種方式
# 直接建立
# 元組的解構
dic = dict([(1, 'one'), (2, 'two'), (3, 'three')])
print(dic)  # {1: 'one', 2: 'two', 3: 'three'}
dic1 = dict(one=1, two=2)
print(dic1) # {'one': 1, 'two': 2}
# fromkeys
# update
# 字典的推導式

# abs() 返回絕對值
i = -88
print(abs(i))   # 88

# sum() 求和
l1 = [i for i in range(11)]
s1 = '12345'
print(sum(l1))  # 55
print(sum(l1, 100))     # 設定初始數為100 # 結果:155
# print(sum(s1))   # 報錯   TypeError: unsupported operand type(s) for +: 'int' and 'str'

# reversed() 將一個序列翻轉, 返回翻轉序列的迭代器 reversed
l = reversed('你好')  # 獲取到的是一個生成器
print(list(l))  # 將生成器中的元素轉換為列表 ['好', '你']
ret = reversed([1, 4, 6, 5, 8, 7])  # 獲取到的是一個生成器
print(list(ret))    # [7, 8, 5, 6, 4, 1]

# zip() 拉鍊方法。函式用於將可迭代的物件作為引數,將物件中對應的元素打包成一個個元組,
# 然後返回由這些元祖組成的內容,如果各個迭代器的元素個數不一致,則按照長度最短的返回
s = 'abcde'
tu = ('Dylan', 'xiaobai', 'niaoren')
l1 = [1, 2, 3, 4]
obj = zip(s, tu, l1)
for i in obj:
    print(i)
print(list(obj))

# ################# 以下方法最最最最最重要 ##############
# min() 求最小值
# max() 最大值與最小值用法相同。
print(min([1, 2, 3, 4]))  # 返回此序列最小值
ret = min([1, 2, -3], 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的值進行比較)返回最小的值對應的鍵
l2 = [('太白', 18), ('alex', 73), ('wusir', 35), ('口天吳', 41)]
print(min(l2))  # ('alex', 73)
print(min(l2, key=lambda x: x[1]))  # ('太白', 18)

# sorted()排序函式
# 語法:sorted(iterable,key=None,reverse=False)
# iterable : 可迭代物件
# key: 排序規則(排序函式),在sorted內部會將可迭代物件中的每一個元素傳遞給這個函式的引數.根據函式運算的結果進行排序
# reverse :是否是倒序,True 倒序 False 正序
l1 = [22, 33, 1, 2, 8, 7, 6, 5]
l2 = sorted(l1)
print(l1)  # [22, 33, 1, 2, 8, 7, 6, 5]
print(l2)  # [1, 2, 5, 6, 7, 8, 22, 33]

l3 = [('大壯', 76), ('雪飛', 70), ('納欽', 94), ('張珵', 98), ('b哥', 96)]
print(sorted(l3))  # [('b哥', 96), ('大壯', 76), ('張珵', 98), ('納欽', 94), ('雪飛', 70)]
print(sorted(l3, key=lambda x: x[1]))  # 返回的是一個列表,預設從低到高。
print(sorted(l3, key=lambda x: x[1], reverse=True))  # # 返回的是一個列表,從高到低

# filter()篩選過濾
# 語法: filter(function,iterable)
# function: 用來篩選的函式,在filter中會自動的把iterable中的元素傳遞給function,
# 然後根據function返回的True或者False來判斷是否保留此項資料
# iterable:可迭代物件
l1 = [2, 3, 4, 1, 6, 7, 8]
print([i for i in l1 if i > 3])  # [4, 6, 7, 8] 返回的是列表
ret = filter(lambda x: x > 3, l1)  # 返回的是個迭代器
print(ret)  # <filter object at 0x10601db70>
print(list(ret))  # [4, 6, 7, 8]

# map() 對映函式
# 語法: map(function,iterable) 可以對可迭代物件中的每一個元素進對映,分別取執行function
# 計算列表中每個元素的平方,返回新列表
lst = [1, 2, 3, 4, 5]


def func(s):
    return s * s


mp = map(func, lst)
print(mp)  # <map object at 0x10581dd30>
print(list(mp))  # [1, 4, 9, 16, 25]

# 改寫成lambda
print(list(map(lambda s: s * s, lst)))  # [1, 4, 9, 16, 25]

# 計算兩個列表中相同位置的資料的和
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]

# 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保住了.

三、閉包

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

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

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

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

第一天價格為: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))

從上面的例子可以看出,基本上完成了我們的要求,但是這個程式碼相對來說是不安全的,因為你的這個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))
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

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

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

    return averager

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

大家仔細看一下這個程式碼,我是在函式中嵌套了一個函式。那麼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)

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

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)  # []

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

閉包的應用:

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