1024程序员节

今天跟着“1024程序员节”的课表开始学 Python。上午的进度虽然只推进了两节(复习基础语法 + 函数)为了防止自己“学了就忘”,我把上午的踩坑记录和知识理解整理了一下。

一、 被 while 和 break 折磨

今天写的第一个完整程序是“重启终端:口令 1024 。代码虽然不长,但我调了好几次。
下面是我写的打卡器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
check_count = 0

while True:
if check_count == 3:
print("今天打卡已上限,去休息吧")
break
task = input("请输入今天的学习内容(输入exit退出): ")
if task == "exit":
print("终端已关闭")
break
if task == "1024":
check_count += 1
print(f"打卡成功,当前已打卡{check_count}/3个。")
print("-" * 20)

最后那句可以不写的,就是看起来更美观一点

给自己记下的两个教训:

  1. check_count = 0 必须写在 while 外面! 我今天才彻底搞懂,如果把这个记分牌写在 while 里面,每次循环一开始,分数就被强行清零了,永远也攒不够 3 次 。
  2. 不加 break 就是是个死循环。 while True 是个无底洞,如果我不写 break 去结束它,即使输入了 exit ,程序也会继续问我 请输入学习内容 。

二、 懂了,但没完全懂的函数(参数和返回值)

上午重点学了 def 定义函数。我把函数整合到了打卡器里,写了进阶版的代码:

1
2
3
4
5
6
7
8
9
def calculate_exp(times):
base_exp = times * 10
if times >= 3:
base_exp += 50
return base_exp

# ... 这里为了方便省略了while循环,主程序是前面标题一的那个代码 ...
my_exp = calculate_exp(check_count)
print(f"获得经验值:{my_exp} 点!")

我们总说“定义一个函数”,我查了一下其实代码里的 def 就是英文 define(定义)的缩写。
那函数又是什么:你放进去 参数 ,按下启动键 执行代码,最后出来了 返回值 。它最大的好处就是把一堆干活的代码“ 打包 ”起来,起个名字,以后需要用到的时候,喊一声名字就行了,不用重复写。

  • def :给电脑下达“造机器”的指令。
  • calculate_exp :给这个机器起的名字(以后叫名字就能用它)。
  • ( times ):机器接收参数的地方,得往里面塞个东西,机器才能运转。
  • :(冒号):这个很重要,它相当于宣告:“ 机器内部的构造说明书,从下一行开始!”所以下一行必须要缩进。
    我理解的 :原来 def 只是在“画图纸”和“造机器”。只要电脑没看到你 “ 按开关 ”(也就是没看到你调用 calculate_exp(3)),这台机器造好了也只会静静躺在那里,根本不会自己跑起来。只有调用它,它才会开始工作。

脑补:print 和 return 到底差在哪?
这是上午最卡我的地方。我还以为写个函数让它打印出来就完事了,今天才明白:

  • return 是交还结果:函数算完账,用 return 把数据悄悄塞回给外面的变量( my_exp ),这一步在屏幕上是看不见的。
  • print 只是让你看个响:外面拿到了 my_exp 这个数据后,我再写个 print,屏幕上才会出现“获得经验值”。

    总结 :没 return,外面的程序就接不到数据;没 print,我就看不到结果。

三、 实战做题:

上午做了一道限时题:算算距离 1024 经验差多少,如果超额了就输出 0,且要求不要打印任何提示语。
我提交的正确代码(后面还有翻车操作):

1
2
3
4
5
6
7
8
9
10
11
# 1. 接收输入:题目要求不要任何提示语,直接接收并转成整数
n = int(input())

# 2. 算差值:目标 1024 减去当前分数 (n * 32)
diff = 1024 - n * 32

# 3. 判断输出:如果差值大于 0,说明还没达到;否则说明达到了或超额
if diff > 0:
print(diff)
else:
print(0)

第一步:接收输入

题目要求 “ 输入一个非负整数 ” ,然后题目是说:“不要打印任何提示语”。

