1. 程式人生 > >Python基礎教程第六章學習筆記——抽象

Python基礎教程第六章學習筆記——抽象

6 抽象

介紹如何將語句組織成函式,告訴計算機如何做事(只告訴一次就可以) 還會介紹引數(parameter)和作用域(scope)概念 遞迴的概念及在程式中的用途

6.1 懶惰即美德

一段程式碼可能要在多處使用,就可以把這段程式碼定義為函式,需要的時候直接呼叫就可以——抽象 fibs = [0, 1] for i in range(8):     fibs.append(fib[-2] + fibs[-1]) >>> fibs [0, 1, 1, 2, 3, 5, 8, 13, 21, 34] 抽象一點: num = raw_input('How many numbers do you wants? ' print fibs(num)                                                                       #呼叫自定義的fibs函式,抽象了程式碼。需要時直接呼叫就OK

6.2 抽象與結構

程式應該是非常抽象的,就像“下載網頁、計算頻率,列印每個單詞的頻率”一樣易懂: page = download_page() freps = compute_frequencies(page) for word, frep in freps:     print word, frep #讀完就知道程式做什麼了,具體細節會在其他地方寫出——在獨立的函式定義中

6.3 建立函式

函式可以呼叫:它執行某種行為並返回一個值;一般內建的callable函式可以用來判斷函式是否可呼叫: >>> import math  >>> x = 1 >>> y = math.sqrt >>> callable(x) False >>> callable(y) True 使用def語句即可定義函式
def hello(name):     return 'Hello, ' + name + '!' 此時就可以呼叫hello函式: >>>print hello('world') Hello, world! >>> print hello('Gumby') Hello, Gumby! #定義斐波那契函式 def fibs(num): result = [0, 1]     for i in range(num-2):         result.append(result[-2] + result[-1])     return result #此時直接呼叫函式就可求斐波那契序列 >>> fibs(10) [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]

6.3.1 記錄函式

想給函式寫文件,讓後面使用函式的人能理解,可以加註釋(以#號開頭) 還可以直接寫上字串,在函式的開頭寫下字串,會作為函式的一部分進行儲存——稱為文件字串: def square(x):     'Calculates the square of the number x'     return x*x #文件字串可以按如下方式訪問: >>> square._doc_ 'Calculates the square of the number x' #內建的help函式非常有用。在互動式直譯器中使用它,就可得到關於函式,包括他的文件字串的資訊: >>> help(square) help on function square in module_main_: square(x)     Calxulates the square of the number x

6.3.2 並非真正函式的函式

有的函式並不會返回值,不同於學術意義上的函式 def test():     print 'This is printed'     return     print 'This is not' >>> x = test() This is printed >>> x >>> >>> print x None

6.4 引數魔法—函式引數的用法有時有些不可思議

6.4.1 值從哪裡來—一般不用擔心這些

6.4.2 我能改變引數嗎

在函式內為引數賦予新值不會改變外部任何變數的值: >>>def try_to_change(n)     n = 'Mr . Gumby' >>> name = 'Mr. Entity' >>> try_to_change(name) >>> name 'Mr. Entity' 字串及數字、元組是不可變的,即無法修改。考慮將可變的資料結構如列表作為引數會發生什麼: def change(n):     n[0] = 'Mr. Gumby' >>> names = ['Mrs. Entity', 'Mrs. Thing'] >>> change(names) >>> names ['Mr. Gumby', 'Mrs. Thing'] 現在引數在外部也改變了,因為兩個變數同時引用一個列表,一個發生發改變另一個隨之改變 為避免這種情況,就可以建立一個列表副本,副本改變就不會影響到原列表:n = names[:]

1 為什麼我想要修改引數—使用引數改變資料結構(如列表、字典)是將程式抽象化的好方法

#編寫一個儲存名字,並能用名字、中間名或姓查詢聯絡人的程式 storage = {} storage['first'] = {} storage['middle'] = {} storage['last'] = {}                                       #初始化資料結構storage。storage 是:{'first':{}, 'middle':{}, 'last':{}} me = 'Magnus Lie Hetland' storage['first']['Magnus'] = [me] storage['middle']['Lie'] = [me] storage['last']['Hetland'] = [me]                 #storage是:{'first':{'Magnus': 'Magnus Lie Hetland'}, 'middle':{'Lie': 'Magnus Lie Hetland'}, 'last':{'Hetland': 'Magnus Lie Hetland'}} >>> storage['middle']['Lie'] ['Magnus Lie Hetland'] my_sister = 'Anne Lie Hetland' storage['first'].setdefault('Anne', []).append(my_sister) storage['middle'].setdefault('Lie',[]).append(my_sister) storage['last'].setdefault('Hetland'. []).append(my_sister)   #storage是:{'first':{'Magnus':'Magnus Lie Hetland', 'Anne':'Anne Lie Hetland'}, 'middle':{'Lie':['Magnus Lie                                                                                                                                        Hetland','Anne Lie Hetland']},'last:{'Hetland':['Magnus Lie Hetland', 'Anne Lie Hetland']}} >>> storage['first']['Anne'] ['Anne Lie Hetland'] >>> storage['middle']['Lie'] ['Magnus Lie Hetland', 'Anne Lie Hetland'] #將人名加入到列表的步驟枯燥,尤其加入很多姓名相同的人,因為需要擴充套件已經儲存了那些名字的列表 如果要寫個大程式來這樣更新列表,那程式會很快變得臃腫笨拙 抽象的要點是要隱藏繁瑣的細節,可用函式來實現。 #定義初始化資料結構函式 def init(data):     data['first'] = {}     data['middle'] ={}      data['last'] = {} >>> storage = {} >>> init(storage) >>> storage {'middle':{},'last':{},'first':{}} #獲取名字函式 def lookup(data, label, name):     return data[label].get(name) >>> lookup(storage, 'middle', 'Lie') ['Magnus Lie Hetland'] #定義擴充資料結構的函式 def store(data, full_name):     names = full_name.split()     if len(names) == 2:         names.insert(1, '')     labels = 'first', 'middle', 'last' for label, name in zip(labels, names):     people = lookup(data, label, name)     if people:         people.append(full_name)     else:         data[label][name] = full_name >>> Mynames = {} >>> init(Mynames) >>> store(Mynames, 'Magnus Lie Hetland') >>> lookup(Mynames,'middle', 'Lie') ['Magnus Lie Hetland'] >>> store(Mynames, 'Robin Hood') >>> store(Mynames, 'Robin locksley') >>> lookup(Mynames, 'first', 'Robin') ['Robin Hood', 'Robin Lockley'] >>> store(Mynames, 'Mr. Gumby') >>> lookup(Mynames, 'middle', '') ['Robin Hood', 'Robin Lockley', 'Mr. Gumby']

2 如果我的引數不可變呢——Python中函式只能修改引數物件本身

引數不可變時,應從函式中返回所有你需要的值(若值多於一個,以元組的形式返回): def inc(x):     return x + 1 >>> foo = 10 >>> print inc(foo) 11 >>> foo 10 所以想使foo發生改變,就要從函式中返回它 >>> foo = 10 >>> foo = inc(foo) >>> foo 11 真想改變引數,可使用一點小技巧,將值放置在列表中: def inc(x):     x[0] = x[0] + 1 >>> foo = [10] >>> inc(foo) >>> foo [11]

6.4.3 關鍵字引數和預設值

之前使用的引數都是位置引數,此節將引入關鍵字引數:程式規模越大,其作用越大 def hello_1(greeting, name):     print '%s, %s!' % (greeting,name) >>> hello_1('Hello','world') Hello, world! >>> hello_1('world','Hello') world, Hello! 如果使用關鍵字引數,即使位置改變,輸出也不會發生改變: >>> hello_1(name='world',greeting='Hello')                           #關鍵字引數主要作用在於可明確每個引數的作用 Hello, world! 如果函式中引數太多,容易搞混其順序及對應的含義,使用關鍵字引數就完全沒有這個問題了 >>> store('Mr. Brainsample', 10, 20, 13, 5) >>>store(patient = 'Mr. Brainsample', hour = 10, minute=20, day=13, month=5) 關鍵字引數最厲害還在於可以在函式中給引數提供預設值: def hello_2(greeting='Hello',name='world'):     print '%s, %s!' %(greeting,name) >>> hello_2() 'Hello, world!' >>> hello_2('Greeting') 'Greeting, world!' >>> hello_2('Greeting', 'universe') 'Greeting,universe!' >>> hello_2(name='Gumby') 'Hello, Gumby!' 關鍵字引數和位置引數可以聯合使用,但應完全清楚程式的功能和引數的意義,否則應儘量避免混合使用 def hello_3(name, greeting='Hello', punctuation='!')     print '%s, %s%s' % (greeting,name,punctuation) >>> hello_3('Mars') Hello, Mars! >>> hello_3('Mars','howdy','...') howdy, Mars... >>> hello_3('Mars', punctuation='.') Hello, Mars. >>> hello_3() TypeError:......

6.4.4 收集引數——使用者可提供任意數量的引數——將引數收集為元組或字典

如之前定義的store函式,可儲存多個名字就好了 >>> store(data, name1, name2, name3)  使用者可給函式提供任意多的引數,實現起來並不難: def print_params(*params):     print params >>> print_params('Testing') ('Testing',) >>> print_params(1, 2, 3) (1, 2, 3) 所以引數前的星號(*)將所有值放置在同一元組中——將值收集起來,然後使用。 def print_params_2(title, *params):     print title     print params >>> print_params_2('Params:', 1, 2, 3) Params: (1, 2, 3) 所以星號(*)的意思就是“收集其餘位置的引數”,如果不提供任何供收集的元素,params就是個空元組: >>> print_params_2('Params: ') Params: () 但是星號(*)不可以用來處理關鍵字引數,需要另外一個能處理關鍵字引數的“收集”的操作——'**' def print_params_3(**params):     print params >>> print_params_3(x=1,y=2,z=3) {'y':2, 'x':1, 'z':3}                                           #返回的是字典而不是元組 聯合使用: def print_params_4(x, y, z=3, *pospar, **keypar):     print x, y, z     print pospar     print keypaer >>> print_params_4(2, 3, 4, 5, 6, 7, foo=1, bar=2) 2 3 4 (5, 6, 7) {'foo': 1, 'bar': 2} >>> print_params_4(1,2) 1 2 3 () {} #實現多個名字同時儲存: def store(data, *full_names):     for full_name in full_names:         names = full_name.split()         if len(names) == 2: names.insert(1,'')         labels = 'first', 'middle', 'last'         for label, name in zip(labels,names):             people = lookup(data, label, name)             if people:                 people.append(full_name)             else:                 data[label][name] = [full_name]

6.4.5 反轉過程——使用*和**也可以執行收集的相反操作(逆過程)

def add(x, y):     return x + y >>> params = (1,2) >>> add(*params)                 *不是收集引數,而是分配它們到另一端——*在呼叫函式時使用而不是定義時使用 3 def hello_3(greeting='Hello',name='world')     print '%s, %s' % (greeting,name) params = {'greeting': 'Well met', 'name': 'Sir Robin'} >>> hello_3(**params) Well met, Sir Robin #在定義和呼叫中都使用*或**,與在定義和呼叫中都不用*和**得到同樣的效果。所以*或**只有在定義或呼叫單獨使用時才有用 def withstars(**kwds):     print kwd['name'], 'is', kwd['age'], 'years old' def withoutstars(kwds):     print kwd['name'], 'is', kwd['age'], 'years old' >>> args = {'names': 'Mr Gumby', 'age': 42} >>> withstars(**args) Mr Gumby is 42 years old >>> withoutstars(args) Mr Gumby is 42 years old 使用拼接(Splicing)操作符“傳遞”引數很有用,因為不用關心引數的個數之類的問題: def foo(x, y, z, m=0, n=0):     print x, y, z, m, n def call_foo(*arg, **kwds):     print 'Calling foo!'     foo(*arg, **kwds)

6.4.6 練習使用引數

def story(**kwds):     return 'Once upon a time, there was a ' \                '%(job)s called %(name)s.' % kwds def power(x, y, *others):     if others:         print 'Received redundant parameters:', others     return pow(x, y) def iterval(start, stop=None, step=1):     'Imitates range() for step > 0'     if stop is None:         start, stop = 0, start     result = []     i = start     while i < stop:         result.append(i)         i += step     return result >>> print story(job ='king', name='Gumby') Once upon a time, there was a king called Gumby. >>> print story(name='Sir Robin', job='brave knight') Once upon a time, there was a brave knight called Sir Robin. >>> params = {'job':'language', 'name':'Python'} >>> story(**params) Once upon a time, there was a language called Python >>> del params['job'] >>> print story(job='stroke of genius', **params) Once upon a time, there was a stroke of genius called Python. >>> power(2, 3) 8 >>> power(3, 2) 9 >>> power(y=3, x=2) 8 >>> params=(5,)*2 >>> power(*params) 3125 >>> power(3, 3, 'Hello, world) Received redundant parameters: ('Hello, world',) 27 >>> iterval(10) [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] >>> iterval(1, 5) [1, 2, 3, 4] >>> iterval(3, 12, 4) [3, 7, 11] >>> power(*iterval(3,7)) Received redundant parameters:(5, 6) 81

6.5 作用域

x = 1  變數和所對應的值用的是個不可見的字典:x引用1. 這類不可見字典叫做名稱空間或作用域,每個函式呼叫都會建立一個新的作用域: def foo():  x=42 >>> x = 1 >>> foo() >>> x 1 最終x的值還是1,因為呼叫函式foo時,新的名稱空間被建立,它作用於foo內的程式碼塊,並不影響全域性中的x。函式內的變數被稱為區域性變數,所以用全域性變數名做區域性變數名沒有問題。 def output(x): print x >>> x= 1 >>> y = 2 >>> output(y) 2 需要在函式內訪問全域性變數怎麼辦?如果只想讀取變數的值(不想重新繫結變數),一般沒問題。 >>> def combine(parameter): print parameter + external >>> external = 'berry' >>> combine('Shrub') Shrubberry 但是這樣引用全域性變數容易引發很多錯誤,所以還要慎重使用。 遮蔽的問題:如果區域性變數或引數的名字和想訪問的全域性變數名相同,全域性變數會被區域性變數遮蔽,從而不能訪問。 此時可使用globals函式獲取全域性變數值——返回全域性變數的字典 def combine(parameter):     print parameter + globals()['parameter'] >>> parameter = 'berry' >>> combine('Shrub') Shrubberry 重新繫結全域性變數——向Python宣告其為全域性變數,否則其自動成為區域性變數: >>> x = 1 def change_global():     global x     x = x+1 >>> change_global() >>> x 2             巢狀作用域——講一個函式放入另一個裡面 def multiplier(factor):     def multiplyByFactor(number):         return number*factor     return multiplyByFactor >>> double = multiplier(2) >>> double(5) 10 >>> multiplier(5)(4) 20

6.6 遞迴——函式可呼叫自身

6.6.1 兩個經典:階乘和冪

階乘,用迴圈來編寫: def factorial(n):     result = n     for i in range(1,n):         result *= i     return result 遞迴的版本: 1的階乘是1 大於1的階乘是n*(n-1)的階乘 def iterfactorial(n):     if n == 1:         return 1     else:         return n*iterfactorial(n-1) 計算冪,pow函式或**運算子:數自乘n-1次的結果 改為迴圈的編寫: def power(x, n):     result = 1     for i in range(n):         result *= x     return result 改為遞迴版本: 對任意數字x,power(x,0)  =1 對任意大於0的數來說,power(x,n)=x*power(x,n-1) def iterpower(x, n):     if n == 0:         return 1     else:         return x*iterpower(x,n-1)

6.6.2 另外一個經典:二元查詢——二分法

查詢1-100中的一個數 若上下限相同就是要猜的數字 否則找兩個數字的中點(上下限的平均值),查詢數字在左側或右側,繼續查詢數字在的那部分。——要查詢的數字小於中間數字,則數字在左側,反之在右側。 def search(sequence, number, lower, upper):     if lower == upper:         assert number == sequence[upper]         return upper     else:         middle = (lower + upper)/2         if number > sequence[middle]:             return search(sequence, number, middle+1, upper)         else:             return search(sequence, number, lower, middle) 函式到處放——函式型程式設計有一些很有用的函式:map、filter和reduce map:使用map函式將序列中的元素全部傳遞給一個函式: >>> map(str, range(10))  #等同於[str[i] for i in range(10)] ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'] filter函式可基於一個返回布林值的函式對元素進行過濾: >>> def func(x):         return x.isalnum() >>> seq = ['foo', 'x41', '?!', '***'] >>> filter(func,seq) ['foo', 'x41'] 使用列表推導式也可以得出: >>> [x for x in seq if x.isalnum()] ['foo', 'x41'] 還可以使用lambda表示式的特性,可以建立短小的函式。 >>> filter(lambda x: x.isalnum(), seq) ['foo','x41'] reduce函式會將序列的前兩個元素與給定的函式聯合使用,並將它們的返回值和第三個元素繼續聯合使用,直到整個序列都處理完畢,並得到一個最終結果。 >>> numbers = [72, 101, 108, 111, 44, 32, 119, 111, 114, 108, 100, 33] >>> reduce(lambda x, y: x+y, numbers) 1161

6.7 小結—抽象的常見知識及函式的特殊知識

抽象:隱藏多於細節的藝術。定義處理細節的函式,可讓程式更加抽象 函式定義:使用def語句定義,由語句組成的塊 引數:提供函式需要的資訊——函式呼叫時設定的變數:位置引數和關鍵字引數,引數給定預設值時是可選的。 作用域:變數儲存在作用域:全域性作用域和區域性作用域,作用域可巢狀。 遞迴:函式呼叫自身,一切用遞迴實現的功能都可以用迴圈來實現。 函式型程式設計:lambda表示式及map、filter和reduce函式

6.7.1 本章新函式

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]])               #呼叫函式,可以提供引數