ARTICLE · INTELLIGENCE

战地情报 · 详情页

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

Python基础语法避坑指南:变量、缩进与字符串详解

Python基础语法避坑指南:变量、缩进与字符串详解

简介:这份68页的Python基础语法合集,专为初学Python的入门者与希望快速回顾语法要点的开发者设计,从解释型动态语言的特点出发,系统梳理变量与多变量赋值、Numbers/Strings/Lists/Tuples/Dictionaries等核心数据类型、运算符、流程控制、函数定义、模块导入等知识,并结合标识符命名规则、严格缩进、多行语句写法、换行输出等细节,帮助读者规避常见误用、夯实基础。资源为单一PDF文档,压缩包1.42MB,便于离线阅读、打印或作为日常速查手册。目前已有2048人学习,内容涵盖内置类型转换、数学函数与随机数函数、if/elif/else条件分支、for/while循环、try-except异常处理等高频率考点;68页紧凑编排,每节配有可运行的示例代码与要点说明,适合自学、备课或面试前快速过一遍语法。

1. Python 基础语法合集:为什么我建议初学者先啃这份 PDF

带过两个转行过来的新人之后,我越来越倾向让初学者先啃语法手册,而不是一上来就追框架。这份 Python 基础语法合集,就是把变量、数据类型、字符串、列表、元组这些最容易混的基础点按顺序铺开,每个点都配了短代码示例。它不能让你写出多漂亮的工程代码,但能让你在报错的时候知道错在哪,而不是对着黑匣子瞎猜。

适合两类人:零基础想系统过一遍语法的,以及写过脚本但一直靠复制粘贴、对缩进和类型边界说不清楚的人。下面我按自己的复习路线,把这份材料拆成几条实战线索,从地基语法一直走到自测闭环。

2. 把语法地基先立住:标识符、缩进、保留字与多行写法的四个边界

2.1 标识符规则里最容易被忽略的三个边界

Python 的标识符由字母、数字、下划线组成,不能以数字开头,而且大小写敏感。这个规则看起来简单,但我在实际代码里见过三种翻车方式。第一种是把下划线开头的变量当作普通变量随便导出,结果在别的模块里用from xxx import *导不进来。单下划线开头的_foo表示“我不希望你直接访问”,它不是一个强约束,但属于约定俗成的边界。双下划线开头的__foo在类里会被重新命名为_ClassName__foo,这是为了避免继承时名字冲突,所以不要以为它只是“私有”两个字能概括的。双下划线开头和结尾的__foo__是 Python 专门留给特殊方法的名字,比如__init__是构造函数,__len__控制len()行为,普通业务代码里最好别自己造这种名字。

大小写敏感这一点,初学者最容易在复制代码时吃亏。UserName和username是两个完全不同的变量,我见过有新人把函数参数写成userName,内部却用username访问,结果抛NameError,他还在怀疑是不是环境装坏了。看这份语法合集时,我建议你每看完一节就在交互式环境里敲一遍,不要只看不写。标识符这一节真正要记的是边界,不是“由字母数字下划线组成”这句话。

# 合法的标识符 user_name = "u1" # 正常变量 _user_age = 28 # 单下划线开头:模块内私有,from xxx import * 不会带上它 __salary = 12000 # 双下划线开头:类里会做名称改写,避免子类冲突 __version__ = "1.0" # 双下划线首尾:特殊方法/属性专用,业务代码别模仿 # 非法或不建议的写法 # 1name = "u2" # 数字开头,SyntaxError # class = "python" # 保留字,SyntaxError

这段代码里_user_age和__salary是两种不同级别的“私有”写法。单下划线只影响from module import *的默认行为,你仍然可以主动import module后访问module._user_age。双下划线则会在类定义时被改名,直接访问obj.__salary会报AttributeError,但通过obj._ClassName__salary还是能摸到,所以它防的是误用,不是恶意攻击。__version__这种名字是协议的一部分,比如__init__、__str__,看到这类标识符,第一反应应该去查对应的特殊方法语义,而不是拿来起业务变量名。

2.2 缩进才是 Python 的代码块边界:从一次缩进错误说起