所以,代码不能写 input(“请输入…:”),只能写:n = int(input())

第二步:算差值

题目说“每完成一道题获得 32 点经验”,那目前的经验就是 n * 32。目标是要攒够 1024 点经验,那距离目标还差多少呢?

就是拿总分减去当前分:1024 - (n * 32)。我把这个算出来的差值装进一个变量里,叫它 diff:

diff = 1024 - n * 32

复盘时的反思:现在我觉得叫 task_count 更好
我上面交上去的代码里,变量用的是 n。这是数学题的老习惯,我当时图省事就写了。
但现在写这篇复盘博客时,我越看越觉得不对劲。代码不仅是给电脑跑的,更是给人看的。n 过几天我自己可能就忘了是啥意思。
如果把它起名叫 task_count( 任务数量,也可以理解成题目数量 ),一眼就能看出这是“ 完成的题目数量 ”,可读性强多了。

第三步:判断输出
题目要求:如果还没达到,输出差值;如果达到或超过了,输出 0。
这时候就要看 diff 的值了:

· 如果 diff > 0:说明还没到 1024,那就直接输出差值。
· 如果 diff <= 0(也就是 else):说明不仅到了,还可能超额了(差值是负数或者0),题目要求这种情况输出 0,那就直接 print(0)。
总结一下我的思考:
刚开始做题时,我容易在“大于、小于”里面绕进去(这也是我踩坑第 3 点写的,看别人写成 if diff >= 1024 直接绕晕了)。
后来我想明白了:我只要关心“还差多少(正数)”就行了,至于超过的部分,题目说不用管,直接给个 0 就行。

运行时我的翻车操作:

1. 输入空回车会死机 :我运行测试的时候手欠,直接按了回车,结果报错,具体它写的什么不记得,但是大概意思就是:int( ) 遇到空字符串(空气)直接罢工了。以后测试这种题,千万不能习惯性按回车。

2. 不要乱加提示语:平时习惯写 input(“请输入…”),但是系统它只认纯数字。

3. 逻辑差点写反:差值是 1024 - 得分。如果超额完成,这个差值就是负数。所以判断是否输出 0,条件是 if diff <= 0 或者像上面那样 if diff > 0 走正常流程。看别人代码时,发现有人写成了 if diff >= 1024,逻辑直接就绕进去了。

四、数据结构:把数据管起来(列表与字典)

下午学了字典(Dict)和列表(List)

  • 字典 :用大括号 { } 包起来,左边是键(Key),右边是值(Value)。
  • 我犯的错 :一开始把年龄写成了 ‘year’ : ‘19’(加了引号变成了字符串)。以后要是拿年龄做加减法计算,绝对会报错,数字千万别加引号。
    先看一段最基础的代码,感受一下:
1
2
3
4
5
6
7
8
9
10
11
12
13
# 1. 创建一个字典
player = {
'name': 'myrvyn',
'year': 19, #我就是这里写错了😅,不要加引号
'hobby': '敲代码'
}

# 2. 读取:通过键名取值
print(player['name']) # 输出:myrvyn

# 3. 修改:直接给键重新赋值
player['year'] = 20 # 年龄改成20岁
print(player['year']) # 输出:20

用下来我觉得字典最好用的地方在于,它能用一个“名字(键)”直接找到对应的“东西(值)”。而且它是可变对象,我们可以随时修改里面的数据,这在做身份卡、存用户信息时非常方便。


菜单循环与功能拆分

然后这是我课上写的代码分解:

1
2
3
4
5
6
# 1. 用字典存储个人身份信息
my_info = {
'name': 'myrvyn',
'year': 19,
'hobby': '敲代码'
}

为什么 my_info 要写在外面?
因为它是“全局”的数据。

1
2
3
4
5
6
7
8
# 2. 功能函数:封装展示个人名片的逻辑
def show_card(info_dict):
print("\n" + "=" * 25)
print("【个人信息卡片】")
print(f"昵称:{info_dict['name']}")
print(f"年龄:{info_dict['year']}")
print(f"爱好:{info_dict['hobby']}")
print("=" * 25 + "\n")

