python之抽象一
6.1 懶惰即美德
假設我們編寫了一小段代碼來計算斐波那契數列:
fibs = [0,1]
for i in range(8):
fibs.append(fibs[-2] + fibs[-1])
fibs = [0,1]
num = input('How many Fibonacci numbers do you want?')
for i in range(num-2)
fibs.append(fibs[-2] + fibs[-1])
print fibs
抽象后
num = input('How many numbers do you want?')
print fibs(num)
6.2 抽象和結構
page = download_page()
freqs = computer_frequencies(page)
for word,freq in freqs:
print word,freq
6.3 創建函數
函數是可以調用,它執行某種行為并且返回一個值。一般來說,內建的callable函數可以用來判斷函數是否可調用:
>>> import math
>>> x = 1
>>> y = math.sqrt
>>> callable(x)
False
>>> callable(y)
True
>>>?
def hello(name):
return 'hello,' + name + '!'
創建一個名為hello的函數,他可以返回一個將輸入的參數作為名字的問候語。可以像使用內建函數一樣使用它:
>>>print hello(‘world’)
hello,world
斐波那契函數
def fibs(num)
resule = [0,1]
for i in range(num-2):
result.append(result[-2] + result[-1])
return result
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.'
_doc_是函數屬性
內建的help函數是非常有用的。在交互解釋器中使用它,就可以得到關于函數,包括它的文檔字符串的信息
>>>help(square)
6.3.2 并非真正函數的函數
數學意義上的函數,總在計算其參數后返回點什么。python的有些函數卻并不返回任何東西。在其他語言中,這類函數可能有其他名字。但是python的函數就是函數,即便它從學術上并不是函數。沒有return語句,或者雖有return語句但return后邊沒有跟任何值得函數不返回值:
def test():
print 'this is printed'
return ? ? ? ? ? 這里的return函數只起到結束函數的作用
print 'this is not'
>>>x = test()
This is printed
可以看到,第2個print語句被跳過了,但是如果test不返回任何值,那么x又引用什么呢?
>>>x
>>>
沒東西,在仔細看看
>>>print x
None
所以所有的函數都返回了東西,:當不需要他們返回值得時候,它們就返回None。
千萬不要被默認行為所迷惑。如果在if語句內返回值,那么要確保其他分支也有返回值,這樣一來當調用者期待一個序列的時候,就不會意外的返回None。
6.4 參數
6.4.1 值從哪里來
寫在def語句中函數名后面的變量通常叫做函數的形式參數,而調用函數的時提供的值是實際參數,或者成為參數。
6.4.2 我能改變參數么
在函數內內為參數賦予新值不會改變外部任何變量的值:
>>> def try_to_change(n): n = 'Mr,Gumby'
...?
>>> name = 'Mrs,Entity'
>>> try_to_change(name)
>>> name
'Mrs,Entity'
>>>?
在try_to_change內,參數n獲得了新值,但是它沒有影響到name變量。n實際上是個完全不同的變量,具體的工作方式類似于下面這樣:
>>> name = 'Mrs,Entity'
>>> n = name ? ? ? ?#這句的作用基本上等于傳參數
>>> n = 'Mr.Gumby' ? #在函數內部完成的
>>> name
'Mrs,Entity'
>>>?
結果是顯而易見的,當變量n改變的時候,變量name不變。同樣,當在函數內部把參數重綁定的時候,函數外的變量不會受到影響。
參數存儲在局部作用域。
字符串是不可變的,即無法被修改。
>>> def change(n): n[0] = 'Mr.Gumby'
...?
>>> names = ['Mrs.Entity','Mrs.Thing']
>>> change(names)
>>> names
['Mr.Gumby', 'Mrs.Thing']
>>>?
本例中,參數被改變了。這就是本例和前面例子中至關重要的區別。前面的例子中,局部變量被賦予了新值,但是這個例子中變量names所綁定的列表的確改變了。
>>> names = ['Mrs.Entity','Mrs.Thing']
>>> n = names
>>> n[0]='Mr.Gumby'
>>> names
['Mr.Gumby', 'Mrs.Thing']
>>>?
這類情況在前面已經出現了多次。當兩個變量同時引用一個列表的時候,他們的確是同時引用一個列表。如果想避免出現這種情況,可以復制一個列表的副本。當在序列中做切片的時候,返回的切片總是一個副本。因此,如果你復制了整個列表的切片,將會得到一個副本:
>>> names = ['Mrs.Entity','Mrs.Thing']
>>> n = names[:]
現在n和name包含兩個獨立的列表,其值相等:
>>> n is names
False
>>> n == names
True
>>>?
如果現在改變n,則不會影響到names:
>>> n[0]='Mr.Gumby'
>>> n
['Mr.Gumby', 'Mrs.Thing']
>>> names
['Mrs.Entity', 'Mrs.Thing']
>>>?
再用change試一下:
>>> change(names[:])
>>> names
['Mrs.Entity', 'Mrs.Thing']
>>>?
現在參數n包含一個副本,而原始的列表是安全的。
函數的局部名稱-----包括參數在內-----并不和外面的函數名稱沖突。
1.為什么我想要修改參數
使用函數改變數據結構是將程序抽象化的好方法。假設需要編號一個存儲名字并且能用名字、中間名或姓查找聯系人的程序,可以使用下面的數據結構:
storage = {}
storage['first'] = {}
storage['middle'] = {}
storage['last'] = {}
storage這個數據結構的存儲方式是帶有3個鍵“first”“middle”和“last”的字典。每個鍵下面都又存儲一個字典。子字典中,可以使用名字作為鍵,插入聯系人列表作為值。比如要把我自己的名字加入這個數據結構,可以像下面這么做:
>>> me = 'Magnus Lie Hetland'
>>> storage['first']['Magnus'] = [me]
>>> storage['middle']['Lie'] = [me]
>>> storage['last']['Hetland'] = [me]
每個鍵下面都存儲了一個以人名組成的列表。本例中,列表中只有我。
現在如果想得到所有注冊的中間名為Lie的人,可以像下面這么做:
>>> storage['middle']['Lie']
['Magnus 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']['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)
標簽(比如middle)以及名字(比如Lie)可以作為參數提供給lookup函數使用,這樣會獲得包含全名的列表。換句話說,如果我的名字已經存儲了,可以像下面這樣做:
>>> lookup(storage,'middle','Lie')
注意,返回的列表和存儲在數據結構中的列表是相同的,所以如果列表被修改了,那么也會影響數據結構(沒有查詢到人的時候就問題不大了,因為函數返回的是None)
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]
store函數執行以下步驟:
1.使用參數data和full_name進入函數,這兩個函數被設置為函數在外部獲得的一些值。
2.通過拆分full_name,得到一個叫names 的列表
3.如果names的長度為2(只有首名和末名),那么插入一個空字符串作為中間名。
4.將字符串“first”、“middle”和“last”作為元組存儲在labels中(也可以使用列表:這里只為了方便而去掉括號)
5.使用zip函數聯合標簽和名字,對于每一個(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 Locksley’]
>>> store(Mynames,'Mr.Gumby')
>>> lookup(Mynames,'middle','')
[‘Robin Hood’,‘Robin Locksley’,‘Mr。Gumby’]
可以看到,如果某些人的名字,中間名或姓相同,那么結果中會包含所有這些人的信息。
2.如果我的參數不可變
函數只能修改參數對象本身。但是如果你得參數不可變----比如數字---又該怎么辦? 這是沒有辦法的,這時候你應該從函數中返回你需要的值(如果值多于一個話就以元組形式返回)。例如,將變量的數值增1的函數可以這樣寫:
>>> def inc(x):return x+1
...?
>>> 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)
def hello_2(greeting,name):
print '%s,%s!' % (greeting,name)
兩個代碼所實現的是完全一樣的功能,只是參數名字反過來了:
>>>hello_1('hello','world')
hello,world
>>>hello_2('hello','world')
hello,world
有些時候,參數的順序是很難記住的,為了讓事情簡單些,可以提供參數的名字:
>>>hello_1(greeting=‘hello’,name=‘world’)
hello,world!
這樣一來,順序就完全沒影響了:
>>>hello_1(name=‘world’,greeting=‘hello’)
hello,world!
但參數名和值一定要對應:
>>>hello_2(greeting='hello',name='world')
world,hello
這類使用參數提供的參數叫做關鍵字參數。它的作用在于可以明確每個參數的作用,也就避免了下面這樣的奇怪的函數調用
>>>storre('Mr. Brainsample',10,20,13,5)
可以使用
>>>store(patient='Mr. Brainsample',hour=10,minute=20,day=13,mouth=5)
盡管這么做打得字多了些,但是很顯然,每個參數的含義變得更加清晰,而且就算弄亂了參數的順序,對程序的功能也沒有任何影響。
關鍵字參數最厲害的地方在于可以在函數中給函數提供默認值:
def hello_3(greeting='hello',name='world'):
print '%s,%s!' % (greeting,name)
當參數具有默認值的時候,調用的時候就不用提供參數了!可以不提供,提供一些或提供所有的參數:
>>>hello_3()
hello,world
>>>hello_3('Greetings')
Greetings,world
>>>hello_3('Greeting','universe')
Greeting,universe
可以看到,位置參數這個方法不錯-----除了在提供名字的時候就要提供問候語。但是如果只想提供name參數,而讓greeting使用默認值該怎么辦呢?
>>>hello_3(name='Gumby')
hello,Gumby
位置和關鍵字參數是可以聯合使用的。把位置參數放置在前面就可以了,如果不這樣做,解釋器會不知道它們到底是誰(也就是它們應該處的位置)。
除非完全清楚程序的功能和參數的意義,否則應該避免混合使用位置參數和關鍵字參數。一般來說,只有在強制要求的參數個數比可修改的具有默認值的參數個數少的時候,才使用上面提到的參數書寫方法。
例如,hello函數可能需要名字作為參數,但是也允許用戶自定義名字,問候語和標點:
def hello_4(name,greeting=‘hello’,punctuation=‘!’):
print ‘%s,%s%s’ % (greeting,name,punctuation)
函數的調用方式很多:
>>>hello_4('Mars')
hello,Mars!
>>>hello_4('Mars','Howdy')
Howdy,Mars!
>>>hello_4('Mars','Howdy','...')
Howdy,Mars...
>>>hello_4('Mars',punctuation='.')
hello,Mars.
>>>hello_4()
Traceback (most recent call last):
? File "<stdin>", line 1, in <module>
如果為name也賦予默認值,那么最后一個語句就不會產生異常
6.4.4 收集參數
有些時候讓用戶提供任意數量的參數是很有用的,比如在名字存儲過程中,用戶每次只能存一個名字。如果能像下面這樣存儲多個名字就更好了:
>>>store(data,name1,name2,name3)
用戶可以給函數提供任意多的參數,實現起來也不難。
試著像下面這樣定義函數:
def print_params(*params):
print params
這里我只指定了一個參數,但是前面加上了個星號。
>>>print_params('Testing')
('Testing',)
?可以看到,結果作為元組打印出來,因為里面有個逗號。所以在參數前使用星號就能打印出元組?那么Params中使用多個參數看看會發生什么:
>>>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('Nothing:')
Nothing:
()
>>>print_params_2('Hmm...',something=42)
會報錯
def print_params_3(**params):
print params
至少解釋器沒有發牢騷,調用下
>>>print_params_3(x=1,y=2,z=3)
{'z':3,'x':1,'y':2}
返回的是字典而不是元組。
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)
123
(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]
>>> d = {}
>>> init(d)
>>> store(d,'Han Solo')
但是現在可以這樣用
>>>store(d,'Luke Skywalker','Anakin Skywalker')
>>>lookup(d,'last','Skywalker')
['Luke Skywalker','Anakin Skywalker']
6.4.5 反轉過程
如何將參數收集為元組和字典,但是事實上,如果使用*和**的話,也可以執行相反的操作。那么函數收集的逆過程是什么樣?
def add(x,y): return x+y
比如說有包含由兩個要想加的數字組成的元組:
params = (1,2)
這個過程或多或少有點像我們上一節中介紹的逆過程。不是要收集參數,而是分配它們在“另一端”。使用*運算符就簡單了------不過是在調用而不是在定義時使用:
>>>add(*params)
3
對于參數列表來說工作正常,只要擴展的部分是最新的就可以。可以使用同樣的技術來處理字典-----使用雙星號運算符。假設之前定義了hello_3,那么可以這樣使用:
>>>params = {'name':'Sir Robin','greeting':'well met'}
>>>hello_3(**params)
Well met,Sir Robin!
在定義或者調用函數時使用星號(或者雙星號)僅傳遞元組或字典,所以可能沒遇到什么麻煩:
>>>def with_stars(**kwds):
print kwds['name'],'is',kwds['age'],'years old'
>>>def without_stars(kwds):
print kwds['name'],'is',kwds['age'],'years old'
>>>args = {'name':'Mr. Gumby','age':42}
>>>with_stars(**args)
Mr. Gumby is 42 years old
>>>without_stars(args)
Mr. Gumby is 42 years old
可以看到,在with_stars中,我在定義和調用函數時都使用了星號。而在without_stars中兩處都沒用,但是得到了同樣的效果。所以星號只在定義函數(允許使用不定數目的參數)或者調用(“分割”字典或者序列)時才有用。
使用拼接操作符“傳遞”參數很有用,因為這樣一來就不用關心參數的個數之類的問題,例如:
def foo(x,y,z,m=0,n=0):
print x,y,z,m,n
def call_foo(*args,**kwds):
print "Calling foo!"
foo(*args,**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 interval(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'}
>>>print 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
>>>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
?
6.5 作用域
內建的vars函數可以返回這個字典:
>>>x = 1
>>>scope = vars()
>>>scope['x']
1
>>>scope['x'] += 1
>>>x
2
一般來說,vars所返回的字典是不能修改的,因為根據官方的Python文檔的說法,結果是未定義的,換句話說,可能得不到想要的結果。
這類“不可見字典”叫做命名空間或者作用域。那么到底有多少個命名空間?除了全局作用域外,每個函數調用都會創建一個新的作用域:
>>>def foo():x = 42
>>>x = 1
>>>foo()
>>>x
1
這里的foo函數改變了變量x,但是在最后的時候,x并沒有變。這是因為當調用foo的時候,新的命名空間就被創建了,它作用于foo內的代碼塊。賦值語句x=42只在內部作用域(局部命名空間)起作用,所以它并不影響外部作用域的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函數獲取全局變量值,該函數的近親是vars,take返回全局變量的字典(locals返回局部變量的字典)。例如,如果前例中有個叫做parameter的全局變量,那么就不能再combine函數內部訪問該變量,因為你有一個與之同名的參數。必要時,能使用globals()['parameter']獲取:
>>>def combine(parameter):
print parameter + globals()['parameter']
>>>parameter = 'berry'
>>>combine(Shrub)
Shrubberry
接下來討論重綁定全局變量(使變量引用其他新值)。如果在函數內部將值賦予一個變量,它自動成為局部變量----除非告知Python將其聲明為全局變量。那么怎么才能告訴Python這是一個全局變量呢?
>>>x=1
>>>def change_global()
global x
x = x+1
>>>change_global()
>>>x
2
嵌套作用域
Python的函數是可以嵌套的,也就是說可以將一個函數放在另一個里面。下面是一個例子 :
def foo()
def bar():
print "hello,world"
bar()
嵌套一般來說并不是那么有用,但它又一個很突出的應用,例如需要用一個函數“創建”另一個,也就意味著可以像下面這樣書寫函數:
def multiplier(factor):
def multiplyByFactor(number):
return number*factor
return multiplyByFactor
一個函數位于另外一個里面,外層函數返回里層函數。也就是說函數本身被返回了----但并沒有被調用。重要的是返回的函數還可以訪問它的定義所在的作用域,換句話說,它帶著它的環境和相關的局部變量。
每次調用外層函數,它內部的函數都被重新綁定,factor變量每次都有一個新的值。由于Python的嵌套作用域,來自外部作用域的這個變量,稍后會被內層函數訪問。例如:
>>>double = multiplier(2)
>>>double(5)
10
>>>triple = multiplier(3)
>>>triple(3)
9
>>>multiplier(5)(4)
20
類似multiplyByFactor函數存儲于封閉作用域的行為叫做閉包
外部作用域的變量一般來說是不能進行重新綁定的。但是Python3.0中,nonlocal關鍵字被引入。它和global關鍵字的使用方式類似,可以讓用戶對外部作用域的變量進行賦值。
6.6 遞歸
遞歸的定義包括它們自身定義內容的引用。由于每個人對遞歸的掌握程度不同,它可能會讓人大傷腦筋。
def recursion():
return recursion()
上述遞歸叫做無窮遞歸。有用的遞歸函數包含以下幾部分:
當函數直接返回值時有基本實例
遞歸實例,包括一個或者多個問題最小部分的遞歸調用
這里的關鍵就是將問題分解為小部分,遞歸不能永遠繼續下去,因為它總是以最小可能性問題結束,而這些問題又存儲在基本實例中。
所以讓函數調用自身。但是怎么實現呢?
6.6.1 兩個經典:階乘和冪
首先,假設想要計算數n的階乘。n的階乘定義為n*(n-1)*(n-2)*。。。*1.很多數學應用中都會用到它。
def factorial(n):
result = n
for i in range(1,n):
result *= i
return result
這個方法可行而且很容易實現。它的過程主要是;首先將result賦到n上,然后result依次與1到n-1的數相乘,最后返回結果。 階乘數學定義:
1的階乘是1
大于1的數n的階乘是n乘n-1的階乘
可以看到,這個定義完全符合剛才所介紹的遞歸的兩個條件。
現在考慮如何定義實現為函數。理解定義本身后,實現其實很簡單:
def ?factorial(n):
if n==1:
return 1
else:
return n * factorial(n-1)
這就是定義的直接實現。只要記住函數調用factorial(n)是和調用factorial(n-1)不同的實體就行。
假設需要計算冪,就像內建的pow函數或者**運算符一樣。可以用很多種方法定義一個數的冪。先看一個簡單的例子:power(x,n) ?(x為n的冪次) 是x自乘n-1次的結果。所以power(2,3) 是2乘以自身3次:2*2*2=8
實現很簡單:
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乘以(x,n-1)的結果。
def pwoer(x,n):
if n == 0:
return 1
else:
return x * power(x,n-1)
6.6.2 另外一個經典:二元查找
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)
完全符合定義。如果lower == upper,那么返回upper,也就是上限。注意,程序設計(斷言)所查找的數字一定會被找到(number == sequence[upper])。如果沒有到達基本實例的話,先找到middle,檢查數字是在左邊還是在右邊,然后使用新的上下限繼續調用遞歸過程。也可以將限制設為可選以方便用。只要在函數定義的開始部分加入下面的條件語句即可:
def search(sequence,number,lower=0,upper=None):
if upper is None:upper = len(sequence)-1
現在如果不提供限制,程序會自動設定查找范圍為整個序列,看看行不行:
>>>seq = [34,67,8,123,4,100,95]
>>>seq.sort()
>>>seq
[4,8,34,67,95,100,123]
>>>search(seq,34)
2
>>search(seq,100)
5
但不必這么麻煩,一則可以直接使用列表方法index,如果想要自己實現的話,只要從程序的開始處循環迭代直到找到數字就行了。
當然可以,使用index沒問題了,但是只使用循環可能效率有點低,剛才說過查找100內的一個數,只需要7個問題即可。用循環的話,在最糟糕的情況下要問100個問題。
標準庫中的bisect模塊可以非常有效的實現二元查找。
可以使用map函數將序列中的元素全部傳遞給一個函數:
>>>map(str,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函數一般來說不能輕松被列表推導式替代,但是通常用不到這個功能。它會將序列的前兩個元素與給定的函數聯合使用,并且將它們的返回值和第三個元素繼續聯合使用,直到整個序列都處理完畢,并且得到一個最終結果。例如,需要計算一個序列的數字的和,可以使用reduce函數加上lambda x,y:x+y (繼續使用相同的數字):
>>>numbers = [72,101,108,108,111,44,32,119,111,114,108,100,33]
>>>reduce(lambda x,y: x+y,numbers)
1161
轉載于:https://blog.51cto.com/pankuo/1661440
總結
以上是生活随笔為你收集整理的python之抽象一的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 搞测量的要时刻保护自己哦!
- 下一篇: LoadRunner监控Linux的三种