news 2026/10/14 23:54:03

Python自学第四天:函数与模块实战记录

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Python自学第四天:函数与模块实战记录

Day4 -- Python。今天这篇不是课程笔记,是我自己连续学Python第四天的完整记录。前三天先后搞定环境搭建、基础语法和流程控制,说实话已经能写点能跑的小东西了,但到函数这里还是卡了一下——不是看不懂语法,而是半天没转过弯来:为什么非要绕这层?直到上手写了个任务清单小工具,才真正体会到函数的价值不是“能用”,而是“用起来清爽”。

这篇内容适合两类人看:一类是跟我一样零基础在自学的,另一类是想教别人学Python的。我会把我今天实际敲过的代码、踩过的坑、总结出来的理解方式全部写出来。不会绕弯子讲什么大理论,就是一台电脑、一个编辑器、一天时间能消化掉的东西。

1. 第四天学习路径:为什么这个阶段必须啃函数

1.1 前三天基础盘点

在聊第四天干了什么之前,先快速过一下之前的基础,不然没法说清楚今天的进度是怎么衔接的。

Day1 搭环境,装Python解释器,选编辑器,跑通第一个print("Hello, World")。这一步没太多好讲的,装官方版本,写代码用VS Code,Python版本选了3.11,日常写脚本完全够用。

Day2 啃变量、数据类型、运算符。整数、浮点数、字符串、列表、字典、布尔值,每种类型都做了几个小练习。花时间最多的是列表和字典的区别:列表是有序排列的箱子,按编号取;字典是带标签的储物柜,按名字取。

Day3 学条件判断和循环。if/elif/else的路径判断,for和while两种循环。说实话前三天学到的东西,拼拼凑凑已经能做点简单的小工具了。但代码一旦超过50行就开始头疼,因为所有逻辑全塞在一块写,一个地方改错,整段逻辑跟着乱。

1.2 第四天的核心目标:函数与模块

之所以把第四天定为“函数日”,是因为前三天那种“从上到下逐行执行”的写法,到这一步已经出现了明显的瓶颈。

我举个例子你就明白了。假设你要在一段代码里连续三次计算某个商品打折后的价格,流程是:原价乘以折扣率,再判断是否满减,最后加上运费。没有函数之前,你得把这段逻辑原样复制三遍。这带来两个问题:第一,代码巨长且密集,读起来累;第二,如果优惠规则变了,你得同时改三个地方,漏改一个就是bug。

函数解决的就是这个问题:把一段逻辑“打包”成一个可以反复调用的单元,用的时候传不同的参数进去,得到对应的结果。改逻辑的时候只改函数内部,用到它的地方自动跟着变。

模块则是更上一层的打包方式。函数打包的是逻辑,模块打包的是多个相关函数和变量的集合。这样写出来的代码,每个文件只管一个功能,比如calc.py管计算,file_tools.py管文件读写,互不干扰,也方便别的项目复用。

还有一个重要目标:学会怎么调试报错。函数引入了“调用栈”的概念,错误不再只是简单的语法提示,而是会告诉你“错误发生在哪个函数里、这个函数是被谁调用的”。今天看到第一眼Traceback (most recent call last)的时候有点懵,后来才发现这其实是Python在非常友好地帮你定位问题。

2. 函数:核心概念拆解

2.1 函数就是一条自动化流水线

不用把函数想得太玄。我理解它的方式是用“自动售货机”来类比:你往里面投硬币、选商品编号,它经过内部处理,吐出对应的饮料。函数也一样——你传给它参数,它执行内部的逻辑,最后返回一个结果给你。

来看今天写的第一个函数:

def calculate_discount(price, discount_rate=0.8): """计算折后价格, 保留两位小数""" final_price = price * discount_rate return round(final_price, 2)

def是定义函数的关键字,后面跟函数名。括号里面是参数列表,price和discount_rate是调用时需要传入的数据。参数后面的=0.8是默认值,意思是调用的时候如果没传折扣率,就默认按八折算。

函数体内部先算price * discount_rate,再用round保留两位小数,return把结果交给外部调用者。

调用方式是这样的:

result = calculate_discount(199, 0.75) print(result) # 149.25

一个有意思的细节是:calculate_discount(199)也可以直接调用,因为折扣率给了默认值,第二参数可以不传。默认参数这种机制尤其适合“大部分时候用一个值,偶尔换另一个值”的场景——比如全场默认打八折,但今天有个会员日要打七五折,那你只需要在调用的时候显式传0.75就行。

