從零開始學Python:第八課-函式和模組

千鋒Python唐小強發表於2020-06-30

在講解本章節的內容之前,我們先來研究一道數學題,請說出下面的方程有多少組正整數解。

從零開始學Python:第八課-函式和模組

你可能已經想到了,這個問題其實等同於將8個蘋果分成四組且每組至少一個蘋果有多少種方案,因此該問題還可以進一步等價於在分隔8個蘋果的7個空隙之間插入三個隔板將蘋果分成四組有多少種方案,也就是從7個空隙選出3個空隙放入隔板的組合數,所以答案是

從零開始學Python:第八課-函式和模組

組合數的計算公式如下所示:

從零開始學Python:第八課-函式和模組

根據我們前面學習的知識,可以用迴圈做累乘來計算階乘,那麼透過下面的Python程式碼我們就可以計算出組合數

從零開始學Python:第八課-函式和模組

的值,程式碼如下所示:


"""

輸入M和N計算C(M,N)

Version: 0.1
Author: 駱昊
"""
m = int(input( 'm = '))
n = int(input( 'n = '))
# 計算m的階乘
fm = 1
for num in range(1, m + 1):
   fm *= num
# 計算n的階乘
fn = 1
for num in range(1, n + 1):
   fn *= num
# 計算m-n的階乘
fm_n = 1
for num in range(1, m - n + 1):
   fm_n *= num
