(內容包括函式、遞回、Lambda、作用域等)
1. 函式
1.1 函式概述
函式是對程式邏輯進行結構化和程序化的一種編程方法,用于封裝一個特定的功能,表示一個功能或者行為,函式是可以重復執行的陳述句塊,是為了滿足高重用和低冗余的最基本的程式結構,一次撰寫可以多次呼叫,【高重用、低冗余】
可以簡單將函式分為內置函式和自定義函式,內置函式是python或已經匯入模塊中已經寫好了的功能,自定義函式是將一段有規律、可重復使用的代碼定義出的一個功能,是程式中一個可管理的部件,【流程分解】
1.2 常用內置函式
python中集成了許多方便的功能,以函式的形式供給使用,官方檔案
1.3 自定義函式
自定義函式,也就是自己創建一個函式,帶有某些用途,可以使用的一個代碼塊,自定義函式使用起來和內置函式一樣:通過運算式進行呼叫,輸入一些引數,得到一個結果,
定義函式遵循以下規則:
- 函式有0個輸入、有1個或多個輸出,
- 使用def關鍵字定義函式;用return或yield進行回傳,無回傳默認return None;函式體中實作功能,
- 具體形式是:def 加自定義函式名定義函式的名字,名字自己取,做到望文生義;之后加小括號,括號中先加普通引數,再加默認引數,最后加可變引數;在引數串列后加冒號,下一行縮進的就是函式主體,即每次呼叫時運行的部分;函式主體中需要有一個或多個回傳值(如果沒有默認回傳None),

1.3.1 自定義函式的定義
自定義函式利用關鍵字def(即簡寫的define)來進行定義,def后要寫一個函式名,函式名要能做到望文生義,符合取名規則(字母開頭,只含字母下劃線和數字,非關鍵字),函式名后是你這個函式所需要的引數(普通引數、默認引數、變參),接著就是冒號和下面縮進書寫的函式體了,函式體中需要帶有回傳值(沒有回傳值默認return None),呼叫時一旦return,意味著呼叫結束,再說說回傳值,回傳值可以是任意型別,但只能回傳一個物件,
#輸入引數日月年,回傳和今天差幾天
import time
def isLeapYear(y):
if y%4==0 and y%100!=0 or y%400==0 :
return True
else :
return False
def daysGone(m,flag):
listGone1 = (365,0,31,59,90,120,151,181,212,243,273,304,334) #平年
listGone2 = (366,0,31,60,91,121,152,182,213,244,274,305,335) #閏年
if flag :
return listGone2[m]
else :
return listGone2[m]
def date_apart(d1,m1,y1):
lastest = [int(time.strftime("%d")),int(time.strftime("%m")),int(time.strftime("%Y"))]
daysofMonth = {1:31,2:28,3:31,4:30,5:31,6:30,7:31,8:31,9:30,10:31,11:30,12:31}
leap = total = 0
for i in range(y1+1,lastest[2],1):
total += 365
if i%4==0 and i%100!=0 or i%400==0 :
leap += 1
total += leap
total += daysGone(lastest[1],isLeapYear(lastest[2])) + lastest[0]
total += 365 + isLeapYear(y1) - daysGone(m1,isLeapYear(y1)) - d1
if y1==lastest[2] : total -= 365
return total
print(date_apart(17,8,1926))
從上面的函式里,可以看出:
①函式可以作為另一個函式的引數
②回傳值的型別可以多樣,但運行到return就會停下
③函式體中可以呼叫其他函式
1.3.2 關于函式引數
函式的引數可以分為實參(普通引數、默認引數)和變參(位置引數、命名引數),定義時:實參必須在變參的前面,建議默認引數放在普通引數后面,得到的引數可以出現或不出現在函式體中(就是說,引數你要來了,用不用隨你自己),呼叫時:輸入引數的時候,可以根據順序一個一個輸,也可以以【引數名=賦的值】的形式,不按順序輸,默認引數不賦值就按默認值輸入,變參就是把不定個數的引數傳入函式,
對于可變引數,可以用任意名字前加一個和兩個*號,寫在引數串列的最后,先單*后雙*的順序,常用【*args,**kargs】和【*vars,**kvars】這兩組,*符號表示需要接收的多出來的list和tuple,用**符號表示需要接收的map和dictionary,換而言之,①引數串列中的形如a=b形式(鍵值對)的命名引數,就傳入**kargs;②引數串列中形如a,b,c這種形式(元組)的位置引數,就傳入*args,
如果實參不夠,會向位置引數(單個*號的變參)借引數;等位置引數借沒了,再問命名引數(兩個*號的變參)借引數(鍵值對),但是鍵必須等于實參的名字,才會把值傳進實參,另一方面,如果實參的位置多了引數,鍵值對就會填進命名引數,成為字典,其他的會按順序填進位置引數,成為元組, 另外,在引數串列中的變參,位置引數必須在命名引數前被賦值,
在呼叫函式時,即使定義的都是實參,也可以用*和**號,*號的作用是展開多個元素的型別,區別:*號只顯示鍵keys(),**號顯示鍵值對items(),
關于默認引數:
默認引數一旦生成,用戶不去手動改變,默認引數就不會變,比如def add(a,b,c=[]), 那一旦第一次呼叫時對c傳入非空串列[1],則c就會被賦值為[1],之后再呼叫的時候就默認是c=[1]而不是[], 同樣的,如果默認引數傳入亂數,第一次會隨機,后面就一直取第一次的隨機值,因此,引數中需要隨機值,一定要用實參去傳入,不能用默認引數,
def tt(a,b,z,*c,**d):
print(a)
print(b)
print(z)
print(c)
print(d)
tt(*range(1,15,3)) #實參不夠,位置引數加入實參
print("---------------------------")
tt(1,*range(2,3),**{"z":20,"bbb":23}) #實參和位置引數不夠,命名引數加入
print("---------------------------")
tt(1,3,5,7,9,11,13,15,f=10,x=16,k="ppp") #實參位置多了,放進變參