这份材料里有一句很容易被划过去的话:“Python 的缩进要求非常严格,必须严格对齐。”很多从 C 或 Java 转过来的人觉得这是小事,直到自己写的函数报出IndentationError: unexpected indent才意识到问题。Python 不用{}括代码块,缩进就是语法本身。同一个代码块里的每一行缩进必须完全一致,而且在 IDE 里看起来对齐了,实际却可能藏着一个 Tab 和四个空格混用的问题。

我一般会建议把编辑器的缩进统一成四个空格,并且打开“显示空白字符”功能。原因很简单:Tab 在同一个编辑器里可能显示为 4 个空格,在另一个编辑器里显示为 8 个,别人一打开你的文件,整个缩进层级就乱了。Python 3 还专门把混用 Tab 和空格的情况提示为TabError: inconsistent use of tabs and spaces in indentation,这是好事情,它把问题提前暴露了。遇到这类报错,不要靠肉眼一行行找,直接选中代码看编辑器状态栏,或者用python -m tabnanny script.py这样的工具列出可疑行。

def judge_score(score): if score >= 60: print("pass") # 属于 if 块 print("with comment") # 和上一行同层级,也是 if 块 else: print("fail") print("done") # 这个属于函数体,不属于 if/else # 错误示范:同一代码块内缩进宽度不一致 # if score >= 60: # print("pass") # print("extra") # 多缩进了一格,IndentationError

上面这个例子说明了缩进级别的层级关系。if下面的print行和if之间缩进 4 个空格,表示它是条件成立时要执行的语句;最后一个print("done")缩进 4 个空格,但和if、else对齐,说明它属于函数体,无论条件真假都会执行。如果你发现某个 print 在条件判断之后仍然执行,先检查它的缩进层级是不是和if平级了。这是排查逻辑问题最快的手段。

2.3 多行语句与多行字符串:用斜杠还是括号?附对照

除了缩进,这份材料还专门讲了多行语句的写法。用反斜杠\可以把一行的语句拆成多行,这是 C 系语言的习惯。但 Python 里其实有更好的选择:只要写在小括号、中括号、花括号内部,就可以直接换行,不需要反斜杠。我自己的习惯是,只要是表达式拼接,一律用小括号包起来换行,避免行尾遗留反斜杠。反斜杠一旦后面不小心跟了个空格,就会变成语法错误,而且这个空格肉眼几乎看不见,属于典型的“看着没问题但一跑就炸”。

total = item_one + \ item_two + \ item_three # 用反斜杠,行尾不能有任何空白 total = (item_one + item_two + item_three) # 用括号,换行自由,推荐写法 # 同一行多条语句用分号 a = 1; b = 2; c = 3 # 三引号多行字符串 text = """第一行 第二行 第三行""" print(text)

注意三引号的作用不只是多行字符串,它还能当多行注释用。但严格来说,三引号包起来的内容是一个字符串对象,如果不把它赋给变量也不参与表达式,它就是一个被丢弃的表达式,解释器不会报错,所以很多人把它当注释。我建议正式代码里不要依赖这种“注释”,因为它在某些静态检查工具里会被认为是无用的字符串表达式。多行语句这条规则配合缩进,构成了 Python 代码最基础的可读性来源。

写法优点风险
反斜杠\直观,单行过长时简单行尾不能有空格,容易踩坑
括号内换行不需要额外符号,IDE 容易自动整理初学者容易忘记闭合括号
三引号适合大段文本、文档字符串不是一个真正的注释,别滥用

这张表不是让你背,而是给你一个选型依据:写长条件、长拼接时优先用括号。if (条件1 and 条件2)这种写法在 Python 里也完全合法,而且能避免and后面的行被误以为是新语句。另一条材料里提到的小细节是分号,a = 1; b = 2; c = 3在交互式环境里可以省行数,但正式脚本里没人这么写,主要是影响可读性,而且出现异常时不好定位是哪一句。你只要知道分号能同行分隔语句就够了,别把它当风格。

这一章看下来,标识符、缩进、多行语句其实都不是“功能”,但它们决定了你后面写出来的代码能不能被 Python 正确解析。我见过太多人把IndentationError看成天书,实际就是把缩进统一、行尾空格清掉,问题就少了一半。

3. 变量与数字类型:类型转换、数学函数与随机数函数的落地用法

