1. 程式人生 > >python基礎重點講解——函式基礎(十三)

python基礎重點講解——函式基礎(十三)

# 函式基礎

## 目標

* 函式的快速體驗
* 函式的基本使用
* 函式的引數
* 函式的返回值
* 函式的巢狀呼叫
* 在模組中定義函式

## 01. 函式的快速體驗

### 1.1 快速體驗

* 所謂**函式**,就是把 **具有獨立功能的程式碼塊** 組織為一個小模組,在需要的時候 **呼叫**
* 函式的使用包含兩個步驟:
    1. 定義函式 —— **封裝** 獨立的功能
    2. 呼叫函式 —— 享受 **封裝** 的成果
* **函式的作用**,在開發程式時,使用函式可以提高編寫的效率以及程式碼的 **重用**

**演練步驟**

1. 新建 `04_函式` 專案
2. 複製之前完成的 **乘法表** 檔案
3. 修改檔案,增加函式定義 `multiple_table():`
4. 新建另外一個檔案,使用 `import` 匯入並且呼叫函式

## 02. 函式基本使用

### 2.1 函式的定義

定義函式的格式如下:

```python
def 函式名():

    函式封裝的程式碼
    ……
```

1. `def` 是英文 `define` 的縮寫
2. **函式名稱** 應該能夠表達 **函式封裝程式碼** 的功能,方便後續的呼叫
3. **函式名稱** 的命名應該 **符合** **識別符號的命名規則**
    * 可以由 **字母**、**下劃線** 和 **數字** 組成
    * **不能以數字開頭**
    * **不能與關鍵字重名**

### 2.2 函式呼叫

呼叫函式很簡單的,通過 `函式名()` 即可完成對函式的呼叫

### 2.3 第一個函式演練

**需求**

* 1. 編寫一個打招呼 `say_hello` 的函式,封裝三行打招呼的程式碼
* 2. 在函式下方呼叫打招呼的程式碼

```python
name = "小明"


# 直譯器知道這裡定義了一個函式
def say_hello():
    print("hello 1")
    print("hello 2")
    print("hello 3")

print(name)
# 只有在呼叫函式時,之前定義的函式才會被執行
# 函式執行完成之後,會重新回到之前的程式中,繼續執行後續的程式碼
say_hello()

print(name)

```

> 用 **單步執行 F8 和 F7** 觀察以下程式碼的執行過程

* 定義好函式之後,只表示這個函式封裝了一段程式碼而已
* 如果不主動呼叫函式,函式是不會主動執行的

#### 思考

* 能否將 **函式呼叫** 放在 **函式定義** 的上方?

    * 不能!
    * 因為在 **使用函式名** 呼叫函式之前,必須要保證 `Python` 已經知道函式的存在
    * 否則控制檯會提示 `NameError: name 'say_hello' is not defined` (**名稱錯誤:say_hello 這個名字沒有被定義**)

### 2.4 PyCharm 的除錯工具

* **F8 Step Over** 可以單步執行程式碼,會把函式呼叫看作是一行程式碼直接執行
* **F7 Step Into** 可以單步執行程式碼,如果是函式,會進入函式內部 

### 2.5 函式的文件註釋

* 在開發中,如果希望給函式添加註釋,應該在 **定義函式** 的下方,使用 **連續的三對引號**
* 在 **連續的三對引號** 之間編寫對函式的說明文字
* 在 **函式呼叫** 位置,使用快捷鍵 `CTRL + Q` 可以檢視函式的說明資訊

> 注意:因為 **函式體相對比較獨立**,**函式定義的上方**,應該和其他程式碼(包括註釋)保留 **兩個空行**

## 03. 函式的引數

**演練需求**

1. 開發一個 `sum_2_num` 的函式
2. 函式能夠實現 **兩個數字的求和** 功能

演練程式碼如下:

```python
def sum_2_num():

    num1 = 10
    num2 = 20
    result = num1 + num2

    print("%d + %d = %d" % (num1, num2, result))

sum_2_num()

```

**思考一下存在什麼問題**

> 函式只能處理 **固定數值** 的相加

**如何解決?**

* 如果能夠把需要計算的數字,在呼叫函式時,傳遞到函式內部就好了!

### 3.1 函式引數的使用

* 在函式名的後面的小括號內部填寫 **引數**
* 多個引數之間使用 `,` 分隔

```python
def sum_2_num(num1, num2):

    result = num1 + num2
    
    print("%d + %d = %d" % (num1, num2, result))

sum_2_num(50, 20)

```

### 3.2 引數的作用

* **函式**,把 **具有獨立功能的程式碼塊** 組織為一個小模組,在需要的時候 **呼叫**
* **函式的引數**,增加函式的 **通用性**,針對 **相同的資料處理邏輯**,能夠 **適應更多的資料**
    1. 在函式 **內部**,把引數當做 **變數** 使用,進行需要的資料處理
    2. 函式呼叫時,按照函式定義的**引數順序**,把 **希望在函式內部處理的資料**,**通過引數** 傳遞

### 3.3 形參和實參

* **形參**:**定義** 函式時,小括號中的引數,是用來接收引數用的,在函式內部 **作為變數使用**
* **實參**:**呼叫** 函式時,小括號中的引數,是用來把資料傳遞到 **函式內部** 用的

## 04. 函式的返回值

