[Toc]
# 第8章 函數
博士出差終于回來了!在今天的課程里,墨博士給小墨講了函數的定義和使用方法。學習Python以來,小墨每次都能聽到函數這個詞,今天終于弄清楚函數到底是什么以及函數如何定義和使用了。一起來看看吧。
時間:早上9:00
場景:墨馨書屋
墨博士:小墨,我聽墨哥哥說你最近學習了很多Python的內容。
小墨:墨哥哥教我的,主要是list和dict這兩種數據類型的使用。
墨博士:嗯,這是比較常用的兩種數據類型。
小墨:我們今天學習什么呢博士?
墨博士:前面呢你已經接觸了很多函數,但這些函數都是Python自帶的,我們只管用就行了。今天呢我們就來看看自己如何來定義和使用函數。
我們開始吧!
## 8.1 為什么要有函數
墨博士:還是來做一個練習:
練習:
輸出3遍Hello
輸出5遍World
輸出4遍Hello
輸出5遍World
輸出3遍Hello
輸出5遍World
這個怎么做?
小墨:如果我是第一天學Python,我可能會這樣:
```
print('Hello')
print('Hello')
print('Hello')
print('World')
print('World')
print('World')
print('World')
print('World')
print('Hello')
print('Hello')
print('Hello')
print('Hello')
print('World')
print('World')
print('World')
print('World')
print('World')
print('Hello')
print('Hello')
print('Hello')
print('World')
print('World')
print('World')
```
不過現在,我會利用循環,就像這樣:
```
for x in range(3):
print('Hello')
for x in range(5):
print('World')
for x in range(4):
print('Hello')
for x in range(5):
print('World')
for x in range(3):
print('Hello')
for x in range(3):
print('World')
```
墨博士:做的不錯。對于重復的事情,就考慮用循環來解決。
> 墨博士提醒:為方便描述,以上的循環稱為循環1、循環2、循環3、循環4和循環5。
但是這段代碼還是顯得很臃腫,你看第1和5個循環是一樣的、第2和第4個循環是一樣的,其他兩個循環跟它們也差不多。
下面呢我們就給它瘦瘦身。
小墨內心:博士你什么時候也瘦瘦身呢?
# [插畫:胖博士變成了苗條的瘦博士]
墨博士:假如說我們用一個變量a替換循環1,變量b替換循環2,也即假設:


