第六章 抽象
第六章 抽象
自定義函數
要判斷某個對象是否可調用,可使用內置函數callable
import math x = 1 y = math.sqrt callable(x)#結果為:False callable(y)#結果為:True使用def(表示定義函數)語句,來定義函數
def sq(name):return name + ',say:Hello,beyond!'print(sq("yanyu"))#結果為:yanyu,say:Hello,beyond! print(sq("sq"))#結果為:sq,say:Hello,beyond!編寫一個函數,返回一個由斐波那契數組成的列表
return語句用于從函數返回值,很重要!!!
1,給函數寫文檔
給函數編寫文檔,以確保其他人能夠理解
可添加注釋(以#打頭的內容)
也可以添加獨立的字符串
放在函數開頭的字符串稱為文檔字符串(docstring),將作為函數的一部分存儲起來
__doc__是函數的一個屬性,屬性名中的雙下劃線表示這是一個特殊的屬性
2,其實并不是函數的函數
在Python中,函數就是函數,即使它嚴格來說并非函數(例如什么都不返回)
def beyond():print("I like beyond band!!!")return #這里的return就是為了結束函數print("beyondyanyu")x = beyond()#結果為:I like beyond band!!! x#結果為:什么也沒有 print(x)#結果為:None參數魔法
修改參數
def beyond(n):n = 'beyondyanyu'yy = 'yanyu' beyond(yy) yy#結果為:'yanyu'與下面代碼段等價
yy = 'yanyu' n = yy n = 'beyondyanyu' yy#結果為:‘yanyu’明顯可見,n變了,但是yy沒發生改變
在函數內部給參數賦值對外部沒有任何影響
參數存儲在局部作用域內
字符串(以及數和元組)是不可變的(immutable),不能修改,只能替換
為新值
于下面代碼段等價
names = ['yanyu','huangjiaju','huangjiaqiang'] n = names n[0] = 'beyond' names#結果為:['beyond', 'huangjiaju','huangjiaqiang']將同一個列表賦給兩個變量時,這兩個變量將同時指向這個列表
也就是說對列表操作的函數,已經對列表本身動手了!!!
要避免這樣的結果,必須創建列表的副本
對序列執行切片操作時,返回的切片都是副本
如果你創建覆蓋整個列表的切片,得到的將是列表的副本
下面開始和函數進行結合
def change(n):n[0] = 'beyond'names = ['yanyu','huangjiaju','huangjiaqiang'] change(names[:]) names#結果為:['beyond', 'huangjiaju', 'huangjiaqiang']1,為何要修改參數(位置參數)
使用函數來修改數據結構(如列表或字典)是一種不錯的方式
編寫程序:存儲姓名,并讓用戶能夠根據姓名、中間姓或姓找人
2,如果參數(位置參數)是不可變的
def inc(x):return x+1foo = 10 inc(foo) foo#結果為:11 def inc(x):x[0] = x[0] + 1foo = [10] inc(foo) foo#結果為:[11]關鍵字參數和默認值
def hello_1(name,hobby):print('{},{}!'.format(name,hobby)) def hello_2(hobby,name):print('{},{}!'.format(hobby,name))hello_1('yanyu','eat')#結果為:yanyu,eat! hello_2('wangsiqi','sleep')#結果為:wangsiqi,sleep! ''' yanyu--name eat---hobby wangsiqi--hobby sleep---name '''hello_1(name='huangjiaju',hobby='sing')#結果為:huangjiaju,sing! hello_1(hobby='sing',name='huangjiaju')#結果為:huangjiaju,sing! hello_2(name='huangjiaju',hobby='sing')#結果為:sing,huangjiaju! def hello_3(name='beyond',hobby='song'):print('{},{}!'.format(name,hobby))hello_3()#結果為:beyond,song! hello_3('huangjiaju')#結果為:huangjiaju,song! hello_3('huangjiaqiang','guitar')#結果為:huangjiaqiang,guitar! hello_3(hobby='game')#結果為:beyond,game!函數hello_4可能要求必須指定name,而hobby和punctuation是可選的
def hello_4(name,hobby='guitar',punctuation='!'):print('{},{}{}'.format(hobby,name,punctuation))hello_4('beyond')#結果為:guitar,beyond! hello_4('beyond','song')#結果為:song,beyond! hello_4('beyond','song','$$$')#結果為:song,beyond$$$ hello_4('beyond',punctuation='...')#結果為:guitar,beyond... hello_4('yanyu',hobby='play football')#結果為:play football,yanyu!hello_4()#結果為:TypeError: hello_4() missing 1 required positional argument: 'name'收集參數
參數前面的星號將提供的所有值都放在一個元組中,也就是將這些值收集起來
def print_params(*params): print(params)print_params('beyondyanyu')#結果為:('beyondyanyu',) print_params(1014, 522, 319)#結果為:(1014, 522, 319)與下面的代碼段等價
星號意味著收集余下的位置參數,如果沒有可供收集的參數,params將是一個空元組。
def print_params_2(title, *params): print(title) print(params)print_params_2('Params:', 1, 2, 3) #結果為: #Params: #(1, 2, 3) print_params_2('Nothing:') #結果為: #Nothing: #()與賦值時一樣,帶星號的參數也可放在其他位置(而不是最后)
在這種情況下你需要做些額外的工作:使用名稱來指定后續參數。
星號不會收集關鍵字參數
def print_params_2(band, *params): print(band) print(params)print_params_2('beyond', age=22)#結果為:TypeError: print_params_2() got an unexpected keyword argument 'age'要收集關鍵字參數,可使用兩個星號;這樣得到的是一個字典而不是元組
def print_params_3(band, **params): print(band) print(params)print_params_3('beyond', x=1, y=2, z=3) ''' 結果為: beyond {'x': 1, 'y': 2, 'z': 3} '''可結合使用這些技術
def print_params_4(x, y, z=3, *pospar, **keypar): print(x, y, z) print(pospar) print(keypar)print_params_4(1, 2, 3, 5, 6, 7, foo=1, bar=2) ''' 結果為: 1 2 3 (5, 6, 7) {'foo': 1, 'bar': 2} ''' print_params_4(1, 2) ''' 結果為: 1 2 3 () {} '''分配參數
分配參數,通過在調用函數(而不是定義函數)時使用運算符*實現的
def add(x, y): return x + ybeyond = (10, 14) add(*beyond)#結果為:24使用運算符**,可將字典中的值分配給關鍵字參數
def hello_3(name='beyond',hobby='song'):print('{},{}!'.format(name,hobby))params = {'name': 'huangjiaju', 'hobby': 'haikuotiankong'} hello_3(**params)#結果為:huangjiaju,haikuotiankong!如果在定義和調用函數時都使用*或**,將只傳遞元組或字典。
對于函數with_stars,我在定義和調用它時都使用了星號,而對于函數without_ stars,我在定義和調用它時都沒有使用,但這兩種做法的效果相同。
只有在定義函數(允許可變數量的參數)或調用函數時(拆分字典或序列)使用,星號才能發揮作用
練習使用參數
def story(**kwds): return 'Once upon a time, there was a {job} called {name}.'.format_map(kwds) def power(x, y, *others): if others: print('Received redundant parameters:', others) return pow(x, y) def interval(start, stop=None, step=1): 'Imitates range() for step > 0' if stop is None: # 如果沒有給參數stop指定值,start, stop = 0, start # 就調整參數start和stop的值result = [] i = start # 從start開始往上數while i < stop: # 數到stop位置result.append(i) # 將當前數的數附加到result末尾i += step # 增加到當前數和step(> 0)之和return resultprint(story(job='band', name='beyond'))#結果為:Once upon a time, there was a band called beyond. print(story(name='huangjiaju', job='singer'))#結果為:Once upon a time, there was a singer called huangjiaju.params = {'job': 'language', 'name': 'Python'} print(story(**params))#結果為:Once upon a time, there was a language called Python.del params['job'] print(story(job='great code', **params))#結果為:Once upon a time, there was a great code called Python.power(2, 3)#結果為:8 power(3, 2)#結果為:9 power(y=3, x=2)#結果為:8params = (5,) * 2 power(*params)#結果為:3125power(3, 3, 'Hello, beyond') ''' 結果為: Received redundant parameters: ('Hello, beyond',) 27 '''interval(10)#結果為:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] interval(1, 5)#結果為:[1, 2, 3, 4] interval(3, 12, 4)#結果為:[3, 7, 11]power(*interval(3, 7)) ''' 結果為: Received redundant parameters: (5, 6) 81 '''作用域
可將變量視為指向值的名稱
執行賦值語句x = 1后,名稱x指向值1;
這幾乎與使用字典時一樣(字典中的鍵指向值),只是你使用的是“看不見”的字典
有一個名為vars的內置函數,它返回這個不可見的字典
一般而言,不應修改vars返回的字典,因為根據Python官方文檔的說法,這樣做的結果是不確定的。換而言之,可能得不到你想要的結果。
這種“看不見的字典”稱為命名空間或作用域。
除全局作用域外,每個函數調用都將創建一個。
在這里,函數foo修改(重新關聯)了變量x,但當你最終查看時,它根本沒變。
這是因為調用foo時創建了一個新的命名空間,供foo中的代碼塊使用。
賦值語句x = 42是在這個內部作用域(局部命名空間)中執行的,不影響外部(全局)作用域內的x。
在函數內使用的變量稱為局部變量(與之相對的是全局變量)。
參數類似于局部變量,因此參數與全局變量同名不會有任何問題。
像下面代碼這樣訪問全局變量是眾多bug的根源。務必慎用全局變量。
def combine(name): print(name + band)band = 'beyond' combine('huangjiaju')#結果為:huangjiajubeyond讀取全局變量的值通常不會有問題,但還是存在出現問題的可能性。
如果有一個局部變量或參數與你要訪問的全局變量同名,就無法直接訪問全局變量,因為它被局部變量遮住了。
如果需要,可使用函數globals來訪問全局變量。這個函數類似于vars,返回一個包含全局變量的字典。(locals返回一個包含局部變量的字典。)
例如,在前面的示例中,如果有一個名為name的全局變量,就無法在函數combine中訪問它,因為有一個與之同名的參數。
然而,必要時可使用globals()[‘name’]來訪問它。
在函數內部給變量賦值時,該變量默認為局部變量
可通過global明確地告訴Python它是全局變量
作用域嵌套
Python函數可以嵌套,即可將一個函數放在另一個函數內
def foo(): def bar(): print("Hello, world!") bar()嵌套通常用處不大,即:使用一個函數來創建另一個函數
一個函數位于另一個函數中,且外面的函數返回里面的函數。也就是返回一個函數,而不是調用它。
重要的是,返回的函數能夠訪問其定義所在的作用域。換而言之,它攜帶著自己所在的環境(和相關的局部變量)!
每當外部函數被調用時,都將重新定義內部的函數,而變量factor的值也可能不同。
由于Python的嵌套作用域,可在內部函數中訪問這個來自外部局部作用域(multiplier)的變量。
像multiplyByFactor這樣存儲其所在作用域的函數稱為閉包。
通常,不能給外部作用域內的變量賦值,但如果一定要這樣做,可使用關鍵字nonlocal。這個關鍵字的用法與global很像,讓你能夠給外部作用域(非全局作用域)內的變量賦值。
遞歸
遞歸意味著引用(這里是調用)自身
遞歸函數通常包含下面兩部分:
基線條件(針對最小的問題):滿足這種條件時函數將直接返回一個值。
遞歸條件:包含一個或多個調用,這些調用旨在解決問題的一部分。
函數調用自身時,是兩個不同的函數[更準確地說,是不同版本(即命名空間不同)的同一個函數]在交流。
階乘和冪
階乘
n的階乘為n × (n-1) × (n-2) × … × 1
可以使用循環:首先將result設置為n,再將其依次乘以1到n-1的每個數字,最后返回result
階乘的數學定義:
1的階乘為1
對于大于1的數字n,其階乘為n-1的階乘再乘以n
其中,函數調用factorial(n)和factorial(n – 1)是不同的實體
def factorial(n): if n == 1: return 1 else: return n * factorial(n - 1)factorial(5)#結果為:120冪
計算冪,可使用內置函數pow和運算符**
power(x, n)(x的n次冪)是將數字x自乘n - 1次的結果,即將n個x相乘的結果。
換而言之,power(2, 3)是2自乘兩次的結果,即2 × 2 × 2 = 8。
冪的數學定義:
對于任何數字x,power(x, 0)都為1
n>0時,power(x, n)為power(x, n-1)與x的乘積
使用遞歸的可讀性高
二分查找
玩個游戲:對方心里想著一個1~100的數字,你必須猜出是哪個。當然,猜
100次肯定猜對,但最少需要猜多少次呢?
思路:
如果上限和下限相同,就說明它們都指向數字所在的位置,因此將這個數字返回
否則,找出區間的中間位置(上限和下限的平均值),再確定數字在左半部分還是右半部分。然后在繼續在數字所在的那部分中查找
關鍵在于元素是經過排序的
Python提供了一些有助于進行這種函數式編程的函數:map、filter和reduce
# 與[str(i) for i in range(10)]等價 list(map(str, range(10))) #結果為:['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']可使用filter根據布爾函數的返回值來對元素進行過濾
def func(x): return x.isalnum()seq = ["foo", "x41", "?!", "***"] list(filter(func, seq))#結果為:['foo', 'x41']如果轉而使用列表推導,就無需創建前述自定義函數
seq = ["foo", "x41", "?!", "***"] [x for x in seq if x.isalnum()]#結果為:['foo', 'x41']filter(lambda x: x.isalnum(), seq)Python提供了一種名為lambda表達式的功能,讓你能夠創建內嵌的簡單函數(主要供map、filter和reduce使用)
如果你要將序列中的所有數相加,可結合使用reduce和lambda x, y: x+y。
等價于內置函數sum,當然,下面這個案例確實還不如sum函數方便。
本章節介紹的新函數
| map(func, seq[, seq, …]) | 對序列中的所有元素執行函數 |
| filter(func, seq) | 返回一個列表,其中包含對其執行函數時結果為真的所有元素 |
| reduce(func, seq[, initial]) | 等價于 func(func(func(seq[0], seq[1]), seq[2]), …) |
| sum(seq) | 返回 seq 中所有元素的和 |
| apply(func[, args[, kwargs]]) | 調用函數(還提供要傳遞給函數的參數) |
總結
- 上一篇: 第五章 条件、循环及其他语句
- 下一篇: “疲客心易惊”上一句是什么