10.Python函数完全指南:从定义到高级用法

**摘要:**函数是Python编程中最核心的概念之一。本文从函数的定义与必要性出发,系统梳理了内置函数、模块函数和自定义函数三大分类,深入讲解了形参与实参、位置传参、关键字传参、传参方式限制、默认参数以及 *args 和 **kwargs 不定长参数等完整参数体系,并通过打包与解包、返回值机制、局部变量与全局变量作用域、函数嵌套与递归调用等内容,帮助读者构建从基础到进阶的函数知识框架。

目录

[1. 什么是函数?](#1. 什么是函数?)

[2. 为什么需要函数?](#2. 为什么需要函数?)

[3. 函数的分类](#3. 函数的分类)

[3.1 内置函数](#3.1 内置函数)

[3.2 模块函数](#3.2 模块函数)

[3.3 自定义函数](#3.3 自定义函数)

[4. 函数参数详解](#4. 函数参数详解)

[4.1 形参与实参](#4.1 形参与实参)

[4.2 位置传参](#4.2 位置传参)

[4.3 关键字传参](#4.3 关键字传参)

[4.4 限制传参方式](#4.4 限制传参方式)

[4.5 默认参数](#4.5 默认参数)

[4.6 不定长参数(*args 和 **kwargs)](#4.6 不定长参数(*args 和 **kwargs))

[5. 参数的打包与解包](#5. 参数的打包与解包)

[5.1 序列解包(* 用于列表/元组)](#5.1 序列解包(* 用于列表/元组))

[5.2 字典解包(** 用于字典)](#5.2 字典解包(** 用于字典))

[5.3 打包与解包结合使用](#5.3 打包与解包结合使用)

[6. 函数的返回值](#6. 函数的返回值)

[6.1 基本用法](#6.1 基本用法)

[6.2 返回多个值](#6.2 返回多个值)

[6.3 没有return语句的情况](#6.3 没有return语句的情况)

[6.4 return终止函数执行](#6.4 return终止函数执行)

[7. 局部变量与全局变量](#7. 局部变量与全局变量)

[7.1 局部变量](#7.1 局部变量)

[7.2 全局变量](#7.2 全局变量)

[7.3 嵌套函数中的变量:nonlocal](#7.3 嵌套函数中的变量:nonlocal)

[8. 函数中调用函数](#8. 函数中调用函数)

[8.1 普通嵌套调用](#8.1 普通嵌套调用)

[8.2 递归调用](#8.2 递归调用)

[8.3 在函数内部定义和调用函数](#8.3 在函数内部定义和调用函数)

[9. 总结](#9. 总结)


1. 什么是函数?

函数是一段可重复使用的代码块,用于完成特定任务。它接受输入(参数),执行一系列操作,然后返回输出(返回值)。可以把函数理解为一个"代码机器":你把原料(参数)放进去,它经过内部处理,产出结果(返回值)。

Python中函数的定义使用 def 关键字,基本语法如下:

python 复制代码
def 函数名(参数列表):
    """文档字符串(可选)"""
    函数体
    return 返回值  # 可选

一个简单的例子:

python 复制代码
def greet(name):
    """向指定的人打招呼"""
    return f"你好,{name}!"
print(greet("小明"))  # 输出:你好,小明!

2. 为什么需要函数?

在编程中使用函数有以下几个重要原因:

  • 代码复用:一次定义,多次调用,避免重复编写相同逻辑。比如计算圆面积的代码,写成函数后每次只需一行调用即可。
  • 模块化:将复杂问题拆解成若干小函数,每个函数只负责一件事,使程序结构清晰、易于理解。
  • 可维护性:当需要修改某段逻辑时,只需修改对应的函数,所有调用处自动生效,降低维护成本。
  • 可测试性:独立的小函数便于编写单元测试,快速定位问题。
  • 可读性:良好的函数命名能直观表达代码意图,让代码像自然语言一样易读。

举个例子,没有函数时重复代码会很多:

python 复制代码
# 没有函数,代码重复
radius1 = 5
area1 = 3.14 * radius1 * radius1
print(f"圆面积:{area1}")
radius2 = 10
area2 = 3.14 * radius2 * radius2
print(f"圆面积:{area2}")

使用函数后简洁且优雅:

python 复制代码
def circle_area(radius):
    return 3.14 * radius * radius
print(f"圆面积:{circle_area(5)}")
print(f"圆面积:{circle_area(10)}")

3. 函数的分类

Python中的函数可以大致分为三类:内置函数模块函数自定义函数

3.1 内置函数

内置函数是Python解释器自带的、无需导入即可直接使用的函数。它们是Python核心的一部分,涵盖了常见的基础操作。常见的内置函数包括:

  • print():输出内容到控制台
  • len():返回对象的长度
  • type():返回对象的类型
  • int()float()str():类型转换函数
  • range():生成整数序列
  • input():接收用户输入
  • max()min()sum():数学统计函数
  • sorted()reversed():序列操作函数
  • enumerate()zip():迭代辅助函数
  • map()filter():高阶函数
python 复制代码
# 内置函数使用示例
numbers = [3, 1, 4, 1, 5, 9]
print(len(numbers))      # 6
print(max(numbers))      # 9
print(sorted(numbers))   # [1, 1, 3, 4, 5, 9]

3.2 模块函数

模块函数是存放在Python标准库或第三方库中的函数,需要通过 import 导入后才能使用。Python拥有丰富的标准库,覆盖了数学计算、文件操作、网络通信、数据处理等各个领域。

python 复制代码
import math
import random
from datetime import datetime
print(math.sqrt(16))              # 4.0
print(random.randint(1, 100))     # 随机整数
print(datetime.now())             # 当前日期时间

常见的模块及其函数:

  • math 模块:sqrt()sin()cos()log()pi
  • random 模块:randint()choice()shuffle()random()
  • os 模块:getcwd()listdir()path.join()
  • json 模块:dumps()loads()dump()load()

3.3 自定义函数

自定义函数是开发者根据实际需求自己编写的函数,使用 def 关键字定义。这是日常开发中最频繁使用的函数类型,能够将业务逻辑封装为可复用的单元。

python 复制代码
def calculate_bmi(weight, height):
    """计算BMI指数"""
    return weight / (height ** 2)
def is_prime(n):
"""判断是否为质数"""
if n < 2:
return False
for i in range(2, int(n ** 0.5) + 1):
if n % i == 0:
return False
return True

4. 函数参数详解

4.1 形参与实参

在函数定义和调用中,参数有两个重要概念:

  • 形参(形式参数) :函数定义时括号中的变量名,它是占位符,表示函数期望接收的数据。例如 def add(a, b) 中的 ab 就是形参。
  • 实参(实际参数) :函数调用时实际传入的具体值。例如 add(3, 5) 中的 35 就是实参。
python 复制代码
def multiply(x, y):   # x 和 y 是形参
    return x * y
result = multiply(4, 7)  # 4 和 7 是实参
print(result)  # 28

4.2 位置传参

位置传参是最基本的传参方式,实参按照位置顺序依次传递给对应的形参。第1个实参给第1个形参,第2个实参给第2个形参,以此类推。

python 复制代码
def describe_person(name, age, city):
    print(f"{name}今年{age}岁,来自{city}")
位置传参:按顺序匹配
describe_person("小明", 18, "北京")
输出:小明今年18岁,来自北京
顺序错误会导致结果异常
describe_person("北京", 18, "小明")
输出:北京今年18岁,来自小明

4.3 关键字传参

关键字传参通过"形参名=值"的方式传递实参,不依赖位置顺序,代码更具可读性。

python 复制代码
def describe_person(name, age, city):
    print(f"{name}今年{age}岁,来自{city}")
关键字传参:顺序无关
describe_person(age=18, city="上海", name="小红")
输出:小红今年18岁,来自上海

关键字传参的优势在于:参数多时不易混淆顺序,代码自解释性强,还能和位置传参混合使用(位置参数必须放在关键字参数之前)。

python 复制代码
# 混合使用:位置参数在前,关键字参数在后
describe_person("小明", city="广州", age=20)
# 输出:小明今年20岁,来自广州

4.4 限制传参方式

Python 3.8+ 提供了两种特殊语法来限制参数的传递方式:

  • 仅限位置参数 :在 / 之前的参数只能通过位置方式传递,不能使用关键字。
  • 仅限关键字参数 :在 * 之后的参数只能通过关键字方式传递,不能使用位置。
python 复制代码
def func(a, b, /, c, d, *, e, f):
    """
    a, b:仅限位置传参(在 / 前面)
    c, d:位置或关键字均可
    e, f:仅限关键字传参(在 * 后面)
    """
    print(f"a={a}, b={b}, c={c}, d={d}, e={e}, f={f}")
正确调用
func(1, 2, 3, 4, e=5, f=6)
func(1, 2, c=3, d=4, e=5, f=6)
错误调用示例:
func(a=1, b=2, 3, 4, e=5, f=6)  # a和b不能使用关键字
func(1, 2, 3, 4, 5, 6)           # e和f必须使用关键字

这种限制在实际开发中非常有用。比如 open() 函数的文件路径就是仅限位置参数,而 sorted()keyreverse 参数则设计为仅限关键字参数,避免调用歧义。

4.5 默认参数

默认参数是在函数定义时给形参指定一个默认值。调用时如果未传递该参数,则使用默认值;如果传递了,则覆盖默认值。默认参数让函数调用更灵活。

python 复制代码
def greet(name, greeting="你好"):
    return f"{greeting},{name}!"
print(greet("小明"))                  # 你好,小明!
print(greet("Tom", greeting="Hello")) # Hello,Tom!

重要注意事项:默认参数的值在函数定义时只计算一次。如果默认值是可变对象(如列表、字典),可能会引发意想不到的问题:

python 复制代码
# 错误示范:默认参数使用可变对象
def add_item(item, items=[]):
    items.append(item)
    return items
print(add_item("a"))  # ['a']
print(add_item("b"))  # ['a', 'b'] ------ 不是预期的 ['b']!
正确做法:使用None作为默认值
def add_item(item, items=None):
if items is None:
items = []
items.append(item)
return items
print(add_item("a"))  # ['a']
print(add_item("b"))  # ['b']

另外,默认参数必须放在非默认参数的后面,否则会报语法错误。

python 复制代码
# 正确:默认参数在后面
def func(a, b=10):
    pass
错误:默认参数在前面
def func(a=10, b):
pass  # SyntaxError

4.6 不定长参数(*args 和 **kwargs)

当不确定函数会接收多少个参数时,可以使用不定长参数:

  • *args:接收任意数量的位置参数 ,将它们打包成一个元组
  • **kwargs:接收任意数量的关键字参数 ,将它们打包成一个字典

这里的名称 argskwargs 只是约定俗成的命名,真正起作用的是 *** 符号,你可以使用其他名称,但建议遵循惯例。

python 复制代码
# *args:接收多个位置参数
def sum_all(*args):
    """计算任意个数字的和"""
    total = 0
    for num in args:
        total += num
    return total
print(sum_all(1, 2, 3))          # 6
print(sum_all(1, 2, 3, 4, 5))    # 15
**kwargs:接收多个关键字参数
def print_info(**kwargs):
"""打印任意关键字参数"""
for key, value in kwargs.items():
print(f"{key}: {value}")
print_info(name="小明", age=18, city="北京")
输出:
name: 小明
age: 18
city: 北京

它们也可以组合使用,此时参数顺序有严格要求:普通参数 → *args → 默认参数 → **kwargs

python 复制代码
def full_func(a, b, *args, c=10, **kwargs):
    print(f"a={a}, b={b}")
    print(f"args={args}")
    print(f"c={c}")
    print(f"kwargs={kwargs}")
full_func(1, 2, 3, 4, 5, c=20, x=100, y=200)
输出:
a=1, b=2
args=(3, 4, 5)
c=20
kwargs={'x': 100, 'y': 200}

5. 参数的打包与解包

参数的打包与解包是Python中非常灵活的特性,使用 *** 操作符可以实现:

  • 打包 :函数定义时,将多个实参收集到一个变量中(即上面讲的 *args**kwargs)。
  • 解包:函数调用时,将序列或字典中的元素拆开,作为独立的实参传递。

5.1 序列解包(* 用于列表/元组)

python 复制代码
def multiply(a, b, c):
    return a * b * c
有一个列表,想把元素分别传给三个参数
numbers = [2, 3, 4]
result = multiply(*numbers)  # 等价于 multiply(2, 3, 4)
print(result)  # 24
元组解包同理
values = (5, 6, 7)
print(multiply(*values))  # 210

5.2 字典解包(** 用于字典)

python 复制代码
def greet(name, age, city):
    print(f"{name},{age}岁,来自{city}")
字典的键必须和形参名一致
person = {"name": "小明", "age": 18, "city": "深圳"}
greet(**person)  # 等价于 greet(name="小明", age=18, city="深圳")
输出:小明,18岁,来自深圳

5.3 打包与解包结合使用

python 复制代码
def calculate(op, *numbers):
    """对任意个数字执行指定操作"""
    if op == "sum":
        return sum(numbers)
    elif op == "product":
        result = 1
        for n in numbers:
            result *= n
        return result
列表解包后传入,函数内部再次打包接收
data = [1, 2, 3, 4, 5]
print(calculate("sum", *data))      # 15
print(calculate("product", *data))  # 120

解包操作在日常开发中非常实用,比如合并列表、合并字典、灵活地传递参数等:

python 复制代码
# 合并列表
list1 = [1, 2, 3]
list2 = [4, 5, 6]
combined = [*list1, *list2]
print(combined)  # [1, 2, 3, 4, 5, 6]
合并字典(Python 3.5+)
dict1 = {"a": 1, "b": 2}
dict2 = {"c": 3, "d": 4}
merged = {**dict1, **dict2}
print(merged)  # {'a': 1, 'b': 2, 'c': 3, 'd': 4}

6. 函数的返回值

函数通过 return 语句将计算结果返回给调用者。关于返回值需要掌握以下要点:

6.1 基本用法

python 复制代码
def add(a, b):
    return a + b
result = add(3, 5)
print(result)  # 8
print(result * 2)  # 16,返回值可以参与后续运算

6.2 返回多个值

Python函数可以返回多个值,本质上是返回一个元组,接收时可以直接用多个变量解包:

python 复制代码
def get_user_info():
    name = "小明"
    age = 18
    city = "杭州"
    return name, age, city  # 实际返回的是一个元组
多变量解包接收
n, a, c = get_user_info()
print(n, a, c)  # 小明 18 杭州
也可以用一个变量接收整个元组
info = get_user_info()
print(info)       # ('小明', 18, '杭州')
print(type(info)) # <class 'tuple'>

6.3 没有return语句的情况

如果函数没有 return 语句,或者 return 后面没有跟值,函数默认返回 None

python 复制代码
def say_hello(name):
    print(f"你好,{name}!")
    # 没有return,默认返回None
result = say_hello("小明")
print(result)  # None
print(type(result))  # <class 'NoneType'>

6.4 return终止函数执行

return 语句执行后,函数立即结束,后面的代码不会被执行:

python 复制代码
def check_age(age):
    if age < 0:
        return "年龄不能为负数"  # 函数在此结束
    if age < 18:
        return "未成年"
    return "已成年"
print(check_age(-5))   # 年龄不能为负数
print(check_age(15))   # 未成年
print(check_age(25))   # 已成年

7. 局部变量与全局变量

7.1 局部变量

在函数内部定义的变量称为局部变量,其作用域仅限于该函数内部。函数执行完毕后,局部变量会被销毁,外部无法访问。

python 复制代码
def my_func():
    x = 100  # 局部变量,只在函数内部有效
    print(f"函数内部:x = {x}")
my_func()         # 函数内部:x = 100
print(x)        # NameError: name 'x' is not defined

不同函数中的局部变量相互独立,互不影响:

python 复制代码
def func_a():
    x = 10
    print(f"func_a中:x = {x}")
def func_b():
x = 20
print(f"func_b中:x = {x}")
func_a()  # func_a中:x = 10
func_b()  # func_b中:x = 20

7.2 全局变量

在函数外部、模块层级定义的变量称为全局变量,可以在整个程序的任何位置访问。

python 复制代码
total = 0  # 全局变量
def add_to_total(value):
print(total)  # 可以读取全局变量
add_to_total(10)  # 0

但是,如果要在函数内部修改 全局变量,需要使用 global 关键字声明:

python 复制代码
total = 0
def add_to_total(value):
global total  # 声明要修改全局变量
total += value
add_to_total(10)
print(total)  # 10
add_to_total(5)
print(total)  # 15

如果没有 global 声明就进行赋值操作,Python会将其视为创建了一个新的局部变量,而不是修改全局变量:

python 复制代码
total = 0
def add_to_total(value):
# 没有global声明
total = value  # 这里创建的是局部变量!
print(f"函数内部total:{total}")
add_to_total(10)       # 函数内部total:10
print(f"全局total:{total}")  # 全局total:0 ------ 全局变量没有被修改

7.3 嵌套函数中的变量:nonlocal

在嵌套函数中,内层函数想要修改外层函数(但不是全局)的变量时,需要使用 nonlocal 关键字:

python 复制代码
def outer():
    count = 0  # 外层函数的局部变量
def inner():
    nonlocal count  # 声明要修改外层函数的变量
    count += 1
    return count
return inner
counter = outer()
print(counter())  # 1
print(counter())  # 2
print(counter())  # 3

这段代码展示了闭包(Closure)的基本用法,inner 函数记住了外层 count 变量的状态。

8. 函数中调用函数

函数可以在内部调用其他函数,这体现了代码复用的思想。主要包括两种形式:普通嵌套调用递归调用

8.1 普通嵌套调用

一个函数在其内部调用另一个函数来完成部分任务,这是日常开发中最常见的模式:

python 复制代码
def is_even(n):
    """判断是否为偶数"""
    return n % 2 == 0
def is_positive(n):
"""判断是否为正数"""
return n > 0
def classify_number(n):
"""综合判断一个数"""
if not is_positive(n):
return "负数或零"
if is_even(n):
return "正偶数"
else:
return "正奇数"
print(classify_number(10))   # 正偶数
print(classify_number(7))    # 正奇数
print(classify_number(-3))   # 负数或零

8.2 递归调用

递归是指一个函数在其内部调用自身 。递归是解决具有自相似结构问题的强大工具,但必须正确设置终止条件,否则会导致无限递归。

经典的递归例子------计算阶乘:

python 复制代码
def factorial(n):
    """递归计算阶乘"""
    if n == 0 or n == 1:  # 终止条件
        return 1
    return n * factorial(n - 1)  # 递归调用
print(factorial(5))  # 120  (5 × 4 × 3 × 2 × 1)

斐波那契数列也是递归的经典应用:

python 复制代码
def fibonacci(n):
    """返回第n个斐波那契数"""
    if n <= 1:
        return n
    return fibonacci(n - 1) + fibonacci(n - 2)
for i in range(10):
print(fibonacci(i), end=" ")
输出:0 1 1 2 3 5 8 13 21 34

需要注意的是,递归虽然代码简洁,但可能有性能问题(重复计算)和栈溢出风险。实际问题中常用记忆化递归 或直接改用迭代来优化:

python 复制代码
# 迭代版阶乘,性能更好
def factorial_iterative(n):
    result = 1
    for i in range(2, n + 1):
        result *= i
    return result
print(factorial_iterative(5))  # 120

8.3 在函数内部定义和调用函数

Python支持在函数内部定义另一个函数(嵌套函数),这在创建闭包、装饰器等场景中非常有用:

python 复制代码
def make_multiplier(factor):
    """创建一个乘法器"""
    def multiplier(x):
        # 内层函数可以访问外层函数的factor参数
        return x * factor
    return multiplier
double = make_multiplier(2)
triple = make_multiplier(3)
print(double(10))  # 20
print(triple(10))  # 30

9. 总结

函数是Python编程中最重要的概念之一,本文从基础到进阶系统梳理了以下内容:

  • 函数的定义 :使用 def 关键字,由函数名、参数列表和函数体组成。
  • 函数的分类:内置函数(开箱即用)、模块函数(需要导入)、自定义函数(按需编写)。
  • 参数体系:形参与实参的区别、位置传参与关键字传参、仅限位置/仅限关键字参数的限制、默认参数的灵活性、*args和**kwargs不定长参数。
  • 打包与解包*** 在定义时打包、在调用时解包,让参数传递极其灵活。
  • 返回值return 语句返回结果,可返回多个值(元组),没有return时默认返回 None
  • 变量作用域 :局部变量只在函数内部可见,全局变量通过 global 声明才能在函数内修改,嵌套函数用 nonlocal 修改外层变量。
  • 函数的互相调用:嵌套调用体现代码复用,递归调用解决自相似问题,嵌套函数定义用于闭包等高级场景。

掌握好函数的使用,是写出高质量Python代码的关键一步。建议读者结合本文中的代码示例进行动手实践,逐步加深理解。

两个易错题:

python 复制代码
# 函数的默认参数,只会在定义函数的瞬间,创建一次。后面默认调用,一直复用一个列表
def test(a, lst1=[1, 2]):  # 定义一个函数
    # 把a添加到列表中
    if a not in lst1:
        lst1.append(a)
    return lst1

print(f'第一次调用test函数的结果是:{test(10)}')  # [1, 2, 10]
print(f'第二次调用test函数的结果是:{test(20)}') # [1, 2, 10, 20]
print(f'第三次调用test函数的结果是:{test(30, lst1=[60, 70])}') # [60, 70, 30]
print(f'第四次调用test函数的结果是:{test(40)}') #[1, 2, 10, 20, 40]

函数的默认参数,只会在定义函数的瞬间,创建一次。后面调用,一直复用一个列表。所以之前的函数调用,对默认list的改变一直有存储。

python 复制代码
# x的指向一直是10,10是不可变的。
def test(x=10):
    x += 1
    print(x)

test()  # 11
test()  # 11
test()  # 11

运行三次,值不变,跟上一题的规则并不冲突。x变量也是创建一次,但是10是个字面常量,是不可变的。第一题,lst1也是创建了一次,指向的地址也是不会再变的,但是地址所代表的列表list是可变的。所以此调用函数,变量x都是10。

相关推荐
leoZ2311 小时前
实战复盘:用 Claude Code 从零搭一个 GitHub PR 统计工具
java·人工智能·python·深度学习·自然语言处理·github·llama
夏天测2 小时前
Python 网络编程入门:从 Socket 底层到 HTTP 并发实战
python·socket·httpx·requests·tcp udp·python网络编程·爬虫基础
玉鸯2 小时前
多 Agent 并行时如何保证状态一致性?
分布式·python·agent
aGdF8E3gQ2 小时前
【Application Insights】采样率对Function App日志收集的影响和解决方法
python·flask·wpf
ATMQuant3 小时前
以AI量化为生:25.vnpy 4.4升级实战 - 魔改版框架如何安全跟进上游
人工智能·python·量化交易·vnpy
卷无止境4 小时前
拯救乱码方块:pandas 绘图中文字体的一揽子解决方案
后端·python
李昊哲小课4 小时前
fastapi sse websocket 智能家居实时控制台
python·websocket·智能家居·fastapi·sse
杰佛史彦明 本王是暴君4 小时前
PyTorch KernelAgent 源码解读 ---(2)--- 总体流程
人工智能·pytorch·python
Zane19944 小时前
别再手写 try/finally 了:一文讲透 with 语句背后的上下文管理器协议
后端·python
李可以量化4 小时前
量化高性能服务框架 Tornado 全面解析(上):异步非阻塞的核心能力与场景落地
大数据·python·量化交易·tornado·qmt·ptrade