当前位置: 首页 > news >正文

Python 3.12 核心语法实战:从数据类型到流程控制

Python 3.12 核心语法实战:从数据类型到流程控制

《Python 实战》基础篇第二篇。承接上篇搭好的虚拟环境,这一篇我把 Python 3.12 的核心语法一个个敲一遍,所有输出都是服务器真实回显。语法书能讲清楚规则,但"坑"只有真跑过才知道长在哪儿。

环境说明

本篇实验全部在上一篇建好的虚拟环境之外,用系统python3(3.12.3)运行,因为要演示的语法特性 3.9/3.10/3.12 都已支持。先确认版本:

$ python3-VPython3.12.3

下面每个示例都是一个独立.py文件,我贴出源码,再贴服务器跑出来的真实结果。

一、变量、字符串与 f-string

Python 里变量不用声明类型,赋值即用。字符串的常用操作很多,f-string 是最舒服的格式化方式:

name="Python"version=3.12pi=3.14159# f-string 基本用法print(f"语言:{name}, 版本:{version}")# 3.8+ 调试写法 f"{x=}"x=42print(f"{x=},{x*2=}")# 字符串常用操作s=" Hello, Python "print(s.strip())print(s.strip().upper())print(s.strip().replace("Python","World"))print("".join(reversed("abc")))# 反转print("py"in"python")# 子串判断print(len(s))

真实回显:

语言: Python, 版本: 3.12 x=42, x*2=84 Hello, Python HELLO, PYTHON Hello, World cba True 17

重点说两个常被忽视的点。f"{x=}"是 Python 3.8 引入的调试写法,它会同时打印变量名和值,调试时比手写print("x=", x)省事太多,我几乎在每段调试代码里都用它。另一个是print("".join(reversed("abc")))得到cba——字符串反转不用自己写循环,reversed配合join一行搞定。

二、数字类型与浮点精度坑

整数、浮点、布尔、复数都是数字类型,但浮点运算有一处经典陷阱:

print("0.1 + 0.2 =",0.1+0.2)# 浮点误差print("0.1 + 0.2 == 0.3 ?",0.1+0.2==0.3)importmathprint("math.isclose:",math.isclose(0.1+0.2,0.3))fromdecimalimportDecimalprint("Decimal:",Decimal("0.1")+Decimal("0.2"))# 精确print("整除与取余:",7//2,7%2,2**10)# 地板除 vs 真除print("5/2 =",5/2," | -5//2 =",-5//2)

真实回显:

0.1 + 0.2 = 0.30000000000000004 0.1 + 0.2 == 0.3 ? False math.isclose: True Decimal: 0.3 整除与取余: 3 1 1024 5/2 = 2.5 | -5//2 = -3

0.1 + 0.2不等于0.3,而是0.30000000000000004,这是二进制浮点的固有问题,不是 Python 的 bug。所以涉及金额、精度比较时,绝不能直接用==,要么用math.isclose允许误差,要么用decimal.Decimal(注意要用字符串构造"0.1",否则一进来就带误差)。7 // 2是整除得37 % 2是取余得12 ** 10是幂运算得1024。最后那个-5 // 2 = -3也值得记:Python 的地板除是向下取整,-2.5向下就是-3,和很多语言不一样。

真实踩坑:脚本名千万别叫 numbers.py

这一段我自己的操作翻过车,原样记下来。我最初把上面这个文件命名为numbers.py,一运行就炸了,而且报错信息非常迷惑:

0.1 + 0.2 = 0.30000000000000004 0.1 + 0.2 == 0.3 ? False math.isclose: True 0.1 + 0.2 = 0.30000000000000004 0.1 + 0.2 == 0.3 ? False math.isclose: True 0.1 + 0.2 = 0.30000000000000004 0.1 + 0.2 == 0.3 ? False math.isclose: True Traceback (most recent call last): File "/usr/lib/python3.12/decimal.py", line 3, in <module> from _decimal import * File "/root/lab-a/numbers.py", line 5, in <module> from decimal import Decimal ImportError: cannot import name 'Decimal' from partially initialized module 'decimal' (most likely due to a circular import) (/usr/lib/python3.12/decimal.py) During handling of the above exception, another exception occurred: Traceback (most recent call last): File "/root/lab-a/numbers.py", line 5, in <module> from decimal import Decimal File "/usr/lib/python3.12/decimal.py", line 8, in <module> from _pydecimal import * File "/usr/lib/python3.12/_pydecimal.py", line 157, in <module> import numbers as _numbers File "/root/lab-a/numbers.py", line 5, in <module> from decimal import Decimal ImportError: cannot import name 'Decimal' from partially initialized module 'decimal' (most likely due to a circular import) (/usr/lib/python3.12/decimal.py)

根因是:我的脚本叫numbers.py,恰好和标准库里的numbers模块同名。当decimal内部执行import numbers时,Python 因为当前目录里有numbers.py,就把标准库的numbers给遮蔽了,进而触发循环导入。上面那三遍重复的0.1+0.2打印,就是numbers.py被当成标准库反复"导入"自己导致的。把文件改名为nums.py后,一切正常。

经验法则:永远不要用标准库已有的名字(如numbers.pyjson.pyemail.pyrandom.py)给自己的脚本命名,这是新手最高频也最隐蔽的坑之一。

三、列表、元组、集合、字典全套

四种内置容器各有所长,下面一次性过完:

# 列表lst=[3,1,2]lst.append(4)lst.sort()print("list:",lst)# 元组t=(1,2,3)print("tuple:",t,"不可变")# 集合a={1,2,3};b={3,4,5}print("set 交集:",a&b,"并集:",a|b)# 字典d1={"a":1,"b":2}d2={"b":3,"c":4}d1.update(d2)print("dict update:",d1)# 3.9+ 合并运算符 |=d3={"a":1,"b":2}|{"b":99,"c":3}print("dict |:",d3)# 字典推导sq={k:k*kforkinrange(1,5)}print("dict comp:",sq)

真实回显:

list: [1, 2, 3, 4] tuple: (1, 2, 3) 不可变 set 交集: {3} 并集: {1, 2, 3, 4, 5} dict update: {'a': 1, 'b': 3, 'c': 4} dict |: {'a': 1, 'b': 99, 'c': 3} dict comp: {1: 1, 2: 4, 3: 9, 4: 16}

要点:列表可变、有序;元组不可变,适合当常量或函数返回多值;集合自动去重、支持交集&并集|;字典里要特别注意|=合并运算符(Python 3.9 引入),它返回新字典且后者覆盖前者同键,上面d3"b"2变成了99。推导式(列表、字典、集合都支持)是 Python 很爽的语法糖,{k: k*k for k in range(1, 5)}一行就生成了平方表。

四、流程控制:if / match / while / for

条件、循环、模式匹配,逐个演示。先if和 Python 3.10 的match

score=85ifscore>=90:g="A"elifscore>=60:g="B"else:g="C"print("grade:",g)defhandle(cmd):matchcmd.split():case["quit"]:return"退出"case["get",name]:returnf"获取{name}"case["set",name,val]:returnf"设置{name}={val}"case_:return"未知命令"print(handle("quit"))print(handle("get user"))print(handle("set user 100"))print(handle("foo bar baz"))i=0whilei<3:forjinrange(2):print(f"while i={i}for j={j}")i+=1defkind(v):matchv:caseint()ifv>0:return"正整型"caseint():return"非正整型"casestr():return"字符串"caselist()asxsiflen(xs)>2:return"长列表"case_:return"其他"print(kind(5),kind(-1),kind("hi"),kind([1,2,3,4]))

真实回显:

grade: B 退出 获取 user 设置 user=100 未知命令 while i=0 for j=0 while i=0 for j=1 while i=1 for j=0 while i=1 for j=1 while i=2 for j=0 while i=2 for j=1 正整型 非正整型 字符串 长列表