为什么要单独写一个 show_card 函数?
因为不封装成函数,每次用户输入 check ,我们都要把上面这几行 print 重新写一遍。封装成函数后,主程序里只要一句 show_card(my_info) 就能直接调用,代码更干净。这就叫“封装”。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 3. 主程序:命令行交互循环
print("(提示: 输入1024打卡, 输入check查看身份, 输入exit退出)")

while True:
user_input = input("请输入指令: ")

# 路由分发:根据不同的指令执行不同的操作
if user_input == "exit":
print("终端已关闭。")
break # 退出无限循环
elif user_input == "1024":
print("打卡成功,经验值+1")
elif user_input == "check":
show_card(my_info) # 调用函数,传入字典
else:
print("未知指令,请重新输入")

讲讲 while True 的感觉。就是可以比喻说:“这就好比我开了一家店,只要顾客没喊 exit(关门),我就一直坐在这里等他们输入指令。”

看着这三段代码,最大的感受就是“逻辑其实不难,就是把生活搬进代码里”。字典就是我的身份档案,函数就是我的展示卡片动作,while 循环就是我在等你跟我说话。

列表在代码里怎么用(简洁了一点):

在代码分解1里面,最后一个代码后面,加上 'skills': ['Python', '写代码', '找Bug']。在“代码分解2”里面倒数第二行,加上 print(f"技能:{info_dict['skills']}")。
理解 :为什么要在字典里塞一个列表?

  • 在写“ 个人信息卡片 ”的时候,我发现“爱好”通常只有一个(比如敲代码),用字符串就够了。但是“技能”往往有多个。
  • 如果我把技能写成 ‘skills’: ‘Python, 写代码, 找Bug’,它本质上还是一个长字符串,以后如果我想单独把 Python 挑出来,就很麻烦。
  • 所以我用了一个列表 [‘Python’, ‘写代码’, ‘找Bug’] 作为字典的值。这就是今天学到的(字典套列表)。当程序打印 info_dict[‘skills’] 的时候,它会原封不动地把整个列表展示出来。这样写,方便以后随时添加新技能。

五、让程序拥有记忆与容错能力(with open)

前面的终端,只要一敲 exit 数据就全丢了,而且用户要是乱输入还会直接崩溃。为了解决这两个问题,我用了两段代码来巩固 with open 和 异常处理。

怎么理解 with open 这几个词?

  • open:就是“打开文件”。
  • as f:给打开的文件起个外号叫 f。接下来读(f.read())或者写(f.write()),直接喊 f 就行。
  • with:这是最核心的词。有了它,就完全不用写 f.close( )。只要代码退出了 with 的缩进范围,不管中间有没有报错,它都会保证把文件安全关上。

1. with open:这到底是啥?

这是以前的“ 非优雅 ”写法(容易漏内存):

1
2
3
f = open("笔记.txt", "r", encoding="utf-8")
content = f.read()
f.close() # 必须手动关门!

不好在哪? 如果 f.read() 的时候代码突然报错崩溃了,程序直接中断,根本走不到 f.close() 这一步。结果就是文件被占用,造成内存泄漏。

这是现在的“ 优雅 ”写法(with open):

1
2
3
with open("笔记.txt", "r", encoding="utf-8") as f:
content = f.read()
# 只要退出了缩进,文件就会自动关闭,就算中途报错也会关!

2. 用 mode=“a” 写一个极简“ Bug记录仪 ”

懂了 with open 之后,我写了一个极简版的代码,把灵感追加存进 txt 文件里:

1
2
3
4
5
6
7
content = input("请输入你的灵感: ")

# mode="a" 代表追加模式,不会覆盖旧内容
with open("灵感.txt", mode="a", encoding="utf-8") as file:
file.write(content + "\n") # 加 \n 是为了换行

print("记录成功!")