3.1 五个标准数据类型与不可变对象的赋值陷阱

材料把标准数据类型分成 Numbers、String、List、Tuple、Dictionary 五大类。这个分类在 Python 3 里依然成立,只是 Numbers 内部的 long 类型已经合并进 int。我建议你记的不是分类本身,而是“哪些是不可变类型”。数字、字符串、元组不可变,列表和字典可变。这里有个很容易翻车的赋值陷阱:b = a对于不可变对象,相当于让 b 指向 a 当前引用的对象,之后给 a 重新赋值,不会影响 b;但对于可变对象,b = a只是复制了引用,修改 b 的内容,a 也会跟着变。

# 不可变对象:数字 a = 100 b = a a = 200 print(b) # 100,b 还指向原来的整数对象 # 可变对象:列表 x = [1, 2, 3] y = x y.append(4) print(x) # [1, 2, 3, 4],x 和 y 指向同一个列表 # 想要独立副本 z = x[:] z.append(5) print(x) # [1, 2, 3, 4] print(z) # [1, 2, 3, 4, 5]

从这个例子里能读出一个关键结论:判断“改了一个变量会不会影响另一个”,不是看变量名,而是看对象是否可变。数字和字符串在重新赋值时,会创建新对象并让变量指向新对象,所以原来的变量不会变。列表这类可变对象,直接在原对象上做修改,所以所有引用它的变量都会感知到变化。材料里提到的del语句也有同样的逻辑,del a删除的是“a 这个引用”,不是立刻销毁对象,如果还有别的变量引用同一个对象,对象仍然存活。

3.2 类型转换函数清单与选择依据

类型转换这节列出了十多个函数,但真正每天都会用到的也就是int()、float()、str()、list()、tuple()这几个。int(x, base)的第二个参数在解析字符串时非常有用,比如用户输入"ff",你用int("ff", 16)就能拿到 255。repr()和str()的区别也值得记一下:str()返回面向用户的字符串,repr()返回面向解释器的表示,很多对象的repr()是可以通过eval()还原的。

# 字符串转数字 num = int("42") print(num + 1) # 43 # 带基数解析十六进制字符串 hex_val = int("ff", 16) print(hex_val) # 255 # 数字列表与元组互转 lst = [1, 2, 3] tup = tuple(lst) lst2 = list(tup) print(tup, lst2) # eval 的坑:不要对不可信字符串用 expr = "1 + 2" print(eval(expr)) # 3

这里要说一个边界:eval()虽然列在类型转换函数里,但它的能力远不止类型转换,它会执行任意 Python 表达式。如果这个表达式来自用户输入,等于把解释器权限交出去了。我一般只在调试脚本里用,或者处理自己生成的配置文件,绝不让它碰外部输入。int()转换时如果遇到int("4.5")会抛ValueError,正确做法是先float()再int(),或者用math.floor()、math.ceil()控制取整方向。

3.3 数学函数与随机数函数的常用组合

数学函数这节最适合用“查表法”学。abs()取绝对值,round()四舍五入,pow()和**等价,sqrt()开平方,ceil()向上取整,floor()向下取整。这里最容易踩坑的是round()的银行家舍入,比如round(2.675, 2)在大多数版本里会得到2.67而不是2.68,因为它处理的是浮点数的二进制表示,不是十进制。如果对精度有要求,用Decimal,别用round()做财务计算。

import math, random # 数学函数组合 scores = [77.6, 88.3, 92.1] average = sum(scores) / len(scores) print(math.floor(average)) # 向下取整 print(math.ceil(average)) # 向上取整 print(round(average, 1)) # 保留一位小数 # 随机数:模拟掷骰子 dice = random.randint(1, 6) # 常用但材料里未列出的函数,等价于 randrange(1, 7) print(dice) # 从列表中随机选一个 player = random.choice(["u1", "u2", "u3"]) print(player) # 打乱列表 cards = [1, 2, 3, 4, 5] random.shuffle(cards) print(cards)

随机数函数这里,材料列了choice、randrange、random、seed、shuffle、uniform。我建议再补一个randint(a, b),它等价于randrange(a, b+1),使用频率很高。seed()的作用是固定随机序列,测试环境里常用,你给seed(10),后续随机结果在每次运行里都一样,方便复现。但注意seed()只影响当前随机数生成器的状态,不是全局魔法,不同 Python 版本或不同进程的生成器算法可能不同,线上环境别指望它跨平台完全一致。