match是 Python 3.10 才有的结构模式匹配,比一长串if/elif清爽太多。它能解构序列:case ["get", name]表示"列表第一个元素是字符串get,剩下的绑定到name";case _是通配兜底。kind函数里还演示了带守卫的模式case int() if v > 0先匹配整型,再用if加条件,以及case list() as xs把匹配到的列表绑定到变量xs继续判断长度。结构化匹配在处理命令解析、协议字段时特别好用。

五、实战一:用 match 写四则运算计算器

把上面的match用起来,做个能识别"3 + 4"这种表达式的小计算器:

defcalc(expr):importre m=re.match(r"\s*([0-9.]+)\s*([+\-*/])\s*([0-9.]+)\s*",expr)ifnotm:return"格式错误"a,op,b=float(m.group(1)),m.group(2),float(m.group(3))matchop:case"+":returna+bcase"-":returna-bcase"*":returna*bcase"/":returna/bifb!=0else"除数不能为0"case_:return"未知运算符"forein["3 + 4","10 - 2","6 * 7","8 / 2","5 / 0","bad"]:print(f"{e!r:>10}=>{calc(e)}")

真实回显:

'3 + 4' => 7.0 '10 - 2' => 8.0 '6 * 7' => 42.0 '8 / 2' => 4.0 '5 / 0' => 除数不能为0 'bad' => 格式错误

这里用正则抽出数字和运算符,再用match分发。注意f"{e!r:>10}"!r把字符串按repr形式(带引号)输出,>10右对齐占 10 列,排版更整齐。5 / 0没有让程序崩溃,因为我提前用if b != 0拦住了除零,这是写计算器必须考虑的边界。

六、实战二:手机通讯录(dict + json 持久化)

再做一个带增删查和文件持久化的通讯录,顺便演示int()类型转换和json落盘:

importjson,os FILE="/root/lab-a/contacts.json"defload():ifos.path.exists(FILE):withopen(FILE,"r",encoding="utf-8")asf:returnjson.load(f)return{}defsave(book):withopen(FILE,"w",encoding="utf-8")asf:json.dump(book,f,ensure_ascii=False,indent=2)book=load()print("初始通讯录:",book)# 增book["张三"]={"phone":"13800000001","age":int(28)}book["李四"]={"phone":"13900000002","age":int(32)}save(book)print("新增后:",book)# 查print("查张三:",book.get("张三"))print("查王五(不存在):",book.get("王五","无此联系人"))# 改book["张三"]["phone"]="13811111111"save(book)# 删removed=book.pop("李四",None)print("删除李四:",removed)save(book)print("最终文件内容:")print(open(FILE,encoding="utf-8").read())

真实回显:

初始通讯录: {} 新增后: {'张三': {'phone': '13800000001', 'age': 28}, '李四': {'phone': '13900000002', 'age': 32}} 查张三: {'phone': '13800000001', 'age': 28} 查王五(不存在): 无此联系人 删除李四: {'phone': '13900000002', 'age': 32} 最终文件内容: { "张三": { "phone": "13811111111", "age": 28 } }

book.get(key, 默认值)是安全取值的写法——键不存在时返回你给的默认值而不是抛KeyError,查王五时返回了"无此联系人"就是它发挥作用。json.dump(..., ensure_ascii=False, indent=2)让中文原样保存、带缩进更可读。文件里"age": 28是数字而非字符串,因为我在存的时候用int(28)做了转换——从外部读进来的数据往往都是字符串,记得按需转成正确的类型。

真实踩坑:路径里的 %s 忘了格式化

这个通讯录脚本我第一版也写错了,原样贴出当时的报错:

初始通讯录: {} Traceback (most recent call last): File "/root/lab-a/contacts.py", line 21, in <module> save(book) File "/root/lab-a/contacts.py", line 12, in save with open(FILE, "w", encoding="utf-8") as f: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ FileNotFoundError: [Errno 2] No such file or directory: '%s/contacts.json'

