🧭 编程学习站

Python 入门 + 进阶训练

公网版 · 手机/电脑自适应

纯新手先看这一框

Python 适合做什么? 数据分析、人工智能/机器学习、自动化脚本(批量重命名/爬虫/办公自动化)、Web 后端(Django/FastAPI)、运维工具。它几乎是「想用代码解决日常问题」的第一选择。

为什么学它? 语法最接近自然语言、不用管内存和类型声明,写同样的功能代码量通常只有 C/Java 的 1/3~1/5,新手最容易「写出东西、获得正反馈」。

怎么跑起来? 三个方式:① 本页每个示例点 ▶ 运行 直接看结果(不用装任何东西);② 装官方解释器(python.org 下 3.12+),命令行敲 python hello.py;③ VS Code + Python 插件,F5 运行。

新手最常踩的坑? 缩进就是语法(用空格别用 Tab 混用)、==is 不是一回事、可变默认参数会「串味」、range 是左闭右开。下面每节都会标红这些坑。

1. Hello World 与运行环境先跑通第一个程序,建立信心

Python 是解释型语言:代码写完由「解释器」逐行翻译给 CPU 执行,不用像 C 那样先编译。好处是改完就能跑;代价是运行速度比编译型语言慢(日常脚本无感)。

# 这是注释,井号开头,解释器会忽略
print("Hello, World!")          # 打印一行文字,自带换行
print("2 + 3 =", 2 + 3)         # 可以一次打印多个值,自动用空格隔开
老师不教别小看 print。调试时它是最快的工具——不确定变量是什么,就 print(type(x), x) 看一眼。但生产代码要用 logging,别把 print 留着(后面干货会讲)。

装环境(想本地跑的话)

  • Windows:python.org 下载 3.12+ 安装包,勾选 “Add to PATH”。
  • 验证:命令行输入 python --version,看到版本号即成功。
  • 交互式 REPL:命令行敲 python 进去做计算器/试语法,exit() 退出。
建议本教程所有示例都能在页面里直接运行,先用起来再说,环境晚点再装也不迟。

2. 变量与动态类型「赋值」本质是给对象贴标签

Python 里 变量不需要声明类型,第一次赋值就创建了它;同一个变量还能再指向别类型的值。

name = "小明"        # 字符串
age = 18             # 整数
height = 1.75        # 浮点数
is_student = True    # 布尔值

print(name, age, height, is_student)
age = "十八"         # 同一个变量可以换类型(动态类型)
print(age)

print(type(name), type(age))   # type() 看变量的真实类型
易错点Python 的「赋值」是把名字绑定到对象,不是开一块内存存值。所以 a = b = [] 会让 a 和 b 指向同一个列表——改 a 会连 b 一起改。想要两份独立的,写 a = []; b = []import copy; b = copy.deepcopy(a)

命名规则

  • 字母/数字/下划线,不能以数字开头;区分大小写(Ageage 是两个变量)。
  • 约定:变量/函数用 snake_case(小写加下划线),常量全大写 MAX_SIZE
  • 不能用关键字(if for def 等)做变量名。

3. 数字与运算整数不会溢出,但浮点有坑

Python 的 int任意精度的——再大的数都不会溢出(只受内存限制),这是它做密码学/大数运算的先天优势。