材料还列了acos、asin、cos这些三角函数,以及pi、e两个常量。实际写代码时我很少直接用math.degrees()和math.radians()之外的三角函数,但如果你要做坐标计算、图像旋转,至少要知道角度和弧度不能混用。math.sin(math.radians(30))才是按角度传参的标准姿势,直接math.sin(30)会把 30 当成弧度,结果差得很远。数学常量pi和e也是同样的道理,用math.pi而不是自己定义3.14,能省掉很多精度上的玄学问题。

4. 字符串操作:截取、转义、格式化与内建方法的排错思路

4.1 截取索引的方向与边界:从左从右怎么取

字符串是 Python 里最“绕”的类型之一,因为它的索引和切片规则同时涉及正反两个方向。从左往右索引从 0 开始,最大到长度减 1;从右往左用负数,-1是最后一个字符。切片s[start:end]是左闭右开的,也就是说取到的字符包括 start,但不包括 end。这个左闭右开规则是所有切片操作的核心,也是新手最容易看晕的地方。

s = "hello" print(s[0]) # h print(s[-1]) # o print(s[1:4]) # ell,索引 1 到 3,不包括 4 print(s[:3]) # hel,省略 start 从头开始 print(s[3:]) # lo,省略 end 到最后 print(s[-3:-1]) # ll,从倒数第 3 个到倒数第 2 个

排错时,我习惯先写一个测试字符串,把每个字符的索引标出来,再验证切片结果。比如hello,索引 0 到 4,负数版本就是 -5 到 -1。s[1:4]之所以是ell,就是因为 start=1 包含 e,end=4 不包含 o。如果你想要整个字符串,写s[:]就行,它返回的是原字符串的副本。对字符串来说这个操作没什么必要,但用在列表上就能得到新的列表副本,和上一章的切片复制是同一个机制。

4.2 转义字符和三引号:什么时候不用再背转义表

材料里给了一张很长的转义字符表,\n、\t、\\、\'这些看一眼就行,不建议死背。真正要理解的是“反斜杠是把特殊字符翻译成控制字符”。比如\n不是两个字符,而是一个换行符。如果你需要输出路径C:\new,直接写"C:\new"会把\n当成换行,这时候有两个办法:用双反斜杠"C:\\new",或者用原始字符串r"C:\new"。

# 原始字符串:不解析转义 print(r"\n\t") # 输出 \n\t 这两个字符 print("\\n\\t") # 同样输出 \n\t # 三引号大段文本,不用写一堆 \n text = """第一行 第二行 第三行""" print(text)

使用原始字符串时有个边界要记住:它不能以单个反斜杠结尾,比如r"abc\"会语法错误,因为最后的反斜杠仍然会转义引号。这种场景我一般改用普通字符串加双反斜杠。三引号最大的价值是处理带格式的文本,比如 HTML 模板、SQL 语句、多行日志,直接换行写,不用手动拼\n,可读性会好很多。

4.3 格式化输出的演进:从 % 到 format

这份材料里主推的是%格式化,比如"My name is %s and weight is %d kg!" % ("u1", 21)。这个写法在 Python 3 里仍然合法,但已经不算主流。format()方法提供了更灵活的字段定位,而 f-string 是 Python 3.6 之后最直观的写法。我的建议是:学语法时把%看懂,因为你要读老代码;写新代码时优先 f-string。

name = "u1" weight = 21 # % 格式化(材料主推) print("My name is %s and weight is %d kg!" % (name, weight)) # format 方法 print("My name is {} and weight is {} kg!".format(name, weight)) # f-string,最推荐 print(f"My name is {name} and weight is {weight} kg!")

这里有个排错点:%s接受任意类型传入,会先转成字符串;%d必须接收整数或能转成整数的值。如果你写"%d" % "21",Python 3 会抛TypeError,而不是自动转换。老代码里这种问题很常见,解决思路是显式int()转换,或者直接改用 f-string,让变量保持原类型输出。

4.4 字符串内建方法:查询类与修改类分开记