* 在程式開發中,有時候,會希望 **一個函式執行結束後,告訴呼叫者一個結果**,以便呼叫者針對具體的結果做後續的處理
* **返回值** 是函式 **完成工作**後,**最後** 給呼叫者的 **一個結果**
* 在函式中使用 `return` 關鍵字可以返回結果
* 呼叫函式一方,可以 **使用變數** 來 **接收** 函式的返回結果

> 注意:`return` 表示返回,後續的程式碼都不會被執行

```python
def sum_2_num(num1, num2):
    """對兩個數字的求和"""

    return num1 + num2

# 呼叫函式,並使用 result 變數接收計算結果
result = sum_2_num(10, 20)

print("計算結果是 %d" % result)

```

## 05. 函式的巢狀呼叫

* 一個函式裡面 **又呼叫** 了 **另外一個函式**,這就是 **函式巢狀呼叫**
* 如果函式 `test2` 中,呼叫了另外一個函式 `test1`
    * 那麼執行到呼叫 `test1` 函式時,會先把函式 `test1` 中的任務都執行完
    * 才會回到 `test2` 中呼叫函式 `test1` 的位置,繼續執行後續的程式碼

```python
def test1():

    print("*" * 50)
    print("test 1")
    print("*" * 50)


def test2():

    print("-" * 50)
    print("test 2")
    
    test1()
    
    print("-" * 50)

test2()

```

### 函式巢狀的演練 —— 列印分隔線

> 體會一下工作中 **需求是多變** 的

**需求 1**

* 定義一個 `print_line` 函式能夠列印 `*` 組成的 **一條分隔線**

```python
def print_line(char):

    print("*" * 50)

```

**需求 2**

* 定義一個函式能夠列印 **由任意字元組成** 的分隔線

```python
def print_line(char):

    print(char * 50)
    
```

**需求 3**

* 定義一個函式能夠列印 **任意重複次數** 的分隔線

```python
def print_line(char, times):

    print(char * times)

```

**需求 4**

* 定義一個函式能夠列印 **5 行** 的分隔線,分隔線要求符合**需求 3**

> 提示:工作中針對需求的變化,應該冷靜思考,**不要輕易修改之前已經完成的,能夠正常執行的函式**!

```python
def print_line(char, times):

    print(char * times)


def print_lines(char, times):

    row = 0
    
    while row < 5:
        print_line(char, times)

        row += 1

```

## 06. 使用模組中的函式

> **模組是 Python 程式架構的一個核心概念**

* **模組** 就好比是 **工具包**,要想使用這個工具包中的工具,就需要 **匯入 import** 這個模組
* 每一個以副檔名 `py` 結尾的 `Python` 原始碼檔案都是一個 **模組**
* 在模組中定義的 **全域性變數** 、 **函式** 都是模組能夠提供給外界直接使用的工具

### 6.1 第一個模組體驗

**步驟**

* 新建 `hm_10_分隔線模組.py`
    * 複製 `hm_09_列印多條分隔線.py` 中的內容,**最後一行 `print` 程式碼除外**
    * 增加一個字串變數

```python
name = "黑馬程式設計師"
```

* 新建 `hm_10_體驗模組.py` 檔案,並且編寫以下程式碼:

```python
import hm_10_分隔線模組

hm_10_分隔線模組.print_line("-", 80)
print(hm_10_分隔線模組.name)
```

#### 體驗小結

* 可以 **在一個 Python 檔案** 中 **定義 變數 或者 函式**
* 然後在 **另外一個檔案中** 使用 `import` 匯入這個模組
* 匯入之後,就可以使用 `模組名.變數` / `模組名.函式` 的方式,使用這個模組中定義的變數或者函式

> **模組**可以讓 **曾經編寫過的程式碼** 方便的被 **複用**!

### 6.2 模組名也是一個識別符號

* 標示符可以由 **字母**、**下劃線** 和 **數字** 組成
* **不能以數字開頭**
* **不能與關鍵字重名**

> 注意:如果在給 Python 檔案起名時,**以數字開頭** 是無法在 `PyCharm` 中通過匯入這個模組的

### 6.3 Pyc 檔案(瞭解)

> `C` 是 `compiled` **編譯過** 的意思

**操作步驟**

1. 瀏覽程式目錄會發現一個 `__pycache__` 的目錄
2. 目錄下會有一個 `hm_10_分隔線模組.cpython-35.pyc` 檔案,`cpython-35` 表示 `Python` 直譯器的版本
3. 這個 `pyc` 檔案是由 Python 直譯器將 **模組的原始碼** 轉換為 **位元組碼**
    * `Python` 這樣儲存 **位元組碼** 是作為一種啟動 **速度的優化**

**位元組碼**

* `Python` 在解釋源程式時是分成兩個步驟的
    1. 首先處理原始碼,**編譯** 生成一個二進位制 **位元組碼**
    2. 再對 **位元組碼** 進行處理,才會生成 CPU 能夠識別的 **機器碼**

* 有了模組的位元組碼檔案之後,下一次執行程式時,如果在 **上次儲存位元組碼之後** 沒有修改過原始碼,Python 將會載入 .pyc 檔案並跳過編譯這個步驟
* 當 `Python` 重編譯時,它會自動檢查原始檔和位元組碼檔案的時間戳
* 如果你又修改了原始碼,下次程式執行時,位元組碼將自動重新建立

> 提示:有關模組以及模組的其他匯入方式,後續課程還會逐漸展開!
>
> **模組是 Python 程式架構的一個核心概念**