Python & AI Tutorials Logo
Python 编程

19. 定义与调用函数

19.1) 什么是函数以及为什么它们很重要

函数(function) 是一段有名字的代码块,用于执行特定任务。你在本书中已经一直在使用函数——print()input()len()type() 等等。这些是 Python 提供的 内置函数(built-in functions)。现在你将学习创建自己的 自定义函数(custom functions),用来组织代码并让它可复用。

为什么函数很重要

函数是编写清晰、可维护程序的基础。它们提供了几个关键优势:

1. 代码可复用性

如果没有函数,每当你想执行某个任务时,都需要复制并粘贴相同的代码。考虑在多个地方计算矩形面积:

python
# 没有函数——重复代码
length1 = 5
width1 = 3
area1 = length1 * width1
print(f"Area 1: {area1}")
 
length2 = 8
width2 = 4
area2 = length2 * width2
print(f"Area 2: {area2}")
 
length3 = 10
width3 = 6
area3 = length3 * width3
print(f"Area 3: {area3}")

Output:

Area 1: 15
Area 2: 32
Area 3: 60

这种重复既繁琐又容易出错。如果你需要更改面积的计算方式(也许要包含单位或进行舍入),你就必须更新每一个位置。函数通过让你把代码写一次并多次使用来解决这个问题。

2. 代码组织

函数把大型程序拆分为更小、更易管理的部分。每个函数处理一个具体任务,使你的代码更容易理解和维护。与其写一个包含数百行的长脚本,不如把相关操作组织到一些有名字的函数中,用清晰的名称表达它们的用途。

3. 抽象(abstraction)

函数把实现细节隐藏在一个简单接口后面。当你调用 len(my_list) 时,你不需要知道 Python 如何统计元素个数——你只需要得到结果。类似地,你的函数也可以为复杂操作提供简单接口,让代码更易用、更易理解。

4. 测试与调试

函数让测试程序的各个部分更容易。你可以先在隔离环境中验证每个函数都能正确工作,然后再把它们组合成更大的程序。当出现问题时,函数帮助你缩小问题发生的位置。

在本章接下来的内容中,你将学习如何定义自己的函数,向它们传递信息,获取返回结果,并清晰地为它们编写文档。这些技能对于编写专业的 Python 代码至关重要。

19.2) 使用 def 定义函数

要在 Python 中创建函数,你需要使用 def 关键字(“define”的缩写)。函数定义的基本结构如下所示:

python
def function_name():
    # 调用函数时运行的代码块
    statement1
    statement2
    # ... 更多语句

让我们分解每一部分:

  • def:告诉 Python 你正在定义函数的关键字
  • function_name:你为函数选择的名称(遵循与变量名相同的规则)
  • ():括号,最终会在里面放参数(我们将在下一节讲解)
  • ::冒号,标记函数头的结束
  • 缩进的代码块:构成函数体的语句(必须缩进)

你的第一个函数

下面是一个打印问候语的简单函数:

python
def greet():
    print("Hello!")
    print("Welcome to Python functions.")
 
# Call the function
greet()

Output:

Hello!
Welcome to Python functions.

当你定义一个函数时,Python 会记住它,但不会立即执行其中的代码。只有当你通过写下函数名并加上括号来 调用(call) 该函数时,代码才会运行:greet()

函数命名约定

函数名遵循与变量名相同的规则(正如我们在第 3 章学到的):

  • 使用小写字母
  • 用下划线分隔单词(snake_case)
  • 以字母或下划线开头,而不是数字
  • 使用能表明函数作用的描述性名称
python
# 好的函数名
def calculate_total():
    pass
 
def get_user_age():
    pass
 
def display_menu():
    pass
 
# 差的函数名(但语法上有效)
def x():  # 不具描述性
    pass
 
def CalculateTotal():  # 应该使用小写
    pass
 
def calc():  # 过于缩写
    pass

注意:这里我们使用 pass 作为占位符(正如我们在第 8 章学到的)。它什么也不做,但允许函数定义在语法上完整。

函数可以包含任何代码

函数体可以包含你目前学过的任何 Python 语句:变量赋值、条件判断、循环(loop),甚至调用其他函数。

python
def check_temperature():
    temperature = 72
    if temperature > 75:
        print("It's warm.")
    elif temperature > 60:
        print("It's comfortable.")
    else:
        print("It's cool.")
 