那么,程序會變成這樣:
```
a = for x in range(3):
print('Hello')
b = for x in range(5):
print('World')
a
b
for x in range(4):
print('Hello')
b
a
for x in range(3):
print('World')
```
**注意**:這個只是偽代碼,并不符合語法要求,不能獨立運行。
小墨,你覺得這樣會不會更清晰一點?
小墨:嗯,就像定義變量一樣,一個地方定義,多個地方使用,這樣就節省了大量重復的代碼。
墨博士:同時如果想更改這個功能,只需要在定義的地方更改,其他調用的地方就跟著一塊改過了,非常方便。
小墨:這個就是函數吧。
墨博士:是的。不過這個只是思路,接下來我們具體說說函數的定義。
## 8.2 無參函數的定義和使用
墨博士:函數,又叫方法,表示一個功能,在python中,函數的定義使用def關鍵字,看如下代碼:
```
# 函數的定義
def a():
for x in range(3):
print('Hello')
```
> 墨博士提醒:關鍵字是編程語言里事先定義的,有特別意義的標識符,有時又叫保留字,Python中的if、for、while、def等都是關鍵字。
這樣我們就定義了一個函數,這里的a即表示函數的名字,這個名字是我們自己起的,起名規則就跟變量名一樣。第3、4兩行稱為函數的函數體,函數定義之后,我們就可以把函數名代替函數體去使用了,比如:
```
# 函數的定義
def a():
for x in range(3):
print('Hello')
# 函數的使用
a()
a()
a()
```
就會輸出9遍Hello。也即使用定義好的函數時,直接使用函數名后加括號就可以了,這里使用了三次a(),相當于使用了三次for循環,也即輸出9次Hello。
好,接下來輪到你了小墨,你把輸出5遍World也改成函數吧。
小墨:好的。更改代碼如下:
```
def a():
for x in range(3):
print('Hello')
# 新增定義函數b,用于輸出5遍World
def b():
for x in range(5):
print('World')
a()
b() # 調用函數b()
for x in range(4):
print('Hello')
b() # 調用函數b()
a()
for x in range(3):
print('World')
```
墨博士:嗯,不錯。這個程序比較短,你的函數體只有一個for循環組成,如果一個程序中某個功能使用了很多次,而這個功能本身又是由很多行代碼組成,就更能體現使用函數的好處了。
小墨:我明白了,定義函數之后寫代碼就好像搭積木了,一個函數就是一個積木,積木內部的東西不需要管,搭積木的人只需要一個個的按需去搭建就可以了。最厲害的是這些積木是可以重用的!
墨博士:總結的很好!下面我們繼續前進,把輸出4遍Hello和3遍Word的for循環也給改造了。
## 8.3 有參函數的定義和使用
墨博士:上面我們已經定義并使用了函數來替換重復功能,我們發現,循環3和循環1(也就是函數a的函數體)的區別,僅僅是輸出Hello次數的區別,如果3也能用a表示,那就更省事了,這就需要我們了解一下參數的概念。
### 8.3.1 形式參數
墨博士:對于函數a來說,現在的功能是輸出3次Hello,如果我想a能輸出4次、5次、甚至任意次數的Hello,應該怎么做呢?
我們可以使用變量n替換掉3:
```
# 函數的定義
def a():
for x in range(n):
print('Hello')
```
小墨:不對吧博士,這樣的話,變量n沒有定義就使用了,不符合語法要求。
墨博士:是的,想要n能使用,需要先定義,這個定義在a后面的()里,如下:
```
# 函數的定義
def a(n):
for x in range(n):
print('Hello')
```
這樣,一個帶參數的方法就定義好了。
這里的n就被稱為函數的參數,嚴格來說是形式參數。
> 墨博士提醒:參與到具體功能中的未知的東西,在方法定義的時候定義在方法名后面的小括號中的參數,稱為形式參數,簡稱形參。
小墨:我也給我的函數加上參數。咦?加了參數之后方法調用出錯了。
墨博士:這就需要了解跟形式參數對應的實際參數了。
### 8.3.2 實際參數
墨博士:當a的定義改變了之后,a的調用方式也要隨著改變。由于a后面的小括號中有了一個n,我們在調用a的時候就需要具體指定這個n是多少,比如:
```
a(3)
```
即將3的值給n,程序會輸出3次Hello,而如果這個值為5,則輸出5次Hello,以此類推。
這里的3、5等具體值,是函數調用的時候給形式參數賦的具體的值,稱為實際參數,簡稱實參。
小墨:哦原來是這樣,形參是方法上定義的變量,實參是給形參變量賦的值。那么原程序就可以改成這樣了:
```
def a(n):
for x in range(n):
print('Hello')
def b(n):
for x in range(n):
print('World')
a(3)
b(5)
a(4)
b(5)
a(3)
b(3)
```
墨博士:很好。你再想想,這個程序還能不能再改進改進呢?
小墨:嗯……,現在的程序中,其實a函數和b函數功能差不多,一個是打印Hello,一個是打印World,是不是也可以做成變量替換,然后在調用的時候分別賦值為Hello或World?
墨博士:是的,這就是函數多個參數的情況。
### 8.3.3 多個參數
墨博士:可以定義一個函數,該函數含有兩個參數n和s,n表示打印多少次,s表示打印的內容,這樣a和b就能合并成一個了。
```
# 兩個參數函數的定義
def a(n, s):
for x in range(n):
print(s)
```
既然函數定義時指明了2個形式參數,調用的時候也需要給兩個形式參數提供對應的實際參數:
```
a(3, 'Hello')
a(5, 'World')
```
這里的3和5賦值給了n,Hello和World賦值給了s,運行程序輸出了3遍Hello和5遍World。
小墨:哇!我們最初的練習也可以再次改進了:
```
def a(n, s):
for x in range(n):
print(s)
a(3, 'Hello')
a(5, 'World')
a(4, 'Hello')
a(5, 'World')
a(3, 'Hello')
a(3, 'World')
```
墨博士:比起最初的5個for循環,是不是簡潔清晰了很多?
> 動動手:定義一個函數,能夠計算x的n次方,其中x和n都不確定。
小墨:嗯,參數的作用還真是強大呀!
墨博士:事實上,Python中的參數遠比這強大的多,比如我們還可以使用默認參數。
### 8.3.4 默認參數
墨博士:以上面打印n遍字符串的例子為例,如果大部分時候都是在打印Hello,只有極少數情況才打印World或其他字符,則可以使用默認參數:
```
def print_str(n, s='Hello'):
for x in range(n):
print(s)
# 打印3遍Hello
print_str(3)
# 打印4遍Hello
print_str(4)
# 打印5遍World
print_str(5, 'World')
```
注意:這里為了程序的可讀性,將方法名a變成了print_str。
這些基本就能滿足你現在的大多數需求了,其他如可變長參數、關鍵字參數等以后再教給你 。
小墨:好的博士,墨哥哥也說過,貪多嚼不爛。
> 動動手:將計算x的n次方的程序改為n默認值為2
## 8.4 有返回值的函數的定義和使用
墨博士:我們再來看一下函數的返回值的用法。比如現需要定義一個函數,計算x的絕對值并輸出。程序如下:
```
def my_abs(x):
if x >= 0:
print(x)
else:
print(-x)
```
這里只是單純的將數值使用print輸出,如果我想通過my_abs函數計算x的絕對值后,還能得到這個絕對值做其他事情,比如乘以100,應該如何做呢?
這就需要使用return語句了。
### 8.4.1 return
墨博士:return語句,用在函數中,表示將函數的計算結果“返回”,此時可以拿一個變量接收函數的返回值。比如:
```
def my_abs(x):
if x >= 0:
return x
else:
return -x
# 接收函數的返回值
n = my_abs(-4)
# 做后續其他處理
print(n * 100)
```
小墨:哦這樣我們就能拿到函數的執行結果了。但是我想到一個問題,如果一個函數中的兩個數我都想要拿出來,該怎么辦?
墨博士:你忘了list是干嘛的了?你可以把兩個數放到一個list中,然后將這個list返回,在外面再從list中把兩個數取出來就行了。
事實上,在Python中,直接返回多個值也是可以的,看下面代碼:
```
def my_return():
return 1, 2
a = my_return()
print(a)
```
輸出結果是(1,2)
小墨:這個也是把1和2作為一個整體拿出來了呀,那我如何才能分別拿到1和2呢?
墨博士:你看輸出的1和2外面有個小括號,這個呢說明是Python中的tuple類型。
tuple,中文稱為元組,相當于一種特殊的list。它的特殊之處在于初始化之后就不能改變了。
```
# list使用[]初始化元素,tuple使用()初始化元素。
names = ('墨小小', '墨妹妹', '墨大元')
for name in names:
print(name)
for i in range(len(names)):
print(names[i])
```
由于元素內容不能修改,tuple并沒有像list一樣提供append()、insert()、pop()等用于添加、插入或刪除的方法,當然也不能通過下標取元素的方式修改。
小墨:好的,我記住了。
> 墨博士提醒:Python中可以使用type函數看任意變量的類型,如下:
```
print(type(a))
```
輸出:
```
<class 'tuple'>
```
> 這就說明a是tuple類型。
### 8.4.2 return注意事項
墨博士:在使用return的時候需要注意一些事項。比如:
```
def a():
for i in range(4):
if i == 2:
return i
print(i)
x = a()
```
結果會輸出什么?
小墨:我猜是輸出0、1、2和3。
墨博士:實際運行,只會輸出0和1。
小墨:為什么會這樣呢?是return之后for循環就結束了嗎?
墨博士:函數體內部的語句在執行時,一旦執行到“return”,就返回return后面的結果,同時函數也就執行完畢,此時如果“return”后面還有其他內容,也不再執行。
小墨:哦也就是整個方法就結束了。
墨博士:是的。有時候這一點也被用于單純的結束函數。比如剛才的代碼,如果我們并不想要函數中返回的i,只是想在i為2的時候結束方法。可以return后面什么都不加。
```
def a():
for i in range(4):
if i == 2:
return
print(i)
```
小墨:這樣也可以?
墨博士:是的,算是方法的一個小技巧吧。好了,函數的內容就先說這么多啦。
## 8.5 本章小結
墨博士總結:本章你學習了函數的使用,這也是編程中最重要的知識之一。函數用于完成一定的功能,定義好了之后能反復使用;為了能夠統一處理類似的情況,函數中引入了變量,也就是參數,省去了定義多個類似函數的麻煩;函數中的形參和實參要對應;函數可以將內部的計算結果返回,用于后續的計算;函數的引入,也使得我們編程的思路發生了改變:先把大問題(功能)劃分成多個小問題(函數),一個一個的小功能解決了,大問題就像搭積木一樣搭起來也就解決了!