Python 零基础教程

Python 函数调用

调用函数是编程中的一个核心概念,它允许你执行预先定义好的代码块,并在程序的各个地方重复使用它们。

本章中,我们将深入探讨调用函数的内部机制、传递实参的方法,以及理解这些实参如何与函数内部的操作产生交互。

1. 理解函数调用

函数调用是一个表达式,它告诉程序去执行某个特定的函数。当你调用一个函数时,你实际上是在告诉程序跳转到该函数的代码块,执行里面的代码,然后(通常)再返回到最初调用该函数的地方继续往下执行。

调用函数的基本语法如下:

function_name(argument1, argument2, ...)
  • function_name: 这是你想要执行的函数的名称。
  • (): 无论函数是否需要接收参数,调用函数时总是需要加上圆括号。
  • argument1, argument2, ...: 这些是你作为输入传递给函数的值(实参)。实参是可选的;有些函数不需要任何实参。

1.1 调用带有参数的函数

假设我们有一个名为 greet 的函数,它接收一个名字作为参数并打印问候语:

def greet(name):
  """这个函数向作为参数传入的人打招呼。"""
  print(f"你好,{name}!")

# 使用实参 "Alice" 调用函数
greet("Alice")  # 输出: 你好,Alice!

# 使用实参 "Bob" 调用函数
greet("Bob")  # 输出: 你好,Bob!

在这个例子中,greet("Alice") 就是一个函数调用。字符串 "Alice" 是被传递给 greet 函数的实参。

1.2 调用不带参数的函数

有些函数在执行时不需要任何实参。例如:

def say_hello():
  """这个函数打印一句简单的问候。"""
  print("你好!")

# 调用函数
say_hello()  # 输出: 你好!

即使 say_hello 不接收任何实参,我们在调用它时仍然必须保留圆括号 ()

2. 向函数传递实参

实参(Arguments)是你在调用函数时提供给它的具体值。函数将使用这些值来完成内部的操作。在 Python 中,有几种不同的方式向函数传递实参:

2.1 位置参数 (Positional Arguments)

位置参数是严格按照其位置或顺序传递给函数的。你传递实参的顺序至关重要,因为函数会按照从左到右的顺序将它们依次赋值给定义好的参数。

def describe_person(name, age, city):
  """这个函数根据姓名、年龄和城市来描述一个人。"""
  print(f"姓名: {name}, 年龄: {age}, 城市: {city}")

# 使用位置参数调用函数
describe_person("Charlie", 30, "London")  # 输出: 姓名: Charlie, 年龄: 30, 城市: London

在这个例子中,因为它们在函数调用中的位置,"Charlie" 被赋值给了 name 参数,30 被赋值给了 age 参数,而 "London" 被赋值给了 city 参数。

重要提示: 如果你以错误的顺序提供位置参数,函数可能会产生意料之外的结果或引发错误。

2.2 关键字参数 (Keyword Arguments)

关键字参数通过显式地指定“参数名=值”的方式传递给函数。这允许你以任意顺序传递实参,只要你写对了参数的名称即可。

def describe_person(name, age, city):
  """这个函数根据姓名、年龄和城市来描述一个人。"""
  print(f"姓名: {name}, 年龄: {age}, 城市: {city}")

# 使用关键字参数调用函数
describe_person(age=30, name="Charlie", city="London")  # 输出: 姓名: Charlie, 年龄: 30, 城市: London

在这里,我们使用关键字参数明确指出了哪个值对应哪个参数。这使得代码更具可读性,并且极大降低了因参数顺序错误而导致 Bug 的风险。

2.3 默认参数值 (Default Argument Values)

你可以为函数的参数指定默认值。如果调用者在调用时没有为该参数提供值,程序就会自动使用设定好的默认值。

def greet(name="Guest"):
  """这个函数向作为参数传入的人打招呼。
  如果没有提供名字,它将默认向 "Guest" 打招呼。"""
  print(f"你好,{name}!")

# 不带任何实参调用函数
greet()  # 输出: 你好,Guest!

# 带有一个实参调用函数
greet("David")  # 输出: 你好,David!

在这个例子中,name 参数拥有默认值 "Guest"。当我们不带参数调用 greet() 时,它使用了默认值。当我们传入 "David" 时,它则使用了我们提供的值。