原因很直白:我起初写的是FILE = "%s/contacts.json",却忘了在后面用% BASE%s替换成真实目录,于是 Python 真的去打开一个名字叫%s/contacts.json的文件,自然不存在。这种"格式化占位符忘了填"的笔误,在拼接路径、日志、SQL 时极常见。修法是直接用拼接好的绝对路径,或者老老实实FILE = "%s/contacts.json" % BASE。报错信息把出错的行和具体的文件名都打印出来了,照着看一眼就能定位。

七、避坑:可变默认参数

函数默认参数如果是可变对象(列表、字典等),会在多次调用间共享,酿成诡异 bug:

defadd_item_bad(item,lst=[]):lst.append(item)returnlstprint("第一次:",add_item_bad(1))print("第二次:",add_item_bad(2))# 期望 [2],实际累积了 [1,2]defadd_item_good(item,lst=None):iflstisNone:lst=[]lst.append(item)returnlstprint("good1:",add_item_good(1))print("good2:",add_item_good(2))

真实回显:

第一次: [1] 第二次: [1, 2] good1: [1] good2: [2]

第二次调用只传了2,却返回了[1, 2]——因为默认列表[]函数定义时只创建一次,之后每次调用都在往同一个列表里追加。正确写法是把默认值设为None,在函数体内再新建列表。这条坑我见过不止一个人栽,尤其是写递归或累积逻辑时。

八、避坑:浅拷贝

copy()和切片得到的是浅拷贝,嵌套的可变对象仍指向同一份:

importcopy a={"scores":[80,90]}b=a.copy()# 浅拷贝b["scores"].append(100)# 影响到了 a!print("a:",a)print("b:",b)print("是同一列表对象?",a["scores"]isb["scores"])c=copy.deepcopy(a)# 深拷贝c["scores"].append(999)print("深拷贝后 a:",a)print("深拷贝后 c:",c)

真实回显:

a: {'scores': [80, 90, 100]} b: {'scores': [80, 90, 100]} 是同一列表对象? True 深拷贝后 a: {'scores': [80, 90, 100]} 深拷贝后 c: {'scores': [80, 90, 100, 999]}

a.copy()之后,改b["scores"]竟然连a也变了,因为b["scores"]a["scores"]同一个列表对象(输出True已证实)。只要涉及嵌套可变结构,要用copy.deepcopy做深拷贝才能彻底隔离。

九、附带几个高频小技巧

最后补几个我写业务代码天天在用的小语法,顺便巩固上面的容器和循环。

names=["张三","李四","王五"]scores=[90,85,88]fori,ninenumerate(names):print(i,n)print("---- zip ----")forn,sinzip(names,scores):print(f"{n}:{s}")# 元组解包a,b,*rest=[1,2,3,4,5]print("a,b,rest =",a,b,rest)# 交换变量x,y=10,20x,y=y,xprint("swap:",x,y)# is vs ==m=[1,2];n=[1,2]print("==:",m==n,"is:",misn)

真实回显:

0 张三 1 李四 2 王五 ---- zip ---- 张三: 90 李四: 85 王五: 88 a,b,rest = 1 2 [3, 4, 5] swap: 20 10 ==: True is: False

enumerate在遍历时顺带给出下标,比自己维护一个计数器清爽;zip把多个可迭代对象按位置"拉链"式配对,处理"名字+分数"这类平行序列时离不开它。a, b, *rest = [1,2,3,4,5]这个写法我很喜欢——*rest把剩下的元素一股脑收进列表,得到rest = [3,4,5],拆包时特别灵活。变量交换x, y = y, x在 Python 里一行就成,不需要中间变量,这是元组解包的语法糖。

最后那个==is的区别值得单独划重点:m == nTrue,因为两个列表的内容相同;m is nFalse,因为is比的是对象身份(内存地址),这是两个不同的列表对象。is只对小整数缓存、单例None这类场景可靠,比较两个值永远用==,不要用is,这是新手写条件判断时很容易写错的一处。

踩坑清单

