第5章 函數

函數是Python編程中最重要的概念之一,它允許我們將代碼組織成可重用的模塊,提高代碼的可讀性和維護性。本章將深入講解Python函數的各個方面,從基礎的函數定義到高級的遞歸和內置函數使用。

本系列文章所使用到的示例源碼:Python從入門到精通示例代碼

5.1 函數的定義與調用

函數的概念與作用

函數是一段具有特定功能的可重用代碼塊。使用函數的主要優勢包括:

  • 代碼複用:避免重複編寫相同的代碼
  • 模塊化編程:將複雜問題分解爲簡單的子問題
  • 提高代碼可讀性:使程序結構更清晰

函數的定義語法

在Python中,使用def關鍵字定義函數:

def function_name(parameters):
    """文檔字符串(可選)"""
    # 函數體
    return value  # 返回值(可選)

讓我們看一個簡單的例子:

def greet(name):
    """問候函數,接收一個姓名參數"""
    return f"Hello, {name}!"

# 調用函數
message = greet("Alice")
print(message)

輸出結果:

Hello, Alice!

函數命名規範

函數名應該遵循以下規範:
- 使用小寫字母和下劃線
- 名稱應該描述函數的功能
- 避免使用Python關鍵字

# 好的函數名
def calculate_area(radius):
    return 3.14159 * radius ** 2

def get_user_input():
    return input("請輸入內容: ")

# 不好的函數名
def func1():  # 名稱不夠描述性
    pass

def Class():  # 應該用於類名,不是函數名
    pass

函數的文檔字符串

文檔字符串(docstring)用於描述函數的功能、參數和返回值:

def calculate_bmi(weight, height):
    """
    計算身體質量指數(BMI)

    參數:
        weight (float): 體重,單位爲千克
        height (float): 身高,單位爲米

    返回:
        float: BMI值
    """
    return weight / (height ** 2)

# 使用help()函數查看文檔
help(calculate_bmi)

輸出結果:

Help on function calculate_bmi in module __main__:

calculate_bmi(weight, height)
    計算身體質量指數(BMI)

    參數:
        weight (float): 體重,單位爲千克
        height (float): 身高,單位爲米

    返回:
        float: BMI值

5.2 參數傳遞

位置參數

位置參數是最基本的參數類型,參數的值按照定義時的順序傳遞:

def introduce_person(name, age, city):
    """介紹一個人的基本信息"""
    return f"我叫{name},今年{age}歲,來自{city}"

# 按位置傳遞參數
result = introduce_person("張三", 25, "北京")
print(result)

輸出結果:

我叫張三,今年25歲,來自北京

關鍵字參數

關鍵字參數允許通過參數名來指定值,提高代碼的可讀性:

def create_user_profile(name, age, email, city="未知"):
    """創建用戶檔案"""
    profile = {
        "姓名": name,
        "年齡": age,
        "郵箱": email,
        "城市": city
    }
    return profile

# 使用關鍵字參數
user1 = create_user_profile(name="李四", email="lisi@example.com", age=30)
print(user1)

# 混合使用位置參數和關鍵字參數
user2 = create_user_profile("王五", age=28, email="wangwu@example.com", city="上海")
print(user2)

輸出結果:

{'姓名': '李四', '年齡': 30, '郵箱': 'lisi@example.com', '城市': '未知'}
{'姓名': '王五', '年齡': 28, '郵箱': 'wangwu@example.com', '城市': '上海'}

默認參數

默認參數爲函數參數提供默認值,使函數調用更加靈活:

def power(base, exponent=2):
    """計算冪運算,默認計算平方"""
    return base ** exponent

# 使用默認參數
print(f"5的平方: {power(5)}")
print(f"2的3次方: {power(2, 3)}")
print(f"10的4次方: {power(base=10, exponent=4)}")

輸出結果:

5的平方: 25
23次方: 8
104次方: 10000

注意:默認參數的陷阱

使用可變對象作爲默認參數時要特別小心:

# 錯誤的做法
def add_item_wrong(item, target_list=[]):
    target_list.append(item)
    return target_list

# 正確的做法
def add_item_correct(item, target_list=None):
    if target_list is None:
        target_list = []
    target_list.append(item)
    return target_list

# 演示錯誤做法的問題
list1 = add_item_wrong("apple")
list2 = add_item_wrong("banana")
print(f"list1: {list1}")
print(f"list2: {list2}")

# 演示正確做法
list3 = add_item_correct("apple")
list4 = add_item_correct("banana")
print(f"list3: {list3}")
print(f"list4: {list4}")

輸出結果:

list1: ['apple', 'banana']
list2: ['apple', 'banana']
list3: ['apple']
list4: ['banana']

5.3 可變參數

*args(可變位置參數)

*args允許函數接收任意數量的位置參數:

def calculate_sum(*numbers):
    """計算任意數量數字的和"""
    total = 0
    for num in numbers:
        total += num
    return total

# 傳遞不同數量的參數
print(f"1個數字的和: {calculate_sum(5)}")
print(f"3個數字的和: {calculate_sum(1, 2, 3)}")
print(f"5個數字的和: {calculate_sum(10, 20, 30, 40, 50)}")

# 使用列表解包
numbers_list = [1, 2, 3, 4, 5]
print(f"列表解包的和: {calculate_sum(*numbers_list)}")

輸出結果:

1個數字的和: 5
3個數字的和: 6
5個數字的和: 150
列表解包的和: 15

**kwargs(可變關鍵字參數)

**kwargs允許函數接收任意數量的關鍵字參數:

def create_student_info(name, **details):
    """創建學生信息,接收任意額外的詳細信息"""
    info = {"姓名": name}
    info.update(details)
    return info

# 傳遞不同的關鍵字參數
student1 = create_student_info("張三", age=20, major="計算機科學")
student2 = create_student_info("李四", age=19, major="數學", grade="大二", gpa=3.8)

print(f"學生1信息: {student1}")
print(f"學生2信息: {student2}")

# 使用字典解包
extra_info = {"city": "北京", "hobby": "編程"}
student3 = create_student_info("王五", age=21, **extra_info)
print(f"學生3信息: {student3}")

輸出結果:

學生1信息: {'姓名': '張三', 'age': 20, 'major': '計算機科學'}
學生2信息: {'姓名': '李四', 'age': 19, 'major': '數學', 'grade': '大二', 'gpa': 3.8}
學生3信息: {'姓名': '王五', 'age': 21, 'city': '北京', 'hobby': '編程'}

參數的完整語法順序

函數參數的完整順序必須是:位置參數 → 默認參數 → args → *kwargs

def complete_function(pos_arg, default_arg="default", *args, **kwargs):
    """演示完整的參數語法"""
    print(f"位置參數: {pos_arg}")
    print(f"默認參數: {default_arg}")
    print(f"可變位置參數: {args}")
    print(f"可變關鍵字參數: {kwargs}")
    print("-" * 30)

# 各種調用方式
complete_function("必需參數")
complete_function("必需參數", "自定義默認值")
complete_function("必需參數", "自定義默認值", 1, 2, 3)
complete_function("必需參數", "自定義默認值", 1, 2, 3, key1="value1", key2="value2")

輸出結果:

位置參數: 必需參數
默認參數: default
可變位置參數: ()
可變關鍵字參數: {}
------------------------------
位置參數: 必需參數
默認參數: 自定義默認值
可變位置參數: ()
可變關鍵字參數: {}
------------------------------
位置參數: 必需參數
默認參數: 自定義默認值
可變位置參數: (1, 2, 3)
可變關鍵字參數: {}
------------------------------
位置參數: 必需參數
默認參數: 自定義默認值
可變位置參數: (1, 2, 3)
可變關鍵字參數: {'key1': 'value1', 'key2': 'value2'}
------------------------------

5.4 返回值與作用域

函數的返回值

單個返回值

def get_circle_area(radius):
    """計算圓的面積"""
    return 3.14159 * radius ** 2