2.4 混合使用位置参数和关键字参数

你可以在一次函数调用中混合使用位置参数和关键字参数,但是位置参数必须始终放在关键字参数之前

def describe_person(name, age, city="Unknown"):
  """这个函数根据姓名、年龄和城市来描述一个人。
  城市 (city) 拥有一个默认值 "Unknown" (未知)。"""
  print(f"姓名: {name}, 年龄: {age}, 城市: {city}")

# 混合使用位置参数和关键字参数调用函数
describe_person("Eve", 25, city="Paris")  # 输出: 姓名: Eve, 年龄: 25, 城市: Paris
describe_person("Eve", 25) # 输出: 姓名: Eve, 年龄: 25, 城市: Unknown

在这个例子中,"Eve"25 作为位置参数传递,而 city 则是作为关键字参数传递的。

重要提示: 一旦你在函数调用中开始使用关键字参数,其后所有的参数都必须是关键字参数。例如,写成 describe_person(name="Eve", 25, "Paris") 会直接导致 SyntaxError(语法错误)。

3. 参数传递与可变性 (Mutability)

理解 Python 是如何处理函数参数的非常重要,尤其是在处理像列表(lists)和字典(dictionaries)这类“可变(mutable)”数据类型时。

Python 使用一种称为“按对象引用传递 (pass by object reference)” 的机制。这意味着当你向函数传递一个实参时,你实际上是在传递该对象在内存中的引用(内存地址),而不是该对象的完整副本。

3.1 不可变数据类型 (Immutable Data Types)

对于不可变数据类型(如整数 integers、浮点数 floats、字符串 strings 和元组 tuples),在函数内部对参数进行的任何重新赋值修改不会影响函数外部的原始对象。这是因为不可变对象在创建后就不能被改变。当你尝试修改一个不可变对象时,Python 会在内存中直接创建一个全新的对象。

def modify_number(x):
  """这个函数尝试修改输入的数字。"""
  x = x + 1
  print(f"函数内部: x = {x}")

number = 10
modify_number(number)  # 输出: 函数内部: x = 11
print(f"函数外部: number = {number}")  # 输出: 函数外部: number = 10

在这个例子中,modify_number 函数让 x 的值增加了。然而,这并没有影响原始的 number 变量,因为整数是不可变的。在函数内部,x = x + 1 实际上是创建了一个的整数对象。

3.2 可变数据类型 (Mutable Data Types)

对于可变数据类型(如列表 lists 和字典 dicts),在函数内部对参数进行的修改(如追加、删除元素)直接影响函数外部的原始对象。因为你正在修改引用所指向的那个同一块内存中的对象。

def modify_list(my_list):
  """这个函数修改输入的列表。"""
  my_list.append(4)
  print(f"函数内部: my_list = {my_list}")

my_list = [1, 2, 3]
modify_list(my_list)  # 输出: 函数内部: my_list = [1, 2, 3, 4]
print(f"函数外部: my_list = {my_list}")  # 输出: 函数外部: my_list = [1, 2, 3, 4]

在这个例子中,modify_list 函数向列表中追加了值 4。这种修改确实影响了原始的 my_list 变量,因为列表是可变的。append 方法直接就地修改了列表对象。

3.3 避免意外的修改

如果你想避免函数意外修改你传入的原始列表或字典,你可以在函数内部(或传参时)创建该对象的一个副本 (copy)。对于列表和字典,你可以使用 .copy() 方法,或者使用 list() / dict() 构造函数。

def modify_list_safely(my_list):
  """这个函数安全地修改输入列表的副本。"""
  new_list = my_list.copy()  # 创建列表的副本
  new_list.append(4)
  print(f"函数内部: new_list = {new_list}")

my_list = [1, 2, 3]
modify_list_safely(my_list)  # 输出: 函数内部: new_list = [1, 2, 3, 4]
print(f"函数外部: my_list = {my_list}")  # 输出: 函数外部: my_list = [1, 2, 3]

在这个例子中,我们在修改列表之前,使用 my_list.copy() 创建了 my_list 的副本并将其赋给 new_list。这确保了外部的原始列表保持完好无损。