10.Python函数完全指南:从定义到高级用法
摘要函数是Python编程中最核心的概念之一。本文从函数的定义与必要性出发系统梳理了内置函数、模块函数和自定义函数三大分类深入讲解了形参与实参、位置传参、关键字传参、传参方式限制、默认参数以及 *args 和 **kwargs 不定长参数等完整参数体系并通过打包与解包、返回值机制、局部变量与全局变量作用域、函数嵌套与递归调用等内容帮助读者构建从基础到进阶的函数知识框架。目录1. 什么是函数2. 为什么需要函数3. 函数的分类3.1 内置函数3.2 模块函数3.3 自定义函数4. 函数参数详解4.1 形参与实参4.2 位置传参4.3 关键字传参4.4 限制传参方式4.5 默认参数4.6 不定长参数*args 和 **kwargs5. 参数的打包与解包5.1 序列解包* 用于列表/元组5.2 字典解包** 用于字典5.3 打包与解包结合使用6. 函数的返回值6.1 基本用法6.2 返回多个值6.3 没有return语句的情况6.4 return终止函数执行7. 局部变量与全局变量7.1 局部变量7.2 全局变量7.3 嵌套函数中的变量nonlocal8. 函数中调用函数8.1 普通嵌套调用8.2 递归调用8.3 在函数内部定义和调用函数9. 总结1. 什么是函数函数是一段可重复使用的代码块用于完成特定任务。它接受输入参数执行一系列操作然后返回输出返回值。可以把函数理解为一个代码机器你把原料参数放进去它经过内部处理产出结果返回值。Python中函数的定义使用def关键字基本语法如下def 函数名(参数列表): 文档字符串可选 函数体 return 返回值 # 可选一个简单的例子def greet(name): 向指定的人打招呼 return f你好{name} print(greet(小明)) # 输出你好小明2. 为什么需要函数在编程中使用函数有以下几个重要原因代码复用一次定义多次调用避免重复编写相同逻辑。比如计算圆面积的代码写成函数后每次只需一行调用即可。模块化将复杂问题拆解成若干小函数每个函数只负责一件事使程序结构清晰、易于理解。可维护性当需要修改某段逻辑时只需修改对应的函数所有调用处自动生效降低维护成本。可测试性独立的小函数便于编写单元测试快速定位问题。可读性良好的函数命名能直观表达代码意图让代码像自然语言一样易读。举个例子没有函数时重复代码会很多# 没有函数代码重复 radius1 5 area1 3.14 * radius1 * radius1 print(f圆面积{area1}) radius2 10 area2 3.14 * radius2 * radius2 print(f圆面积{area2})使用函数后简洁且优雅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()高阶函数# 内置函数使用示例 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拥有丰富的标准库覆盖了数学计算、文件操作、网络通信、数据处理等各个领域。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关键字定义。这是日常开发中最频繁使用的函数类型能够将业务逻辑封装为可复用的单元。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 True4. 函数参数详解4.1 形参与实参在函数定义和调用中参数有两个重要概念形参形式参数函数定义时括号中的变量名它是占位符表示函数期望接收的数据。例如def add(a, b)中的a和b就是形参。实参实际参数函数调用时实际传入的具体值。例如add(3, 5)中的3和5就是实参。def multiply(x, y): # x 和 y 是形参 return x * y result multiply(4, 7) # 4 和 7 是实参 print(result) # 284.2 位置传参位置传参是最基本的传参方式实参按照位置顺序依次传递给对应的形参。第1个实参给第1个形参第2个实参给第2个形参以此类推。def describe_person(name, age, city): print(f{name}今年{age}岁来自{city}) 位置传参按顺序匹配 describe_person(小明, 18, 北京) 输出小明今年18岁来自北京 顺序错误会导致结果异常 describe_person(北京, 18, 小明) 输出北京今年18岁来自小明4.3 关键字传参关键字传参通过形参名值的方式传递实参不依赖位置顺序代码更具可读性。def describe_person(name, age, city): print(f{name}今年{age}岁来自{city}) 关键字传参顺序无关 describe_person(age18, city上海, name小红) 输出小红今年18岁来自上海关键字传参的优势在于参数多时不易混淆顺序代码自解释性强还能和位置传参混合使用位置参数必须放在关键字参数之前。# 混合使用位置参数在前关键字参数在后 describe_person(小明, city广州, age20) # 输出小明今年20岁来自广州4.4 限制传参方式Python 3.8 提供了两种特殊语法来限制参数的传递方式仅限位置参数在/之前的参数只能通过位置方式传递不能使用关键字。仅限关键字参数在*之后的参数只能通过关键字方式传递不能使用位置。def func(a, b, /, c, d, *, e, f): a, b仅限位置传参在 / 前面 c, d位置或关键字均可 e, f仅限关键字传参在 * 后面 print(fa{a}, b{b}, c{c}, d{d}, e{e}, f{f}) 正确调用 func(1, 2, 3, 4, e5, f6) func(1, 2, c3, d4, e5, f6) 错误调用示例 func(a1, b2, 3, 4, e5, f6) # a和b不能使用关键字 func(1, 2, 3, 4, 5, 6) # e和f必须使用关键字这种限制在实际开发中非常有用。比如open()函数的文件路径就是仅限位置参数而sorted()的key和reverse参数则设计为仅限关键字参数避免调用歧义。4.5 默认参数默认参数是在函数定义时给形参指定一个默认值。调用时如果未传递该参数则使用默认值如果传递了则覆盖默认值。默认参数让函数调用更灵活。def greet(name, greeting你好): return f{greeting}{name} print(greet(小明)) # 你好小明 print(greet(Tom, greetingHello)) # HelloTom重要注意事项默认参数的值在函数定义时只计算一次。如果默认值是可变对象如列表、字典可能会引发意想不到的问题# 错误示范默认参数使用可变对象 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, itemsNone): if items is None: items [] items.append(item) return items print(add_item(a)) # [a] print(add_item(b)) # [b]另外默认参数必须放在非默认参数的后面否则会报语法错误。# 正确默认参数在后面 def func(a, b10): pass 错误默认参数在前面 def func(a10, b): pass # SyntaxError4.6 不定长参数*args 和 **kwargs当不确定函数会接收多少个参数时可以使用不定长参数*args接收任意数量的位置参数将它们打包成一个元组。**kwargs接收任意数量的关键字参数将它们打包成一个字典。这里的名称args和kwargs只是约定俗成的命名真正起作用的是*和**符号你可以使用其他名称但建议遵循惯例。# *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小明, age18, city北京) 输出 name: 小明 age: 18 city: 北京它们也可以组合使用此时参数顺序有严格要求普通参数 → *args → 默认参数 → **kwargs。def full_func(a, b, *args, c10, **kwargs): print(fa{a}, b{b}) print(fargs{args}) print(fc{c}) print(fkwargs{kwargs}) full_func(1, 2, 3, 4, 5, c20, x100, y200) 输出 a1, b2 args(3, 4, 5) c20 kwargs{x: 100, y: 200}5. 参数的打包与解包参数的打包与解包是Python中非常灵活的特性使用*和**操作符可以实现打包函数定义时将多个实参收集到一个变量中即上面讲的*args和**kwargs。解包函数调用时将序列或字典中的元素拆开作为独立的实参传递。5.1 序列解包* 用于列表/元组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)) # 2105.2 字典解包** 用于字典def greet(name, age, city): print(f{name}{age}岁来自{city}) 字典的键必须和形参名一致 person {name: 小明, age: 18, city: 深圳} greet(**person) # 等价于 greet(name小明, age18, city深圳) 输出小明18岁来自深圳5.3 打包与解包结合使用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解包操作在日常开发中非常实用比如合并列表、合并字典、灵活地传递参数等# 合并列表 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 基本用法def add(a, b): return a b result add(3, 5) print(result) # 8 print(result * 2) # 16返回值可以参与后续运算6.2 返回多个值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 tuple6.3 没有return语句的情况如果函数没有return语句或者return后面没有跟值函数默认返回None。def say_hello(name): print(f你好{name}) # 没有return默认返回None result say_hello(小明) print(result) # None print(type(result)) # class NoneType6.4 return终止函数执行return语句执行后函数立即结束后面的代码不会被执行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 局部变量在函数内部定义的变量称为局部变量其作用域仅限于该函数内部。函数执行完毕后局部变量会被销毁外部无法访问。def my_func(): x 100 # 局部变量只在函数内部有效 print(f函数内部x {x}) my_func() # 函数内部x 100 print(x) # NameError: name x is not defined不同函数中的局部变量相互独立互不影响def func_a(): x 10 print(ffunc_a中x {x}) def func_b(): x 20 print(ffunc_b中x {x}) func_a() # func_a中x 10 func_b() # func_b中x 207.2 全局变量在函数外部、模块层级定义的变量称为全局变量可以在整个程序的任何位置访问。total 0 # 全局变量 def add_to_total(value): print(total) # 可以读取全局变量 add_to_total(10) # 0但是如果要在函数内部修改全局变量需要使用global关键字声明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会将其视为创建了一个新的局部变量而不是修改全局变量total 0 def add_to_total(value): # 没有global声明 total value # 这里创建的是局部变量 print(f函数内部total{total}) add_to_total(10) # 函数内部total10 print(f全局total{total}) # 全局total0 —— 全局变量没有被修改7.3 嵌套函数中的变量nonlocal在嵌套函数中内层函数想要修改外层函数但不是全局的变量时需要使用nonlocal关键字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 普通嵌套调用一个函数在其内部调用另一个函数来完成部分任务这是日常开发中最常见的模式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 递归调用递归是指一个函数在其内部调用自身。递归是解决具有自相似结构问题的强大工具但必须正确设置终止条件否则会导致无限递归。经典的递归例子——计算阶乘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)斐波那契数列也是递归的经典应用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需要注意的是递归虽然代码简洁但可能有性能问题重复计算和栈溢出风险。实际问题中常用记忆化递归或直接改用迭代来优化# 迭代版阶乘性能更好 def factorial_iterative(n): result 1 for i in range(2, n 1): result * i return result print(factorial_iterative(5)) # 1208.3 在函数内部定义和调用函数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)) # 309. 总结函数是Python编程中最重要的概念之一本文从基础到进阶系统梳理了以下内容函数的定义使用def关键字由函数名、参数列表和函数体组成。函数的分类内置函数开箱即用、模块函数需要导入、自定义函数按需编写。参数体系形参与实参的区别、位置传参与关键字传参、仅限位置/仅限关键字参数的限制、默认参数的灵活性、*args和**kwargs不定长参数。打包与解包*和**在定义时打包、在调用时解包让参数传递极其灵活。返回值return语句返回结果可返回多个值元组没有return时默认返回None。变量作用域局部变量只在函数内部可见全局变量通过global声明才能在函数内修改嵌套函数用nonlocal修改外层变量。函数的互相调用嵌套调用体现代码复用递归调用解决自相似问题嵌套函数定义用于闭包等高级场景。掌握好函数的使用是写出高质量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的改变一直有存储。# x的指向一直是1010是不可变的。 def test(x10): x 1 print(x) test() # 11 test() # 11 test() # 11运行三次值不变跟上一题的规则并不冲突。x变量也是创建一次但是10是个字面常量是不可变的。第一题lst1也是创建了一次指向的地址也是不会再变的但是地址所代表的列表list是可变的。所以此调用函数变量x都是10。