Python 从入门语法到算法实习工程化:基础、数据结构、脚本、日志与项目模板

这篇文章是给“想投算法实习”的 Python 学习路线。它从最基础的语法开始,但目标不是停在 ifforlist 这些知识点,而是一路接到真实项目里会用到的工程能力:函数拆分、模块组织、文件读写、异常处理、日志、命令行参数、配置文件、实验记录和可复现脚本。

算法实习里的 Python 通常有两种用法:一种是写算法题,要求代码清楚、边界严谨;另一种是写项目和实验脚本,要求数据能读、模型能跑、参数能改、日志能查、结果能复现。这篇文章会把两条线接起来。

1. Python 程序从哪里开始

最简单的 Python 程序:

1
print("Hello, Python")

运行时,Python 解释器会从上到下执行代码。和 C++ 不同,Python 不需要先写 main(),也不需要编译成可执行文件。

但项目里仍然建议保留一个清晰入口:

1
2
3
4
5
6
def main():
print("Hello, Python")


if __name__ == "__main__":
main()

这段代码的意思是:当这个文件被直接运行时,执行 main();如果它被别的文件导入,就不自动执行 main()

这是 Python 工程里非常常见的结构。以后你写训练脚本、数据处理脚本、评估脚本,基本都可以从这个模板开始。

2. 变量和基础类型

Python 变量不用提前声明类型:

1
2
3
4
age = 21
score = 92.5
name = "Richard"
is_student = True

常见基础类型:

类型 示例 用途
int 10 整数
float 3.14 小数
str "hello" 文本
bool True 判断真假
NoneType None 表示空值

可以用 type() 查看类型:

1
2
x = 10
print(type(x)) # <class 'int'>

算法题里常见输入都是字符串,需要手动转换:

1
2
n = int(input())
x = float(input())

如果一行里有多个整数:

1
a, b = map(int, input().split())

这句的意思是:

  • input() 读入一整行字符串。
  • .split() 按空格切开。
  • map(int, ...) 把每个部分转成整数。
  • a, b = ... 解包成两个变量。

3. 运算符和表达式

常见数学运算:

1
2
3
4
5
6
7
print(7 + 2)   # 9
print(7 - 2) # 5
print(7 * 2) # 14
print(7 / 2) # 3.5
print(7 // 2) # 3
print(7 % 2) # 1
print(2 ** 3) # 8

算法里最常用的是:

  • //:整除,比如求中点、去掉数字最后一位。
  • %:取余,比如判断奇偶、循环下标、哈希。
  • **:幂运算。

判断奇偶:

1
2
def is_even(x):
return x % 2 == 0

计算十进制数字位数:

1
2
3
4
5
6
7
8
9
10
def count_digits(n):
if n == 0:
return 1

count = 0
while n > 0:
n //= 10
count += 1

return count

4. 字符串:文本处理的基础

字符串是 NLP、日志处理、文件路径、数据清洗的基础。

1
2
3
4
5
6
s = "algorithm"

print(s[0]) # a
print(s[-1]) # m
print(s[0:4]) # algo
print(len(s)) # 9

常见方法:

1
2
3
4
5
6
7
text = "  Hello Python  "

print(text.strip()) # 去掉两边空格
print(text.lower()) # 转小写
print(text.upper()) # 转大写
print(text.replace("Python", "World"))
print("a,b,c".split(",")) # ['a', 'b', 'c']

格式化字符串推荐用 f-string:

1
2
3
4
name = "Richard"
score = 92.5

print(f"{name}'s score is {score}")

项目里 f-string 很常用,比如拼日志、拼文件名、输出指标:

1
2
3
4
5
epoch = 3
loss = 0.2451
acc = 0.8734

print(f"epoch={epoch}, loss={loss:.4f}, acc={acc:.4f}")

:.4f 表示保留四位小数。

5. 条件判断:让程序做选择

1
2
3
4
5
6
7
8
9
10
11
12
score = 86

if score >= 90:
grade = "A"
elif score >= 80:
grade = "B"
elif score >= 70:
grade = "C"
else:
grade = "D"

print(grade)

复杂条件:

1
2
3
4
5
6
7
8
if age >= 18 and has_ticket:
print("can enter")

if user_type == "admin" or user_type == "owner":
print("can edit")

if not is_valid:
print("invalid input")

写条件判断时,优先处理非法情况:

1
2
3
4
5
def safe_divide(a, b):
if b == 0:
return None

return a / b

这种写法很适合工程代码,因为异常情况被提前挡住,主逻辑会更清晰。

6. 循环:重复执行和遍历数据

for 适合遍历:

1
2
3
4
numbers = [3, 5, 7]

for x in numbers:
print(x)

如果需要下标:

1
2
for i, x in enumerate(numbers):
print(i, x)

while 适合“不知道循环几次”的场景:

1
2
3
4
5
6
n = 12345
count = 0

while n > 0:
n //= 10
count += 1

算法题里常见的循环模板:

1
2
3
4
5
6
7
8
for i in range(n):
...

for i in range(1, n):
...

for i in range(n - 1, -1, -1):
...

边界很重要。长度为 n 的列表,合法下标是 0n - 1

7. 列表、元组、字典、集合

list

列表保存一组有顺序的数据:

1
2
3
4
5
nums = [4, 1, 7, 2]

nums.append(9)
nums[0] = 100
print(nums)

切片:

1
2
3
print(nums[:2])
print(nums[2:])
print(nums[::-1])

tuple

元组通常表示固定组合:

1
2
point = (3, 4)
x, y = point

算法里常用 tuple 存坐标、边、状态:

1
2
edge = ("a", "b", 5)
state = (row, col, steps)

dict

字典保存 key-value:

1
2
3
4
5
6
freq = {}

for ch in "banana":
freq[ch] = freq.get(ch, 0) + 1

print(freq)

dict.get(key, default) 的意思是:如果 key 存在,就返回对应值;如果不存在,就返回默认值。

这在计数里非常常用。

set

集合用于去重和快速判断是否出现过:

1
2
3
4
5
6
seen = set()

for x in [1, 2, 2, 3]:
seen.add(x)

print(seen)

图搜索里常用 visited 集合:

1
2
3
4
5
visited = set()
visited.add(start)

if node not in visited:
...

8. 函数:从脚本变成可维护代码

函数是工程化第一步。

1
2
def add(a, b):
return a + b

一个函数最好只做一件事:

1
2
3
4
5
6
7
8
9
10
11
def read_lines(path):
with open(path, "r", encoding="utf-8") as f:
return [line.strip() for line in f]


def count_words(lines):
freq = {}
for line in lines:
for word in line.split():
freq[word] = freq.get(word, 0) + 1
return freq

这样写比把所有逻辑堆在一个文件里更容易测试和复用。

推荐给函数写简单 docstring:

1
2
3
4
def top_k(freq, k):
"""Return top-k items sorted by frequency descending."""
items = sorted(freq.items(), key=lambda item: (-item[1], item[0]))
return items[:k]

9. 列表推导式和生成式

列表推导式让代码更短:

1
squares = [x * x for x in range(10)]

带条件:

1
evens = [x for x in range(20) if x % 2 == 0]

但不要为了短而牺牲可读性。复杂逻辑建议写成普通循环。

生成式适合节省内存:

1
total = sum(x * x for x in range(1000000))

10. 排序:算法题和数据处理都常用

Python 内置排序:

1
2
nums = [4, 1, 7, 2]
print(sorted(nums))

按复杂规则排序:

1
2
3
4
items = [("apple", 3), ("banana", 5), ("cat", 3)]

items = sorted(items, key=lambda item: (-item[1], item[0]))
print(items)

这里的排序规则是:

  • 先按数量降序。
  • 如果数量一样,按单词字典序升序。

这类写法在推荐、搜索、排行榜、词频统计里很常见。

11. 文件读写:让程序处理真实数据

读文本文件:

1
2
3
4
5
6
from pathlib import Path

path = Path("data/input.txt")

with path.open("r", encoding="utf-8") as f:
lines = [line.strip() for line in f]

写文件:

1
2
3
4
5
out_path = Path("outputs/result.txt")
out_path.parent.mkdir(parents=True, exist_ok=True)

with out_path.open("w", encoding="utf-8") as f:
f.write("hello\n")

推荐使用 pathlib.Path,比手写字符串路径更稳。

读 JSON:

1
2
3
4
import json

with open("config.json", "r", encoding="utf-8") as f:
config = json.load(f)

写 JSON:

1
2
with open("metrics.json", "w", encoding="utf-8") as f:
json.dump({"accuracy": 0.87}, f, ensure_ascii=False, indent=2)

ensure_ascii=False 可以避免中文被转义。

12. 异常处理:让程序失败得更清楚

异常处理不是为了隐藏错误,而是为了让错误更可控。

1
2
3
4
5
6
7
def load_json(path):
try:
with open(path, "r", encoding="utf-8") as f:
return json.load(f)
except FileNotFoundError:
print(f"file not found: {path}")
return None

工程里不要随便写:

1
2
3
4
try:
...
except:
pass

这会把真正的 bug 吃掉。更好的方式是捕获具体异常,并输出足够信息。

13. logging:比 print 更适合项目

print() 适合临时调试,但项目里更推荐 logging

1
2
3
4
5
6
7
8
9
10
11
12
import logging

logging.basicConfig(
level=logging.INFO,
format="%(asctime)s | %(levelname)s | %(name)s | %(message)s",
)

logger = logging.getLogger(__name__)

logger.info("start training")
logger.warning("missing value found")
logger.error("failed to load file")

日志等级:

等级 用途
DEBUG 详细调试信息
INFO 正常运行信息
WARNING 有风险但还能继续
ERROR 出错,当前操作失败
CRITICAL 严重错误

训练脚本里可以这样记录:

1
2
3
4
5
6
7
8
9
10
for epoch in range(num_epochs):
train_loss = train_one_epoch(...)
valid_acc = evaluate(...)

logger.info(
"epoch=%d train_loss=%.4f valid_acc=%.4f",
epoch,
train_loss,
valid_acc,
)

注意这里没有用 f-string,而是把变量作为参数传给 logger。这是 logging 推荐的常见写法,日志等级关闭时可以减少不必要的字符串格式化开销。

14. argparse:让脚本能从命令行改参数

不要把参数写死在代码里:

1
2
learning_rate = 0.001
batch_size = 32

更好的方式是用 argparse

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import argparse


def parse_args():
parser = argparse.ArgumentParser()
parser.add_argument("--data-path", type=str, required=True)
parser.add_argument("--epochs", type=int, default=10)
parser.add_argument("--lr", type=float, default=1e-3)
parser.add_argument("--seed", type=int, default=42)
return parser.parse_args()


def main():
args = parse_args()
print(args)


if __name__ == "__main__":
main()

运行:

1
python train.py --data-path data/train.csv --epochs 20 --lr 0.0005

这对算法实习非常重要。因为实验经常需要改数据路径、学习率、batch size、模型名、随机种子。如果每次都改代码,就很难复现。

15. 项目结构:不要所有代码都放一个文件

一个适合算法实习作品的小项目结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
project/
README.md
requirements.txt
configs/
baseline.json
data/
raw/
processed/
outputs/
checkpoints/
logs/
metrics/
src/
__init__.py
data.py
features.py
model.py
train.py
evaluate.py
utils.py
scripts/
run_baseline.py
tests/
test_features.py

每个文件大概负责:

  • README.md:项目说明、运行方式、结果。
  • requirements.txt:依赖包。
  • configs/:实验配置。
  • data/:数据,不建议把大数据直接传 GitHub。
  • outputs/:模型、日志、指标。
  • src/data.py:读取和清洗数据。
  • src/features.py:特征工程。
  • src/model.py:模型定义。
  • src/train.py:训练流程。
  • src/evaluate.py:评估流程。
  • src/utils.py:通用工具函数。

如果项目很小,可以不用这么复杂,但至少要把“读取数据、核心逻辑、命令行入口”分开。

16. requirements 和虚拟环境

记录依赖:

1
pip freeze > requirements.txt

安装依赖:

1
pip install -r requirements.txt

建议每个项目使用独立虚拟环境:

1
python -m venv .venv

Windows PowerShell 激活:

1
.venv\Scripts\Activate.ps1

macOS / Linux:

1
source .venv/bin/activate

虚拟环境的意义是:不同项目的依赖不互相污染。

17. 随机种子:让实验可复现

算法实验里经常有随机性,比如数据划分、初始化、采样。

1
2
3
4
5
6
7
import random
import numpy as np


def set_seed(seed):
random.seed(seed)
np.random.seed(seed)

如果用 PyTorch,还会加:

1
2
3
4
import torch

torch.manual_seed(seed)
torch.cuda.manual_seed_all(seed)

可复现不等于每次完全一样,但至少要尽量记录影响结果的随机因素。

18. 配置文件:把实验参数放到外面

简单配置可以用 JSON:

1
2
3
4
5
6
{
"data_path": "data/train.csv",
"epochs": 10,
"learning_rate": 0.001,
"batch_size": 32
}

读取:

1
2
3
4
5
6
import json


def load_config(path):
with open(path, "r", encoding="utf-8") as f:
return json.load(f)

也可以同时支持命令行和配置文件:

1
2
3
4
5
def main():
args = parse_args()
config = load_config(args.config)

config["epochs"] = args.epochs or config["epochs"]

这样默认参数放配置文件,临时实验参数从命令行覆盖。

19. 一个完整训练脚本骨架

下面是一个简化版工程脚本,适合放进项目里改造:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
import argparse
import json
import logging
from pathlib import Path


def setup_logging(log_path=None):
handlers = [logging.StreamHandler()]

if log_path is not None:
Path(log_path).parent.mkdir(parents=True, exist_ok=True)
handlers.append(logging.FileHandler(log_path, encoding="utf-8"))

logging.basicConfig(
level=logging.INFO,
format="%(asctime)s | %(levelname)s | %(name)s | %(message)s",
handlers=handlers,
)


def parse_args():
parser = argparse.ArgumentParser()
parser.add_argument("--config", type=str, required=True)
parser.add_argument("--output-dir", type=str, default="outputs/exp1")
parser.add_argument("--seed", type=int, default=42)
return parser.parse_args()


def load_config(path):
with open(path, "r", encoding="utf-8") as f:
return json.load(f)


def save_metrics(metrics, output_dir):
output_dir = Path(output_dir)
output_dir.mkdir(parents=True, exist_ok=True)

with (output_dir / "metrics.json").open("w", encoding="utf-8") as f:
json.dump(metrics, f, ensure_ascii=False, indent=2)


def main():
args = parse_args()
output_dir = Path(args.output_dir)
setup_logging(output_dir / "logs" / "run.log")

logger = logging.getLogger(__name__)
logger.info("loading config from %s", args.config)

config = load_config(args.config)
logger.info("config=%s", config)

# 这里替换成真实的数据读取、训练和评估逻辑
metrics = {
"accuracy": 0.0,
"macro_f1": 0.0,
}

save_metrics(metrics, output_dir)
logger.info("saved metrics to %s", output_dir)


if __name__ == "__main__":
main()

这个骨架体现了几个工程习惯:

  • 参数从命令行进来。
  • 配置从文件读取。
  • 日志既输出到终端,也保存到文件。
  • 输出目录自动创建。
  • 指标保存成 JSON,方便之后比较实验。

20. 算法实习项目代码要注意什么

面试官或导师看项目代码时,通常会关心:

  • 能不能一条命令跑起来。
  • README 是否说清楚环境、数据、运行方式。
  • 代码有没有拆分,不是一整个超长 notebook。
  • 参数是否写死。
  • 是否有日志和指标输出。
  • 是否能复现实验结果。
  • 文件路径是否跨机器可用。
  • 出错信息是否清楚。

一个好的项目不一定很大,但应该让别人能理解、能运行、能验证。

21. 最小练习清单

下面这些练习很适合从 Python 初级语法走到工程化:

  1. 写一个词频统计程序,输入文本文件,输出 top-k 高频词。
  2. dict.get() 统计字符出现次数。
  3. 手写一个排序函数,再用 sorted(..., key=...) 改写。
  4. 写一个 read_jsonwrite_json 工具函数。
  5. 把一个脚本改成 main() 入口结构。
  6. 给脚本加入 logging,同时输出到终端和文件。
  7. 给脚本加入 argparse,支持 --input--output--top-k
  8. pathlib 创建输出目录。
  9. 给实验结果保存 metrics.json
  10. 整理一个最小项目结构,并写 README 说明怎么运行。

如果这些都能顺手写出来,你的 Python 就不只是“会语法”,而是已经开始接近实习项目里真正需要的工程能力。

22. 推荐学习顺序

对于算法实习,建议这样学:

  1. 基础语法:变量、字符串、条件、循环。
  2. 数据结构:list、tuple、dict、set。
  3. 函数拆分:输入、处理、输出分开。
  4. 算法基础:计数、搜索、排序、双指针、哈希。
  5. 文件处理:txt、csv、json。
  6. 工程脚本:main()argparselogging
  7. 项目组织:README、requirements、configs、src。
  8. 实验管理:随机种子、日志、指标、输出目录。

Python 初级语法是地基,工程技术是把地基搭成房子的方式。对算法实习来说,两者都要有:语法让你能写,工程习惯让你的代码能被别人运行、检查和继续维护。

Python 从入门语法到算法实习工程化:基础、数据结构、脚本、日志与项目模板

https://richardf123.github.io/2026/06/24/python-for-algorithm-internship-engineering-guide/

作者

RichardF

发布于

2026-06-24

更新于

2026-06-24

许可协议