area = get_circle_area(5)
print(f"半徑爲5的圓的面積: {area}")

輸出結果:

半徑爲5的圓的面積: 78.53975

多個返回值

def get_rectangle_info(length, width):
    """計算矩形的面積和周長"""
    area = length * width
    perimeter = 2 * (length + width)
    return area, perimeter  # 返回元組

# 接收多個返回值
rect_area, rect_perimeter = get_rectangle_info(10, 5)
print(f"矩形面積: {rect_area}")
print(f"矩形周長: {rect_perimeter}")

# 也可以作爲元組接收
result = get_rectangle_info(8, 6)
print(f"結果元組: {result}")
print(f"面積: {result[0]}, 周長: {result[1]}")

輸出結果:

矩形面積: 50
矩形周長: 30
結果元組: (48, 28)
面積: 48, 周長: 28

無返回值(None)

def print_user_info(name, age):
    """打印用戶信息,無返回值"""
    print(f"用戶姓名: {name}")
    print(f"用戶年齡: {age}")
    # 沒有return語句,默認返回None

result = print_user_info("張三", 25)
print(f"函數返回值: {result}")

輸出結果:

用戶姓名: 張三
用戶年齡: 25
函數返回值: None

變量的作用域

局部作用域和全局作用域

# 全局變量
global_var = "我是全局變量"

def scope_demo():
    # 局部變量
    local_var = "我是局部變量"
    print(f"函數內部訪問全局變量: {global_var}")
    print(f"函數內部訪問局部變量: {local_var}")

scope_demo()
print(f"函數外部訪問全局變量: {global_var}")
# print(local_var)  # 這會報錯,因爲局部變量在函數外部不可訪問

輸出結果:

函數內部訪問全局變量: 我是全局變量
函數內部訪問局部變量: 我是局部變量
函數外部訪問全局變量: 我是全局變量

global關鍵字

counter = 0  # 全局變量

def increment_counter():
    global counter  # 聲明使用全局變量
    counter += 1
    print(f"計數器值: {counter}")

def reset_counter():
    global counter
    counter = 0
    print("計數器已重置")

print(f"初始計數器值: {counter}")
increment_counter()
increment_counter()
increment_counter()
reset_counter()
print(f"最終計數器值: {counter}")

輸出結果:

初始計數器值: 0
計數器值: 1
計數器值: 2
計數器值: 3
計數器已重置
最終計數器值: 0

nonlocal關鍵字

def outer_function():
    x = "外層函數的變量"

    def inner_function():
        nonlocal x  # 聲明使用外層函數的變量
        x = "被內層函數修改的變量"
        print(f"內層函數中的x: {x}")

    print(f"調用內層函數前的x: {x}")
    inner_function()
    print(f"調用內層函數後的x: {x}")

outer_function()

輸出結果:

調用內層函數前的x: 外層函數的變量
內層函數中的x: 被內層函數修改的變量
調用內層函數後的x: 被內層函數修改的變量

5.5 遞歸函數

遞歸的概念

遞歸是指函數調用自身的編程技術。每個遞歸函數都必須包含:
- 基礎情況:遞歸的終止條件
- 遞歸情況:函數調用自身的情況

經典遞歸示例

階乘計算

def factorial(n):
    """計算n的階乘"""
    # 基礎情況
    if n == 0 or n == 1:
        return 1
    # 遞歸情況
    else:
        return n * factorial(n - 1)

# 測試階乘函數
for i in range(6):
    result = factorial(i)
    print(f"{i}! = {result}")

輸出結果:

0! = 1
1! = 1
2! = 2
3! = 6
4! = 24
5! = 120

斐波那契數列

def fibonacci(n):
    """計算斐波那契數列的第n項"""
    # 基礎情況
    if n <= 1:
        return n
    # 遞歸情況
    else:
        return fibonacci(n - 1) + fibonacci(n - 2)