1.3.3 函式體
函式定義中,縮進的部分就是函式體,函式體是主要負責實作函式功能并回傳值的地方,函式體以冒號起始,且縮進,第一行如果用檔案字串(三個冒號),就會被認作是在寫函式的說明檔案,在函式體中可以寫幾乎所有陳述句,甚至再定義一個函式,如果只是寫一個函式名,函式體還沒有決定,可以用pass關鍵字先做占位, 函式體中需含有回傳值,用return或yield關鍵字回傳,如果沒有回傳值,或只寫了return沒跟變數,默認回傳空值None,函式體中(包括回傳值)可以呼叫其他函式或自己,
1.3.4 回傳值
為什么有了print陳述句直接輸出,還要return呢?這是因為,函式只幫我們解決了一個問題,而不是這整件事,舉上面的例子,我想知道是不是閏年,輸出是不是沒有用,但是回傳一個True,卻可以作為后續函式的實參輸入,
用return和yield兩個關鍵詞可以將需要的變數進行回傳,回傳值在呼叫函式的時候,作為函式運行后的輸出,
同樣可以回傳值,return和yield兩個關鍵詞的區別是什么呢?return一次全部輸出,輸出后結束;yield配合next或send一起使用,每次輸出一步,下次呼叫再輸出下一步,簡單的說,return從一而終,yield阻塞在yield這一句,用next呼叫從這句yield開始,到yield再阻塞,
從上圖的結果可以看出,return直接在第一次回圈中輸出了第一次回圈值,就結束了;而next+yield則第一次運行到yield就不再運行,被下一個next激活后,從上一次yield開始運行,回圈后到下一個yield又不運行了,這就是yield,
1.4 函式的呼叫
在自定義函式定義完以后,可以進行呼叫,只有呼叫的時候,才會檢查自定義函式寫的是不是正確,呼叫的時候,你將自定義函式需要的引數寫到函式名后面的引數串列中,可以用位置引數和命名引數的方法,需要注意個數,位置引數需要注意位置,另外,對于幫助檔案,用help(NAME)即可(不加引數名), 函式的結果就是函式運行后的回傳值,
1.5 遞回 Recursion
在函式定義中,也可以在函式體中呼叫其他函式,簡單地說,如果被呼叫的是自己或者幾個函式相互呼叫,就叫做遞回,嚴格地說, 適合用遞回的是:【能把問題分解成為規模更小的、具有與原問題有著相同解法的問題,】 遞回成功有一些先決條件:①隨著遞回深度的加深(次數變多),問題應該越來越簡單;②遞回中應該有一個判斷(遞回出口),在問題最簡單的時候回傳不再需要呼叫的回傳值;③遞回的次數不是無限制的,不做設定遞回1000次左右就會堆疊溢位,【在一定次數中,深度增加規模減小,最后會結束】
遞回的特點:寫起來簡單,容易讓人理解,沒有復雜嵌套,容易堆疊溢位,多次呼叫費時效率低,
資料、資料結構、問題描述是遞回形式的,應該想起遞回,
遞回的兩種思路:①執行下一次用到上一次的結果(遞推);終止時需要上一層的條件(回溯),
1.6 Lambda運算式
Lambda運算式是用來簡化編程者作業量的一種匿名函式,特點是:允許你快速定義一個單行的最小函式,它的唯一語法形式是:
在式中,冒號前的變數就是函式中的引數串列,冒號后就是函式中的回傳值的運算式,Lambda運算式看起來和用起來簡單,更pythonic,但內部邏輯和定義一個函式是一樣的,且不支持多分支語言和例外處理程式,用法上,它的輸入就是引數串列,輸出就是回傳值的結果,它可以配合filter、sorted、map、reduce等函式一同使用,
1.7 幫助檔案
在使用一個函式的時候,就算起名已經望文生義了,但是你還是需要一些更多的支持,比如說幫助檔案或說明書,在自定義函式中,只要利用檔案字串,就可以方便的進行幫助檔案的書寫,
具體來說,想寫一個函式的幫助檔案,你需要在定義函式后,函式體的第一行利用字串(一行用普通引號,多行用三個引號)進行輸入,整個檔案字串都會被視作幫助檔案,呼叫時,用help(NAME)函式進行查看,只需要寫函式名,不要寫引數串列,