check_temperature()

Output:

It's comfortable.

多个函数定义

你可以在程序中按需定义任意数量的函数。每个函数都是独立的,并且可以分别调用:

python
def morning_greeting():
    print("Good morning!")
 
def evening_greeting():
    print("Good evening!")
 
# Call each function
morning_greeting()
evening_greeting()

Output:

Good morning!
Good evening!

函数定义顺序

在 Python 中,你必须先定义函数,再调用它。Python 解释器从上到下读取代码,所以如果你尝试在定义之前调用函数,就会得到错误:

python
# WARNING: This will cause a NameError - for demonstration only
# PROBLEM: Function called before it's defined
say_hello()  # NameError: name 'say_hello' is not defined
 
def say_hello():
    print("Hello!")

正确顺序是先定义,再调用:

python
# 正确:先定义
def say_hello():
    print("Hello!")
 
# 然后调用
say_hello()

Output:

Hello!

不过,只要这些调用发生在所有定义之后,函数也可以调用文件中稍后才定义的其他函数:

python
def first_function():
    print("First function")
    second_function()  # 这样没问题——在运行时调用
 
def second_function():
    print("Second function")
 
# 在我们调用第一个函数之前,这两个函数都已定义
first_function()

Output:

First function
Second function

函数会创建局部作用域

在函数内部创建的变量只在该函数内部存在。这称为 局部作用域(local scope)(我们会在第 21 章详细探讨)。现在先理解:函数内部发生的事情只会留在函数内部:

python
def create_message():
    message = "This is local"
    print(message)
 
create_message()
 
# This would cause an error:
# print(message)  # NameError: name 'message' is not defined

Output:

This is local

变量 message 只在函数运行期间存在。一旦函数结束,变量就会消失。

使用 pass 的空函数

有时你想先定义函数结构,之后再实现它。可以使用 pass 作为占位符:

python
def future_feature():
    pass  # TODO: 稍后实现这里
 
# 该函数存在且可以被调用,但什么也不做
future_feature()  # Runs without error, does nothing

在你先勾勒程序结构、再补充细节时,这很有用。

19.3) 调用函数并传递实参

定义函数会创建一段可复用的代码,但要让函数真正强大,你需要向它们传递信息。这些信息通过 实参(arguments) 传入。

参数 vs 实参

在继续之前,我们先澄清两个经常被混淆的术语:

  • 参数(parameter):函数定义中的变量名,用来接收一个值
  • 实参(argument):调用函数时传入的实际值
python
def greet(name):  # 'name' is a parameter
    print(f"Hello, {name}!")
 
greet("Alice")  # "Alice" is an argument

Output:

Hello, Alice!

可以把参数看作占位符,把实参看作填入这些占位符的实际数据。

定义带参数的函数

要定义一个接收输入的函数,在括号内添加参数名即可:

python
def greet_person(name):
    print(f"Hello, {name}!")
    print("Nice to meet you.")
 
# 使用不同的实参调用
greet_person("Alice")
print()  # 为了可读性的空行
greet_person("Bob")

Output:

Hello, Alice!
Nice to meet you.
 
Hello, Bob!
Nice to meet you.

参数 name 在函数内部充当变量。每次调用函数时,name 都会取得你提供的实参值。

多个参数

函数可以接收多个参数,用逗号分隔:

python
def calculate_rectangle_area(length, width):
    area = length * width
    print(f"A rectangle with length {length} and width {width}")
    print(f"has an area of {area} square units.")
 
calculate_rectangle_area(5, 3)
print()
calculate_rectangle_area(10, 7)

Output:

A rectangle with length 5 and width 3
has an area of 15 square units.
 
A rectangle with length 10 and width 7
has an area of 70 square units.

调用带多个参数的函数时,顺序很重要。第一个实参会传给第一个参数,第二个实参传给第二个参数,以此类推。这些称为 位置实参(positional arguments)

位置实参

使用位置实参时,Python 会根据位置把实参与参数匹配起来:

python
def describe_pet(animal_type, pet_name):
    print(f"I have a {animal_type}.")
    print(f"My {animal_type}'s name is {pet_name}.")
 
describe_pet("dog", "Buddy")
print()
describe_pet("cat", "Whiskers")

Output:

I have a dog.
My dog's name is Buddy.
 
I have a cat.
My cat's name is Whiskers.

如果你把顺序弄混,就会得到意外结果:

python
def describe_pet(animal_type, pet_name):
    print(f"I have a {animal_type}.")
    print(f"My {animal_type}'s name is {pet_name}.")
 
# Arguments in wrong order
describe_pet("Buddy", "dog")

Output:

I have a Buddy.
My Buddy's name is dog.

这在技术上是有效的 Python,但会产生毫无意义的输出,因为实参位于错误的位置。

关键字实参

为了避免与位置相关的错误,你可以在调用函数时通过显式指定参数名来使用 关键字实参(keyword arguments)

python
def describe_pet(animal_type, pet_name):
    print(f"I have a {animal_type}.")
    print(f"My {animal_type}'s name is {pet_name}.")
 
# Using keyword arguments - order doesn't matter
describe_pet(animal_type="dog", pet_name="Buddy")
print()
describe_pet(pet_name="Whiskers", animal_type="cat")

Output:

I have a dog.
My dog's name is Buddy.
 
I have a cat.
My cat's name is Whiskers.

使用关键字实参时,顺序并不重要,因为 Python 会按名称而不是位置将实参与参数匹配。

混用位置实参与关键字实参

你可以在一次函数调用中混用位置实参与关键字实参,但位置实参必须在前:

python
def create_profile(username, email, age):
    print(f"Username: {username}")
    print(f"Email: {email}")
    print(f"Age: {age}")
 
# Mixing positional and keyword arguments
create_profile("alice123", email="alice@example.com", age=25)

Output:

Username: alice123
Email: alice@example.com
Age: 25

不过,你不能把位置实参放在关键字实参之后:

python
# WARNING: This will cause a SyntaxError - for demonstration only
# PROBLEM: Positional argument after keyword argument
# create_profile(username="alice123", "alice@example.com", 25)
# SyntaxError: positional argument follows keyword argument

实参数量必须匹配

调用函数时,你必须提供正确数量的实参(除非函数有默认值,我们会在第 20 章讲解):

python
def add_numbers(a, b):
    result = a + b
    print(f"{a} + {b} = {result}")
 
add_numbers(5, 3)  # 正确:2 个参数对应 2 个参数形参

Output:

5 + 3 = 8

提供太少或太多实参会导致错误:

python
# WARNING: These will cause TypeErrors - for demonstration only
 
# PROBLEM: Too few arguments
# add_numbers(5)
# TypeError: add_numbers() missing 1 required positional argument: 'b'
 
# PROBLEM: Too many arguments
# add_numbers(5, 3, 2)
# TypeError: add_numbers() takes 2 positional arguments but 3 were given

使用表达式作为实参

实参不一定是简单的值——你可以使用任何表达式:

python
def display_total(price, quantity):
    total = price * quantity
    print(f"Total cost: ${total:.2f}")
 
# 使用表达式作为实参
base_price = 10
display_total(base_price * 1.1, 5)  # 加价 10% 后的价格
display_total(15 + 5, 3 * 2)        # 两个实参都是表达式

Output:

Total cost: $55.00
Total cost: $120.00

Python 会先计算每个表达式,然后再把得到的值传给函数。

在函数内部调用函数

函数可以调用其他函数,从而形成一个操作层级结构。这是一种强大的技术,能让你把复杂任务拆分为更小、更易管理的部分。

下面是一个计算房间面积的示例:

python
def calculate_area(length, width):
    return length * width
 
def display_room_info(room_name, length, width):
    area = calculate_area(length, width)
    print(f"Room: {room_name}")
    print(f"Dimensions: {length} x {width}")
    print(f"Area: {area} square feet")
 
display_room_info("Living Room", 15, 12)

Output:

Room: Living Room
Dimensions: 15 x 12
Area: 180 square feet

注意:这里我们使用了 return,我们将在下一节详细探讨。现在只要理解:calculate_area() 会把结果发送回调用它的函数即可。

下面是另一个示例,展示函数如何在彼此基础上构建——一个温度转换系统:

python
def celsius_to_fahrenheit(celsius):
    return (celsius * 9/5) + 32
 
def format_temperature(fahrenheit):
    return f"{fahrenheit:.1f}°F"
 
