从Day1打卡开始,很多人会觉得“变量?格式化字符串?这不有手就行”。但我在带打卡营这些年里,见过太多第一天就翻车的人——不是代码写不出来,而是变量名的坑、格式化符号的坑,能把一个简单的print卡到怀疑人生。这篇就专门把这两个基础点拆开揉碎讲清楚,往前走两步都是实打实要用的东西,零基础的朋友可以放心跟着走。
1 变量到底是什么——先破除一个误区
1.1 变量的本质:标签,不是盒子
很多老师和教程喜欢说“变量是装数据的盒子”,这个比喻在Python里是会害人的。C语言里变量确实可以理解成盒子——你在栈上声明了一个int a,内存里就划出4个字节,名字就是这块内存的地址别名。但Python不是这个模型。
a = [1, 2, 3] b = a b.append(4) print(a) # [1, 2, 3, 4]看到没,你改了b,结果a也变了。如果变量是“盒子”,把a的内容复制到b里,b改append不会影响a。真正的机制是:a = [1, 2, 3]这行代码,先创建了一个列表对象,然后就在这个对象上贴了一张写着“a”的便利贴;b = a不是在复制对象,而是又贴了一张写着“b”的便利贴。两张便利贴指向同一个列表对象,所以b.append等于a.append。
用id()函数可以验证:
a = [1, 2, 3] b = a print(id(a)) # 比如 140567890123456 print(id(b)) # 一模一样id返回的是对象的内存地址。a和b的id相同,说明它们引用的是同一个对象。这就是“变量是引用,不是容器”的核心含义。理解这一点,后面学列表、字典、函数传参时才能少踩至少一半的坑。
1.2 动态类型与“不需要声明”的真相
Python变量不需要提前声明类型,因为类型是运行时由对象决定的。你可以让一个变量先装整数,再装字符串,再装列表:
x = 10 print(type(x)) # <class 'int'> x = "hello" print(type(x)) # <class 'str'>这在Java、C#里直接报错,Python允许。好处是写起来快,坏处是长期项目里类型混乱容易出问题。所以后来有了类型注解(typing),但那只是给开发者和IDE看的标注,Python运行时根本不管。
还有一个关键点:变量必须赋值后才能使用。很多人第一天就会踩这个:
print(score) # NameError: name 'score' is not defined因为Python是解释执行。任何变量在使用前,必须先有一条赋值语句让它“存在”。这个规则看起来简单,但配合作用域时就会闹出UnboundLocalError,后面第2章细说。
1.3 命名规则与约定:基础但天天踩
硬性规则就三条:变量名只能由字母、数字、下划线组成;不能以数字开头;不能是Python关键字。
# 不能这么写 1name = "张三" # SyntaxError class = 100 # 关键字,SyntaxError # 可以这么写 name_1 = "张三" _name1 = 100 # 单下划线开头常表示“内部”变量关键字可以用keyword.kwlist查看全部列表,常见的有if、for、while、class、def、return、import、from、None、True、False等。关键字的判断很简单:编辑器会自动变色,如果你写的名字是高亮关键字色,那就不能用作变量名。
软性约定同样重要。社区通用的命名风格是snake_case,即全小写加下划线:
user_name = "张三" total_score = 89.5 max_value = 100不推荐用拼音缩写或毫无意义的单字母名。我见过有人写mzlx = "张三"(名字类型),三个月后自己都看不懂。命名就该让人一眼明白含义:是用户名就用user_name,是总分数就用total_score,宁可长一点也别省。
2 变量进阶:作用域、初始化与解包
2.1 作用域:只有函数才隔离,if和for都靠边站
很多从C语言转过来的朋友,习惯认为“if块里的变量在外面用不了”,在Python里这是错的。Python的作用域只有两种主要级别:模块级(全局)和函数级(局部)。if、for、while这些语句块,不会产生新的作用域。
if True: flag = 1 print(flag) # 1,正常运行for循环里产生的循环变量,在循环结束后依然存在:
for i in range(3): pass print(i) # 2,还能用真正产生作用域的是函数:
def test(): x = 10 print(x) # NameError这里x是函数内部的局部变量,函数执行完后就不存在了。同理,如果函数内试图读取一个全局变量,Python允许;但如果函数内给同名变量赋值,Python会认为这个变量是局部的,而且会在赋值前报错:
count = 10 def add(): count = count + 1 # UnboundLocalError: local variable 'count' referenced before assignment这是因为Python在编译函数时,看到了函数体内有对count的赋值操作,于是将整个函数作用域里的count都视为局部变量。解决办法是在函数里声明global count,或者改用可变对象。第一天不必深究,但要记住这个规律,否则后面写函数时会反复踩。
2.2 变量初始化:什么时候必须初始化和可变默认参数
新手常犯的毛病是用一个变量前不做初始化。累加器、计数器、字符串拼接这类场景必须初始化,否则直接运算会报错或得到错误结果:
sum_score = 0 scores = [89, 92, 78] for score in scores: sum_score += score print(sum_score)sum_score先初始化为0,再在循环里累加。如果不写第一行,循环第一轮就会报NameError。
还有一种坑是函数的可变默认参数。定义函数时若用可变对象做默认值,它会在函数定义时创建一次,之后所有调用都共用同一个对象:
def add_item(item, items=[]): items.append(item) return items print(add_item(1)) # [1] print(add_item(2)) # [1, 2]第二次调用竟然保留了第一次的结果。正确做法是从不把可变对象作为默认参数:
def add_item(item, items=None): if items is None: items = [] items.append(item) return items这一步是Python里极其经典的坑,第一天把它记住,等于白赚了一个别人要用报错才能换来的经验。
2.3 多重赋值与解包:写出更优雅的代码
Python支持一行同时给多个变量赋值:
name, age, city = "张三", 18, "北京"更妙的是交换变量不用第三变量:
a, b = 2, 3 a, b = b, a print(a, b) # 3 2右侧先构造一个元组(3, 2),再按顺序解包赋值给左侧的a、b。Python元组的特性让这个操作天然支持。
解包还能用在函数返回值或循环中:
first, *rest = [1, 2, 3, 4] print(first) # 1 print(rest) # [2, 3, 4]这里*rest把剩余的元素收进列表。类似地,遍历字典时可以for key, value in dict.items()同时取出键和值,这在处理数据时非常实用。
3 格式化字符串:三种写法一次说清
格式化字符串,本质就是把变量插到一段文本模板里。Python历史上主要有三种方式:%格式、str.format()和f-string。我强烈建议第一天就把三种都认识一遍,然后专注练习第三种f-string。
3.1 %格式化:老字号但该退场了
这是从C语言流传下来的传统写法,用%符号占位:
name = "张三" age = 18 print("我叫%s,今年%d岁" % (name, age))%s对应字符串,%d对应整数,%f对应浮点数。传入多个值时,需要放在一个元组里。%s几乎可以接受任意类型,而%d对字符串会直接报TypeError:
print("%d" % "18") # TypeError: %d format: a real number is required, not str这种写法的最大问题是类型和参数的对应关系要靠人工维护。后面还有转义百分号的麻烦,要写%%才能输出一个百分号:
print("完成度:%d%%" % 80) # 完成度:80%用起来有割裂感,阅读时注意力要在字符串和后面参数之间来回跳。虽然现在老项目里还能见到,你看到能读懂就行,新代码不推荐再用。
3.2 str.format():过渡时代的全能选手
Python 2.6到3.10之间,format()是主流。它用花括号{}做占位符,没有了%的类型限制:
name = "张三" age = 18 print("我叫{},今年{}岁".format(name, age))支持位置参数、索引和关键字:
print("{}和{}".format("小明", "小红")) # 按顺序 print("{1}和{0}".format("小明", "小红")) # 索引调序 print("{name}今年{age}岁".format(name="张三", age=18)) # 关键字还支持对齐、填充、小数位数等格式控制。最常用的是保留两位小数:
score = 89.666 print("成绩:{:.2f}".format(score)) # 成绩:89.67以及左对齐加宽度:
print("{:<10}*".format("名字")) # 名字 *format()的问题在于代码量偏大,尤其在变量多的时候,占位符和参数列表之间容易错位,而且命名冲突时不好排查。它是f-string的过渡方案,功能极全但写起来偏繁琐。
3.3 f-string:现代Python的首选
Python 3.6开始引入的f-string,是目前最接近“在字符串里直接写变量”的方案。用法是字符串前加f或F,内部用花括号写表达式:
name = "张三" age = 18 print(f"我叫{name},今年{age}岁")这就是最终形态:直接在花括号里写变量名,甚至写表达式、函数调用:
a, b = 10, 20 print(f"两数之和为{a + b}") # 两数之和为30 print(f"姓名是{name.upper()}") # 姓名是张三的upper结果f-string的格式控制语法与format一致,比如保留两位小数:
pi = 3.14159 print(f"圆周率约等于{pi:.2f}") # 3.14这是初学者最容易用到的功能之一。输出百分比时还能用.1%直接把小数转成百分比:
rate = 0.8734 print(f"命中率:{rate:.1%}") # 命中率:87.3%f-string性能也最好,每轮循环拼接大量日志时,它比format快不少。具体数字我手边不写死,但实测中使用场景下差距明显,它也是Python官方持续推荐的方案。
3.4 f-string里的三个细节
一是引号冲突。花括号内部和字符串本身会争引号:
# 下面的写法会报错 f"{'name':{width}}" # 某些版本会误读其实f-string内部用单引号包字符串没问题,麻烦的是外层也用了单引号。最简单的做法是内部用双引号、外部用单引号,或者反过来:
name = "张三" print(f"{name.upper()}") # 花括号内调用方法时写不带引号更稳 print(f"{'abc'!r}") # python会把repr结果放进来,得到 'abc'二是输出大括号本身。花括号在f-string里是特殊字符,要输出一个字面意义上的花括号,需要连写两个:
print(f"集合包含: {{{name}}}") # 集合包含: {张三}不写双花括号的话,Python会尝试把name当作表达式,后面跟着的“}”会报错。
三是花括号内不能出现反斜杠。比如想在f-string里对字符串做转义拼接,会报语法错误:
print(f"路径: {C:\\new\\folder}") # SyntaxError遇到这种情况,先把这个字符串加到一个变量里,再放进f-string:
path = "C:\\new\\folder" print(f"路径: {path}")别跟解释器硬刚,绕一下就好了。
三种格式化方式对比如下:
| 方式 | 可读性 | 性能 | 灵活性 | 推荐程度 |
|---|---|---|---|---|
| %格式化 | 较差 | 中等 | 低 | 新代码不推荐 |
| str.format() | 中等 | 较慢 | 高 | 老项目维护时用 |
| f-string | 最好 | 最快 | 高 | 强烈推荐 |
4 Day1打卡实操:设计一套完整练习
4.1 任务拆解与代码实现
打卡任务不要只做一道题,得做一个走通全流程的迷你案例。我给训练营设计了三道递进题目,从最简单的打印到综合应用,基本覆盖今天所有知识点。
题目一:变量定义与f-string基础输出
# 定义变量 name = "李四" age = 24 height = 1.75 # 用f-string输出一句话自我介绍 print(f"我叫{name},今年{age}岁,身高{height}米")输出结果:
我叫李四,今年24岁,身高1.75米这一题的验收点是:变量名是否用了snake_case,age是否存为数字而非字符串,f-string的写法是否规范。很多新手会直接把身高写成字符串,虽然是可行的,但在后续计算时会出大问题。
题目二:温度转换
# 华氏温度转摄氏温度,公式 C = (F - 32) * 5 / 9 f = 98.6 c = (f - 32) * 5 / 9 print(f"华氏{f}度 = 摄氏{c:.2f}度")输出结果:
华氏98.6度 = 摄氏37.00度这里考察的是运算符优先级和格式化控制。(f - 32) * 5 / 9的括号不能省,否则f - 32 * 5 / 9结果是完全不同的。.2f控制只输出两位小数,如果忽略这个格式符,会得到一长串浮点数。
题目三:BMI计算综合练习
# BMI = 体重(kg) / 身高(m)的平方 weight = 70 height = 1.75 bmi = weight / (height ** 2) print(f"体重:{weight}kg,身高:{height}m,BMI指数为{bmi:.1f}") if bmi < 18.5: print("体重过轻") elif bmi < 24: print("体重正常") elif bmi < 28: print("体重超重") else: print("肥胖")输出结果:
体重:70kg,身高:1.75m,BMI指数为22.9 体重正常这一题引入了简单的条件判断,展示变量计算后如何参与逻辑分支。打卡营我建议这题必做,因为第一天就把变量+格式化字符串+if判断串起来,后面学函数时思路会通畅很多。
4.2 打卡自测清单
把你的代码跑完,对着这张表自查一下:
- 变量名是否有意义,格式是否为snake_case。
- f-string是否在前面加了f前缀,花括号内的变量名有没有拼错。
- 数值型输出是否用了格式控制,像
.2f或.1f。 - 有没有故意用
+去拼接字符串和数字,直接TypeError报错。 - 代码能否不加修改地连续运行两次,确保没有残留状态问题。
每一条对应一个常见的Day1错误。尤其是最后一条,编程环境里运行一次和运行两次结果一样,是代码健壮性的基本要求。很多人第一次跑成功就打卡,等到第二次运行时,变量残留导致输出错乱,提交作业时被打回,这种教训特别常见。
4.3 扩展方向自己动手
完成基础三题后,可以再试试:
# 把今天的三个主题串起来:变量管理、循环、格式化输出 students = [ ("张三", 89.5), ("李四", 92.0), ("王五", 76.0), ] for idx, (name, score) in enumerate(students, 1): print(f"第{idx}名:{name},成绩:{score:.1f}")这里使用了列表存元组、enumerate生成序号、f-string格式化输出三个知识点,比单纯打印一行难度高,但第一天啃下来收获很大。
5 常见问题与避坑实录
5.1 f-string引号冲突和变量拼写
我见过最多的问题,是把f-string放在单引号字符串里,又在花括号里想用单引号做字符串字面量。比如想输出一个人的职业:
name = "张三" # 这种写法在Python 3.12前会报错 print(f"{name}是'程序员'")花括号里出现单引号,而外层也是单引号,Python无法判断哪里结束。最简单的方案是用双引号包字符串:
print(f"{name}是'程序员'") # 双引号包裹,内部自由使用单引号另一个常见问题是花括号内的变量拼写错误。Python的报错信息虽然会提示NameError,但新手经常盯着整行代码看不出问题。建议函数一套印出来的,先检查花括号内部的变量名是否和定义时完全一致,大小写、下划线都不能差。
5.2 变量名别用内置名和关键字
list、dict、str这类内置类型名称,很多人习惯拿来当变量名:
list = [1, 2, 3] print(list)第一行能运行,真正的问题发生在后面:
next_list = list(range(5)) # TypeError: 'list' object is not callable因为list这个名字已经被你覆盖成列表对象了,原来的list()构造方法被遮蔽。同理,也别用input、print、max这些内置函数名当变量名,否则后面都要踩“TypeError: 'int' object is not callable”之类的报错。让IDE帮你识别高亮,只要看见内置名被改色,就换个名字。
5.3 类型错位:字符串不能直接拼接数字
新手最经典的三分钟错误:
age = 18 print("我今年" + age + "岁") # TypeError: can only concatenate str (not "int") to strPython不会像JavaScript那样自动把数字转成字符串。解决办法有两种:
# 方法一:显式强转 print("我今年" + str(age) + "岁") # 方法二:f-string直接解决 print(f"我今年{age}岁")我在打卡营里反复强调:凡是想把数字变量塞进一段话里,优先思考f-string,而不是去拼加号。这能避免至少九成以上的类型错误。
5.4 花括号排版和可视化对齐
f-string的对齐功能是日志和报表排版的神器。先说基本语法:
name1 = "张三" name2 = "欧阳娜娜" print(f"{name1:<10}成绩优秀") print(f"{name2:<10}成绩良好")<10的意思是左对齐,占位10个字符宽度。输出时,短名字后面会补空格,形成整齐的一列:
张三 成绩优秀 欧阳娜娜 成绩良好配合制表符\t效果更好。做报表、打印统计信息时,这个功能能让你写的命令行程序看起来专业不少。第一天就养成习惯,以后写日志时体验完全不一样。
5.5 混合类型输出时优先级检查
格式化字符串里的表达式,尤其是涉及加减乘除时,很容易把逻辑写错。比如:
price = 9.9 count = 3 print(f"总价:{price * count:.2f}元")花括号内的price * count先计算,再套用.2f格式化。如果有人写成{count}件,总价{price * count:.2f}元,要特别注意花括号内只能有一个表达式加一个格式符,格式符紧跟在表达式后,中间不能有空格。空格不是报错,但有些老版本会解析异常,养成规范习惯就好。
6 打卡后的个人体会
带过这么多轮Day1打卡,我发现一个规律:能在第一天把f-string练熟、把变量是“标签”这个概念内化的人,后面学函数、学循环、学文件操作时都特别顺。因为这个起点决定了你看待数据的方式正确与否——变量只是名字,对象才真正装着数据;格式化输出不是锦上添花,而是调试和对外沟通的基本功。
有些朋友觉得这些内容太简单,跳过去直接学爬虫。真到写代码时,还是得回来查f-string怎么对齐、变量作用域怎么理解。第一天老老实实写完三道题、对照自测清单过一遍,比刷十道“选择题”有用得多。明天进入数字类型和类型转换,今天打好的底子会派上大用场。