复盘:

  • 之前学的是 mode=“w”( 覆盖写入 ),只要用 “w”,以前写的东西就全没了。
  • 这次用了 mode=“a”(追加),它就像翻到笔记本的下一页接着写,之前的记录稳稳保留。这就是让程序拥有“ 记忆 ”的基础。

结合课上的截图,我发现 open 函数其实大有学问。它不仅仅是一个 “ 打开 ” 动作,里面还有好几个关键参数:

1
2
3
# 优雅写法完整版
with open(r"C:\Users\1\Desktop\test.txt", encoding="utf-8", mode="w") as f:
content = f.read()

这行代码里包含了这几个关键点:

1. 文件路径 :

课上的笔记写了这两个概念,我一开始也绕晕了,后来用快递地址和指路来理解,瞬间就通了:

绝对路径 :( 像写快递地址 )

  • 概念:从电脑的“盘符”(比如 C盘、D盘)开始,一直写到你想要的文件名,一步都不落下。
  • 代码示例:C:\Users\1\Desktop\today\test\test2.txt

    我的理解:就像寄快递,必须写明“中国-江西省-南昌市-某某区-某某路-某某小区-几栋几号”。不管你人在哪里,只要拿到这个完整的地址,就一定能找到目标。

相对路径: ( 像在家里指路 )

概念:以你当前运行的代码文件所在的位置为起点,去找到目标文件。

代码示例 :

  • 如果文件和代码在同一个文件夹里,直接写文件名就行:“test2.txt” 。
  • 如果在代码文件的上一级目录,写:“…/test2.txt” 。

    我的理解:就像你在家里坐在沙发上,跟室友说 “ 去厨房拿个苹果 ”。你不需要说 “ 去地球上中国某某小区的厨房 ”,因为室友(电脑)知道你现在就在家里(当前代码目录),它顺着你的位置去找厨房就行。

  • 注意 :Windows 路径里有反斜杠 \,为了防止被识别为转义字符(比如 \n),前面一定要加个 r(代表 raw string)。

2. 编码 (encoding):

  • 一定要写 encoding=“utf-8” 。如果电脑默认用 GBK ,读中文就会乱码。
  • 为什么一定要写? 记得课上学长说的:计算机底层只认识 0 和 1,把中文变成 0 和 1 的规则就叫 “ 编码 ”。不同系统默认的编码不一样(这是我查的Windows 经常默认是 GBK,而 Mac/Linux 默认是 UTF-8)。
  • 乱码:如果不指定 encoding=“utf-8”,写进去的中文在别人的电脑上打开可能全是一堆问号或者“锟斤拷”。
  • 结论:只要写文件,无脑加上 encoding=“utf-8”

3. 模式 (mode):

这是最核心的参数,决定了你打开文件后能干嘛:

  • mode=“r”(只读模式,默认): 只能看,不能改。如果文件不存在,程序会直接报错 FileNotFoundError。

课后补充:带 + 和 b 的模式:

  • r+、w+、a+ :代表既能读又能写。
  • rb、wb :代表以二进制模式读写。如果要处理图片、视频、音频这些非文本文件,就必须用带 b 的模式,且不能指定 encoding 。

总结:
平时写代码,如果能用相对路径(比如就直接写 “log.txt”)就用相对路径,因为代码挪到别的电脑上也能直接跑。

如果实在找不到,或者必须用绝对路径,记得在 Windows 下加个 r 防转义

基础异常处理:

有了文件操作还不够,如果用户乱输入,程序还是会崩溃。我写了个“成绩查询系统”来练习 try 和 except 。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
scores = {"语文": 91, "数学": 90, "英语": 77}
subject = input("请输入科目: ")

try:
# 尝试拿数据,如果输入了字典里没有的科目,会报 KeyError
print(f"你的{subject}成绩是 {scores[subject]} 分")

except KeyError:
# 接住错误,程序不崩溃,温柔地提示你
print("没有找到该科目的记录,请检查拼写~")

else:
# 只要 try 里面没报错,就会执行这里
print("查询完毕!")