def display_temperature_conversion(celsius):
    fahrenheit = celsius_to_fahrenheit(celsius)
    formatted = format_temperature(fahrenheit)
    print(f"{celsius}°C equals {formatted}")
 
# Use the complete conversion system
display_temperature_conversion(25)
display_temperature_conversion(0)
display_temperature_conversion(100)

Output:

25°C equals 77.0°F
0°C equals 32.0°F
100°C equals 212.0°F

在这个例子中,display_temperature_conversion() 调用 celsius_to_fahrenheit() 来完成转换,然后调用 format_temperature() 来格式化结果。每个函数都有一个单一、清晰的职责,让代码易于理解和维护。

19.4) 使用 return 返回结果

到目前为止,我们的函数执行的是动作(例如打印),但它们没有把值返回给调用它们的代码。return 语句让函数计算一个结果,并把它发送回调用者。

基本的 return 语句

下面是一个计算并返回值的简单函数:

python
def add_numbers(a, b):
    result = a + b
    return result
 
# 捕获返回值
sum_value = add_numbers(5, 3)
print(f"The sum is: {sum_value}")

Output:

The sum is: 8

当 Python 遇到 return 语句时,会发生两件事:

  1. 函数立即停止执行(return 之后的任何代码都会被忽略)
  2. 指定的值被发送回调用者

直接返回值

你不需要在返回之前把结果存到变量里。你可以直接返回一个表达式:

python
def multiply(a, b):
    return a * b
 
result = multiply(4, 7)
print(f"4 × 7 = {result}")

Output:

4 × 7 = 28

这更简洁,也是简单计算时的首选风格。

使用返回值

一旦函数返回一个值,你就可以在任何需要使用值的地方使用它:

python
def calculate_discount(price, discount_percent):
    discount_amount = price * (discount_percent / 100)
    return discount_amount
 
original_price = 100
discount = calculate_discount(original_price, 20)
 
# 在计算中使用返回值
final_price = original_price - discount
print(f"Original price: ${original_price:.2f}")
print(f"Discount: ${discount:.2f}")
print(f"Final price: ${final_price:.2f}")

Output:

Original price: $100.00
Discount: $20.00
Final price: $80.00

return 会立即退出函数

当 Python 执行 return 语句时,函数会立刻停止。任何在 return 之后的代码都不会被执行:

python
def check_age(age):
    if age < 18:
        return "Minor"
    # 这一行只会在 age >= 18 时运行
    return "Adult"
 
print(check_age(15))
print(check_age(25))

Output:

Minor
Adult

这种行为对于处理函数中的不同情况很有用。一旦你确定了结果,就可以立即返回,而不需要再检查其他条件。

下面是一个示例,演示 return 如何停止执行:

python
def process_number(n):
    if n < 0:
        return "Negative"
    print("This line runs for non-negative numbers")
    if n == 0:
        return "Zero"
    print("This line runs for positive numbers")
    return "Positive"
 
print(process_number(-5))
print()
print(process_number(0))
print()
print(process_number(10))

Output:

Negative
 
This line runs for non-negative numbers
Zero
 
This line runs for non-negative numbers
This line runs for positive numbers
Positive

没有 return 的函数

如果一个函数没有 return 语句,或者它只有一个不带值的 return,那么函数会返回 None

python
def greet(name):
    print(f"Hello, {name}!")
    # 没有 return 语句
 
result = greet("Alice")
print(f"The function returned: {result}")

Output:

Hello, Alice!
The function returned: None

类似地,一个单独的 return(不带值)也会返回 None

python
def process_data(data):
    if not data:
        return  # 提前退出,返回 None
    print(f"Processing: {data}")
    return "Success"
 
result1 = process_data("")
result2 = process_data("some data")
 
print(f"Result 1: {result1}")
print(f"Result 2: {result2}")

Output:

Processing: some data
Result 1: None
Result 2: Success

返回多个值

Python 函数可以通过用逗号分隔来返回多个值。Python 会自动将它们打包成一个元组(tuple)(正如我们在第 15 章学到的):

python
def calculate_rectangle(length, width):
    area = length * width
    perimeter = 2 * (length + width)
    return area, perimeter
 
# 解包返回的元组
rect_area, rect_perimeter = calculate_rectangle(5, 3)
print(f"Area: {rect_area}")
print(f"Perimeter: {rect_perimeter}")