函数体第一行那段"""折后价格, 保留两位小数"""是文档字符串,叫 docstring。它不参与计算,但是一个非常好的习惯:过了几天你再回来看这段代码,或者别人接手你的项目,看一眼文档字符串就知道这个函数是干嘛的,不用逐行推断。

2.2 参数传递的四种姿势

今天花了比较多的时间搞明白参数这门学问。Python函数按照传参方式不同,有四种常见写法,数量掌握之后,实际写代码会灵活很多。

第一种是位置参数。这是最直观的:调用时按定义顺序传值。比如calculate_discount(199, 0.75),199 对应price,0.75 对应discount_rate。一旦参数多了,位置就容易记混。

第二种是关键字参数。调用时直接把参数名写出来:calculate_discount(discount_rate=0.75, price=199)。这种方式的好处是顺序无所谓了,语义也非常清晰,看代码的时候一眼就能明白每个值的用途。

第三种是默认参数。前面已经演示过:定义时给参数一个默认值,调用时可以不传。这里有一个容易误入的坑,我后面会专门讲:默认参数如果用了可变类型(比如列表、字典),会埋下非常隐蔽的bug。

第四种是可变参数。用*args接收任意多个位置参数,用**kwargs接收任意多个关键字参数。这个名字也是约定俗成的,加不加都行,但args和kwargs基本成了社区共识。

def log_orders(*args, **kwargs): print("位置参数:", args) print("关键字参数:", kwargs) log_orders(101, 102, customer="某客户", amount=88.5)

这个函数能接收任意数量的参数。101和102会被收进args元组,customer和amount会被收进kwargs字典。

可变参数的典型意义在于:写通用工具的时候,你事先不知道调用方会传多少个数据,用*args可以通吃。今天我只做了初步练习,但这段代码让我一下子明白了Python在设计上“灵活到近乎放纵”的哲学。

2.3 作用域:今晚最大的坑

说真的,函数语法本身不难。今天花了最多时间的是理解作用域。什么叫作用域?一句话:变量在哪里能被看见,在哪里就失效。

先看一段让我今晚反复折腾的代码:

total = 0 def add_to_total(value): total = total + value return total print(add_to_total(5))

看起来没问题对吧?函数里读取全局的total,加上value,再返回。但一运行就报错:

UnboundLocalError: local variable 'total' referenced before assignment

这个报错的核心意思:Python在函数内部发现有代码对total做赋值操作,于是把total判断为局部变量。既然是局部变量,就要遵守“先赋值再使用”的规则。但total = total + value这行,右边的total在赋值之前就被读取了,于是报错。

解决办法是给函数内部的total加上global声明,明确告诉Python:这里操作的是全局变量。

total = 0 def add_to_total(value): global total total = total + value return total

但说句实在话,global能不用就不用。全局变量意味着这个函数不再“独立”,它依赖外部的一个状态,一旦项目复杂起来,你很难追踪到底是哪个函数偷偷改了total。更好的做法是把total作为参数传进函数,返回值再接管新结果:

total = 0 total = add_to_total(total, 5) total = add_to_total(total, 10)

这样够明确:每一次调用输入输出都被显式地写出来。我踩过这个坑之后,形成的习惯是:函数尽量不依赖函数外部的东西,所有的输入靠参数,所有的输出靠返回值。

如果函数里面想用全局变量但又不需要修改它,那么直接读就行,不报错;一旦你想赋值,就必须global声明或者改用返回值。这是我今天最深刻的记忆点,也想提醒每个初学者:看到UnboundLocalError先别慌,十有八九就是局部变量和全局变量撞了名字。

3. 模块:给代码分区,管理起来才不慌

3.1 模块解决的是“文件太长”的问题

函数是把逻辑打包,模块是把函数打包。当一个项目里函数越来越多,比如几十个函数都堆在一个文件里,你要找一个函数得滚半天;而且不同函数之间可能操作不同类型的事务,混在一起会影响维护。

模块化的思路是:按职责拆分文件。今天我的模拟项目里就分了三个文件:

  • main.py:程序入口,负责交互流程
  • task_manager.py:任务数据的增删改查
  • file_helper.py:负责保存和读取文件

