ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Python入门第5天:从变量列表函数到文本统计实战

Python入门第5天:从变量列表函数到文本统计实战 今天是我 python 入门第 5 天前四天差点把我劝退——不是看不懂代码而是不知道学这些东西到底能干嘛。print() 会了变量会了if 会了但一合上教程脑子里还是空的。直到第 5 天我试着把变量、列表、函数和文件读写串起来做了一个超简单的文本统计器才真正有了“我会编程”的感觉。如果你也正学到第 5 天或者刚接触 Python 四五天、正在犹豫要不要继续这篇内容就是写给你的。我会把第 5 天的学习内容拆开讲包括变量和类型转换、列表切片、函数定义以及如何用这些知识完成一个能跑通的小项目。前四天你已经装好了 Python、写过几个 print 和 if那今天就往“组合逻辑”的方向走一步。放心不需要任何天赋只要愿意跟着敲天黑前你也能跑出第一个属于自己的小工具。1. 第 5 天学什么学习路线与思维转变1.1 为什么第 5 天是个分水岭很多人学 Python 死在第四五天不是因为内容突然变难而是因为前几天的知识都是“孤立的点”。第 1 天装环境第 2 天学 print 和变量第 3 天学 if 和 while第 4 天学 for 和列表到了第 5 天如果还继续一个知识点一个知识点地往下看你很快就会陷入“看懂了但不会写”的状态。第 5 天的正确打开方式是把前面学过的变量、类型、列表、循环、条件判断这些点用一个新的语法结构“函数”串起来。就像你手里有了一堆积木之前只是把它们挨个摆出来看今天才是第一次拼成一个小房子。所以第 5 天不要贪多更不要跑去啃什么高阶内容。我给自己定的任务只有三件彻底搞懂变量的本质和类型转换熟练使用列表和切片开始用 def 封装代码。如果能再来一个小项目收尾那这一天的学习效率会非常高。1.2 我的一天时间安排少而精才能真学会第 5 天我给自己安排的时间是上午一小时、下午一小时、晚上一小时中间穿插动手练习绝不一天刷十节课。上午只解决“变量和类型转换”。因为 input() 在 Python 里返回的是字符串而你要拿它做数字运算时总会碰到“转 int 却报错”之类的问题。把这一块搞透后面写任何交互程序都不卡壳。下午只看“列表和切片”。列表是 Python 里最常用的容器切片更是省事神器。我见过很多人学了半年还是背不住 list[::-1] 的作用其实就是因为当时没搞懂三段的边界规则。晚上开始写函数再做一个文本统计的小项目。这个小项目会逼着你把上午和下午的知识全用一遍用了才知道自己哪里没懂。你可以根据自己作息调整时间但核心原则不变宁可少学两个新语法也要把学过的语法练到手熟。第 5 天不是比谁看的多而是比谁能在不看教程的情况下独立写出一个小程序。1.3 从“抄代码”到“拆代码”的习惯转换前四天你可能都是照着教程一行行敲这很正常。但第 5 天开始我强烈建议你做一件看起来很“破坏”的事把别人的代码先运行成功然后故意改坏它。比如教程里写了for i in range(5):你就改成for i in range(5, 0, -1):看看会发生什么函数里明明有 return你删掉 return 再打印一下调用结果列表切片本来是a[1:3]你改成a[3:1]看看结果是不是空。这种玩法的价值在于报错信息会成为你的老师。新手普遍怕报错一看到红字就慌但 Python 的报错其实写得很清楚它会告诉你在哪个文件、哪一行、发生了什么错误。你故意把代码弄坏就能在安全的状态下提前熟悉各种报错真正写代码时遇到它们就不会慌。从第 5 天起把心态从“复读机”切换到“实验员”。遇到不懂的就去改、去试、去观察这比任何教程都管用。2. Python 变量进阶标签不是盒子类型转换别踩坑2.1 用 id() 看清变量和对象的关系很多教程告诉你“变量就像盒子里面装了值”这句话对初学者友好但到了第 5 天你会发现它可能会误导你。真正的理解是Python 变量更像一个贴在对象上的标签。你用a [1, 2, 3]时并不是把 [1,2,3] 这个“盒子”放进变量 a 里而是把变量名 a 指向了内存中那个列表对象。如果再加上一句b a那 b 和 a 指向的是同一个列表对象。试试下面这段代码a [1, 2, 3] b a b.append(4) print(a) # [1, 2, 3, 4] print(b) # [1, 2, 3, 4] print(id(a), id(b)) # 两个值相同很多人会认为 a 没有变实际上 a 也跟着变了。原因就是 a 和 b 只是同一个列表的两个标签。想复制一份独立的列表要用切片或者copy()b a[:] b.append(5) print(a) # [1, 2, 3, 4] a 不变可以这样理解a 是某个文件在桌面上的快捷方式b 是另一个快捷方式两个快捷方式指向同一个文件。你修改文件内容不管从哪个快捷方式打开内容都会变。想复制文件本身必须重新创建一份。判断两个变量是否指向同一个对象用id()或is而不是。只比较值是否相等is才比较身份是否相同。这个知识点第 5 天能理解到“清楚”程度就够了后面学函数参数传递时还会再遇到。2.2 input() 返回字符串数字计算前记得转类型第 5 天最常见的报错之一是TypeError: can only concatenate str (not int) to str。出现这个错十有八九是你忘了处理 input() 的返回类型。官方文档写得很明白input() 接收用户输入时返回的一定是字符串。哪怕用户输入的是“18”你在代码里拿到的也是字符串18不是整数 18。所以当你写“明年年龄”这种程序时必须先转类型age input(请输入你的年龄) print(type(age)) # class str age int(age) next_year_age age 1 print(明年你, next_year_age, 岁)这里如果用age 1直接计算Python 并不知道该怎么把字符串和整数相加所以直接抛 TypeError。解决办法就是在计算前手动转成 int。同理5 3你会得到53这是个字符串拼接不是整数加法。Python 不会因为你看到 5 和 3 长得像数字就自动帮你做加法。这是很多新手第 5 天容易懵的地方实际跑一遍就明白了。2.3 类型转换的四个隐藏陷阱除了一头一尾的字符串和整数互转Python 的类型转换里还有几个特别容易坑人的点我单独拿出来讲。第一个是int(3.9)。很多人以为它会四舍五入成 4其实结果永远是 3它只会把小数部分直接砍掉。想四舍五入要用round(3.9)。第二个是int(3.9)。如果你想把字符串 “3.9” 变成整数直接 int() 会抛ValueError。正确做法是先转成浮点数再转成整数int(float(3.9))。第三个是bool(False)。这是一个经典误解因为字符串False非空所以转成布尔值时是 True。在 Python 里只有一个空字符串会变成 False其他非空字符串都是 True。同样地bool([0])是 True因为列表里有一个元素而空列表bool([])才是 False。第四个是True 1。布尔类型其实是整数类型的子类True 是 1False 是 0。所以在做统计或者遇到奇怪的算术结果时你要想到这个特性。我把这些整理成了一张速查表表达式结果原因int(10.9)10直接截断小数不四舍五入int(10.9)vsround(10.9)10 vs 11想要四舍五入请用 roundint(float(3.9))3先转浮点才能再转 intbool(False)True非空字符串都是 Truebool([])False空列表是 FalseTrue 12bool 是 int 的子类看到这些你可能会觉得 Python 有点“不讲武德”但这些特性都有历史原因和设计逻辑。第 5 天只需要记住类型转换不一定按人的直觉走动手验证永远比猜靠谱。2.4 自测清单第 5 天能不能过关我给自己出过一套“类型转换自测题”每次都像在做小游戏。你也能试试先预判结果然后运行代码验证print(int(10.5)) print(float(4.2)) print(str(123) 4) print(bool()) print(bool(0)) print(5 3) print(5 3) print(int(12.9)) # 这句会报错但有价值如果大多数结果你都能答对说明类型转换这一关过了。如果答错也别灰心把报错信息和结果对照看一遍比背十遍笔记都有效。第 5 天开始我建议你养成一个习惯写代码时多用type()函数检查数据。比如print(type(变量))跑一下看到底是str还是int还是list。别怕那行输出碍眼它能帮你少走很多弯路。3. 列表与切片Python 最常用的“容器手感”3.1 增删改查的五个日常方法列表是你往后写爬虫、写数据分析、写自动化脚本时天天打交道的数据类型。第 5 天不需要记所有方法但下面这几个必须形成肌肉记忆。append()在列表末尾追加一个元素这个最常用比如收集用户输入时就靠它。insert(index, item)在指定位置插入元素比如列表原来有[a, b]你想把x插到第 1 个位置就写lst.insert(1, x)。pop()删除并返回末尾元素pop(index)删除指定位置的元素并返回它。它和remove()最大的区别是pop 靠索引remove 靠值。remove(value)按值删除而且只删除第一个匹配的元素。如果值不存在会抛ValueError。还有个del关键字它不是方法但也很常用del lst[0]删除指定索引位置的元素但不返回。写一个简单例子感受一下shopping [牛奶, 面包] shopping.append(鸡蛋) print(shopping) # [牛奶, 面包, 鸡蛋] shopping.insert(1, 苹果) print(shopping) # [牛奶, 苹果, 面包, 鸡蛋] item shopping.pop() print(item) # 鸡蛋 print(shopping) # [牛奶, 苹果, 面包] shopping.remove(苹果) print(shopping) # [牛奶, 面包]在实际写程序时你要想清楚自己的需求是“需要知道删除的是哪个元素”用 pop是“只要列表里别再有这个值”用 remove是“按位置暴力删除”用 del。我见过不少新手只知道 append结果想删元素时卡半天今天把其它几个也练熟后面写代码会顺畅很多。3.2 切片左闭右开一次搞懂 start、stop、step切片是 Python 最优雅的语法之一也是很多新手第 5 天怎么都记不住的东西。其实它只要抓两个关键词左闭右开、步长。格式是lst[start:stop:step]start 是开始索引stop 是结束索引但 stop 对应的元素取不到。也就是说实际取的是[start, stop)这个半开区间。nums [0, 1, 2, 3, 4, 5] print(nums[1:4]) # [1, 2, 3] print(nums[:3]) # [0, 1, 2] 开头省略 start从 0 开始 print(nums[3:]) # [3, 4, 5] 结尾省略 stop取到末尾 print(nums[::-1]) # [5, 4, 3, 2, 1, 0] print(nums[4:1:-1]) # [4, 3, 2] step 为负数时从右往左取为什么设计成左闭右开因为这样计算区间长度非常方便len(nums[1:4])就是4 - 1 3。而且两个连续区间[1,4)和[4,7)能无缝衔接不会重叠也不会漏数据。负索引也别怕-1是最后一个-2是倒数第二个。负索引和切片配合使用很好用比如想取除第一个和最后一个之外的中间部分可以直接写nums[1:-1]。切片还有一个隐藏技能就是它会返回一个新列表。因此想复制列表时写new_lst old_lst[:]是最简单的深拷贝方式。你可以回看前面变量那节这就是解决“两个变量互相影响”的最直接办法。我把常用切片整理成一张表方便你贴在笔记里写法结果说明lst[:]整个列表副本常用于复制列表lst[1:]去掉第一个元素的列表相当于跳过头lst[:-1]去掉最后一个元素的列表尾部不要lst[::-1]倒序列表最优雅的反转方法lst[::2]隔一个取一个取偶数位元素lst[1:5:2]从索引1到4步长2取[1,3]切片这东西你只看永远觉得玄自己把start、stop、step各改几次跑一遍很快就能形成直觉。3.3 列表推导式用一行代码完成筛选第 5 天能在“看得懂”层面接触一下列表推导式就行但我要说我自己的经历当我第一次看懂列表推导式时整个人都精神了因为到这一步你才真正感受到 Python 的“简洁”不是吹的。假设你想从一个 1 到 10 的列表里筛出所有偶数用普通循环要写四五行列表推导式一行就够了numbers list(range(1, 11)) even_numbers [x for x in numbers if x % 2 0] print(even_numbers) # [2, 4, 6, 8, 10]它的读法是对 numbers 里的每一个 x如果x % 2 0成立就把 x 放进新列表里。本质就是一个 for 加一个 if只是被压缩成了一行。在实际项目里筛选列表、清洗数据时这样写又快又清爽。比如在文件里读进很多行想去掉空行可以写lines [line.strip() for line in lines if line.strip()]但第 5 天要切记一点不要堆多重循环。新手阶段一重循环加一个条件就够用列出所有数字的平方这类简单任务也可以用它squares [x * x for x in range(1, 6)] print(squares) # [1, 4, 9, 16, 25]列表推导式的本质是“一次性构建新列表”如果你想要的是逐行处理、过程复杂那还是老老实实写 for 循环。代码的可读性比“看着很厉害”重要得多。4. 函数与 def让代码变成可以反复使用的积木4.1 def 只是定义名字调用时才会执行前四天你可能已经写过“顺序执行”的代码但到了第 5 天你必须开始习惯用函数组织代码。否则越往后写代码就越是挤成一坨自己想改都找不到位置。函数定义的语法非常简单def greet(name): return 你好 name result greet(小明) print(result) # 你好小明很多新手的第一个困惑是为什么def下面几行在我调用 greet 之前没有输出因为 Python 执行到 def 时只是把函数体存起来并把函数名绑定到这段代码上并不会真的去运行它。真正运行发生在你调用greet(小明)那一刻。这也意味着函数调用必须发生在定义之后。如果你颠倒顺序print(greet(小明)) # NameError: name greet is not defined def greet(name): return 你好 name运行时就会报错因为执行到调用时greet 还没被绑定到函数对象上。你写代码时最好先定义后使用。函数参数分位置参数和关键字参数。位置参数是按顺序传def describe(name, age): return name 今年 str(age) 岁 print(describe(小红, 16)) # 小红今年16岁 print(describe(age16, name小红)) # 关键字参数可以不按顺序现在你可能觉得这很基础但第 5 天养成“把一段独立逻辑放进函数”的习惯后面写项目时才会少吃苦。4.2 return 和 print 的“糊涂账”我认为“函数没有 return 却拿去用”是第 5 天最容易踩、也最让人难受的一个坑。很多人写了这样的代码def add(a, b): print(a b) result add(3, 5) print(result) # 第二行输出 None运行结果第一行是 8第二行是 None。原因是 add 函数只把ab打印了出来并没有把计算结果“返回”给调用者。result 拿到的其实是函数默认的返回值 None。你可以这样理解print 是函数把结果喊出来给你看return 是函数把结果写在纸条上递给你。只看不说话你手里当然什么都没有。想拿到计算结果必须用 returndef add(a, b): return a b result add(3, 5) print(result) # 8这里还有一个隐藏知识点如果函数没有 return它执行到最后一行也会默认返回 None。你可以在自己的代码里写return单独使用来提前结束函数def check(age): if age 18: return 未成年 return 成年检查return和print是否分清的标准就是把函数返回的值交给另一个变量继续用。如果那个变量总是 None就要回头看看自己是不是只写了一堆 print忘了写 return。4.3 默认参数用可变对象会出大乱子最后讲一个你可能在第 5 天不太会遇到但一旦遇到就特别难排查的坑默认参数别用可变对象。看这个例子def add_item(item, target[]): target.append(item) return target print(add_item(1)) # [1] print(add_item(2)) # [1, 2] print(add_item(3)) # [1, 2, 3]第二行你应该期待得到[2]结果却是[1, 2]。原因在于默认参数target[]只在函数定义时创建一次之后每次调用如果没有传 target都会复用这同一个列表。于是第一次调用往里面添了 1第二次再添 2列表就累积了。正确的做法是默认值用 None在函数内部创建新列表def add_item(item, targetNone): if target is None: target [] target.append(item) return target这个坑实际在面试里、复杂项目里经常出现。第 5 天你只要知道“默认值如果写一个可变对象会有共享问题”这件事就够了。你可以把这个案例抄下来等后面学完可变对象拷贝再回头品味。5. 小项目实战用第 5 天所学写一个文本统计器5.1 需求拆解与伪代码今天的高潮项目是写一个文本统计器。别觉得“项目”两个字离你很远这个程序很单纯读取一个纯英文文本文件然后输出文本里有多少个字符、多少个单词以及出现次数最多的 3 个单词。我为什么强调英文因为英文单词天然用空格分隔用 split() 就能切出来。如果是中文一句话连在一起暂时还不适合新手处理。等你以后学了第三方库再回来处理中文也不迟。先把思路写成伪代码读取一个叫 article.txt 的文件得到字符串。用split()按空白字符把文章切成单词列表。遍历单词列表用一个字典记录每个单词出现次数。按出现次数从高到低排序取出前 3 个。打印结果。这个项目把前面学的字符串、列表、字典、循环、函数、切片、排序全都串起来了。第 5 天能跟着这个思路走通就说明你的基础知识开始变成生产力。5.2 完整参考代码与逐行讲解先在当前目录下新建一个article.txt随便写几句话比如python is fun and python is easy today is the fifth day然后创建一个word_counter.py输入下面代码def read_file(filename): with open(filename, r, encodingutf-8) as file: return file.read() def count_words(text): words text.split() word_count {} for word in words: word_count[word] word_count.get(word, 0) 1 return word_count def top_words(counts, n3): sorted_items sorted(counts.items(), keylambda item: item[1], reverseTrue) return sorted_items[:n] def main(): text read_file(article.txt) print(总字符数, len(text)) print(总单词数, len(text.split())) for word, count in top_words(count_words(text)): print(f{word}: {count}次) if __name__ __main__: main()逐行说几个关键点。open(filename, r, encodingutf-8)里的encodingutf-8能避免很多中文路径或内容的编码问题建议每次读写文本都写上。with 语句负责自动关闭文件比裸 open 再 close 更安全。word_count.get(word, 0)这段是“有则取值无则返回 0”的简写。它比word_count[word] 1更安全因为如果字典里还没有这个键直接取会抛 KeyError。用 get 就能在第一次遇到某单词时返回 0然后再加 1 变成 1。这种写法你以后会一直用到。sorted(counts.items(), keylambda item: item[1], reverseTrue)是按字典的 value 降序排序。counts.items() 得的是很多 (单词, 次数) 的元组lambda 指定排序依据为元组里的第二个元素即次数。最后[:n]取出前三个。第 5 天如果看 lambda 觉得吃力也可以先写成普通函数但建议第一次见到就理解它在干什么一个匿名的小函数在这里只是告诉 sorted “按哪个值排”。这个不要求你立刻自己会写但见过后再遇见就不陌生。最后那个if __name__ __main__:是 Python 脚本的标准写法意思是只有当这个文件被直接运行时才调用 main()。如果你以后把它当模块导入到别处它就不会一上来就执行统计。先不用理解得太深按这个格式抄下来就行。5.3 文件读取和中文分词的后续扩展运行这个脚本时你可能会遇到的问题之一就是文件路径。如果你把article.txt放在脚本同一目录下直接用文件名就行。如果你的文件放在别处Windows 下的路径要注意反斜杠# 错误写法 # text read_file(C:\Users\me\article.txt) # 正确写法一双反斜杠转义 text read_file(C:\\Users\\me\\article.txt) # 正确写法二使用原始字符串 text read_file(rC:\Users\me\article.txt) # 更推荐的写法用 pathlib from pathlib import Path text Path(article.txt).read_text(encodingutf-8)pathlib 是 Python 面向对象的文件路径处理方式第 5 天可以先知道后面越用会觉得越香。它不需要你手动拼路径字符串也不怕反斜杠问题。如果你的文件内容特别多第 5 天可以先不考虑性能能跑通已经赢了。想统计中文文本出现频率text.split()按空格分会让一句话黏在一起此时可以引入第三方库 jieba 做分词比如import jieba words jieba.lcut(text)不过那已经是你学完更多基础之后的事了。今天先把这个英文版项目跑起来把前端到后端整条链路打通收获比想象中大得多。6. 新手高频报错与排查心得6.1 别怕报错先读最后一行我第 5 天最大的进步就是不再把报错当“世界末日”。其实 Python 的 traceback 是它给你的免费线索里面藏着解决问题的关键信息。看到报错时先深呼吸然后按这三步处理看最后一行那里挂着异常类型和一句话描述比如IndexError: list index out of range。从下往上翻找到File xxx.py, line 行号它会告诉你是哪个文件、哪一行出了问题。回到代码对应行检查是不是索引越界、类型不对、变量名拼错或者漏了 return。举个例子如果看到File test.py, line 6, in module print(name) NameError: name name is not defined那你要做的事就是去看看第 6 行前是不是变量名写错了或者这个变量压根没赋值。很多时候问题就这么简单。6.2 Python 环境与安装类问题速查第 5 天你可能已经折腾过几个第三方库了比如有人建议你安装 numpy、cv2 之类的。我在这里把常见的环境问题列一下后面遇到直接查表。首先是“python 不是内部或外部命令”。这个几乎都是因为安装 Python 时没有勾选Add Python to PATH。解决办法有两个一是重新运行安装包勾上那个选项二是手动把 Python 安装目录和它的 Scripts 子目录加到系统环境变量 PATH 里。装了之后记得重开命令行窗口否则不会立即生效。第二个是pip install 某个包时提示No matching distribution found。这通常是默认源不稳定可以换成国内镜像源pip install numpy -i https://pypi.tuna.tsinghua.edu.cn/simple第三个是 Windows 安装 Python 时遇到0x80070643之类的错误。这个错误跟系统更新环境有关常见于旧版 Windows。我的建议是不要只依赖微软商店可以直接从 Python 官网下载完整安装包右键“以管理员身份运行”重新安装。如果你用的是 Linux 系统安装 Python 可以考虑系统包管理器比如sudo apt install python3或者用 pyenv 管理版本。新手阶段不需要自己手动编译容易耗掉大量耐心。6.3 第 5 天最常见的 5 个运行时报错把下面这张表截图保存写代码时遇到对应问题直接对照着改。报错含义解决方法NameError: name x is not defined变量 x 没定义或拼写错误检查变量名是否一致确认是否忘记赋值TypeError: can only concatenate str (not int) to str字符串和整数不能直接拼接用str()把数字转字符串或用 f-stringValueError: invalid literal for int() ...int() 不能转换当前字符串确保字符串内容是纯数字比如123IndexError: list index out of range索引超过列表长度打印len(lst)检查长度别写死索引AttributeError: NoneType object has no attribute ...函数返回了 None你却调用它的方法检查函数是否有 return是否真的返回了对象前四个都是入门高频第五个很多新手遇到时最懵。比如你写print(len(text.split()).lower())如果text.split()没有问题这不会报错。但如果你把早期函数的返回值没有 return 直接拿来操作它就会变成 None然后你想调用.lower()或.append()Python 就会告诉你 NoneType 没有这个方法。这时侯不要怀疑人生先回头看看操作对象是不是不该为 None。第 5 天还有一个排查技巧特别适合新手我习惯叫它“拆开做”。遇到一个复杂表达式报错比如print(sorted(counts.items(), keylambda item: item[1], reverseTrue)[:3])如果你不确定哪里出问题拆成多行items counts.items() sorted_items sorted(items, keylambda item: item[1], reverseTrue) top3 sorted_items[:3] print(top3)这样每个中间变量都能单独打印看到哪一步不对就能快速定位。别觉得拆开写很“幼稚”真正的生产代码也是人写的可读性永远比花式一行流更重要。我在第 5 天学完之后最深的一个体会是搞定一个程序的关键不是把所有语法都背下来而是知道去哪里看报错、敢不敢去改动代码验证猜测。如果你也能做到这一点Python 入门路上最难的那道心理坎基本就过去了。我个人最后还有一个小小的“加练”建议今天跑通文本统计器之后把输出前三改成前十把按单词统计改成按字母统计再改一改你就会发现哪些地方是死记硬背哪些地方是真的懂了。学习编程这件事自己动手改一行比盯着教程看十遍有用得多。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进