1.8 作用域
先說說賦值,賦值是將你取的變數名和這個物件建立連接,也就是說,賦值是名字指向新的物件,而不是通過名字直接改變物件,
如果在自己的函式中和腳本主函式中的變數重名了,怎么辦呢? 如果僅僅是參考外部變數,那么按LEGB順序在不同作用域查找該名字,
順序:locals??enclosing function??globals??__builtins__,(即:內部嵌套函式??包含內部嵌套函式的函式自身??全域作用域??內置作用域),作用域的優先級從高到低,作用域的范圍由小到大, 其中:
- locals:函式內部名字空間,包括區域變數和形參
- enclosing function: 外部嵌套函式的名字空間
- globals: 函式定義所在模塊的名字空間
- __builtins__: 內置模塊的名字空間
內置作用域是預先定義好的,在__builtins__模塊中,這些名稱主要是一些關鍵字,例如open、range、quit等;全域作用域是檔案/模塊級別的,每個.py檔案中處于頂層的變數都是全域作用域范圍內的變數;本地作用域是函式內部屬于本函式的作用范圍,因為函式可以嵌套函式,嵌套的內層函式有自身的內層范圍;嵌套函式的本地作用域是屬于內層函式的范圍,不屬于外層,
當一條命令需要用到某個物件時,他會按照LEGB順序,先找本層函式中有沒有,

從上面圖中可以看出,在1,4兩次print中,列印的是全域變數x,在呼叫了k以后,列印的是k中的函式內部名字空間,而m無法被腳本讀取, 解釋一下,引數串列和函式中定義的變數叫做區域變數,只能在這個區域呼叫,而且不能改變更大一級的變數的值,更大一級也只能呼叫自己同級定義的函式,不能呼叫在函式中定義的函式,
既然引數串列中傳進引數的改變不能使全域變數改變,那這樣是不是說,函式只能通過回傳值改變全域變數呢? 并不是,引入global關鍵字,就可以從函式中改變全域變數的值,如圖:

1.9 函式設計理念
函式應力求獨立于外部,輸入盡量用引數,輸出用return ; 只有在真正需要的時候,才去用全域變數;函式的目標應該單一、統一; 每個函式應該相對地小;盡量不去改變其他模塊檔案中的變數,
1.10 函式型別檢查
Python3.5以后,加入了函式型別檢查功能,在定義函式的引數串列所列舉的引數后加入冒號和型別規定引數串列中形參的輸入型別,在引數串列后用->符號規定回傳值型別,
def sum(a:int,b:[int])->float:
for i in b:
c += i
return a*1.0+c
在上面的代碼定義中,要求了引數a的型別必須是int型, 引數b的型別必須是list型別,且b中的每個元素必須是整數,回傳值的型別必須是浮點型小數, 如果呼叫函式時,輸入的實參和形參型別不相同,在IDE中就會進行警告,但是不影響函式的正常運行,
1.11方法命名
在Pyhton中,不僅可以對屬性進行命名,如a=b對于方法也可以進行命名,如c=math.sqrt,這樣,c就和math.sqrt等效,print(c(4))會輸出2.0,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/235881.html
標籤:Python