每个文件各自独立,但可以互相引用。这样最大的好处是:定位bug快。如果报错信息显示task_manager.py第几行有问题,那我直接锁定这个文件就行,不需要翻遍整个项目。

3.2 import 的几种用法

Python引入模块有三种常用方式,今天都试了一遍。

最直接的是import加模块名:

import task_manager

调用的时候要带模块名前缀:task_manager.add_task(...)。这样做的好处是命名空间清晰,不同模块里即使有同名函数也不会冲突(比如两个模块都定义了parse)。

第二种是from直接导入具体的函数:

from task_manager import add_task, remove_task

调用的时候直接add_task(...)就行,不用前缀。代码更短更简洁,代价是如果导入的函数同名,后导入的会覆盖先导入的,而且代码可读性稍差——别人看到add_task不知道它来自哪个模块。

第三种是用as起别名:

import file_helper as fh

适用于模块名太长或频繁使用的情况,比如import datetime as dt在写日期处理的场合几乎成了标配。

初学者我建议先养成import 模块名的习惯,哪怕调用时多写几个字。等到项目结构慢慢变大,你自然能感受到什么时候需要切换成from...import。

3.3 pip 安装第三方库

标准库之外,Python的价值有大半来自第三方库——别人写好、发布到公共仓库的代码,装好就能用。安装工具叫 pip,命令行执行即可:

pip install requests

这里要跟初学者特别说一下怎么确认装成功了。在代码里写import requests,如果没报ModuleNotFoundError,就是装好了。另外国内网络环境下 pip 有时候很慢,我实测下来可以换用国内镜像源,比如清华、阿里云的镜像,速度会快很多。命令是:

pip install requests -i https://pypi.tuna.tsinghua.edu.cn/simple

不过提醒一句:第三方库固然省事,但它本质上是“别人的代码”,要看看它是否还在维护、文档是否齐全。毕竟你未来项目的依赖会越来越多,选库就像选搭子,靠谱非常重要。

4. 实战:一个任务清单小工具

4.1 功能设计和代码结构

今天学函数和模块,单纯做练习题很容易无聊。我给自己定了一个小项目:做一个命令行版的任务清单管理器。功能需求很简单:

  1. 添加任务
  2. 查看全部任务
  3. 标记任务完成
  4. 删除任务
  5. 退出程序

为什么要做这个?因为它同时用到了列表、字典、循环、条件判断和函数,是一个把前三天和今天学的知识完整串起来的项目。功能不难,但参考价值很高。

设计上,我先确定数据格式:任务用字典表示,比如{"id": 1, "name": "买菜", "done": False},所有任务放在一个列表里。然后按职责拆分函数:

  • show_menu():打印操作菜单
  • add_task(tasks, name):添加任务,返回新的列表
  • list_tasks(tasks):打印所有任务
  • complete_task(tasks, task_id):按ID标记完成
  • remove_task(tasks, task_id):按ID删除任务

每个函数只做一件事,互不干扰。这就是函数设计的核心思路:单一职责。

4.2 完整代码与运行过程

先写函数部分,这是今晚的重点:

def show_menu(): print("\n欢迎使用任务清单工具") print("1. 添加任务") print("2. 查看全部任务") print("3. 标记任务完成") print("4. 删除任务") print("0. 退出程序") def add_task(tasks, name): task_id = len(tasks) + 1 task = {"id": task_id, "name": name, "done": False} tasks.append(task) return tasks def list_tasks(tasks): if not tasks: print("当前还没有任务。") return for task in tasks: status = "[已完成]" if task["done"] else "[未完成]" print(f"{task['id']}. {task['name']} {status}") def complete_task(tasks, task_id): for task in tasks: if task["id"] == task_id: task["done"] = True print(f"任务 '{task['name']}' 已标记完成。") return tasks print("没有找到对应任务。") return tasks def remove_task(tasks, task_id): for task in tasks: if task["id"] == task_id: tasks.remove(task) print(f"任务 '{task['name']}' 已删除。") return tasks print("没有找到对应任务。") return tasks

然后是主程序。这里用循环让程序一直运行,根据用户输入来调用不同的函数:

import sys def main(): tasks = [] while True: show_menu() choice = input("请输入操作序号: ").strip() if choice == "1": name = input("请输入任务名称: ").strip() tasks = add_task(tasks, name) elif choice == "2": list_tasks(tasks) elif choice == "3": task_id = int(input("请输入要完成的任务ID: ").strip()) tasks = complete_task(tasks, task_id) elif choice == "4": task_id = int(input("请输入要删除的任务ID: ").strip()) tasks = remove_task(tasks, task_id) elif choice == "0": print("退出程序。") sys.exit(0) else: print("无效输入,请重新选择。") if __name__ == "__main__": main()

整个流程就是:启动 -> 显示菜单 -> 等待输入 -> 根据输入做对应操作 -> 循环。这种模式在纯命令行小工具里非常常见,以后写更复杂的项目,主循环也是这个思路:解析输入,分发到处理函数,等待下一次输入。

运行效果就是这样的对话式界面:

欢迎使用任务清单工具 1. 添加任务 2. 查看全部任务 3. 标记任务完成 4. 删除任务 0. 退出程序 请输入操作序号: 1 请输入任务名称: 买菜 欢迎使用任务清单工具 ... 请输入操作序号: 2 1. 买菜 [未完成]

代码跑通的那一刻,说实话是有一点点成就感的,因为这是第一次把函数、列表、字典、循环都拼在一起,完成一个真正有交互的“产品”。

4.3 这个案例告诉我们什么

把任务清单写完再回头看,函数的意义就很明朗了。add_task、complete_task、remove_task这三个函数各有分工,主程序只需要知道“调谁、传什么、拿什么”,完全不必关心它们内部的实现细节。

这就是抽象和封装在现实项目中的价值:越往上层越简单,越往下层越专注。主循环像是一个指挥中心,每个函数像是一个专项小组,指挥中心只负责调度,出事却知道自己该找哪个小组。

另外,用返回值return tasks的方式更新列表,同时也是在练习一种“不可变更新”的思路——纯函数风格。虽然 Python 里列表是可变对象,直接在函数内部tasks.append(task)就能影响外部变量,但显式返回新列表会让调用处的代码意图更透明。这个习惯在以后学更复杂的函数式写法时会很有优势。

5. 常见报错与排查实测记录

5.1 报错信息速查表

今天各种报错真是不少,我把实际遇到的按“现象 -> 原因 -> 解决”整理成了表格。这些应该是Python初学者最高频的报错类型了,值得反复看。

报错信息典型原因解决思路
SyntaxError: unexpected indent不该缩进的地方多按了Tab或空格检查代码块缩进是否一致,删除多余缩进
NameError: name 'xxx' is not defined变量名拼写错误或使用了未定义的变量检查拼写和定义位置
TypeError: add_task() takes 1 positional argument but 2 were given调用函数时传参数量不匹配核对函数定义与实际调用参数数量
TypeError: unsupported operand type(s)不同类型数据做了运算,比如字符串加整数检查运算双方的数据类型
ValueError: invalid literal for int() with base 10输入内容不能转成整数,比如输入了字母转换前先校验输入内容,或使用异常处理
IndexError: list index out of range索引超出列表长度确认列表长度,或改用len()判断
UnboundLocalError: ... referenced before assignment函数内赋值变量与外层变量冲突,变量被当作局部变量使用global声明或改传参数

5.2 比报错更隐蔽的坑:可变默认参数

有些错误不是运行时报出来的,而是程序结果悄悄错了。最著名的就是“可变默认参数陷阱”,我今天特地把这个坑挖出来看了一下。

看这段:

def add_task_with_tag(tag, tags=[]): tags.append(tag) return tags

第一次调用没问题:add_task_with_tag("学习")返回["学习"];第二次调用add_task_with_tag("运动")你可能会以为返回["运动"],但实际返回的是["学习", "运动"]。

原因在于:默认参数tags=[]在函数定义的时候只创建一次。之后每次调用如果没有显式传入新列表,用的都是同一个列表对象,所以状态被累积下来了。当函数体修改了这个列表,修改就会一直保留。

正确的写法是:

def add_task_with_tag(tag, tags=None): if tags is None: tags = [] tags.append(tag) return tags

把默认值改成None,然后在函数内部重新创建列表。这样每次调用都是独立的数据。这个坑我在实际项目中遇到过不止一次,尤其当你写一个工具库给别人用,对方连续调几次之后发现有数据残留,排查起来相当费时间。

5.3 今天总结的几招排查技巧

除了具体报错,今天养成的几个排查习惯也很值得一说。

