拓冰建站拓冰建站
首页 / 资讯中心 / 正文

Python函数详解:从基础定义到参数、返回值与作用域实战

1. 从“重复劳动”到“一劳永逸”为什么你需要函数如果你刚开始接触Python或者任何编程语言你写的代码可能像一条长长的流水线从上到下一行接一行。比如你想打印三次问候语可能会这样写print(你好世界) print(你好世界) print(你好世界)这看起来没什么问题。但想象一下如果这个问候语需要在你的程序里出现一百次而且每次的格式可能还要稍微调整一下比如加上当前用户名。你会怎么做把上面三行代码复制粘贴一百遍吗这不仅是体力活更是一场维护的噩梦。一旦你需要修改问候语的格式比如从“你好”改成“Hello”你就得在一百个地方做同样的修改漏掉一个就会导致程序行为不一致。函数就是来解决这个问题的。你可以把函数理解为一个“代码盒子”。你把一段常用的、完成特定任务的代码比如“打印带用户名的问候语”放进这个盒子里并给盒子贴上一个标签函数名。以后每当需要执行这个任务时你不需要再把盒子里的代码重写一遍只需要喊一声这个标签的名字盒子里的代码就会自动运行。这个“喊名字”的动作在编程里叫做“函数调用”。而那个“代码盒子”的创建过程就叫做“函数定义”。从“重复劳动”到“定义一次调用无数次”这就是函数带来的最直观的价值代码复用。它让你的代码变得更简洁、更易读、也更容易维护。今天我们就来彻底拆解这个编程中最基础也最重要的概念——函数我会结合十多年的编码经验告诉你那些官方文档里不会写的细节和“坑”。2. 函数定义打造你的第一个“代码工具箱”定义函数就是创建那个“代码盒子”的过程。Python中使用def关键字来定义。2.1 定义语法与核心组件一个完整的函数定义包含以下几个部分def 函数名(参数1 参数2 ...): 函数说明文档可选 # 函数体具体的执行代码 返回值我们来逐一看一下每个部分def 这是定义函数的固定关键字告诉Python解释器“嘿我要开始定义一个函数了”。函数名 这是你给“代码盒子”贴的标签。命名规则和变量名基本一致只能包含字母、数字和下划线且不能以数字开头。最重要的是名字要有意义calculate_average就比func1好一万倍。好的函数名让人一眼就知道它是干什么的。圆括号() 这是函数的标志之一。即使函数不需要任何输入圆括号也不能省略。参数 放在圆括号内是函数的“输入接口”。你可以把它想象成工具箱的“入料口”。参数是可选的一个函数可以没有参数也可以有多个。我们会在下一章详细拆解。冒号: 这是Python语法块的开始标志冒号后面缩进的代码都属于这个函数。函数体 所有缩进的代码通常是4个空格或一个Tab就是函数的具体实现逻辑。这是函数的核心。return语句 用于指定函数的“输出”也就是返回值。它标志着函数的执行到此结束并将return后面的值传递出去。return也是可选的。函数说明文档 写在三引号内的字符串用于解释函数的作用、参数和返回值。这不是给机器看的是给你和其他开发者看的“使用说明书”。虽然可选但我强烈建议你为每一个函数都写上几个月后你自己都会感谢自己。2.2 你的第一个函数从打印到计算让我们抛开简单的print(“Hello”)来定义一个有点实际用处的函数。假设我们经常需要计算一个列表里所有数字的总和。没有函数的世界繁琐版numbers1 [1 2 3 4 5] sum1 0 for num in numbers1: sum1 num print(f列表1的和是 {sum1}) numbers2 [10 20 30] sum2 0 for num in numbers2: sum2 num print(f列表2的和是 {sum2})每计算一个新列表你都得把for循环重写一遍。有函数的世界优雅版def calculate_sum(number_list): 计算给定数字列表的总和。 参数: number_list (list): 一个包含数字的列表。 返回: int/float: 列表中所有数字的总和。 total 0 for num in number_list: total num return total # 现在使用函数变得极其简单 list_a [1 2 3 4 5] result_a calculate_sum(list_a) print(f列表A的和是 {result_a}) # 输出列表A的和是 15 list_b [10 20 30] result_b calculate_sum(list_b) print(f列表B的和是 {result_b}) # 输出列表B的和是 60看我们只定义了一次calculate_sum就可以对任意列表轻松调用。代码的复用性和可读性得到了质的提升。这里number_list就是一个参数它代表了函数执行时需要的外部数据。3. 函数参数详解灵活控制你的“工具箱”参数让函数变得强大而灵活。根据传递数据的方式Python的函数参数主要有以下几种形式。3.1 位置参数最直接的方式这是最常见的形式。调用函数时传入实参实际参数的顺序必须与定义函数时形参形式参数的顺序一一对应。def greet(name greeting): 向指定的人发出指定的问候 print(f{greeting} {name}!) greet(Alice Hello) # 输出Hello Alice! greet(Hi Bob) # 输出Hi Bob! (这显然不是我们想要的顺序错了)在上面的错误示例中因为我们把两个实参的顺序弄反了导致函数逻辑错乱。所以使用位置参数时顺序至关重要。3.2 关键字参数指名道姓避免混淆为了避免位置参数顺序错误的问题我们可以在调用时使用关键字参数即明确指定参数名。greet(greetingHi nameBob) # 输出Hi Bob!这次即使顺序是greeting在前name在后但因为指明了参数名函数依然能正确理解。这大大提高了代码的可读性和健壮性。在参数较多时关键字参数是首选。3.3 默认参数提供“保底”选项在定义函数时我们可以给某些参数指定一个默认值。这样在调用函数时如果省略了这些参数它们就会使用默认值。def greet_v2(name greetingHello): 向指定的人发出问候默认说Hello print(f{greeting} {name}!) greet_v2(Charlie) # 输出Hello Charlie! (使用了默认的greeting) greet_v2(David Good morning) # 输出Good morning David! (覆盖了默认值)一个至关重要的经验之谈默认参数必须指向不可变对象这是一个经典的“坑”。我们来看一个例子def add_item(item item_list[]): # 危险默认参数是可变对象列表 item_list.append(item) return item_list print(add_item(apple)) # 输出[apple] print(add_item(banana)) # 你期望输出 [banana] 但实际输出[apple banana]为什么第二次调用会包含第一次的”apple”因为默认参数item_list[]在函数定义时就被创建了并且在整个程序运行期间这个默认的列表对象只有一个。后续所有使用默认参数的调用操作的其实是同一个列表对象。正确的做法是使用None作为默认值在函数内部进行判断和创建def add_item_safe(item item_listNone): if item_list is None: item_list [] # 每次调用如果需要都创建一个新的空列表 item_list.append(item) return item_list print(add_item_safe(apple)) # 输出[apple] print(add_item_safe(banana)) # 输出[banana] 符合预期3.4 可变参数*args 和 **kwargs有时你无法确定函数会接收多少个参数。比如你想写一个计算任意多个数字和的函数。这时就需要可变参数。*args(非关键字可变参数) 接收任意多个位置参数并在函数内部将其打包成一个元组。def sum_all(*numbers): 计算传入的所有数字之和 print(f接收到的参数类型是 {type(numbers)} 内容是 {numbers}) total 0 for num in numbers: total num return total result sum_all(1 2 3 4 5) print(result) # 输出15 # 终端会先打印接收到的参数类型是 class ‘tuple’ 内容是 (1 2 3 4 5)**kwargs(关键字可变参数) 接收任意多个关键字参数并在函数内部将其打包成一个字典。def print_info(**info): 打印传入的所有关键字信息 print(f接收到的参数类型是 {type(info)} 内容是 {info}) for key value in info.items(): print(f{key}: {value}) print_info(nameAlice age30 cityNew York) # 输出 # 接收到的参数类型是 class ‘dict’ 内容是 {‘name’: ‘Alice’ ‘age’: 30 ‘city’: ‘New York’} # name: Alice # age: 30 # city: New York在函数定义中*args必须出现在**kwargs之前。它们让函数接口变得极其灵活。3.5 参数组合使用规则当多种参数形式混合使用时必须遵循严格的顺序位置参数 - 默认参数 - *args - **kwargsdef complex_func(a b c10 *args **kwargs): print(fa{a} b{b} c{c}) print(fargs: {args}) print(fkwargs: {kwargs}) complex_func(1 2 3 4 5 nameAlice age25) # 输出 # a1 b2 c3 (12给了ab3覆盖了c的默认值10) # args: (4 5) (剩余的位置参数打包成元组) # kwargs: {‘name’: ‘Alice’ ‘age’: 25} (关键字参数打包成字典)理解这个顺序是写出健壮、灵活函数的关键。4. 返回值函数的“产出物”与None的奥秘函数不仅仅可以执行操作如打印更重要的是它能“计算”并“返回”一个结果供调用者使用。这是通过return语句实现的。4.1 return语句的作用与特点结束函数执行一旦执行到return函数会立即停止后续代码不再运行。返回数据将return后面的表达式的值传递回函数调用处。def get_max(num1 num2): if num1 num2: return num1 else: return num2 print(这行代码永远不会被执行) # 因为函数在上一行已经返回了 max_value get_max(5 10) print(max_value) # 输出104.2 没有return语句会怎样如果一个函数没有return语句或者return后面没有跟任何值Python会默认返回一个特殊值None。def just_print(): print(我只打印不返回) result just_print() print(f”函数的返回值是 {result} 类型是 {type(result)}“) # 输出 # 我只打印不返回 # 函数的返回值是 None 类型是 class ‘NoneType’4.3 深入理解NoneTypeNone在Python中是一个单独的数据类型NoneType它表示“空”、“无”或“没有值”。它不等于空字符串””、数字0或空列表[]。它是一个独立的单例对象内存中只有一个None。判断一个变量是否为None必须使用is或is not而不是。x None if x is None: # 正确做法 print(x是None) if x None: # 不推荐虽然有时能工作但不符合Python风格 print(这样判断不严谨)为什么因为is比较的是两个对象在内存中的身份id是否相同而比较的是值。对于None这种单例对象用is判断更准确、更快也是PEP8编码规范所推荐的。在函数中None常被用作默认返回值或者作为操作失败的标志。例如在一个查找函数中如果没找到目标返回None就是一种常见的模式。def find_user(users user_id): for user in users: if user[‘id’] user_id: return user # 循环结束都没找到隐式返回 None user_list [{‘id’: 1 ‘name’: ‘Alice’} {‘id’: 2 ‘name’: ‘Bob’}] user find_user(user_list 3) if user is None: print(“未找到该用户”)5. 函数说明文档与开发工具辅助清晰的文档是优秀代码的一半。函数说明文档Docstring就是函数的自述文件。5.1 如何编写好的DocstringPython官方推荐使用三引号”””将文档字符串包裹起来并紧跟在函数定义的下方。常见的格式如Google风格如下def calculate_bmi(weight_kg height_m): 根据体重公斤和身高米计算身体质量指数BMI。 计算公式为 BMI weight / (height ** 2) 参数: weight_kg (float): 体重单位为公斤。 height_m (float): 身高单位为米。 返回: float: 计算得到的BMI值。 如果身高小于等于0则返回None。 示例: calculate_bmi(70 1.75) 22.857142857142858 if height_m 0: return None return weight_kg / (height_m ** 2)好的文档应该包含函数功能简述、参数说明类型和含义、返回值说明类型和含义、可能抛出的异常以及可选的示例。这能极大地方便你自己和协作者日后阅读和使用代码。5.2 在PyCharm中利用文档在PyCharm这类现代IDE中函数文档的作用被发挥得淋漓尽致。悬停提示当你在代码中调用calculate_bmi时将鼠标光标悬停在函数名上PyCharm会自动弹出一个小窗口显示该函数的签名和文档字符串的第一部分让你快速了解如何使用。参数提示当你输入函数名和左括号calculate_bmi(后PyCharm会弹出一个提示框显示函数需要哪些参数以及当前输入光标所在参数的含义。快速查看文档在函数名上按CtrlQ(Windows/Linux) 或CmdJ(Mac)可以打开一个独立的文档查看窗口显示完整的文档字符串。自动生成在函数定义行下方输入三引号”””并回车PyCharm会根据函数签名参数名自动生成一个文档字符串模板你只需要填充内容即可。养成写文档的习惯并善用IDE的提示功能能让你在开发复杂项目时事半功倍减少“这个函数是干嘛用的”、“这个参数该传什么”之类的困惑。6. 函数的嵌套调用与执行流程函数不仅可以被直接调用还可以在一个函数的内部调用另一个函数这称为函数的嵌套调用。它是构建复杂程序逻辑的基石。6.1 嵌套调用的执行过程理解嵌套调用的关键在于理解“调用栈”的概念。你可以把它想象成一叠盘子。当你调用一个函数时就把这个“函数盘子”放在最上面压栈。这个函数执行时如果它又调用了另一个函数就把新的“函数盘子”再压上去。被调用的函数执行完毕后它的“盘子”就被拿走出栈控制权回到调用它的那个函数。def func_a(): print(开始执行函数A) func_b() # 在A内部调用B print(回到函数A继续执行) return A完成 def func_b(): print(开始执行函数B) func_c() # 在B内部调用C print(回到函数B继续执行) return B完成 def func_c(): print(执行函数C) return C完成 result func_a() print(f”最终结果 {result}“)输出结果开始执行函数A 开始执行函数B 执行函数C 回到函数B继续执行 回到函数A继续执行 最终结果 A完成执行流程如下主程序调用func_a()func_a入栈。func_a打印第一句然后调用func_b()func_b入栈压在func_a上面。func_b打印第一句然后调用func_c()func_c入栈压在func_b上面。func_c执行打印并返回。func_c出栈。控制权回到func_b栈顶现在是它它继续执行打印第二句并返回。func_b出栈。控制权回到func_a它继续执行打印第二句并返回。func_a出栈。控制权回到主程序打印最终结果。这个过程清晰地展示了代码执行的层次感和顺序。在调试程序时IDE的调试器会直观地展示这个调用栈帮助你定位问题发生在哪一层函数调用中。6.2 嵌套调用的实际应用模块化设计嵌套调用鼓励我们将大问题分解成小问题。每个函数只负责一个明确、简单的任务。通过嵌套调用这些小任务被组合起来解决大问题。例如一个处理用户订单的程序main()函数是主流程。它调用validate_order(order)函数验证订单。validate_order内部可能又分别调用check_inventory(item)检查库存和validate_payment(payment_info)验证支付。check_inventory内部可能调用query_database(product_id)查询数据库。这种“分而治之”的思想让代码结构清晰每个函数都易于编写、测试和维护。7. 函数中的变量作用域分清“家里”和“外面”变量在哪里定义决定了它在哪些地方可以被访问。这就是作用域。理解作用域是避免变量命名冲突和诡异Bug的关键。7.1 局部变量 vs 全局变量局部变量在函数内部定义的变量。它只在定义它的那个函数内部有效存活函数执行完毕后局部变量就会被销毁。不同函数里的局部变量即使名字相同也互不干扰。def func1(): local_var “我在func1里” # 局部变量 print(local_var) def func2(): local_var “我在func2里” # 另一个同名的局部变量 print(local_var) func1() # 输出我在func1里 func2() # 输出我在func2里 # print(local_var) # 这里如果取消注释会报错NameError 因为local_var在外面不存在全局变量在函数外部、模块最顶层定义的变量。它在整个模块文件内都有效。global_var “我是全局变量” def read_global(): print(f”在函数内部读取全局变量 {global_var}“) # 可以读取 def modify_global_wrong(): global_var “我想修改它” # 错误这实际上是创建了一个新的同名局部变量 print(f”在函数内部 {global_var}“) modify_global_wrong() # 输出在函数内部 我想修改它 print(f”全局变量实际是 {global_var}“) # 输出全局变量实际是 我是全局变量 没被修改7.2 global 和 nonlocal 关键字如果想在函数内部修改全局变量必须使用global关键字声明。global_var “我是全局变量” def modify_global_right(): global global_var # 声明要修改的是全局变量global_var global_var “我真的修改了它” print(f”在函数内部 {global_var}“) modify_global_right() # 输出在函数内部 我真的修改了它 print(f”全局变量现在是 {global_var}“) # 输出全局变量现在是 我真的修改了它nonlocal关键字用于嵌套函数中。它声明要修改的变量不是局部变量也不是全局变量而是外层非全局作用域的变量。def outer(): outer_var “我在outer函数里” def inner(): nonlocal outer_var # 声明要修改的是外层函数outer的变量 outer_var “我被inner修改了” print(f”inner: {outer_var}“) inner() print(f”outer: {outer_var}“) # 这里打印的也是修改后的值 outer() # 输出 # inner: 我被inner修改了 # outer: 我被inner修改了经验之谈慎用全局变量虽然全局变量用起来方便但它破坏了函数的封装性使得函数的行为依赖于外部状态难以理解和调试。在多人协作或大型项目中滥用全局变量是“面条代码”的罪魁祸首之一。一个好的实践是尽量将函数设计为“纯函数”即函数的输出只依赖于输入参数不修改任何外部状态包括全局变量。这样函数更易于测试和推理。如果多个函数需要共享状态考虑使用类Class来封装数据和操作这是更面向对象、更可控的方式。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门