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