现象根因正确做法
0.1 + 0.2 == 0.3False二进制浮点固有误差math.iscloseDecimal
文件命名为numbers.py导致循环导入同名遮蔽标准库模块脚本名避开标准库保留名
FileNotFoundError: '%s/...'路径字符串%s忘了格式化直接拼绝对路径或补% BASE
可变默认参数跨调用串数据默认对象只创建一次默认设None,体内新建
浅拷贝后改一个影响另一个嵌套对象仍共享引用copy.deepcopy
match匹配不到缺少case _兜底总加一个通配兜底分支

总结

这一篇把 Python 3.12 的骨架过了一遍:变量与 f-string 让表达简洁,Decimalmath.isclose兜住浮点精度,四种容器加|=合并运算符覆盖日常数据结构,match模式匹配比if/elif更优雅也更强,两个小实战把语法落到了能跑的东西上。而那几个坑——标准库同名遮蔽、路径占位符漏填、可变默认参数、浅拷贝共享引用——都是我亲手敲错、看着报错改过来的,比任何记忆都牢。

语法只是工具,踩坑才是学费。写这篇时我刻意在每个示例后面都贴了真实回显,而不是只给结论——因为语法背得再熟,不亲手敲一遍、不亲眼看看报错长什么样,遇到真问题还是两眼一抹黑。下一篇可以聊函数、类与模块化,把这套基础真正用起来。


本文实验均在华为云 Flexus X 实例(Ubuntu 24.04, Python 3.12.3)上真实执行。

http://www.jsqmd.com/news/1261716/

相关文章:

  • MetricFlow:现代化指标定义与SQL编译的完整指南
  • 在编程过程中,字体的选择不仅影响美观,还直接关系到编程效率和舒适度
  • Translumo:打破语言障碍的实时屏幕翻译神器,让外语内容一目了然
  • AI搜索长尾词冷启动困局破解:72小时极速建模法,含真实电商/教育/医疗三行业数据集
  • AI代码生成与艺术风格融合:本地部署Codex转生成摇曳鳗项目实践指南
  • AMD Ryzen 处理器性能调优终极指南:用RyzenAdj释放你的硬件潜力
  • 长期项目中使用Taotoken Token Plan套餐的成本控制实践
  • DDrawCompat完整指南:让老游戏在现代Windows上完美运行
  • HR数据驱动转型关键突破(离职预测模型上线72小时见效实录)
  • 人生动力生成系统的SOP
  • AI如何赋能产品经理的需求排序与决策优化
  • 大模型开发实战:从零构建智能Agent全指南
  • Windows 11系统优化终极指南:3种高效方案对比与全面实操手册
  • 虚拟机安装Linux:开发者首选环境搭建指南
  • 双系统引导修复:Windows更新后Ubuntu启动项丢失解决方案
  • m4s-converter:3步将B站缓存视频转换为永久MP4的完整指南
  • 知识星球内容爬虫与PDF转换的技术实现与架构设计
  • 科研写作利器:千笔工具的核心功能与实战技巧
  • 工业AI质检系统:YOLOv5与PLC的实时集成实践
  • League Akari:告别英雄联盟游戏中的决策焦虑,本地化智能助手如何提升你的游戏体验
  • LVDS/CSI-2数据流控制:深入解析TI HSI链路列表与FIFO阈值配置
  • 2026最新!高中生练英语听力一定要知道的5个好用学习平台
  • 英雄联盟自动化工具箱:3分钟掌握League Akari的终极使用指南
  • Transformer大模型数据并行训练优化实践
  • 2026广州工装装修公司哪家靠谱|本地高口碑商用装企甄选汇总 - 装修新知
  • LangGraph多智能体系统实战:从原理到构建AI协作应用
  • 中医AI智能诊疗系统部署指南:从零搭建仲景大语言模型
  • 从大模型到智能体:AI技术范式的经济性与工程实践
  • 对比直接使用厂商API体验Taotoken在路由容灾与稳定性方面的优势
  • AI Agent工程实践:从技术架构到组织变革