目录宏观视角C 与 Python 的底层差异变量机制与内存模型数据类型与隐形陷阱运算符与逻辑炸弹函数、模块与包语法规范与命名Pythonic 思维一、宏观视角C 与 Python 的底层差异学语法之前先建立底层认知。计算机执行程序归根结底只做两件事处理数据加减乘除、逻辑运算和搬数据从内存搬到屏幕或者内存内部挪位置。所有程序都是这两件事的组合。1.1 编译型 vs 解释型C 语言编译型预处理#include把多文件组装起来→ 编译转机器码→ 链接把多个目标文件拼成可执行文件。代码一改必须重新编译。Python解释型底层是 C 实现的 CPython。它先把源码编译成字节码再由解释器执行。改完直接跑。严格说解释型不是绝对概念CPython 一样有编译环节只是对使用者不可见。这个差异的直接后果C 的语法错误在编译期就被抓出来Python 的很多错误要等代码真正跑到那一行才暴露。后面第四节的死亡代码现场就是这个机制造成的。1.2 静态类型 vs 动态类型C静态类型int a 10;是向系统申请一块固定大小的内存并约定这块二进制只能按整数解释。类型在编译期锁死。Python动态类型a 10不用声明运行时自动推断。但这里有个特别容易被误解的点Python 是动态类型同时是强类型。动态指的是变量不绑定类型强类型指的是对象有明确类型、操作受严格限制。print(11)它不会像 JS 那样偷偷把1转成1再加。该报错就报错。1.3 内存管理的分水岭C 语言有指针能直接操作内存地址。老师用辣椒打比方有人喜欢辣椒因为辣有人不喜欢也是因为辣。指针就是这样的特性——灵活、高效、可控但也带来野指针、内存泄漏、数组越界。C 里申请了内存忘了free程序越跑越慢最后只能重启。Python 没有指针内存的申请和释放全自动。代价是运行速度不如 C。1.4 类型的本质内存底层只有二进制。类型决定两件事占多少字节以及这段二进制怎么解释整数浮点字符。同一个二进制串按 int 解释和按 float 解释值完全不同。int a的本质就是申请一块空间约定按整数解析。Python 里变量不直接存值存的是对象的地址对象自己带着类型信息。二、变量机制与内存模型这一节是整个基础篇最核心的底层原理也是选择题最爱考的地方。2.1 变量不是盒子是标签C 的思维变量是盒子把 10 塞进叫 a 的盒子里。盒子的类型和大小固定。Python 的思维一切皆对象。执行a 10时Python 先在内存里建好整数对象10然后a作为一张标签贴在这个对象上。a里面存的是对象的地址引用。所以a10ahello# 不是把盒子里的东西换掉而是把标签撕下来贴到另一个对象上旧对象如果没人引用会被垃圾回收。2.2 小整数缓存机制总统套房x5y5print(id(x),id(y))# 两个地址完全相同m289n289print(id(m),id(n))# 通常不同原理-5到256是使用频率最高的整数Python 启动时就在内存里预先建好这些对象常驻不走。老师管这叫常住贵宾住总统套房。你给x和y都赋 5两张标签贴在同一个房间。289 超出范围不是贵宾每次赋值都临时租新房间所以地址不同。这个范围是有出处的CPython 源码里定义的[-5, 256]。** 这里有个坑必须说清**网上很多笔记写257 is 257是 False这句话只说对了一半。同一个代码块里编译期会做常量折叠可能让两个 257 指向同一个对象# 同一个代码对象内可能为 Trueprint(257is257)# 交互式逐行输入时通常 True# 独立创建才是 Falsea257b257print(aisb)# 通常 False但同一条语句里不保证结论不要用is比较数值。缓存机制是实现细节不是语言规范不要依赖。判断值相等一律用。2.3 is 与 高频考点比较值是否相等。is比较是不是同一个对象内存地址相同。a[1,2]b[1,2]print(ab)# True值一样print(aisb)# False两个不同的列表对象唯一推荐用is的场景判断是否为None。ifxisNone:# 规范写法ifxNone:# 能跑但不规范None 只有一个用 is 更准确也更快2.4 三个查身份的内置函数函数作用示例id(obj)内存地址身份id(a)type(obj)对象的类型type(a)→class listisinstance(obj, 类型)是否是某类型的实例isinstance(a, list)→ Trueisinstance比type更好用因为它支持继承关系的判断2.5 动态但强类型类型决定了对象有什么本事列表能 append、排序元组不能改字典能按键查。三、数据类型与隐形陷阱3.1 整数 intPython 的整数理论上无上限只受内存限制。C 里算 2 的 64 次方会溢出需要自己写数组模拟大数Python 直接算。阿凡提棋盘放米第 1 格 1 粒之后每格翻倍共 64 格print(2**64-1)# 18446744073709551615Python 算得毫无压力3.2 浮点数 float高频丢分点浮点数是二进制存储十进制小数很多无法精确表示。print(0.10.2)# 0.30000000000000004print(0.10.20.3)# FalseOJ 铁律绝对不要写if a 0.3:。要用差值判断。ifabs(a-0.3)1e-9:print(相等)另外整数和浮点混合运算结果是浮点。5 / 2永远是 2.5不是 2。3.3 布尔类型与真值判定True和False是 int 的子类True 1、False 0可以直接参与算术。在条件判断里以下值被判为FalseFalse、0、0.0、0j复数零、None、 空字符串、[]空列表、()空元组、{}空字典、set()空集合其余一切为 True。这就是if []:走 else 分支的原因。if[]:print(不会打印)else:print(空列表是假)# 这里执行这个特性可以用来写简洁代码比如if not lst:判断列表是否为空比if len(lst) 0:更 Pythonic。3.4 字符串 strPython没有单独的字符类型a也是字符串长度 1。四种定界符...、...、...、...。互相嵌套可以省掉转义。print(他说你好)# 双引号包单引号print(他说你好)# 单引号包双引号原始字符串r...反斜杠不作转义。print(len(r\n))# 2是一个反斜杠加一个 nprint(len(\n))# 1是一个换行符字符串不可变所有修改操作都返回新字符串原字符串不变。sabcs.upper()# 返回 ABCprint(s)# 还是 abcss.upper()# 要赋值回去才生效这个不可变特性后面讲元组、列表排序时会反复用到记住一句话不可变对象的任何操作都不改原对象。3.5 编码与解码乱码的根源计算机只认字节。encode()把字符串转字节串decode()把字节串还原字符串。s你好bs.encode(utf-8)print(b)# b\xe4\xbd\xa0\xe5\xa5\xbd6 个字节print(b.decode(utf-8))# 你好编码和解码必须用同一套字符集否则乱码或报UnicodeDecodeError。补充两点老师没细说的英文字符在 ASCII、UTF-8、GBK 里编码一致所以纯英文不会乱码。汉字在 UTF-8 里占 3 字节在 GBK 里占 2 字节。len(你好)是 2字符数len(你好.encode(utf-8))是 6字节数这两个长度不是一回事。3.6 NoneNone是 NoneType 类型的唯一值表示空、“没有”。函数没有 return 时默认返回 None。判断用is None不用 None。前面 2.3 讲过原因。四、运算符与逻辑炸弹4.1 算术运算符运算符含义示例注意/真除5 / 2→2.5结果永远是 float//整除地板除5 // 2→2-5 // 2→-3%取余5 % 2→1余数符号跟除数走**幂3 ** 2→9右结合地板除的坑极高频//是向下取整往数轴更低的方向走不是去掉小数部分。print(5//2)# 2print(-5//2)# -3不是 -2print(-5%2)# 1不是 -1记法地板除和取余是一对满足a (a // b) * b (a % b)。代入a-5, b2验证-3 * 2 1 -5成立。幂运算右结合print(3**3**3)# 等价于 3 ** (3 ** 3) 3 ** 27不是 (3**3)**3**还能开方9 ** 0.5得 3.0。pow(a, b, mod)可以算(a**b) % mod大数取模时比先算幂再取模快得多。4.2 赋值运算符与 陷阱复合赋值x 3等价于x x 3。Python 没有和--。x5x# 这行不报错但也不自增就是 (x)x 还是 5--x# 同理-(-x)x 还是 5这是从 C 转过来的同学最容易犯的错。写i会直接 SyntaxError写i不报错但没效果更隐蔽。4.3 关系运算符与链式比较Python 支持数学式的链式比较C 不支持。x3if1x5:# Python 写法等价于 1 x and x 5print(在区间内)// C 里必须这么写if(1xx5)C 里写1 x 5会先算1 x得 1 或 0再拿这个结果和 5 比逻辑完全错了而且编译器不报错。⚠️ 条件表达式里禁止单个等号ifx3:# SyntaxErrorifx3:# 正确C 里if (x 3)是赋值返回值 3 为真条件永远成立坑了无数人。Python 直接在语法层面禁止这是它比 C 友好的地方。4.4 短路求值与它的返回值真相and、or、not是 Python 的逻辑运算符不是、||、!。短路规则A and BA 为假直接返回 A不计算 B。A or BA 为真直接返回 A不计算 B。⚠️ 返回值真相超高频and和or返回的不是 True/False而是参与运算的那个表达式的值。只有not一定返回布尔值。print(3and5)# 5print(0and5)# 0print(3or5)# 3print(0or5)# 5print(not3)# False理解方法and找第一个假值找不到就返回最后一个or找第一个真值找不到就返回最后一个。这个特性常被用来写默认值nameuser_inputor匿名# 输入为空字符串时取默认值4.5 死亡代码现场短路求值导致的隐蔽报错b3print(b5anda5)# 正常输出 False不报错b6print(b5anda5)# NameError: name a is not defined第一次没报错不是因为代码对而是因为b 5为假and短路后面的a 5根本没执行解释器也就没发现a不存在。教训测试时没报错不代表代码没问题可能只是你的测试数据没走到那条路径。这也是 OJ 上本地能过、提交 WA的常见原因之一。4.6 为什么 C 不会这样Python 会这是理解两种语言的关键差异C编译期就扫描全部代码a未声明编译直接失败压根跑不起来。Python不管编译字节码阶段只在实际执行到那一行时才解析名字。没走到就不报。所以 C 的语法错误是静态暴露Python 的很多错误是动态暴露。写 Python 要更依赖充分的测试覆盖。4.7 条件顺序优化老师讲得很实用的一个点既然有短路多个条件组合时把最容易失败的放前面能大幅减少计算。老师的例子一个女子瑜伽培训班全部是女生。门口核验身份条件有两个——“是不是男生和身份证号是否在名单内”。显然性别判断又快又能筛掉大部分人。# 好ifis_male(person)ornotin_list(person.id):reject()# 差ifnotin_list(person.id)oris_male(person):reject()数据量小感觉不出来数据量到千万级差距就明显了。同理用and时把大概率不成立的放前面用or时把大概率成立的放前面。4.8 成员、身份与优先级in/not in成员测试。列表是线性扫描集合和字典是哈希定位后者快得多。is/is not身份判断。优先级从高到低成员访问 . 下标 [] 幂 ** 正负号 乘除 加减 比较 not and or记不住就加括号永远不会错。4.9 类型转换int(5.9)# 5直接截断不四舍五入int(10)# 10int(0b1010,2)# 10第二参数指定进制float(3.14)# 3.14str(123)# 123bool()# Falsebool(abc)# Trueint()是截断不是四舍五入int(5.9)得 5int(-5.9)得 -5。要四舍五入用round()注意round(2.5)是 2 不是 3银行家舍入这个坑实际项目中会咬人。字符串转数字字符串必须是合法数字int(abc)直接报ValueError。五、函数、模块与包5.1 函数从 C 的合约思维过渡C 的函数声明是一份合同输入什么类型、返回什么类型写死在头文件里。甲方调用乙方实现。intadd(inta,intb);// 合约传两个 int返回 intPython 的函数不写类型靠约定defadd(a,b):returnab但没有类型不代表没有约束——传进去的东西能不能相加运行时才知道。这也是为什么 Python 需要写测试。老师的一个类比很到位跟 AI 对话也是这样你要说清楚输入是什么、要求是什么、期望输出什么格式。写函数就是给对方一份清晰的接口说明。5.2 模块、包、库模块一个.py文件。包一个文件夹里面放多个模块通常带__init__.py。库可以是一个模块也可以是多个包/模块的集合。__init__.py的作用就是告诉 Python这个文件夹是个包按包来对待。简单项目里放个空文件就行。标准库随解释器安装math、random、collections第三方库要先pip installnumpy、requests。5.3 import 的五种写法importmath# math.sqrt(16)frommathimportsqrt# sqrt(16)importmathasm# m.sqrt(16)frommathimportsqrtass# s(16)frommathimport*# 全部导入命名冲突风险高尽量别用from math import *最省事也最危险容易覆盖你自定义的同名函数。实际项目里基本禁用。5.4name属性每个模块都有__name__直接运行这个文件时__name__ __main__被别的文件 import 时__name__等于模块名# hello.pydeftest():print(run directly)if__name____main__:test()这样写的好处直接运行python hello.py会执行test()但别的文件import hello时不会自动执行只提供函数。模块自测试的标准写法。是两个下划线不是_name_ASR 转写和手敲都容易漏。六、语法规范与命名6.1 缩进就是语法Python 用冒号 缩进界定代码块没有花括号。同一块代码缩进必须一致通常 4 个空格。ifx0:print(正数)# 属于 ifprint(结束)# 不属于 if差一个空格可能直接IndentationError也可能逻辑跑偏但不报错——后者更可怕老师说他曾为一个多余的空格排查很久。混合 Tab 和空格是另一个隐形炸弹。不同编辑器 Tab 宽度不同肉眼看着对齐实际不一致。统一用 4 个空格。6.2 注释单行# 注释习惯#后空一格。多行三引号包裹但必须在语句外才算注释。写在函数第一行的三引号字符串是 docstring文档字符串不是注释但有类似作用。6.3 命名规则可以用的字符字母、数字、下划线、汉字。不能数字开头不能有空格和标点不能用关键字。最容易被忽略的坑不要用内置名当变量名。id55print(id(3))# TypeError: int object is not callableid、type、int、str、list、dict、max、min、sum、len、input、print——这些都不能当变量名。一旦覆盖后续调用全部失效而且报错信息往往莫名其妙。查关键字importkeywordprint(keyword.kwlist)命名风格建议变量和函数用snake_case小写下划线类用PascalCase常量用UPPER_CASE。这是 PEP 8 的约定不是强制但大家都这么写。6.4 版本Python 2 已停止维护新代码一律用 3.x。官网是python.org.org不是.cn这个是老师反复强调的考点。同一个大版本内的小版本3.10.1 → 3.10.2通常只修 bug不加功能。升级大版本时第三方库可能不兼容所以项目要锁版本、用虚拟环境隔离。七、OJ 实战生存指南OJ 的判题逻辑很死板逻辑对只能拿部分分格式完全匹配才满分。这一节讲的是真正在考场上保命的东西。7.1 输入格式的四种情况情况一一行一个数据nint(input())# 整数xfloat(input())# 浮点nameinput()# 字符串不用转换情况二一行多个同类型数据# 输入10 20a,bmap(int,input().split())# 输入1.0,2.0逗号分隔x,ymap(float,input().split(,))拆解过程input()得到整行字符串 →.split()按分隔符切成列表 →map(int, ...)批量转类型 → 序列解包给变量。情况三一行多个数据但个数不定numslist(map(int,input().split()))不要试图用固定变量接直接转列表后面用len()和循环处理。情况四一行混合类型# 输入张三 95name,scoreinput().split()scoreint(score)# 只对数字部分单独转split()出来的全是字符串字符串部分不用管数字部分单独转。7.2 输出陷阱绝对不要和机器交流。OJ 是机器阅卷你多写一个请输入、多写一个结果是直接 0 分。# 错print(结果是,total)# 对print(total)小数位数必须精确控制print(f{value:.2f})# 保留两位print(f{value:.3f})# 保留三位:.2f会四舍五入。注意round()的银行家舍入和:.2f的行为在边界值上可能有差异OJ 判题以:.2f为准。7.3 全角/半角空格真正的大坑OJ 对空格极其敏感。中文输入法打出来的空格是全角占两个字符宽英文是半角。肉眼看着差不多提交上去格式错误。老师的保命神技不要去手动敲空格直接从题目里复制样例输出粘进代码在前面加f然后把数字部分换成{}包裹的变量。# 样例输出杨枝甘露 18# 复制整行改成print(f{best_name}{best_price})这样空格来自题目本身绝对不会错。如果拿不准是不是全角可以在编辑器里选中那一行看看空格宽度是否一致。7.4 例题精讲计算两点间距离题目输入两行每行是x,y格式的坐标逗号分隔输出两点距离保留两位小数。importmath x1,y1map(float,input().split(,))x2,y2map(float,input().split(,))distmath.sqrt((x1-x2)**2(y1-y2)**2)print(f{dist:.2f})几个易错点坐标可能是小数必须用float不是int。分隔符是逗号split(,)一定要写参数不写默认按空格拆。数学里的中括号[]在 Python 里不存在表达式分组只能用圆括号()。开方两种写法math.sqrt()或** 0.5。考试环境如果连math都不让导入用** 0.5。7.5 例题精讲数据清洗与统计题目第一行整数n表示数据个数第二行n个整数。过滤掉所有小于 0 的异常值输出最大值、最小值、平均值保留两位小数空格分隔。nint(input())datalist(map(int,input().split()))# 过滤valid[xforxindataifx0]print(f{max(valid)}{min(valid)}{sum(valid)/len(valid):.2f})老师课堂上的真实翻车一开始忘了过滤条件直接算 max/min结果和样例对不上。调了半天才发现漏了过滤掉小于 0这一步。教训题目描述里每一句话都是条件逐句对照别跳读。看起来很长的故事背景往往是废话但过滤、“保留两位”、取最大值这种短句全是得分点。另一个坑max、sum这些是内置函数变量名千万别叫max、min、avg。7.6 例题精讲存款利息题目三行输入——初始金额、年限、年利率。计算到期利息不含本金保留两位小数。principalfloat(input())yearsint(input())ratefloat(input())interestprincipal*(1rate)**years-principalprint(f{interest:.2f})这题的关键不在代码在读懂复利。老师说他一开始按单利算本金 × 利率 × 年数死活对不上。后来拿样例数据倒推才发现是复利利滚利。单利本金 × 利率 × 年数复利本金 × (1利率)^年数 - 本金遇到对不上样例的题倒推是最快的办法拿题目的输入和输出代入公式反推它的计算方式比自己猜快得多。七、Pythonic 思维这段是老师课上讲得最有价值的部分我单独拎出来。不要炫技先找现成的。老师原话大意解决问题时先想有没有现成的函数没有就看标准库标准库没有再找别人写好的轮子。Python 的本质不是让你从零手写而是又快速、又简单、又稳定地利用现成的东西。想炫技证明自己强应该去用 CPython 不鼓励这个。举个例子反转一个整数# C 思维取余取整一位一位抠n12345rev0whilen0:revrev*10n%10n//10# Python 思维转字符串切片反转转回整数n12345revint(str(n)[::-1])后者一行的效果和前者的循环完全一样。功能上都没错但后者才是利用了 Python 特性的写法。同类的还有sum(range(1,101))# 而不是写循环累加max(lst)# 而不是自己遍历比较[xforxindataifx0]# 而不是 for appenda,bb,a# 而不是用临时变量但要注意初学阶段先把代码写出来是最重要的优化和理解 Pythonic 写法是第二阶段的事。老师也说了“现在这个阶段先把代码写出来”。