Python學習筆記-第六章 抽象


本章會介紹關於抽象的常見知識及函數的特殊知識。

第六章 抽象

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



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章