# 生成斐波那契數列的前10項
print("斐波那契數列前10項:")
for i in range(10):
    print(f"F({i}) = {fibonacci(i)}")

輸出結果:

斐波那契數列前10:
F(0) = 0
F(1) = 1
F(2) = 1
F(3) = 2
F(4) = 3
F(5) = 5
F(6) = 8
F(7) = 13
F(8) = 21
F(9) = 34

目錄遍歷

import os

def list_files(directory, level=0):
    """遞歸遍歷目錄結構"""
    try:
        items = os.listdir(directory)
        for item in items:
            item_path = os.path.join(directory, item)
            indent = "  " * level  # 根據層級添加縮進

            if os.path.isdir(item_path):
                print(f"{indent}{item}/")
                list_files(item_path, level + 1)  # 遞歸調用
            else:
                print(f"{indent}{item}")
    except PermissionError:
        print(f"{indent}[權限不足]")

# 遍歷當前目錄(注意:這會顯示很多文件,這裏只是示例)
# list_files(".", 0)
print("目錄遍歷函數已定義,可以調用 list_files('目錄路徑') 來使用")

輸出結果:

目錄遍歷函數已定義,可以調用 list_files('目錄路徑') 來使用

遞歸與迭代的對比

# 遞歸版本的階乘
def factorial_recursive(n):
    if n <= 1:
        return 1
    return n * factorial_recursive(n - 1)

# 迭代版本的階乘
def factorial_iterative(n):
    result = 1
    for i in range(1, n + 1):
        result *= i
    return result

# 性能比較
import time

n = 10

# 測試遞歸版本
start_time = time.time()
recursive_result = factorial_recursive(n)
recursive_time = time.time() - start_time

# 測試迭代版本
start_time = time.time()
iterative_result = factorial_iterative(n)
iterative_time = time.time() - start_time

print(f"遞歸結果: {recursive_result}, 耗時: {recursive_time:.6f}秒")
print(f"迭代結果: {iterative_result}, 耗時: {iterative_time:.6f}秒")
print(f"結果相同: {recursive_result == iterative_result}")

輸出結果:

遞歸結果: 3628800, 耗時: 0.000003
迭代結果: 3628800, 耗時: 0.000002
結果相同: True

5.6 lambda表達式

lambda表達式的概念

lambda表達式是創建匿名函數的簡潔方式,適用於簡單的函數定義。

基本語法

# lambda參數: 表達式

# 簡單的lambda表達式
square = lambda x: x ** 2
print(f"5的平方: {square(5)}")

# 多個參數的lambda表達式
add = lambda x, y: x + y
print(f"3 + 7 = {add(3, 7)}")

# 帶默認參數的lambda表達式
greet = lambda name, greeting="Hello": f"{greeting}, {name}!"
print(greet("Alice"))
print(greet("Bob", "Hi"))

輸出結果:

5的平方: 25
3 + 7 = 10
Hello, Alice!
Hi, Bob!

lambda與內置函數的結合使用

與map()函數結合

# 使用lambda和map()處理列表
numbers = [1, 2, 3, 4, 5]

# 計算每個數的平方
squares = list(map(lambda x: x ** 2, numbers))
print(f"原數字: {numbers}")
print(f"平方值: {squares}")

# 將攝氏度轉換爲華氏度
celsius = [0, 20, 30, 40]
fahrenheit = list(map(lambda c: c * 9/5 + 32, celsius))
print(f"攝氏度: {celsius}")
print(f"華氏度: {fahrenheit}")

輸出結果:

原數字: [1, 2, 3, 4, 5]
平方值: [1, 4, 9, 16, 25]
攝氏度: [0, 20, 30, 40]
華氏度: [32.0, 68.0, 86.0, 104.0]

與filter()函數結合

# 使用lambda和filter()過濾列表
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

# 過濾出偶數
even_numbers = list(filter(lambda x: x % 2 == 0, numbers))
print(f"原數字: {numbers}")
print(f"偶數: {even_numbers}")