字符串方法有几十个,按作用分成两类记会轻松很多。查询类包括find()、index()、count()、startswith()、endswith();修改类包括replace()、strip()、lower()、upper()、split()、join()。这里最容易搞混的是find()和index():find()找不到子串时返回 -1,index()找不到时抛ValueError。所以如果你只是判断有没有,用in或find();如果你认为一定有,用index()让它尽早暴露问题。

text = "hello python world" # 查询 print(text.find("python")) # 6 print(text.startswith("hello")) # True print(text.count("o")) # 2 # 修改 print(text.replace("world", "code")) print(text.upper()) print(text.split()) # ['hello', 'python', 'world'] print("-".join(["a", "b", "c"])) # a-b-c print(" padded ".strip()) # padded

split()和join()是逆操作,一个是把字符串按分隔符切成列表,一个是把列表用分隔符拼回字符串。我处理表格数据、命令行参数时经常用这两个方法。另外,strip()默认去掉两侧空白字符,不只是空格,还包括\t、\n,这个容易被人忽略。如果你只想去掉某一侧,用lstrip()和rstrip()。

5. 避坑:这份语法合集里最容易翻车的五个点与排查步骤

读这份材料时,很多例子在命令行里能跑通,但一挪到实际项目就出问题。下面这五个点是我在带人时反复遇到的,每一个都对应一份真实报错记录,按现象、原因、解决三步拆开。

5.1 缩进报错:看着对齐了,一跑就炸

现象:代码在编辑器里显示得很整齐,但一运行就报IndentationError: unexpected indent或TabError,报错行指向某个看似无辜的print。你反复调整空格数量,问题依然随机出现在不同行。

原因:材料强调“缩进必须严格对齐”,但没强调 Tab 和空格的区别。很多编辑器默认把 Tab 显示成 4 个空格的宽度,所以肉眼看起来对齐了,实际代码里既有 Tab 又有空格。Python 解释器按字符判断缩进层级,同一个代码块里混用两种字符就会报错,而且报错位置往往不是真正的源头,而是解释器第一次发现缩进冲突的那一行。

解决:把所有缩进统一成 4 个空格。在编辑器设置里关闭 Tab 缩进,或者打开“显示空白字符”。已经混用的文件,可以用python -m tabnanny script.py找出具体位置。如果文件是别人给的,全选代码后用编辑器的“将 Tab 转换为空格”批量处理,然后再运行。之后我写 Python 之前都会先确认编辑器右下角的缩进提示。

5.2 单元素元组忘了加逗号:类型不是元组

现象:写了t = (5),然后用type(t)检查,发现不是tuple,而是int。用t = ("hello")拿到的是字符串,而不是元组。更隐蔽的是,t = (a, )这种写法被后来的维护者“好心”删掉逗号后,程序不报错,但行为变了。

原因:括号在 Python 里有两种作用,一种是改变运算优先级,一种是表示元组。当括号里只有一个元素且没有逗号时,解释器把它当作分组括号,不会创建元组。材料里虽然写了“创建只有一个元素的元组,必须加逗号”,但很多人只读不验,或者只在创建tuple()空元组时记得加括号,碰到字面量又忘了。

解决:创建单元素元组时写成(5,),逗号不能省。检查时用type()而不是靠眼睛。如果心里没底,可以用tuple([5])这种显式构造来避免括号语义歧义。我在代码评审时看到return (result,)这类写法,都会顺手在旁边注释一句“这里的逗号是故意的”,防止后面被删掉。

5.3 格式化类型不匹配:%d 收到字符串就翻车

现象:执行"%d" % "21"报TypeError: %d format: a number is required, not str。在 Python 2 里这段代码可能能跑,但在 Python 3 里直接失败。报错信息里的“a number is required”很容易让新手误以为变量值不是数字,其实只是类型字符串。

原因:%d要求参数是整数,而变量里存的是字符串。材料里的例子都是手动写好的类型,实际业务中变量可能来自配置文件、命令行参数、用户输入,全是字符串,直接拼接就会炸。%s却能接受任意类型,所以同一个格式化字符串里混用%s和%d时,错误只在%d那一个位置暴露,看起来像灵异事件。