a = 10
b = 3
print(a + b)      # 13
print(a - b)      # 7
print(a * b)      # 30
print(a / b)      # 3.3333...  注意:/ 永远返回浮点数
print(a // b)     # 3   整除(向下取整)
print(a % b)      # 1   取余
print(a ** b)     # 1000  乘方

# 大整数随便来
big = 2 ** 1000
print(big)        # 一个 300 多位的整数,不报错
浮点坑0.1 + 0.2 == 0.3 的结果是 False!因为二进制浮点无法精确表示 0.1。涉及钱的场合用 decimal.Decimal,不要用 float。比较浮点用 abs(a-b) < 1e-9 而不是直接 ==
老师不教// 是「向下取整」不是「截断」:-7 // 2 == -4(不是 -3)。负数取整除法要小心。还有海象运算符 :=(3.8+)能在表达式里顺便赋值:if (n := len(x)) > 10: 避免算两次。

4. 字符串不可变、可切片、能套公式

字符串用单引号或双引号都行(推荐一致)。它不可变:不能改其中某个字符,只能生成新字符串。

s = "hello"
print(s[0])        # h   索引从 0 开始
print(s[-1])       # o   负数从末尾往前数
print(s[1:4])      # ell 切片 [起:止),左闭右开
print(s[::2])      # hlo 步长 2
print(s[::-1])     # olleh  反转字符串的惯用法!

# f-string:最推荐的格式化方式(3.6+)
name, score = "小红", 95
print(f"{name} 考了 {score} 分")          # 小红 考了 95 分
print(f"百分数:{score / 100:.1%}")        # 百分数:95.0%
print(f"对齐:{name:>5}")                # 右对齐占 5 位
易错点字符串不可变s[0] = "H" 会报错。想改就切片拼新串:s = "H" + s[1:]。还有:用 + 在循环里拼几万次字符串极慢,应该用 "".join(parts)
建议处理路径用 pathlib.Path 而不是字符串拼 "/";处理正则用 re 模块;多行文本用三引号 """..."""

5. 列表 list最常用容器,能装任何东西

列表是可变的有序序列,元素类型可以混搭。增删改查 + 切片 + 推导式是必会四项。

nums = [3, 1, 2]
nums.append(4)        # 末尾加 → [3,1,2,4]
nums.insert(0, 9)     # 下标0插入 → [9,3,1,2,4]
nums.remove(1)        # 删第一个值为1的 → [9,3,2,4]
popped = nums.pop()   # 弹出末尾4,列表变[9,3,2]
nums[0] = 100         # 改 → [100,3,2]
print(nums[1:])       # [3,2] 切片
print(len(nums))      # 3 长度
print(2 in nums)      # True 是否包含

# 列表推导式:一行生成新列表(极常用)
squares = [x*x for x in range(1, 6)]     # [1,4,9,16,25]
evens = [x for x in range(10) if x % 2 == 0]  # [0,2,4,6,8]
易错点list.sort() 原地排序、返回 None;想要「排序后返回新列表」用 sorted(list)。新手常写 x = nums.sort() 然后 x 是 None。另外 copy = nums 是浅拷贝(共享同一对象),要 copy = nums.copy()nums[:]
老师不教推导式底层是 C 循环,比手写 for 快不少。但别嵌套超过两层、别写复杂条件,否则可读性崩塌——那种情况老老实实写 for 循环。

6. 元组 / 集合 / 字典三种容器各管一摊事

元组 tuple —— 不可变的有序序列

point = (3, 4)
x, y = point         # 解包!一次性取出
print(x, y)          # 3 4
# point[0] = 5       # 报错:元组不可变
# 用途:函数返回多个值、作字典键(列表不行)

集合 set —— 去重 + 成员判断飞快

s = {1, 2, 2, 3}     # {1, 2, 3} 自动去重
print(2 in s)        # True 判断极快 O(1)
a, b = {1,2,3}, {2,3,4}
print(a & b)         # {2, 3} 交集
print(a | b)         # {1,2,3,4} 并集
print(a - b)         # {1} 差集

字典 dict —— 键值对,查表神器

d = {"name": "小明", "age": 18}
print(d["name"])             # 小明
d["age"] = 19                # 改
d["city"] = "北京"           # 增
print(d.get("tel", "无"))    # 无  取不到给默认值,不会报错
for k, v in d.items():       # 同时取键和值
    print(k, "=", v)
易错点d["不存在的键"] 会抛 KeyError。安全取用 d.get(key, 默认值)。遍历时不要边遍历边删字典元素,先收集要删的键再删,或用字典推导重建。
老师不教collections 模块里 Counter(计数)、defaultdict(缺键自动给默认值)、deque(两端高效增删队列)是日常利器,比手写循环优雅且快。

7. 流程控制if / for / while 的 Python 写法

# if:注意冒号 + 缩进
x = 7
if x > 10:
    print("大")
elif x > 5:
    print("中")      # 会走这里
else:
    print("小")

# for:直接遍历元素(不用下标)
for i in range(5):          # range(5) = 0,1,2,3,4
    print(i)
for i, v in enumerate(["a","b","c"]):   # 同时拿下标和值
    print(i, v)

# while
n = 3
while n > 0:
    print(n)
    n -= 1
# break 跳出 / continue 跳过本次
易错点range(a, b)左闭右开,不含 b。想遍历 1~10 写 range(1, 11)。还有:判断「是否为空」直接写 if lst: 而非 if len(lst) > 0:,更 Pythonic。
建议99% 情况用 for ... in 可迭代对象 而不是 for i in range(len(x))。要下标就用 enumerate

8. 函数def、参数、作用域、lambda

def greet(name, greeting="你好"):     # greeting 是默认参数
    return f"{greeting},{name}!"

print(greet("小明"))          # 你好,小明!
print(greet("小红", "早安"))   # 早安,小红!

# *args 收位置参数,**kwargs 收关键字参数
def show(*args, **kwargs):
    print("位置:", args)
    print("关键字:", kwargs)
show(1, 2, a=3, b=4)

# lambda:一行小函数
square = lambda x: x * x
print(square(5))             # 25
经典巨坑默认参数只在函数定义时求值一次!如果用可变对象做默认值会「串味」:
def bad(x=[]):        # 错误写法!
    x.append(1)
    return x
print(bad())   # [1]
print(bad())   # [1, 1]  同一个列表被反复用!

def good(x=None):     # 正确写法
    if x is None:
        x = []
    x.append(1)
    return x
老师不教变量查找顺序是 LEGB:Local 局部 → Enclosing 外层函数 → Global 全局 → Builtin 内置。函数内部想改全局变量要写 global x;嵌套函数改外层要写 nonlocal x。新手经常忘了这俩关键字导致「改不动」。

9. 文件 I/O用 with 自动关文件

# 写文件
with open("note.txt", "w", encoding="utf-8") as f:
    f.write("第一行\n")
    f.write("第二行\n")
# 离开 with 块,文件自动关闭(即使中间报错)

# 读文件
with open("note.txt", "r", encoding="utf-8") as f:
    content = f.read()        # 全读
    print(content)

# 逐行读(大文件省内存)
with open("note.txt", "r", encoding="utf-8") as f:
    for line in f:
        print(line.strip())
易错点一定要指定 encoding="utf-8",否则 Windows 下默认 GBK,读中文文件会 UnicodeDecodeError。模式 "w"清空原文件,追加用 "a"。永远用 with,别手动 f.close()——忘了关或中途报错都会漏关。
建议JSON 数据用 import json; json.dump(obj, f) / json.load(f),比自己拼字符串靠谱一万倍。

10. 异常处理让程序崩得优雅

try:
    n = int(input("输入数字:"))
    print(10 / n)
except ValueError:               # 转不成整数
    print("那不是数字")
except ZeroDivisionError:        # 除以 0
    print("不能除以 0")
else:
    print("没有异常才走这里")
finally:
    print("无论怎样都执行(常用于释放资源)")

# 主动抛异常
def check_age(a):
    if a < 0:
        raise ValueError("年龄不能为负")
    return a
易错点别写 except:(不写异常类型)——它会连 KeyboardInterruptSystemExit 一起吞掉,Ctrl+C 都停不了程序,调试时想死。永远捕获具体异常。
老师不教自定义异常继承 Exception(别继承 BaseException)。捕获后用 raise ... from e 保留原始堆栈链,排查问题更快。

11. 迭代器与生成器惰性求值,省内存又优雅

生成器用 yield 替代 return,每次调用「暂停」并返回一个值,下次从暂停处继续——适合处理大数据流。

def count_up(n):
    i = 0
    while i < n:
        yield i       # 暂停并返回 i
        i += 1

for x in count_up(3):
    print(x)          # 0 1 2

# 生成器表达式:比列表推导省内存(不一次性生成全部)
nums = (x*x for x in range(1000000))   # 此刻几乎不占内存
print(next(nums))     # 0
print(next(nums))     # 1
建议读大文件/算海量数据时用生成器:内存占用从「几个 G」降到「几 KB」。配合 itertools(chain/islice/groupby)能写出极简洁的流式处理。

12. 装饰器不改动原函数的「外挂」

装饰器本质是「接收一个函数、返回一个新函数」的高阶函数。靠闭包记住原函数的引用。

import time, functools

def timer(func):                       # 计时装饰器
    @functools.wraps(func)             # 保留原函数名/文档
    def wrapper(*args, **kwargs):
        t = time.time()
        result = func(*args, **kwargs)
        print(f"{func.__name__} 用时 {time.time()-t:.3f}s")
        return result
    return wrapper

@timer                                # 等价于 do = timer(do)
def do():
    time.sleep(0.5)

do()
易错点忘写 @functools.wraps(func) 会导致被装饰函数的 __name____doc__ 变成 wrapper 的,调试日志和文档全乱。装饰器还能带参数、能叠加多个(执行顺序从下往上),进阶时再展开。

13. 面向对象class、self、继承、魔术方法

class Person:
    def __init__(self, name, age):    # 构造方法,self 是实例自身
        self.name = name
        self.age = age
    def greet(self):
        return f"我是{self.name},{self.age}岁"

    @property                          # 把方法当属性用
    def is_adult(self):
        return self.age >= 18

class Student(Person):                # 继承
    def __init__(self, name, age, sid):
        super().__init__(name, age)   # 调父类构造
        self.sid = sid

p = Student("小明", 16, "001")
print(p.greet(), p.is_adult)          # 我是小明,16岁 False
易错点方法第一个参数必须叫 self(名字可改但约定如此),调用时不用传。继承方法要扩展父类逻辑,用 super().方法() 调父类,别直接写父类名——多继承时 MRO(方法解析顺序)会乱。
老师不教Python 3.7+ 有 @dataclass 装饰器,自动生成 __init__/__repr__/__eq__,写数据类极省事:
from dataclasses import dataclass
@dataclass
class Point:
    x: float
    y: float
还有 __slots__ 能锁死属性、大幅省内存(创建几百万个对象时关键)。

14. 模块与包 / 虚拟环境组织代码、隔离依赖

# math_utils.py
def add(a, b): return a + b

# main.py
import math_utils              # 导入整个模块
from math_utils import add     # 只导入一个函数
print(add(1, 2))

# __name__ 技巧:直接运行做测试,被导入时不执行
if __name__ == "__main__":
    print("只有直接运行本文件才会打印")
建议真正的项目用虚拟环境隔离依赖:python -m venv venv 创建,激活后 pip install 包名 装的包只在这个项目里,不会污染全局。pip freeze > requirements.txt 记录依赖,别人 pip install -r requirements.txt 一键复现。
易错点循环导入(A 导入 B,B 又导入 A)会报错。把公共定义抽到第三个模块,或用函数内局部 import 推迟导入时机来破环。

15. 并发GIL 真相:什么时候该用哪个

必懂GIL(全局解释器锁)让同一进程里 Python 字节码同一时刻只有一个线程在跑。结论:CPU 密集任务用多线程不会变快,要用 multiprocessing(多进程,真并行);IO 密集任务(网络/文件)用 threadingasyncio 能明显提速(等待时让出 GIL)。
import concurrent.futures, time

def work(n):
    time.sleep(0.5)
    return n * n

# 线程池:适合 IO 密集
with concurrent.futures.ThreadPoolExecutor(4) as ex:
    results = list(ex.map(work, range(8)))
print(results)

# 进程池:适合 CPU 密集
with concurrent.futures.ProcessPoolExecutor() as ex:
    results = list(ex.map(work, range(8)))
print(results)
老师不教asyncio 是单线程内的「协程并发」,用 async def + await,适合高并发网络服务(几万连接)。但它不是多线程,遇到 time.sleep 这种阻塞调用照样卡住——要用 asyncio.sleep。新手最容易在这里踩坑。

16. 标准库与工程化别重复造轮子

Python 自带「电池」(batteries included),这些标准库几乎天天用:

模块干啥
collectionsCounter/defaultdict/deque/namedtuple
pathlib面向对象的文件路径处理
datetime日期时间(时区用 zoneinfo)
logging分级日志(别用 print 上生产)
typing类型注解(大项目配合 mypy)
itertools/re迭代器工具 / 正则
argparse命令行参数解析
建议中大型项目加类型注解def add(a: int, b: int) -> int:,再用 mypy 静态检查,能在运行前抓出一半以上的类型 bug。测试用 pytest,比自带 unittest 简洁得多。

🧪 动手实验室代码已写好,点 ▶ 直接看结果(手机/电脑通用)

下面每个程序都已预填好。想验证就点 ▶ 运行;想改就在框里改再点运行;右上角可切语言、↺ 重置回到初始代码。

① 基础:打印与运算

② 列表:推导式 + 切片

③ 函数:默认参数 + 可变参数

④ 字典:词频统计

⑤ 生成器:惰性求和

💡 连老师都不会教你的干货(工业级技巧)这些学校不教,但公司里天天用

  1. int 任意精度:Python 整数不会溢出,适合大数/密码学,但比 C 的定长 int 慢,别拿它做超高频数值循环。
  2. is vs == 的坑:a is b 比的是「是不是同一个对象」;小整数(-5~256)和短字符串会被解释器缓存复用,所以 256 is 256 为 True 但 257 is 257 可能为 False。永远用 == 比值。
  3. 可变默认参数串味:def f(x=[]) 会在多次调用间共享同一个列表,正确写法是 x=None 后再初始化。
  4. 浅拷贝 vs 深拷贝:list.copy() / dict.copy() 只复制第一层,嵌套的可变对象仍是共享;要完全独立用 copy.deepcopy
  5. GIL 真相:CPU 密集用 multiprocessing,IO 密集用 threading/asyncio。别指望多线程加速纯计算。
  6. f-string 的隐藏能力:支持表达式 {x*2}、转换符 {s!r}(repr)、{x:>5} 对齐、{pi:.2f} 精度,比 % 和 .format() 都强。
  7. 海象运算符 :=(3.8+):在表达式内赋值,避免重复计算,如 while (line := f.readline()):
  8. 推导式别过度:嵌套超过两层或条件复杂就改回 for 循环,可读性优先。
  9. 用 pathlib 别用 os.path:Path("a/b.txt").read_text(encoding="utf-8") 一行读完,跨平台路径分隔符自动处理。
  10. 用 with 管理一切资源:文件、锁、数据库连接都用上下文管理器,异常也能正确释放。
  11. enumerate 优于 range(len):for i, v in enumerate(lst) 更清晰,还避免下标越界。
  12. collections 四件套:Counter(计数)、defaultdict(缺键给默认)、deque(双端队列)、namedtuple(轻量结构化),取代大量手写逻辑。
  13. __slots__ 省内存:类里定义 __slots__ = ("x","y") 禁止动态加属性,创建百万实例时内存可降数倍。
  14. 类型注解 + mypy:大项目必备,运行前静态抓类型错误,相当于给 Python 加了半套编译期检查。
  15. join 优于 + 拼接:循环里 "".join(parts) 比反复 + 快得多(后者每次都新建字符串)。
  16. logging 别用 print:生产环境用 logging 分级(DEBUG/INFO/WARNING/ERROR)并输出到文件,print 无法关、无级别、难定位。
  17. 虚拟环境隔离依赖:每个项目独立 venv + requirements.txt,避免 A 项目升级包把 B 项目搞崩。
  18. 别捕获裸 except:except ValueError: 这类具体异常,裸 except: 连 Ctrl+C 都吞掉,调试噩梦。

📝 自测题点「查看答案」核对,先自己想

1. 0.1 + 0.2 == 0.3 的结果是?

查看答案

False。二进制浮点无法精确表示 0.1,存在舍入误差。比较浮点应用 abs(a-b) < 1e-9;涉及金额用 decimal.Decimal

2. 以下代码连续调用两次会输出什么?

def f(x=[]):
    x.append(1)
    return x
f(); f()
查看答案

第一次 [1],第二次 [1, 1]。因为默认列表在函数定义时创建一次、被所有调用共享。解决:默认参数用 None

3. 反转字符串 "hello" 最 Pythonic 的写法是?

查看答案

s[::-1]。切片步进为 -1 即从尾到头取全部字符。

4. CPU 密集任务想提速,该用多线程还是多进程?

查看答案

多进程multiprocessing)。因为 GIL 限制同一进程内只有一个线程在跑字节码,多线程对纯计算无效;多进程各有独立解释器和 GIL,能真并行。

5. 判断列表为空,哪种更好:if len(lst) == 0: 还是 if not lst:

查看答案

if not lst: 更好。空容器在布尔上下文就是 False,更简洁、更 Pythonic,且对字符串/字典/集合同样适用。