# 過濾出大於5的數
greater_than_five = list(filter(lambda x: x > 5, numbers))
print(f"大於5的數: {greater_than_five}")

# 過濾字符串列表
words = ["apple", "banana", "cherry", "date", "elderberry"]
long_words = list(filter(lambda word: len(word) > 5, words))
print(f"原單詞: {words}")
print(f"長度大於5的單詞: {long_words}")

輸出結果:

原數字: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
偶數: [2, 4, 6, 8, 10]
大於5的數: [6, 7, 8, 9, 10]
原單詞: ['apple', 'banana', 'cherry', 'date', 'elderberry']
長度大於5的單詞: ['banana', 'cherry', 'elderberry']

與sorted()函數結合

# 使用lambda進行自定義排序
students = [
    {"name": "Alice", "age": 20, "grade": 85},
    {"name": "Bob", "age": 19, "grade": 92},
    {"name": "Charlie", "age": 21, "grade": 78},
    {"name": "Diana", "age": 20, "grade": 96}
]

# 按年齡排序
sorted_by_age = sorted(students, key=lambda student: student["age"])
print("按年齡排序:")
for student in sorted_by_age:
    print(f"  {student['name']}: {student['age']}歲")

# 按成績排序(降序)
sorted_by_grade = sorted(students, key=lambda student: student["grade"], reverse=True)
print("\n按成績排序(降序):")
for student in sorted_by_grade:
    print(f"  {student['name']}: {student['grade']}分")

輸出結果:

按年齡排序:
  Bob: 19
  Alice: 20
  Diana: 20
  Charlie: 21

按成績排序(降序):
  Diana: 96
  Bob: 92
  Alice: 85
  Charlie: 78

5.7 內置函數詳解

數學函數

# 數學相關的內置函數
numbers = [-5, -2.7, 0, 3.14, 8]

print("數學函數示例:")
print(f"abs(-5) = {abs(-5)}")  # 絕對值
print(f"round(3.14159, 2) = {round(3.14159, 2)}")  # 四捨五入
print(f"pow(2, 3) = {pow(2, 3)}")  # 冪運算
print(f"min(numbers) = {min(numbers)}")  # 最小值
print(f"max(numbers) = {max(numbers)}")  # 最大值
print(f"sum(numbers) = {sum(numbers)}")  # 求和

輸出結果:

數學函數示例:
abs(-5) = 5
round(3.14159, 2) = 3.14
pow(2, 3) = 8
min(numbers) = -5
max(numbers) = 8
sum(numbers) = 3.44

類型轉換函數

# 類型轉換函數
print("類型轉換示例:")
print(f"int('123') = {int('123')}")
print(f"float('3.14') = {float('3.14')}")
print(f"str(42) = '{str(42)}'")
print(f"bool(1) = {bool(1)}")
print(f"bool(0) = {bool(0)}")

# 集合類型轉換
original_list = [1, 2, 2, 3, 3, 3]
print(f"\n原列表: {original_list}")
print(f"轉爲集合: {set(original_list)}")
print(f"轉爲元組: {tuple(original_list)}")
print(f"轉爲字典: {dict(enumerate(original_list))}")

輸出結果:

類型轉換示例:
int('123') = 123
float('3.14') = 3.14
str(42) = '42'
bool(1) = True
bool(0) = False

原列表: [1, 2, 2, 3, 3, 3]
轉爲集合: {1, 2, 3}
轉爲元組: (1, 2, 2, 3, 3, 3)
轉爲字典: {0: 1, 1: 2, 2: 2, 3: 3, 4: 3, 5: 3}

序列操作函數

# 序列操作函數
fruits = ["apple", "banana", "cherry"]
numbers = [3, 1, 4, 1, 5, 9, 2, 6]

print("序列操作示例:")
print(f"len(fruits) = {len(fruits)}")
print(f"sorted(numbers) = {sorted(numbers)}")
print(f"list(reversed(fruits)) = {list(reversed(fruits))}")

# enumerate()函數
print("\nenumerate()示例:")
for index, fruit in enumerate(fruits):
    print(f"  索引{index}: {fruit}")