解决:先int()转换再格式化,或者改用 f-string。还要注意int()的边界:int("4.5")会抛ValueError,因为int()不接受带小数点的字符串,要先转float()再转int()。排查时在格式化前打印type(),能直接看到数据类型,不用猜。

5.4 Python 2 语法残留:print 是语句,long 是类型

现象:把材料里某些例子抄到 Python 3 里运行,报SyntaxError: Missing parentheses in call to 'print',或者看到long类型名称飘红。在交互式环境里,输入print "hello"会直接语法错误,而不是输出字符串。

原因:这份材料的不少例子带有早期 Python 2 的痕迹,比如print x、数字后面的L后缀、cmp()函数。Python 3 中print是函数,必须加括号;long已经合并到int;cmp()被移除,替代方案是(a > b) - (a < b)。很多初学者以为是自己电脑环境坏了,其实是语法版本问题。

解决:统一用 Python 3 语法。print("x")代替print x;看到数字后面的L后缀直接删掉;遇到cmp()逻辑,按(a > b) - (a < b)重写。如果照着材料练习时频繁遇到这类错误,先在文件头部写清楚用的 Python 版本,再逐条检查。我用python --version确认环境后,就把所有print语句全局替换了一遍。

5.5 变量复制变成引用:列表跟着一起变

现象:b = a之后,b.append(1),结果a也多了元素。很多人误以为 Python 的赋值都是拷贝,于是出现“一个变量改动,另一个莫名跟着变”的灵异事件。字典也有同样问题,d2 = d1后修改d2,d1同步变。

原因:列表和字典是可变对象,b = a只是让b和a指向同一个对象。材料在数字类型部分讲了不可变对象,但没特别强调列表的引用语义。实际上,数字、字符串、元组赋值后互相独立,列表、字典、集合赋值后共享同一个对象,这是新手期最常遇到的隐性坑。

解决:需要独立副本时用a[:]、a.copy()或copy.deepcopy(a)。如果列表里还有嵌套列表,浅拷贝只能复制外层,内层仍然是引用,这时要用copy.deepcopy()。排查时可以在赋值后打印a is b,返回True就说明是同一对象,改一个必然会变另一个。

a = [1, 2, 3] b = a[:] # 浅拷贝 b.append(4) print(a) # [1, 2, 3] print(b) # [1, 2, 3, 4] import copy nested = [[1], [2]] deep = copy.deepcopy(nested) deep[0].append(99) print(nested) # [[1], [2]] print(deep) # [[1, 99], [2]]

现在我遇到可变对象赋值,第一反应是问自己“我要的是引用还是副本”。这个习惯能省掉一大半难以定位的“变量被悄悄改掉”类问题。

6. 把这份语法合集变成自己的速查表:用自测脚本验证掌握度

6.1 把知识点变成断言

读这份材料最大的陷阱是“眼睛会了,手不会”。我给自己的方法是把每个知识点压缩成一个assert,写进一个syntax_check.py,每次读完就跑一遍。断言通过不是目的,目的是让报错告诉你哪里理解偏了。下面是一个能跑通的最小骨架,它覆盖了缩进、单元素元组、格式化、切片、列表复制五个关键点:

# syntax_check.py name = "python" if name == "python": print("缩进正常") t = (5,) assert type(t) is tuple # 单元素元组必须有逗号 score = 88 assert f"{score}" == "88" # f-string 输出字符串 word = "python" assert word[1:4] == "yth" # 切片左闭右开 a = [1, 2] b = a[:] b.append(3) assert a == [1, 2] and b == [1, 2, 3] # 切片复制得到独立列表 print("all checks passed")

跑这个脚本时,把报错当成线索而不是挫败。SyntaxError大多指向缩进、括号或引号问题,回到第 2 章查;AssertionError是知识理解有偏差,比如切片边界、元组逗号、可变对象复制。我会建议你每读完一个章节,就往上追加两条断言,把材料里的示例改成“输入一个值,断言输出是什么”,而不是只在命令行里看打印。这样做有一个额外好处:当 Python 版本升级或你换了环境,脚本能帮你快速确认基础语法没有被破坏。从那以后,我每次拿到新的语言手册,都会先花半小时写一个自检脚本,把所有容易混的边界变成断言,跑通后才算真正读过。希望帮到你。

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

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