结合我的成绩查询代码,它的运行逻辑是这样的:

1. try(尝试区):

1
2
subject = input("请输入科目: ")
print(f"你的{subject}成绩是 {scores[subject]} 分")

我告诉电脑:“ 你先试着执行这两行代码 ”。如果我输入了“ 数学 ”,字典 scores 里面有,电脑顺利打印出成绩。
2. except KeyError(兜底区):

如果我手欠,输入了“ 物理 ”或者“ 体育 ”或者其他什么,字典里根本没有这个键。按照以前的脾气,电脑会直接爆红报错(KeyError),程序当场死机。
但现在我写了 except KeyError: 这就相当于告诉电脑:“如果你遇到了找不到键(KeyError)的错误,别死机,来执行我这里的代码。”所以它会温柔地打印一句:“ 没有找到该科目的记录 ”。
3. else(顺利区):

1
2
else:
print("查询完毕")

只要 try 里面的代码没有发生任何报错,顺利跑完了,它才会接着执行 else 里面的代码。
我的脑补 :
以前只要我输入错东西,程序直接一片爆红死机。现在有了 try 和 except:

  • try 就是 “ 你试着跑一下 ”。
  • except KeyError 就是 “ 如果报了这个错,我来接住 ”。
  • else 是 “ 如果一切顺利,最后打印这句话 ” 。

    这样程序就有了容错能力

六、 限时挑战赛:B题与C题复盘

在做完前面的终端后,我挑战了下面两道题。这两道题让我对“枚举”和“基础复杂度”有了深刻的体会。

C题:连续打卡

题目大意:输入一个只包含 0 和 1 的字符串(1代表打卡了),求最长连续 1 的长度。
示例:输入 11011101,输出 3。

思路拆分:
题目给的提示非常有用:“ cur 记当前连续几个 1,best 记历史最长 ”。
这就像打游戏,cur 是你当前的连杀数,best 是你本局的最高连杀记录。一旦死了一次(遇到 ‘0’),cur 就归零了,但 best 会永远记住你最辉煌的时刻。

我的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
s = input()
cur = 0 # 当前连续 1 的个数
best = 0 # 历史最长连续 1 的个数

for char in s:
if char == '1':
cur += 1 # 遇到 1,当前连击数 +1
if cur > best:
best = cur # 顺手更新历史最高纪录
else:
cur = 0 # 遇到 0,连击中断,归零

print(best)

B题:寻找节日数字

题目大意:输入一个整数 n,统计 1 到 n 之间,包含字符 ‘4’ 的整数有多少个。
示例:输入 50,输出 14(因为 4, 14, 24, 34, 40~49 里面都有 4)。

思路拆分:
一开始我想得很复杂,去算个位、十位是不是 4。但题目提示直接把我看傻了:“把数字变成字符串,用 “4” in str(i) 就能判断”。
对哦,用 str(i) 把数字变成文本,然后直接看 “4” 在不在里面。

至于我看课表里说的 “ 基础复杂度 ”,题目里有一句话:“1 到 100000 共 10 万次判断,对电脑来说是眨眼的事。” 我觉得数 10 万个数要累死,但对 CPU 来说,连一秒钟都不用🤡。

我的代码:

1
2
3
4
5
6
7
8
9
10
n = int(input())
count = 0 # 计数器

# 枚举 1 到 n 的所有数字
for i in range(1, n + 1):
# 将整数 i 转成字符串,检查 '4' 是否在里面
if "4" in str(i):
count += 1 # 如果包含,计数器加 1

print(count)

总结:
这两道题让我明白了,写程序不需要像人脑那样去 “ 硬算 ”。

  • 遇到数数的问题,用 for 循环去枚举(一个个试)就行了,电脑最不怕的就是重复劳动。
  • 遇到判断包含的问题,把数字转成字符串去查找,往往是会比数学公式更简单的。
  • 这两道题的代码都没有超过 10 行,但解决的都是非常实际的问题。