# 計算C(M,N)的值
print(fm // fn // fm_n)

函式的作用

不知大家是否注意到,上面的程式碼中我們做了三次求階乘,雖然m、n、m - n的值各不相同,但是三段程式碼並沒有實質性的區別,屬於重複程式碼。世界級的程式設計大師Martin Fowler先生曾經說過:“ 程式碼有很多種壞味道,重複是最壞的一種!”。要寫出高質量的程式碼首先要解決的就是重複程式碼的問題。對於上面的程式碼來說,我們可以將計算階乘的功能封裝到一個稱為“函式”的程式碼塊中,在需要計算階乘的地方,我們只需要“呼叫函式”就可以了。

定義函式

從零開始學Python:第八課-函式和模組

在Python中可以使用def關鍵字來定義函式,和變數一樣每個函式也應該有一個漂亮的名字,命名規則跟變數的命名規則是一致的。在函式名後面的圓括號中可以放置傳遞給函式的引數,就是我們剛才說到的函式的自變數,而函式執行完成後我們會透過return關鍵字來返回函式的執行結果,就是我們剛才說的函式的因變數。一個函式要執行的程式碼塊(要做的事情)也是透過縮排的方式來表示的,跟之前分支和迴圈結構的程式碼塊是一樣的。大家不要忘了def那一行的最後面還有一個:,之前提醒過大家,那是在英文輸入法狀態下輸入的冒號。

我們可以透過函式對上面的程式碼進行重構。 所謂重構,是在不影響程式碼執行結果的前提下對程式碼的結構進行調整。重構之後的程式碼如下所示。


""
"

輸入M和N計算C(M,N)

Version: 0.1
Author: 駱昊
" ""


# 定義函式:def是定義函式的關鍵字、fac是函式名,num是引數(自變數)
def fac(num):
    "" "求階乘" ""
   result = 1
    for n in range( 1, num + 1):
       result *= n
   # 返回num的階乘(因變數)
    return result


m = int(input( 'm = '))
n = int(input( 'n = '))
# 當需要計算階乘的時候不用再寫重複程式碼而是直接呼叫函式fac
# 呼叫函式的語法是在函式名後面跟上圓括號並傳入引數
print(fac(m) // fac(n) // fac(m - n))

函式的引數

引數的預設值

如果函式中沒有return語句,那麼函式預設返回代表空值的None。另外,在定義函式時,函式也可以沒有自變數,但是函式名後面的圓括號是必須有的。Python中還允許函式的引數擁有預設值,我們可以把上一課“CRAPSdubo遊戲”的搖篩子獲得點數的功能封裝成函式,程式碼如下所示。


"""

引數的預設值1

Version: 0.1
Author: 駱昊
"""
from random import randint


# 定義搖篩子的函式,n表示篩子的個數,預設值為2
def roll_dice(n=2):
   """搖篩子返回總的點數"""
   total = 0
   for _ in range(n):
       total += randint(1, 6)
   return total


# 如果沒有指定引數,那麼n使用預設值2,表示搖兩顆篩子
print(roll_dice())
# 傳入引數3,變數n被賦值為3,表示搖三顆篩子獲得點數
print(roll_dice(3))

我們再來看一個更為簡單的例子。


"""

引數的預設值2

Version: 0.1
Author: 駱昊
"""


def add (a= 0, b= 0, c= 0):
    """三個數相加求和"""
    return a + b + c


# 呼叫add函式,沒有傳入引數,那麼a、b、c都使用預設值0
print(add())         # 0
# 呼叫add函式,傳入一個引數,那麼該引數賦值給變數a, 變數b和c使用預設值0
print(add(1))        # 1
# 呼叫add函式,傳入兩個引數,1和2分別賦值給變數a和b,變數c使用預設值0
print(add(1, 2))     # 3
# 呼叫add函式,傳入三個引數,分別賦值給a、b、c三個變數
print(add(1, 2, 3))  # 6
# 傳遞引數時可以不按照設定的順序進行傳遞,但是要用“引數名=引數值”的形式
print(add(c=50, a=100, b=200))    # 350

注意:帶預設值的引數必須放在不帶預設值的引數之後,否則將產生SyntaxError錯誤,錯誤訊息是:non-default argument follows default argument,翻譯成中文的意思是“沒有預設值的引數放在了帶預設值的引數後面”。

可變引數

接下來,我們還可以實現一個對任意多個數求和的add函式,因為Python語言中的函式可以透過星號表示式語法來支援可變引數。所謂可變引數指的是在呼叫函式時,可以向函式傳入0個或任意多個引數。將來我們以團隊協作的方式開發商業專案時,很有可能要設計函式給其他人使用,但有的時候我們並不知道函式的呼叫者會向該函式傳入多少個引數,這個時候可變引數就可以派上用場。下面的程式碼演示了用可變引數實現對任意多個數求和的add函式。


"""

可變引數

Version: 0.1
Author: 駱昊
"""


# 用星號表示式來表示args可以接收0個或任意多個引數
def add(*args):
   total = 0
   # 可變引數可以放在for迴圈中取出每個引數的值
   for val in args:
       total += val
   return total


# 在呼叫add函式時可以傳入0個或任意多個引數
print(add())
print(add(1))
print(add(1, 2))
print(add(1, 2, 3))
print(add(1, 3, 5, 7, 9))
從零開始學Python:第八課-函式和模組

用模組管理函式

不管用什麼樣的程式語言來寫程式碼,給變數、函式起名字都是一個讓人頭疼的問題,因為我們會遇到 命名衝突這種尷尬的情況。最簡單的場景就是在同一個.py檔案中定義了兩個同名的函式,如下所示。



def 
foo
():

   print( 'hello, world!')


def foo ():
   print( 'goodbye, world!')


foo()     # 大家猜猜呼叫foo函式會輸出什麼

當然上面的這種情況我們很容易就能避免,但是如果專案是團隊協作多人開發的時候,團隊中可能有多個程式設計師都定義了名為foo的函式,這種情況下怎麼解決命名衝突呢?答案其實很簡單,Python中每個檔案就代表了一個模組(module),我們在不同的模組中可以有同名的函式,在使用函式的時候我們透過import關鍵字匯入指定的模組再使用 完全限定名的呼叫方式就可以區分到底要使用的是哪個模組中的foo函式,程式碼如下所示。

module1.py



def 
foo
():

   print( 'hello, world!')

module2.py



def 
foo
():

   print( 'goodbye, world!')

test.py


import module1

import module2

# 用“模組名.函式名”的方式(完全限定名)呼叫函式,
module1.foo()    # hello, world!
module2.foo()    # goodbye, world!

在匯入模組時,還可以使用as關鍵字對模組進行別名,這樣我們可以使用更為簡短的完全限定名。

test.py


import module1 
as m1

import module2 as m2

m1.foo()     # hello, world!
m2.foo()    # goodbye, world!

上面的程式碼我們匯入了定義函式的模組,我們也可以使用from...import...語法從模組中直接匯入需要使用的函式,程式碼如下所示。

test.py


from module1 
import foo


foo()    # hello, world!

from module2 import foo

foo()    # goodbye, world!

但是,如果我們如果從兩個不同的模組中匯入了同名的函式,後匯入的函式會覆蓋掉先前的匯入,就像下面的程式碼中,呼叫foo會輸出hello, world!,因為我們先匯入了module2的foo,後匯入了module1的foo 。如果兩個from...import...反過來寫,就是另外一番光景了。

test.py


from module2 
import foo

from module1 import foo

foo()     # hello, world!

如果想在上面的程式碼中同時使用來自兩個模組中的foo函式也是有辦法的,大家可能已經猜到了,還是用as關鍵字對匯入的函式進行別名,程式碼如下所示。

test.py


from module1 
import foo 
as f1

from module2 import foo as f2

f1()     # hello, world!
f2()    # goodbye, world!

標準庫中的模組和函式

Python標準庫中提供了大量的模組和函式來簡化我們的開發工作,我們之前用過的random模組就為我們提供了生成隨機數和進行隨機抽樣的函式;而time模組則提供了和時間操作相關的函式;上面求階乘的函式在Python標準庫中的math模組中已經有了,實際開發中並不需要我們自己編寫,而math模組中還包括了計算正弦、餘弦、指數、對數等一系列的數學函式。隨著我們進一步的學習Python程式設計知識,我們還會用到更多的模組和函式。

Python標準庫中還有一類函式是不需要import就能夠直接使用的,我們將其稱之為內建函式,這些內建函式都是很有用也是最常用的,下面的表格列出了一部分的內建函式。

從零開始學Python:第八課-函式和模組

簡單的總結

函式是功能相對獨立且會重複使用的程式碼的封裝。學會使用定義和使用函式,就能夠寫出更為優質的程式碼。當然,Python語言的標準庫中已經為我們提供了大量的模組和常用的函式,用好這些模組和函式就能夠用更少的程式碼做更多的事情。

Python 900集全套影片教程(全家桶)

你們要的Python影片教程,我也整理好了,嫌看字累,影片給安排上,寵不寵粉就說吧!

學習的可以留言或者私我:“Python” 獲取!


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69923331/viewspace-2701433/,如需轉載,請註明出處,否則將追究法律責任。

相關文章