第一,学会读Traceback。报错信息不要只看最后一行,从下往上看,它会告诉你哪个文件的第几行触发了什么错误,以及这个函数是被谁调用的。后面那一串调用链信息对定位问题非常有用。

第二,用print()在半路输出中间变量。写函数的时候,不确定某个值到底算对没有,我会在关键计算前后加个临时print(),把中间量打出来看一眼。这对新手来说是成本最低的调试手段。

第三,先复制最小复现代码再改动原程序。遇到复杂错误,我会把出问题的那几行单独抽出来,放到一个临时文件里测试,而不是在原文件里大改特改。保持最小复现,能更快锁定真正的原因。

6. 卡住时的破局思路与打卡建议

在结尾之前,想聊聊一个被很多人忽略的学习方法问题。我今天在作用域那里卡了很久,一度有点烦躁。后来让我静下来的方法是:先不纠结原理,照着示例代码敲一遍,跑通之后再慢慢改几个参数,观察输出变化。原理是在动手过程中突然理解的,而不是靠死记硬背。

对正在用“Day N”打卡方式自学编程的人,我有一个很实际的建议:每天不要贪多,但要保证当天能输出一个亲手跑通的结果。哪怕是一个很小的功能、一段十几行的脚本,也比你读十页语法教程有用。因为写代码是一项肌肉记忆型的技能,只有手动了,脑子才真正跟上。

不管你是用Day几的进度在学,核心都是保持节奏。今天学的函数是后面理解面向对象编程的基石,明天我的计划是试着把这个任务清单工具升级成带文件保存的版本,下次再继续分享。

学习日记就到这里,最后说一句:所有我写的代码都是从第一天开始一行一行积累出来的。你不一定需要每天都写很多,但坚持每天都写一点点,总有一天你会发现回头写的脚本,跟三五天前已经不是一个量级了。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/14 23:53:49

高并发概率抽奖系统HCUGOA:蝗虫式流量下的架构与整活复盘

做社区运营和活动系统这行久了,你会习惯一个场面:活动公告一发出,服务器瞬间涌入几千个平时根本没见过的人,抽完奖、领完东西,下一秒就消失得干干净净。我们把这群人叫“蝗虫”,跟自然界那玩意儿一样&#…

作者头像 李华
网站建设 2026/10/14 23:50:58

植物叶片病害YOLO数据集4900张实战解析与训练全攻略

做植物病害识别这块,如果你也是从零开始攒数据、标框、转格式、调参一路走过来的,应该懂这份标题为"植物叶片病害检测数据集 | 4900张YOLO智慧农业数据集"的含金量。我拿到这份数据集实测了一轮,把训练流程、格式细节、坑点全部走了…

作者头像 李华
网站建设 2026/10/14 23:48:40

开题答辩全攻略:校园物物交换系统设计实战复盘

如果让我只保留一条开题答辩的经验,我会说是:把“我要做个系统”变成“我要解决一个具体问题”。拿“面向高校校园的物物交换系统设计与实现”这个题目来说,它看起来是个常见的课设选题,但答辩现场老师问的从来不是“系统能不能做…

作者头像 李华
网站建设 2026/10/14 23:48:39

MFC对话框美化实战:BCGControlBar Pro集成指南

1. 项目概述:为什么MFC程序员现在还在认真考虑BCG?你打开一个十年前的老项目,界面还是Windows 98风格的灰白按钮、无边框静态文本、毫无层次感的对话框——不是因为团队技术落后,而是因为MFC本身不带现代UI渲染能力。它像一辆可靠…

作者头像 李华
网站建设 2026/10/14 23:48:16

Kubernetes控制面雪崩:从Connection Refused到磁盘满的排查与恢复

作为一个常年跟Kubernetes 和 KubeSphere 打交道的人,我最怕看到的告警其实不是“节点离线”,而是控制台突然打不开、kubectl 敲什么都是connection refused。这种故障表面上看是网络问题,但排查到最后,十次里有六七次都不是网络本…

作者头像 李华
网站建设 2026/10/14 23:48:10

梦幻花园调研:三消+建造闭环与关卡难度曲线设计

1. 这次调研的起点:为什么盯着梦幻花园不放 做游戏调研这些年,我有个习惯:每隔一阵子,就会挑一款在榜单上待了很久、但你平时未必会认真玩的产品,强迫自己以普通玩家的身份,从零开始体验一遍。不是看它的活…

作者头像 李华