Output:

Area: 15
Perimeter: 16

你也可以将该元组作为单个值来接收:

python
def get_student_info():
    name = "Alice"
    age = 20
    grade = "A"
    return name, age, grade
 
# 作为元组捕获
student = get_student_info()
print(f"Student info: {student}")
print(f"Name: {student[0]}")

Output:

Student info: ('Alice', 20, 'A')
Name: Alice

返回不同类型

函数可以根据情况返回不同类型的值:

python
def divide(a, b):
    if b == 0:
        return "Error: Division by zero"
    return a / b
 
result1 = divide(10, 2)
result2 = divide(10, 0)
 
print(f"10 / 2 = {result1}")
print(f"10 / 0 = {result2}")

Output:

10 / 2 = 5.0
10 / 0 = Error: Division by zero

虽然这样可行,但通常更好的做法是用不同方式处理错误(我们会在第 VII 部分学习异常)。现在只要理解:函数可以返回不同类型,不过保持一致通常更清晰。

19.5) 使用文档字符串为函数编写文档

随着程序规模增长、你创建更多函数,为每个函数记录它做什么就变得至关重要。Python 提供了使用 文档字符串(docstrings)(documentation strings)来记录函数的内置方式。

什么是文档字符串?

文档字符串(docstring) 是一个字符串字面量,它作为函数(或模块、类、方法)中的第一条语句出现。它描述函数做什么、接受哪些参数、以及返回什么。文档字符串用三引号括起来("""'''),因此可以跨多行。

python
def calculate_area(length, width):
    """Calculate the area of a rectangle.
    
    Takes the length and width of a rectangle and returns the area.
    """
    return length * width

为什么文档字符串很重要

文档字符串有几个重要用途:

  1. 自文档化(Self-Documentation):它们解释函数做什么,而不需要读者去分析代码
  2. IDE 支持:许多开发工具会在你使用函数时以工具提示显示文档字符串
  3. help() 函数:Python 内置的 help() 函数会显示文档字符串
  4. 专业实践:有良好文档的代码更容易维护,也更容易与他人分享

基本文档字符串格式

对于简单函数,一行文档字符串就足够了:

python
def greet(name):
    """Print a personalized greeting."""
    print(f"Hello, {name}!")
 
# Access the docstring
print(greet.__doc__)

Output:

Print a personalized greeting.

文档字符串应该是对函数作用的简洁描述,用命令式写法(“Calculate...”“Return...”“Print...”),而不是描述式写法(“This function calculates...”)。

多行文档字符串

对于更复杂的函数,使用包含以下内容的多行文档字符串:

  • 第一行的简要摘要
  • 一个空行
  • 更详细的描述
  • 关于参数的信息
  • 关于返回值的信息
python
def calculate_discount(price, discount_percent):
    """Calculate the discounted price.
    
    Takes an original price and a discount percentage, then returns
    the amount of discount that should be applied.
    
    Parameters:
    price (float): The original price before discount
    discount_percent (float): The discount percentage (0-100)
    
    Returns:
    float: The discount amount in the same currency as the price
    """
    return price * (discount_percent / 100)
 
# Use help() to see the full docstring
help(calculate_discount)

Output:

Help on function calculate_discount in module __main__:
 
calculate_discount(price, discount_percent)
    Calculate the discounted price.
    
    Takes an original price and a discount percentage, then returns
    the amount of discount that should be applied.
    
    Parameters:
    price (float): The original price before discount
    discount_percent (float): The discount percentage (0-100)
    
    Returns:
    float: The discount amount in the same currency as the price

文档字符串约定

Python 针对如何编写文档字符串有既定约定(记录在 PEP 257 中)。以下是关键指南:

1. 使用三重双引号"""docstring"""

python
def good_example():
    """This follows the convention."""
    pass
 
def also_valid():
    '''This works but is less common.'''
    pass

2. 一行文档字符串应该放在一行内

python
def add(a, b):
    """Return the sum of a and b."""
    return a + b

3. 多行文档字符串应先有摘要行,然后是一个空行

python
def process_order(order_id, items):
    """Process a customer order and update inventory.
    
    This function validates the order, checks inventory availability,
    calculates the total cost, and updates the inventory database.
    
    Parameters:
    order_id (str): Unique identifier for the order
    items (list): List of item dictionaries with 'product' and 'quantity'
    
    Returns:
    dict: Order summary with 'total', 'status', and 'confirmation_number'
    """
    # 函数实现写在这里
    pass

描述参数与返回值

在记录参数与返回值时,要具体说明:

  • 参数名:与函数中的实际参数名一致
  • 类型:期望的数据类型是什么(我们会在第 43 章学习类型提示)
  • 用途:该参数用于做什么
  • 返回值:函数返回什么,以及在什么条件下返回
python
def find_student(student_id, students):
    """Find a student by ID in a list of student records.
    
    Parameters:
    student_id (int): The unique ID number of the student to find
    students (list): List of student dictionaries, each containing 'id' and 'name'
    
    Returns:
    dict: The student dictionary if found, None if not found
    """
    for student in students:
        if student['id'] == student_id:
            return student
    return None

对具有多种返回类型的函数编写文档字符串

当一个函数根据情况可能返回不同类型时,要记录所有可能性:

python
def safe_divide(a, b):
    """Divide two numbers with error handling.
    
    Parameters:
    a (float): The dividend
    b (float): The divisor
    
    Returns:
    float: The quotient if division is successful
    str: An error message if b is zero
    """
    if b == 0:
        return "Error: Cannot divide by zero"
    return a / b

访问文档字符串

你可以通过三种方式访问函数的文档字符串:

1. 使用 __doc__ 属性

python
def example():
    """This is an example function."""
    pass
 
print(example.__doc__)

Output:

This is an example function.

2. 使用 help() 函数

python
def calculate_bmi(weight, height):
    """Calculate Body Mass Index.
    
    Parameters:
    weight (float): Weight in kilograms
    height (float): Height in meters
    
    Returns:
    float: BMI value
    """
    return weight / (height ** 2)
 
help(calculate_bmi)

Output:

Help on function calculate_bmi in module __main__:
 
calculate_bmi(weight, height)
    Calculate Body Mass Index.
    
    Parameters:
    weight (float): Weight in kilograms
    height (float): Height in meters
    
    Returns:
    float: BMI value

3. 在交互式开发环境中:大多数 IDE 和代码编辑器会在你将鼠标悬停在函数名上,或输入函数名时,以工具提示显示文档字符串。

什么时候写文档字符串

你应该为以下情况编写文档字符串:

  • 所有公共函数:旨在被程序的其他部分或其他程序员使用的函数
  • 复杂函数:任何仅凭名称与参数无法立刻看出用途或行为的函数
  • 参数不直观的函数:当仅靠参数名不足以完全说明期望值时

你可能会跳过以下情况的文档字符串:

  • 非常简单、显而易见的函数:例如 def add(a, b): return a + b,其名称与参数已经把目的说得非常清楚
  • 私有的辅助函数:仅在某个更大函数内部使用的小型内部函数(尽管这些也能从简短文档字符串中受益)

文档字符串不是注释

请记住,文档字符串和注释的用途不同:

  • 文档字符串:描述函数做什么以及如何使用它(接口)
  • 注释(comments):解释代码内部如何工作(实现)
python
def calculate_grade(score, total):
    """Calculate the percentage grade from a score.
    
    Parameters:
    score (int): Points earned
    total (int): Total points possible
    
    Returns:
    float: The percentage grade (0-100)
    """
    # 避免除以零
    if total == 0:
        return 0.0
    
    # 计算百分比并四舍五入到小数点后 2 位
    percentage = (score / total) * 100
    return round(percentage, 2)

文档字符串告诉使用者函数做什么以及如何使用。注释则向阅读代码的人解释特定的实现细节。

养成良好的文档习惯

编写清晰的文档字符串是一种能带来回报的习惯:

  • 写函数的同时写文档字符串:不要等到以后——当函数目的在你脑中最清晰时就记录下来
  • 保持文档字符串更新:当你改变函数行为时,更新它的文档字符串
  • 简洁但完整:包含所有必要信息,但避免不必要的冗长
  • 在有帮助时使用示例:对于复杂函数,在文档字符串中提供一个用法示例可能非常有价值

良好的文档会让你的代码更专业、更易维护,并且对他人(包括未来的你自己)更有价值。

© 2025. Primesoft Co., Ltd.
support@primesoft.ai