本章會介紹關於抽象的常見知識及函數的特殊知識。
第六章 抽象
6.1 懶惰即美德
一段代碼只用編寫一次,下次使用的時候就可以直接拿出來用。用抽象的方式來完成,只需要告訴計算機去做,不用特別說明怎麼做。這就是程序員的偷懶方法。
6.2 抽象和結構
抽象可以節省很多工作,並且使得程序更容易被讀懂。就像我們告訴計算機下載網頁,計算詞頻,而不用說明具體的細節,細節部分會在函數定義中完成。
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函數了
fibs(10) #返回包含10個數的斐波那契數列
6.3.1 記錄函數
給函數寫說明文檔,可以加入註釋(#開頭的語句),還有一個方式是直接寫上字符串,如果在函數開關寫下字符串,它會作爲函數的一部分進行存儲,這稱爲文檔字符串。
def square(x):
'Calculates the square of the number x.' #文檔字符串
return x*x
#訪問文檔字符串
square.__doc__ #doc前後分別是兩個下劃線。
help(square) #可以得到更詳細的函數信息。
6.3.2 並非真正函數的函數
python中有些函數不返回任何東西,不是學術意義上的函數。比如沒有return語句,或有return語句,但後面沒有任何值。
當函數沒有返回值時,會返回None.
def test():
print('this is printed')
return #return只起到結束函數的作用
print('This is not')
x=test() #返回值 this is printed
x #什麼都沒有
print(x) #返回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變量沒有影響。
name='Mrs.Entity'
n=name #用name給n賦值
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=['Mrs.Entity','Mrs.Thing']
n=names[:]
n is names #False,n是name的副本
n==names #True,兩個參數值相等
n[0]='Mr Gumby'
n #返回值['Mr Gumby', 'Mrs.Thing']
names #返回值['Mrs.Entity', 'Mrs.Thing']。names沒有被改變。
change(names[:])
names #返回值['Mrs.Entity', 'Mrs.Thing']。names沒有被改變。
1.爲什麼要修改參數
要更新列表的時候需要一項一項更新很麻煩,而且可讀性差。
抽象的要點就是隱藏更新時的煩瑣細節。
def init(data):
data['first']={}
data['middle']={}
data['last']={}
storage={}
init(storage)
storage #返回值{'first': {}, 'middle': {}, 'last': {}}
在編寫存儲名字的函數前,先寫個獲得名字的函數:
def lookup(data,label,name):
return data[label].get(name)
標籤以及名字可以作爲參數提供給lookup函數使用,這樣會獲得包含命名的列表。在名字已經存儲的情況下可以用下面的代碼獲取:
lookup(storage,'middle','lie') #返回中間名是lie的數據
返回的列表和存儲在數據結構中的列表是相同的,如果列表被修改了,也會影響數據結構。
#編寫可以查詢人名的函數
def init(data):#data作爲存儲表,初始化,data中的存儲格式爲{‘first':{},'middle':{},'last':{})
data['first']={}
data['middle']={}
data['last']={}
def lookup(data, label, name):
return data[label].get(name) #get方法根據鍵找到值
def store(data,full_name):
names=full_name.split()#split方法,加入分隔符,默認爲空格
if len(names)==2:names.insert(1,'')#如沒有中間名插入空格
labels='first','middle','last'
for label,name in zip(labels,names):#zip內建函數,支持將可迭代對象作爲參數,將對象中的元素打包成一個個元組,返回由這些元組組成的列表
people=lookup(data,label,name)
if people: #這裏一定要注意縮進!!!
people.append(full_name) #如果對應的label(first,middle,last)的name已經存在,就把全名添加進去
else:
data[label][name]=[full_name]#鍵不存在時,自動添加鍵值
MyNames={}
init(MyNames)
store(MyNames,'Magnet Lie Hetland')
lookup(MyNames,'middle','Lie') #返回值['Magnet Lie Hetland'].
#zip舉例
a = [1,2,3]
b = [4,5,6]
c = [4,5,6,7,8]
zip(a,c) #python2.x 返回[(1, 4), (2, 5), (3, 6)],新版本返回<zip object at 0x00000000035F4808>
2.如果我的參數不可變呢
Python中,函數只能修改參數對象本身,但是如果你的參數不可變(比如是數字),那就沒有辦法了。
這時就要從函數中返回所有你需要的值(值多於一個就以元組形式返回)。
#將變量數值增加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 關鍵字參數和默認值
目前爲止我們所使用的參數都叫做位置參數,位置甚至比名字更重要。本節中引入的這個功能可以迴避位置問題。
#下面的兩個函數hello_1和hello_2實際上是一樣的,雖然參數名字不一樣,決定結果是參數的順序。
def hello_1(greeting,name):
print('%s,%s!'%(greeting,name))
def hello_2(name,greeting):
print('%s,%s!'%(name,greeting))
hello_1('Hello','World')
hello_2('Hello','World') #只要這裏的參數順序一樣,顯示的結果都一樣。
可以通過提供參數的名字來避免弄混參數順序:
hello_1(name='World',greeting='Hello') #返回Hello,World!,雖然順序不一樣,顯示的順序還是對的。
這類使用參數名提供的參數叫關鍵字參數。還可以在函數中給參數提供默認值。
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!
hello_3(name='World') #返回Greeting,World!
#可以允許用戶任意組合名字,問候語和標點
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',punctuation='.') #返回hello,Mars.
hello_4('Mars',greeting='Top of the morning to ya') #返回Top of the morning to ya,Mars!
hello_4() #錯誤用法,name沒有設定默認值
6.4.4 收集參數
在參數前加星號(*)可以打印出元組,參數前的星號將所有的值收集起來使用。
def print_params(*params):
print(params)
print_params(1,2,3) #返回元組(1, 2, 3)
print_params('testing') #返回長度爲1的元組('testing',)
可以聯合普通參數
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_param_3(**params):
print(params)
print_param_3(x=1,y=2,z=3)
{'y': 2, 'z': 3, 'x': 1}
def print_param_4(x,y,z=3,*pospar,**keypar):
print(x,y,z)
print(pospar)
print(keypar)
print_param_4(1,2,3,4,5,6,7,foo=1,bar=2) #返回1 2 3,(4, 5, 6, 7),{'foo': 1, 'bar': 2}
#修改6.4.2中的例子,將store函數改爲如下格式
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,'Luke Skywalker','Anakin Skywalker')
lookup(d,'last','Skywalker') #返回值['Luke Skywalker', 'Anakin Skywalker']
6.4.5 反轉過程
收集參數的逆過程,即分配參數。是在調用的時候使用。
def add(x,y):return x+y
param=(1,2)
add(*param) #返回3.
def hello_3(greeting='Hello',name='World'):
print('%s,%s!' %(greeting,name))
params={'name':'Sir Robin','greeting':'Wellmet'}
hello_3(**params) #返回Wellmet,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)
without_stars(args) #兩個函數的返回值一致,都是Mr.Gumby is 42 years old
在with_star中,定義和調用函數時都使用了星號。星號只有在定義函數(允許使用不定數目的參數)或者調用(“分割”字典或者序列)時纔有用。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.
power(2,3) #返回8
power(3,2) #返回9
power(y=3,x=2) #返回8
params=(5,)*2 #值爲(5,5)
power(*params) #返回3125
power(3,3,'Hello,World') #返回Received redundant parameters: ('Hello,World',) 27
interval(10) #報錯'int' object has no attribute 'stop'
interval(1,5) #返回[1, 2, 3, 4]
interval(3,12,4) #返回[3, 7, 11]
power(*interval(3,7)) #返回Received redundant parameters: (5, 6) 81。interval(3,7)得到[3,4,5,6],power函數裏將3,4賦值給x,y,(5,6)給other.
6.5 作用域
變量可以看作是值的名字,變量到值就像字典一樣,但是不可見。這類“不可見字典”叫做命名空間或作用域。vars函數可以返回作用域。
x=1
scope=vars()
scope['x'] #返回值1
scope['x']+=1
x #返回2
def foo():x=42
x=1
foo()
x #返回1,x=42只在內部作用域起作用,不影響外部x的值。
def combine(parameter):print(parameter+external)
external='berry'
combine('Shrub') #返回Shrubberry
在函數內部賦予一個變量,它會自動成爲局部變量,除非告知python將其聲明爲全局變量
x=1
def change_global():
global x #聲明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
triple=multiplier(3)
triple(3) #返回9
multiplier(5)(4) #返回20
每次調用外部函數,內部的函數都被重新綁定,multiplyByFactor函數存儲子封閉作用域的行爲叫閉包。6.6 遞歸
遞歸就是隻引用自身。
有用的遞歸函數包含下面幾部分:
1.當函數直接返回值時有基本實例
2.遞歸實例,包括一個或者多個問題最小部分的遞歸調用。
6.6.1 兩個經典:階乘和冪
#階乘的遞歸版本
def factorial(n):
if n==1:
return 1
else:
return n*factorial(n-1)
#計算冪的遞歸版本
def power(x,n):
if n==0:
return 1
else:
return x*power(x,n-1)
大多數情況下,遞歸可以用循環來代替,大多數時候更高效,遞歸更易讀懂。
6.6.2 另外一個經典:二元查找
def search(sequence,number,lower=0,upper=None): #int格式不能使用None,新版本怎麼修改呢?
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)
內建函數的用法和含義可以在官方網站上查到:https://docs.python.org/2/library/functions.html#built-in-functions