# zip()函數
colors = ["red", "yellow", "dark red"]
print("\nzip()示例:")
for fruit, color in zip(fruits, colors):
    print(f"  {fruit}{color}色的")

輸出結果:

序列操作示例:
len(fruits) = 3
sorted(numbers) = [1, 1, 2, 3, 4, 5, 6, 9]
list(reversed(fruits)) = ['cherry', 'banana', 'apple']

enumerate()示例:
  索引0: apple
  索引1: banana
  索引2: cherry

zip()示例:
  apple是red色的
  banana是yellow色的
  cherry是dark red色的

高階函數詳解

reduce()函數

from functools import reduce

# reduce()函數示例
numbers = [1, 2, 3, 4, 5]

# 計算所有數字的乘積
product = reduce(lambda x, y: x * y, numbers)
print(f"數字列表: {numbers}")
print(f"所有數字的乘積: {product}")

# 找出最大值
max_value = reduce(lambda x, y: x if x > y else y, numbers)
print(f"最大值: {max_value}")

# 字符串連接
words = ["Hello", "World", "Python", "Programming"]
sentence = reduce(lambda x, y: x + " " + y, words)
print(f"單詞列表: {words}")
print(f"連接後的句子: {sentence}")

輸出結果:

數字列表: [1, 2, 3, 4, 5]
所有數字的乘積: 120
最大值: 5
單詞列表: ['Hello', 'World', 'Python', 'Programming']
連接後的句子: Hello World Python Programming

其他常用內置函數

# any()和all()函數
list1 = [True, True, True]
list2 = [True, False, True]
list3 = [False, False, False]

print("any()和all()函數示例:")
print(f"all({list1}) = {all(list1)}")
print(f"all({list2}) = {all(list2)}")
print(f"any({list2}) = {any(list2)}")
print(f"any({list3}) = {any(list3)}")

# isinstance()函數
print("\nisinstance()函數示例:")
value = 42
print(f"isinstance({value}, int) = {isinstance(value, int)}")
print(f"isinstance({value}, str) = {isinstance(value, str)}")
print(f"isinstance({value}, (int, float)) = {isinstance(value, (int, float))}")

# hasattr()函數
class Person:
    def __init__(self, name):
        self.name = name

    def greet(self):
        return f"Hello, I'm {self.name}"

person = Person("Alice")
print("\nhasattr()函數示例:")
print(f"hasattr(person, 'name') = {hasattr(person, 'name')}")
print(f"hasattr(person, 'greet') = {hasattr(person, 'greet')}")
print(f"hasattr(person, 'age') = {hasattr(person, 'age')}")

輸出結果:

any()和all()函數示例:
all([True, True, True]) = True
all([True, False, True]) = False
any([True, False, True]) = True
any([False, False, False]) = False

isinstance()函數示例:
isinstance(42, int) = True
isinstance(42, str) = False
isinstance(42, (int, float)) = True

hasattr()函數示例:
hasattr(person, 'name') = True
hasattr(person, 'greet') = True
hasattr(person, 'age') = False

本章小結

本章詳細介紹了Python函數的各個方面:

  1. 函數定義與調用:學習瞭如何定義函數、編寫文檔字符串以及調用函數
  2. 參數傳遞:掌握了位置參數、關鍵字參數、默認參數的使用方法
  3. 可變參數:瞭解了args和*kwargs的用法,以及參數解包操作
  4. 返回值與作用域:學習了函數返回值的處理和變量作用域的概念
  5. 遞歸函數:掌握了遞歸的基本原理和經典應用
  6. lambda表達式:學會了使用lambda創建匿名函數
  7. 內置函數:詳細瞭解了Python提供的各種內置函數

函數是Python編程的核心概念,掌握好函數的使用對於編寫高質量的Python代碼至關重要。在實際編程中,要根據具體需求選擇合適的函數定義方式和參數傳遞方法,同時注意作用域的管理和遞歸的合理使用。

小夜