网上讲 Python 基础语法的文章没有一万也有八千但我带过的很多同学还是会在第一周卡住。卡住他们的普遍不是字典不会背、循环分不清而是“每一步都看懂了关掉教程对着空白文件不知道下一行该写什么”。所以我决定把入门阶段真正绕不开的语法点重新串一遍讲一讲变量、类型、判断、循环、函数、容器、文件处理这些核心东西以及我在实际写代码时在这些语法上踩过的一些坑。这不是官方文档的精简版更像是一个写了多年 Python 的人帮你把这套基础语法按实战顺序排了个队。零基础的同学看完至少能独立写出一个能处理数据、能读写文件的小脚本有一点基础但总感觉逻辑散乱的同学也许会在几个细节上找到“原来如此”的感觉。1. 开发环境先“够用就好”别在起步阶段折腾工具链1.1 版本选择不是越新越好但要认准 Python 3如果你现在才刚开始学 Python直接选 Python 3 的较新稳定版就行不用纠结非要追最新小版本。很多老教程还在用 Python 2 的写法比如print hello这种没有括号的语法在 Python 3 里一跑就报SyntaxError。我见过不止一个新手拿 Python 3 环境去跑 Python 2 的示例代码第一反应就是“是不是我电脑装错了”其实只是版本语法变了。安装的时候有一个细节容易被忽略在安装向导的第一个界面一定要记得勾选“Add Python to PATH”之类的选项否则安装完在终端输入python系统根本找不到命令这一步能省掉后面一堆环境变量配置的麻烦。还有一种选择是安装集成了一堆科学计算、数据分析包的发行版它自带了编辑器、笔记本环境和上百个常用库适合之后要走数据分析方向的人。但如果你只是想先把语言基础打牢我更推荐用纯粹的官方安装包配一个轻量编辑器就够。原因很简单工具链越复杂出问题的地方就越多而起步阶段你最不想浪费时间去研究环境问题。1.2 第一个程序print 之外先学会读懂报错安装完成之后随便建一个.py文件写三行print(你好Python) name 小明 print(欢迎你 name)这三行代码虽然简单但它把“输出、变量、字符串拼接”一次性串起来了。能跑通之后你就算真正进入了 Python 世界。接下来要做的不是急着学下一招而是学会主动制造错误、看懂错误提示。比如你把第一行括号去掉运行后会看到SyntaxError: invalid syntax提示是“语法无效”并且会给出行号告诉你第几行出了问题。新手最怕看到红字其实报错恰恰是学习效率最高的时刻。Python 的报错已经算非常友好大多数时候你只需要看最后几行就能知道是“语法错误”“变量没定义”还是“类型不对”。我建议从第一天起就养成一个习惯出了报错先自己读一遍试着把错误类型和出现位置大声念出来再复制到搜索引擎里搜。绝大部分基础问题都能在这一步自己解决这也是程序员最重要的能力之一。1.3 编辑器、命令行和“小本本”不同阶段的工具选择关于用什么写代码争议一直很大。我的看法是一开始真的不用追求什么重型全家桶。一个带语法高亮的代码编辑器配合一个终端窗口已经足够你完成前两个月的所有练习。等代码越写越长你自然会想要更好的代码补全、调试功能和项目管理能力那时候再切换成更完整的集成开发环境也不迟。工具是服务人的不要反过来被工具绑架。还有一类写着很方便的环境叫单元格式笔记本能一段一段运行代码非常适合做探索性练习。前期可以用它快速验证想法但正式写脚本、做小项目时我会建议你回归到.py文件。单元格式笔记本太容易让人养成“从头到尾再跑一遍才安心”的习惯而正常脚本应该能够独立运行不依赖运行顺序。这个习惯早一点建立后面写工程化代码会顺很多。2. 变量和基本类型把“自由”和“意外”放在一起理解2.1 变量是贴标签不是装盒子很多新手学变量时会下意识把它想成“一个装数据的盒子”这个类比在 Python 里其实是错的。更准确的说法是变量名是一张便利贴你把它贴到某个值上面。比如a 10 a hello这两行在 Python 里完全合法。变量a先贴着整数10后来又改贴字符串hello。整型、字符串、列表、字典什么值都能贴这就是动态类型的含义变量本身不限制类型类型是由它当前贴着的值决定的。这个设计让 Python 写起来非常灵活也带来了一个代价当代码量变大后一个变量可能在不同时间点被赋成不同类型如果你没记住它的变化轨迹很容易在某个地方拿到一个“意料之外”的值。所以在实际项目里我会尽量让变量名传达它的用途比如用user_count而不是n并且在一段逻辑内不给同一个变量重复赋不同类型的值。Python 的常规内置类型里有几个是入门必须记住的整数、浮点数、字符串、布尔值。还有一个容易忽略的点是查看类型的内置函数type()当你拿到一个不确定是什么的数据直接print(type(变量名))能马上看到类型别再靠猜了。2.2 字符串引号、拼接和 f-string字符串大概是新手打交道最多的类型也是最容易踩小坑的地方。Python 里单引号和双引号都能表示字符串这纯粹是为了方便——当字符串本身包含引号时你可以换另一种引号把字符串包起来避免转义。比如He said hi就不需要写反斜杠。字符串拼接也是最基础的操作city 北京 print(我住在 city)这种拼接只适合少量字符串。一旦拼接的内容变多比如要拼好几个变量代码就会变得又长又容易漏空格。所以 Python 3.6 以后有了 f-string写起来非常自然name 小红 age 24 print(f我叫{name}今年{age}岁)f-string 的规则很简单在字符串前面加一个f然后把变量名用花括号写在字符串里。这个写法可读性强得多而且因为是在字符串内部做替换不容易出现忘记把数字转成字符串的问题。我写脚本时九成以上的字符串格式化都用 f-string。2.3 类型转换那些一看就明的 TypeError 是怎么来的刚开始写代码的人几乎都会撞上一次TypeError。最典型的场景是字符串和数字相加age 18 print(年龄是 age)运行后报TypeError: can only concatenate str (not int) to str错误信息已经说得很直白——字符串只能和字符串拼接不能直接拼整数。这时候你需要把数字转成字符串或者干脆用 f-stringage 18 print(f年龄是{age})反过来用户在终端输入的内容默认也是字符串即使输入的是数字num input(请输入一个数字) print(num 1)这段代码同样会报错因为input()返回的是字符串不能直接和整数相加。这时要先做类型转换num int(input(请输入一个数字))类型转换函数里也有一个小坑需要注意int(3.5)会直接报错因为字符串里带小数点不能直接当成整数解析但你可以先float(3.5)再转int。所以不要想当然地以为int()什么字符串都能接。2.4 布尔值与真值测试if 后面其实不一定要写比较布尔类型只有两个值True和False。它最常见的用法是出现在if判断中。但 Python 里有一个很容易被忽略的重要特性任何值都可以直接作为“真值”来测试而不一定要和True/False做比较。在 Python 的规则里下面这些值会被当作假False、0、0.0、空字符串、空列表[]、空字典{}、空集合、None。其余绝大多数值都会被当作真。这意味着你可以写出简洁得多的判断name input(请输入名字) if name: print(f你好{name}) else: print(名字不能为空)这里没有写if name ! 效果却完全一样。很多老手会默认用这种写法新同学如果不知道这个规则看代码时会觉得莫名其妙。有一点需要特别提醒字符串False不是假的。它非空所以会被当作真值。初学者很容易误以为“只要字符串内容是 False 就会走 else 分支”实际上只有空字符串才是假。这个细节冷不丁就会坑到你。3. if 与循环程序开始有逻辑3.1 缩进并非风格而是语法如果你之前接触过其他编程语言第一次看到 Python 代码时最容易注意到的就是它用缩进来表示代码块。其他语言通常用花括号把一段代码包起来缩进只是给人看的好习惯但在 Python 里缩进是会报错的语法规则。比如if 3 1: print(成立) print(这句和 if 无关)第二行前面有四个空格它属于if内部第三行没有缩进表示条件结束。如果你把缩进弄乱比如同一层代码有些用 Tab 有些用四个空格运行时会报IndentationError: unexpected indent这也是新手高频报错之一。我的建议是编辑器里默认把 Tab 键自动转成四个空格全局统一用空格。以后不管在什么团队、什么项目里都不会因为缩进方式闹出问题。有同学觉得这种强制缩进很烦但换个角度想这其实是 Python 在逼着所有写代码的人保持排版整洁。你去读任何一份规范的 Python 项目源码都会发现层次结构一目了然这反而是它的一大优点。3.2 条件判断别把 和 混着用条件判断的基本形态是if ... elif ... else注意是elif不是其他语言里的else ifscore 85 if score 90: print(优秀) elif score 60: print(及格) else: print(不及格)这个结构是自上而下判断的一旦某个条件满足后面的elif和else就不会再执行。所以写多条件判断时顺序是有讲究的把范围更大的条件放在后面范围更小的条件放前面避免一个条件把另一个条件“吞掉”。新手最容易犯的一个错误是把赋值当成了比较。比如if score 100: print(满分)这会直接报语法错误因为if后面需要的是表达式而单等号是在做赋值。等价的比较应该写if score 100:还有个很实用的细节Python 支持连着比比如0 x 10一步判断x是否在区间内这比写x 0 and x 10更直观。条件组合用and、or、not优先级上not最高然后and最后or。拿不准的时候加括号没有坏处。3.3 for 和 while 的分工Python 里有两种主要循环for和while用途有明显区别。for适合“遍历一个已知序列”比如列表、字符串、字典或者一个固定长度的范围for i in range(5): print(i)range(5)会生成 0 到 4 的整数序列注意不包括 5。你还可以range(1, 6)指定从 1 到 5甚至range(1, 10, 2)指定步长为 2生成奇数序列。for的优点是自动管理“下一个该取哪个”不会出现手动忘记更新计数器的问题。while适合“条件成立就继续直到某个状态改变”比如你希望一直读取用户输入直到输入特定内容才退出answer while answer ! quit: answer input(请输入内容输入 quit 退出) print(f你输入了{answer})循环体里还有两个控制语句break表示立即跳出整个循环continue表示跳过本次进入下一轮。举个例子输出 1 到 10 之间的偶数for i in range(1, 11): if i % 2 ! 0: continue print(i)i % 2 ! 0说明是奇数continue跳过print于是只有偶数会输出。如果某次循环里想彻底停止就把continue换成break。3.4 上手练习九九乘法表与猜数字学了语法不动手等于没学。我给零基础的同学推荐两个经典练习一个是九九乘法表一个是猜数字。九九乘法表能练到嵌套循环、字符串格式化和range的边界for i in range(1, 10): for j in range(1, i 1): print(f{j} x {i} {i * j}\t, end) print()外层循环控制行数内层循环控制每一行输出多少列\t是制表符让等式对齐end表示这一行不换行每行结束以后再统一print()换行。这段代码跑出来就是标准的乘法表。猜数字游戏能练到while、条件判断、break和类型转换import random target random.randint(1, 100) while True: guess int(input(猜一个 1 到 100 的数字)) if guess target: print(猜对了) break elif guess target: print(再大一点) else: print(再小一点)有个冷门但好用的语法可以顺便提一下for...else。当循环正常结束没有被break跳出时会执行else里的代码。比如想在一组数字里找目标值没找到时才做提示用这个结构就非常清晰。大多数新手不知道这个特性但它能省掉一个标志变量。4. 函数从“复制粘贴”到“封装复用”4.1 写函数不是因为范式是因为你受够了重复新手学函数时容易产生一个疑问为什么不直接把代码从头写到尾非要拆成一个一个函数等你真正撞到“重复”这堵墙就明白了。假如你在程序里多次需要计算长方形面积而且面积公式要统一修改你会发现自己得在五六处地方改同一个乘法逻辑改漏一处就会出非常隐蔽的 bug。这时候把逻辑抽成一个函数所有地方都调用它问题就解决了def area(width, height): return width * height函数由三部分组成def关键字开始定义函数名 括号里的参数决定入参return决定返回值。调用时写好参数函数执行完把结果“递”回来。写惯了以后你会发现函数最大的价值不只是减少重复而是给了代码一个名字。一个叫calc_total_price的函数哪怕不看函数体读者也知道它在算什么这大大提升了可读性。我经常跟人讲函数就像一道菜谱写一次之后可以反复照着做每次只需要传入不同的食材。4.2 参数的五种姿势位置、关键字、默认值、*args 和 **kwargsPython 函数的参数形态很灵活刚入门至少要把前三种掌握。位置参数是最直观的调用时按顺序传入def greet(name, greeting): return f{greeting}{name} greet(张三, 早上好)关键词参数是按参数名传不依赖顺序greet(greeting晚上好, name李四)这两种可以混着用但位置参数必须放在关键词参数前面否则语法会报错。默认参数则是在定义时给一个默认值调用方不传就使用默认值def greet(name, greeting你好): return f{greeting}{name} greet(王五) # 用默认的你好 greet(王五, 嗨) # 覆盖默认值再往后还有不定长参数*args和**kwargs前者把多余的位置参数收成一个元组后者把多余的关键词参数收成一个字典。这个阶段了解即可但看懂它们是大有帮助的因为很多第三方库的函数签名里都会出现这两个名字。4.3 作用域为什么函数里的变量“碰不得”函数内部创建的变量默认是局部变量只在函数内部生效。函数外部的代码是访问不到的def my_func(): secret 42 return secret print(my_func()) # 输出 42 print(secret) # 报错NameError反过来函数内部能不能直接读取全局变量能读但尽量不要去修改它。一旦你在函数里给它赋值Python 会默认认为这是一个新的局部变量并可能报错total 0 def add(n): total total n # 报错UnboundLocalError return total这里出问题的原因就是函数里有对total的赋值Python 就把total当作局部变量局部变量还没定义就被引用了。正确的做法是把修改结果返回出来让外部自行覆盖total 0 def add(current_total, n): return current_total n total add(total, 10) total add(total, 5)这种模式比在函数内部写global要安全得多。global虽然能解决问题但会让函数产生“隐藏副作用”项目一大很容易让人发懵。我的经验是尽量让函数只通过参数接收数据、通过返回值输出数据别动不动就来全局变量。4.4 抽函数的实际案例从杂乱的脚本到清晰的计算函数举个真实业务里常见的场景计算订单金额价格超过 100 块就打九折。最开始的脚本可能是price1 120 count1 2 total1 price1 * count1 if total1 100: total1 * 0.9 print(f第一笔订单{total1}) price2 30 count2 5 total2 price2 * count2 if total2 100: total2 * 0.9 print(f第二笔订单{total2})写两笔订单还能忍受写十笔就会非常烦躁。抽成函数后是这样的def calc_total(price, count): total price * count if total 100: total * 0.9 return total print(f第一笔订单{calc_total(120, 2)}) print(f第二笔订单{calc_total(30, 5)})改动一个逻辑只需要改函数内部一处。以后要加满 200 减 20 之类的新规则也只需要扩展一个函数而不是去所有地方翻旧代码。这一步看似简单但它是从“写脚本”走向“写程序”的分水岭。判断标准很简单当你发现自己又在复制粘贴同一段逻辑就是该抽函数的信号了。5. 四大内置容器列表、元组、字典、集合到底怎么选5.1 列表大多数时候的默认选择列表是 Python 里最有存在感的容器它的特点是有序、可变、元素可以重复。可以往里追加、删除、修改也支持切片操作fruits [苹果, 香蕉, 橙子] fruits.append(西瓜) # 末尾追加 fruits.remove(香蕉) # 按值删除 print(fruits[1]) # 按索引访问 print(fruits[1:3]) # 切片得到索引1到2的子列表切片是列表操作里很值钱的一个技能。nums[1:3]表示从索引 1 到索引 2nums[:3]表示从头到索引 2nums[::2]表示每隔一个取一个nums[::-1]直接得到反转的列表。这些写法看着花哨实际处理数据时天天用。新手还要知道列表是可变的这带来一个和分析相关的小现象你把一个列表变量传进函数在函数里修改这个列表外部的列表也会跟着变。因为变量贴的是同一个列表对象而不是复制了一份。如果你确实想复制一份用复制名 原列表[:]或者list(原列表)。5.2 元组不可变所以更适合当“固定结构”元组长得和列表很像用的是圆括号最大的区别是创建之后不能增删改point (10, 20) point[0] 30 # 报错TypeError不可变听起来像是限制但这恰恰是它的价值。当你写一个函数返回值时可以用元组一次性返回多个数据def get_min_max(lst): return min(lst), max(lst) low, high get_min_max([3, 1, 4, 1, 5])右边返回的是一个元组左边的low, high直接完成解包赋值。这种代码比多次从result[0]、result[1]取值要清爽得多。平时如果只是一个临时的小数据结构我会优先考虑元组因为它固定了形态后续不容易被误改。5.3 字典查询与映射场景下的主角如果数据需要“根据一个键找到对应值”比如根据名字查年龄、根据商品编号查库存就用字典user_ages { 小明: 18, 小红: 20, 小刚: 19 } print(user_ages[小明])字典的底层是哈希表查找一个键的速度非常快而且不随数据量增加而线性变慢这在数据规模变大以后比列表扫描要有优势。字典最常见的坑是访问不存在的键会报KeyErrorprint(user_ages[小王]) # 报错不想报错的话有两种方式一种是用if 小王 in user_ages先判断另一种是直接用get方法print(user_ages.get(小王, 0))get的第二个参数是找不到时的默认值返回默认值而不是报错。这个习惯一定要养成在处理外部传入的数据时尤其重要。我常常用一个练习让新手体会字典的威力统计一段字符串里每个字符出现的次数。text hello python counts {} for ch in text: counts[ch] counts.get(ch, 0) 1 print(counts)一行counts.get(ch, 0)把“读到已有字符就加一读到新字符就初始化成 1”的逻辑全包了比用if ch in counts写两行分支简洁得多。5.4 集合去重和集合运算的捷径集合是无序且元素唯一的容器最常用的场景就是去重nums [1, 2, 2, 3, 3, 4] unique set(nums) print(unique) # {1, 2, 3, 4}再比如判断两个列表里有哪些共同的元素a set([1, 2, 3]) b set([2, 3, 4]) print(a b) # 交集{2, 3} print(a | b) # 并集{1, 2, 3, 4} print(a - b) # 差集{1}这种写法比在列表里两层循环遍历高效得多。日常开发里数据分析、去重、权限判断都会大量用到集合。四种容器总结成一张表选型的时候看一眼就够了容器有序可变元素重复典型场景列表是是允许有顺序的数据集合元组是否允许固定结构、多返回值字典否是键不允许键值映射、按名查询集合否是不允许去重、集合运算5.5 推导式简洁有余可读性需要把握列表推导式是 Python 里让人又爱又恨的语法因为太简洁了。把for循环生成的列表压缩成一行squares [x * x for x in range(10)]这等价于squares [] for x in range(10): squares.append(x * x)还可以加条件even_squares [x * x for x in range(10) if x % 2 0]字典也有推导式word_lengths {word: len(word) for word in [apple, banana, pear]}推导式写起来很爽但有个底线不要嵌套太深。看到三层以上嵌套的推导式任何人阅读都需要花时间拆解。我个人的习惯是最多一层for加一层if再复杂就老老实实回退到普通循环可读性优先。6. 文件读写与异常处理让程序可以从容失败6.1 with open管理文件比“记得关闭”更可靠文件读写是很多脚本绕不开的能力而新手最常见的写法隐患是“忘记关闭文件”。打开文件以后如果不手动关闭程序可能一直占着文件句柄轻则影响文件访问重则在 Windows 上导致其他程序写不了这个文件。比较安全的姿势是使用with语句with open(message.txt, w, encodingutf-8) as f: f.write(你好文件读写)with语法的含义是进入这个代码块时打开文件无论块内代码是否执行成功退出代码块时都会自动关闭文件。你不需要写f.close()也不用担心异常导致文件没关干净。读文件也类似with open(message.txt, r, encodingutf-8) as f: content f.read() print(content)文件模式上r读、w写、a追加。这里尤其注意w模式会把原文件内容清空再写入如果你只是想在末尾添加内容要用a。我看过不少人把不该清的配置清掉了就是因为没用对模式。6.2 编码和路径说不完的 utf-8 和那些绝对路径读写文本文件时编码问题是绕不开的坑。最常见的表现是文件读出来全是乱码。原因通常是打开文件时没有指定编码系统使用了默认编码而你的文件保存成的是 UTF-8。所以我的建议是读写文本文件时统一显式指定encodingutf-8不要依赖系统默认with open(note.txt, r, encodingutf-8) as f: content f.read()路径方面新手容易掉进去的坑是硬编码一个带盘符的绝对路径比如C:/Users/xxx/Desktop/data.txt。这种写法换一台电脑就废了而且一旦路径里有中文或空格还会引发各种奇奇怪怪的报错。更稳的做法是使用相对路径把数据文件和.py脚本放在同一个目录下直接写文件名或者写data/xxx.txt这样的相对路径。这样整个项目复制到别处也能运行。6.3 try/except 的正确用法不要吞掉错误程序运行时的错误分两类能预判的和不能预判的。用户输入了不合理的数字、要读取的文件不存在、网络请求超时这些都算“业务上可能出现的状况”。处理它们的常用手段是try/excepttry: num int(input(请输入一个数字)) print(10 / num) except ValueError: print(你输入的不是有效数字) except ZeroDivisionError: print(不能除以 0)这段代码的核心思路是把可能出问题的那一步放进try根据不同的错误类型分别给出反馈。程序不会崩溃而是以一种“有反馈”的方式继续运行。但这里有一个非常值得警惕的反模式try: # 某段可能有问题的操作 pass except: pass这种“裸 except 加空处理”等于把错误完全吞掉了。日志里没有任何记录人完全不知道发生了什么。最怕的是程序看起来还在运行结果数据已经算错了这种“静默失败”比直接报错可怕得多。正确的习惯是尽量精确捕获异常类型哪怕暂时不知道具体类型也用except Exception至少把错误内容打印出来。此外还有两个语法经常被人忽略else块在没有异常时执行finally块无论如何都会执行。复杂场景下可以配合使用新手阶段先把try/except用对就足够了。6.4 学会看 traceback最后一行才是重点很多新同学一看到报错信息拉出一大串就头晕其实 Python 的 traceback 并不难读。举个例子Traceback (most recent call last): File demo.py, line 7, in module result 10 / num ZeroDivisionError: division by zero最后一行是错误类型和错误描述这是最重要的信息。往上看有具体的文件名、行号、以及那一行代码的内容。通常新手只需要看最后两行就能定位问题在哪一行、是什么类型、什么原因。报错信息确实是英文的但这恰恰是学习的一部分。刚开始可以借助翻译工具不要随手把整段报错复制到群里问“有没有人能看懂”先自己尝试读一遍再针对错误类型去搜索解决得会快很多。我见过太多同学卡在报错分析这一步其实只要耐心读两行大部分答案就在眼前。回到开头说的那个场景——看着教程都会关掉教程就写不出来。这很正常完全不是因为你“没有天赋”。语法本身是一堆规则但真正让你把规则变成能力的是拿着这些基础语法去解决自己手头的小问题。找个小需求比如整理文件夹里的文件名或者给某个文本做词频统计遇到不懂的地方再回头翻本文里对应的部分写起来报错改坚持几周你会突然发现自己已经能读懂很多代码了。最后再分享一个我一直在用的习惯每天固定写二十分钟极小的代码片段而不是周末一次性猛啃三个小时这种“少量高频”的练习方式对打牢 Python 基础语法的效果要远好于任何速成方案。