🐍 Python 第五课：传说之境——工程化、架构与部署
课程目标：掌握测试驱动开发（TDD），让代码质量有保障学会类型提示，让代码自文档化代码规范与自动化工具，告别"屎山"Docker 容器化部署，一次构建到处运行性能优化与Profiling，找到瓶颈设计模式与架构思想，写出"优雅"的代码微服务入门，为大型系统做准备1) 回顾上节课上节课我们学会了：生成器与迭代器：省内存的神器装饰器：给函数"开挂"上下文管理器：资源管理更优雅并发编程：多线程、多进程、异步IO数据库操作：SQLite与SQLAlchemyWeb开发：Flask框架常见问题解答：Q：异步代码里能直接用time.sleep()吗？A：不行！time.sleep()会阻塞整个事件循环。要用await asyncio.sleep()，这样等待时可以把控制权交给其他任务。Q：SQLAlchemy的session什么时候关闭？A：推荐用上下文管理器：with Session() as session:，或者确保在请求结束时关闭，避免连接泄漏。2) 测试驱动开发（TDD）：先写测试，再写代码为什么要测试？手动测试的痛苦：改一行代码，手动测10个功能，漏了一个就线上故障重构的勇气：有测试覆盖，才敢大胆重构文档作用：测试用例就是最佳使用示例pytest：Python测试之王# 安装 pip install pytest pytest-cov基本用法：# calculator.py def add(a, b): return a + b def divide(a, b): if b == 0: raise ValueError("不能除以零") return a / b# test_calculator.py import pytest from calculator import add, divide def test_add(): assert add(2, 3) == 5 assert add(-1, 1) == 0 assert add(0.1, 0.2) == pytest.approx(0.3) # 浮点数比较 def test_divide(): assert divide(10, 2) == 5 assert divide(7, 2) == 3.5 def test_divide_by_zero(): with pytest.raises(ValueError, match="不能除以零"): divide(10, 0)运行测试：pytest -v # 详细输出 pytest -s # 显示print输出 pytest --cov=calculator # 覆盖率报告 pytest -k "add" # 只运行名字包含add的测试Fixture：测试的"脚手架"import pytest from sqlalchemy import create_engine from sqlalchemy.orm import sessionmaker from models import Base, User @pytest.fixture def db_session(): """每个测试用例都使用全新的内存数据库""" engine = create_engine('sqlite:///:memory:') Base.metadata.create_all(engine) Session = sessionmaker(bind=engine) session = Session() yield session # 提供session给测试用例 session.close() # 测试结束后清理 def test_create_user(db_session): user = User(name="张三", email="zhangsan@example.com") db_session.add(user) db_session.commit() result = db_session.query(User).first() assert result.name == "张三"Mock：模拟外部依赖from unittest.mock import Mock, patch import requests def get_weather(city): """获取天气（实际会调用外部API）""" resp = requests.get(f"https://api.weather.com/{city}") return resp.json() def test_get_weather(): # 模拟requests.get，避免真实网络请求 mock_response = Mock() mock_response.json.return_value = {"temp": 25, "city": "北京"} with patch('requests.get', return_value=mock_response): result = get_weather("北京") assert result["temp"] == 25TDD实战：Red-Green-Refactor流程：Red：先写测试（会失败，因为功能还没实现）Green：写最少代码让测试通过Refactor：重构代码，保持测试通过# 第一步：写测试（Red） def test_password_strength(): assert is_strong_password("123") == False assert is_strong_password("abcdef") == False assert is_strong_password("Abcdef1!") == True # 大写+小写+数字+特殊字符 # 第二步：实现功能（Green） def is_strong_password(password): if len(password) < 8: return False has_upper = any(c.isupper() for c in password) has_lower = any(c.islower() for c in password) has_digit = any(c.isdigit() for c in password) has_special = any(c in "!@#$%^&*" for c in password) return all([has_upper, has_lower, has_digit, has_special]) # 第三步：重构（保持测试通过） # 可以提取验证规则，让代码更可扩展3) 类型提示：让代码自文档化基础类型from typing import List, Dict, Optional, Union, Tuple def greet(name: str) -> str: return f"Hello, {name}" def process_data(items: List[int]) -> Dict[str, int]: return {"count": len(items), "sum": sum(items)} def find_user(user_id: int) -> Optional[User]: """可能返回None""" return session.query(User).get(user_id) def parse_value(value: Union[str, int]) -> float: """接受字符串或整数，返回浮点数""" return float(value)复杂类型from typing import Callable, Iterator, TypeVar, Generic # 类型变量 T = TypeVar('T') def first(items: List[T]) -> Optional[T]: return items[0] if items else None # 可调用对象 def executor(func: Callable[[int, int], int], a: int, b: int) -> int: return func(a, b) # 生成器类型 def fibonacci(n: int) -> Iterator[int]: a, b = 0, 1 for _ in range(n): yield a a, b = b, a + b类与泛型from typing import Generic, TypeVar T = TypeVar('T') class Stack(Generic[T]): def __init__(self) -> None: self._items: List[T] = [] def push(self, item: T) -> None: self._items.append(item) def pop(self) -> T: if not self._items: raise IndexError("Stack is empty") return self._items.pop() # 使用 int_stack: Stack[int] = Stack() int_stack.push(42) # int_stack.push("hello") # 类型检查器会报错！类型检查工具# mypy：静态类型检查 pip install mypy mypy my_project/ # 检查整个项目 # 集成到编辑器（VS Code） # 安装Python插件，开启类型检查4) 代码质量：从"能跑"到"优雅"代码格式化：Blackpip install black black my_project/ # 一键格式化，无需争论风格Black的原则：单引号变双引号行长度限制88字符（可配置）统一的缩进和换行"不妥协的格式化"，减少团队争论代码检查：Flake8 + isortpip install flake8 isort flake8 my_project/ # 检查PEP8规范、语法错误 isort my_project/ # 自动排序import配置.flake8：[flake8] max-line-length = 88 extend-ignore = E203, W503 # 与Black兼容 exclude = .git,__pycache__,venvPre-commit：提交前自动检查pip install pre-commit配置.pre-commit-config.yaml：repos: - repo: https://github.com/psf/black rev: 23.1.0 hooks: - id: black - repo: https://github.com/PyCQA/flake8 rev: 6.0.0 hooks: - id: flake8 - repo: https://github.com/PyCQA/isort rev: 5.12.0 hooks: - id: isort安装钩子：pre-commit install # 以后每次git commit都会自动检查5) Docker容器化：一次构建，到处运行Dockerfile编写# 使用官方Python基础镜像 FROM python:3.11-slim # 设置工作目录 WORKDIR /app # 安装依赖 COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt # 复制应用代码 COPY . . # 暴露端口 EXPOSE 5000 # 运行应用 CMD ["gunicorn", "-w", "4", "-b", "0.0.0.0:5000", "app:app"].dockerignore：__pycache__ *.pyc .env .git venv/ .pytest_cache/Docker Compose：多容器编排# docker-compose.yml version: '3.8' services: web: build: . ports: - "5000:5000" environment: - DATABASE_URL=postgresql://user:pass@db:5432/mydb depends_on: - db - redis db: image: postgres:15 environment: POSTGRES_USER: user POSTGRES_PASSWORD: pass POSTGRES_DB: mydb volumes: - postgres_data:/var/lib/postgresql/data redis: image: redis:7-alpine nginx: image: nginx:alpine ports: - "80:80" volumes: - ./nginx.conf:/etc/nginx/nginx.conf depends_on: - web volumes: postgres_data:常用命令：docker-compose up -d # 后台启动所有服务 docker-compose logs -f web # 查看web服务日志 docker-compose exec web bash # 进入web容器 docker-compose down # 停止并删除容器6) 性能优化：找到瓶颈，精准打击Profiling：性能分析# 使用cProfile import cProfile import pstats def slow_function(): total = 0 for i in range(1000000): total += i ** 2 return total # 方式1：命令行 # python -m cProfile -s cumulative my_script.py # 方式2：代码内 profiler = cProfile.Profile() profiler.enable() result = slow_function() profiler.disable() stats = pstats.Stats(profiler) stats.sort_stats('cumulative') stats.print_stats(10) # 打印前10个最耗时的函数line_profiler：逐行分析pip install line_profilerfrom line_profiler import profile @profile def heavy_computation(): result = [] for i in range(10000): temp = [x ** 2 for x in range(1000)] # 这行慢？ result.append(sum(temp)) return result # 运行：kernprof -l -v script.py优化策略问题解决方案示例循环太慢向量化（NumPy）、Cythonnp.sum(arr) vs sum(list)I/O阻塞异步、多线程aiohttp vs requests内存不足生成器、分块处理pd.read_csv(chunksize=...)重复计算缓存（lru_cache）、Redis@functools.lru_cache缓存示例：from functools import lru_cache import time @lru_cache(maxsize=128) def fibonacci(n): if n < 2: return n return fibonacci(n-1) + fibonacci(n-2) # 第一次计算fib(30)慢，之后瞬间返回 start = time.time() print(fibonacci(30)) # 832040 print(f"耗时：{time.time() - start:.4f}秒") start = time.time() print(fibonacci(30)) # 瞬间完成，从缓存读取 print(f"耗时：{time.time() - start:.4f}秒")7) 设计模式：前辈的智慧单例模式：只有一个实例# 方式1：模块天然单例 # singleton.py class Database: def __init__(self): self.connection = "连接池" db = Database() # 导入时创建 # 其他文件：from singleton import db，永远是同一个对象 # 方式2：装饰器 def singleton(cls): instances = {} def get_instance(*args, **kwargs): if cls not in instances: instances[cls] = cls(*args, **kwargs) return instances[cls] return get_instance @singleton class Logger: def __init__(self): self.level = "INFO"工厂模式：创建对象的艺术from abc import ABC, abstractmethod class Animal(ABC): @abstractmethod def speak(self): pass class Dog(Animal): def speak(self): return "汪汪" class Cat(Animal): def speak(self): return "喵喵" class AnimalFactory: @staticmethod def create(animal_type: str) -> Animal: animals = { "dog": Dog, "cat": Cat } if animal_type not in animals: raise ValueError(f"未知动物：{animal_type}") return animals[animal_type]() # 使用 dog = AnimalFactory.create("dog") print(dog.speak()) # 汪汪观察者模式：事件订阅from typing import List, Callable class EventManager: def __init__(self): self._listeners: Dict[str, List[Callable]] = {} def subscribe(self, event_type: str, listener: Callable): if event_type not in self._listeners: self._listeners[event_type] = [] self._listeners[event_type].append(listener) def notify(self, event_type: str, data): for listener in self._listeners.get(event_type, []): listener(data) # 使用 def send_email(user): print(f"发送邮件给 {user['email']}") def send_sms(user): print(f"发送短信给 {user['phone']}") event_manager = EventManager() event_manager.subscribe("user_registered", send_email) event_manager.subscribe("user_registered", send_sms) # 用户注册后 event_manager.notify("user_registered", { "email": "user@example.com", "phone": "13800138000" })8) 微服务架构入门单体 vs 微服务单体应用微服务一个代码库，一起部署多个独立服务，独立部署简单，适合小团队复杂，需要DevOps能力技术栈统一各服务可用不同技术一个bug影响全局服务隔离，故障隔离服务拆分示例：电商系统monolithic/ microservices/ ├── app.py ├── user-service/ (用户服务) ├── models.py │ ├── app.py ├── views.py │ └── Dockerfile └── templates/ ├── order-service/ (订单服务) │ ├── app.py ├── payment-service/ (支付服务) │ └── ... └── api-gateway/ (API网关) └── nginx.conf服务间通信REST API：# user-service 提供用户查询 @app.route('/api/users/<int:id>') def get_user(id): return jsonify(User.query.get_or_404(id).to_dict()) # order-service 调用 user-service import requests def create_order(user_id, items): # 查询用户信息 resp = requests.get(f"http://user-service:5000/api/users/{user_id}") user = resp.json() # 创建订单... return Order(user_id=user_id, items=items)消息队列（RabbitMQ/Celery）：异步解耦# 订单服务发布事件 from celery import Celery celery = Celery('tasks', broker='redis://redis:6379/0') @celery.task def send_order_notification(order_id): # 异步发送通知，不阻塞主流程 order = Order.query.get(order_id) send_email(order.user.email, "订单已创建") # 创建订单后调用 send_order_notification.delay(order.id)9) 实战项目：完整的企业级应用项目：微服务架构的任务调度系统task-scheduler/ ├── docker-compose.yml ├── nginx/ ├── services/ │ ├── api-gateway/ # API网关（Nginx/FastAPI） │ ├── auth-service/ # 认证服务（JWT） │ ├── task-service/ # 任务管理服务 │ ├── worker-service/ # 任务执行器（Celery） │ └── notification-service/ # 通知服务 ├── shared/ │ └── models/ # 共享模型定义 └── tests/ └── integration/ # 集成测试核心代码示例：# task-service/app.py from fastapi import FastAPI, Depends, HTTPException from sqlalchemy.orm import Session from typing import List import schemas, models from database import get_db from celery import Celery app = FastAPI(title="Task Service") celery_app = Celery('tasks', broker='redis://redis:6379/0') @app.post("/tasks/", response_model=schemas.Task) def create_task(task: schemas.TaskCreate, db: Session = Depends(get_db)): db_task = models.Task(**task.dict()) db.add(db_task) db.commit() db.refresh(db_task) # 异步执行任务 execute_task.delay(db_task.id) return db_task @app.get("/tasks/", response_model=List[schemas.Task]) def list_tasks(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)): return db.query(models.Task).offset(skip).limit(limit).all() @celery_app.task def execute_task(task_id: int): """Celery任务：实际执行工作""" # 更新状态为运行中 update_task_status(task_id, "running") try: # 执行实际工作... time.sleep(10) # 模拟耗时操作 update_task_status(task_id, "completed") notify_completion.delay(task_id) except Exception as e: update_task_status(task_id, "failed", str(e)) raise10) 学习路线与资源进阶书单书名适合阶段内容《流畅的Python》中级Pythonic写法、高级特性《Python Cookbook》中高级实用技巧与模式《设计模式：可复用面向对象软件的基础》高级设计模式经典《构建高性能Web站点》高级性能优化、架构《微服务设计》高级微服务架构原则优秀开源项目学习Flask：轻量级Web框架，源码易读Celery：分布式任务队列FastAPI：现代异步Web框架Django：全功能Web框架Scrapy：爬虫框架，学习架构设计持续精进写博客：把学到的教给别人，是最好的学习参与开源：从提Issue、修文档开始技术分享：内部分享、技术会议关注PEP：了解Python最新发展结语：从学徒到大师Python学习之路：入门 → 进阶 → 高级 → 专家 → 大师 │ │ │ │ │ print 类与 装饰器 架构 创造 变量 对象 异步 设计 新范式 循环 数据库 测试 优化 函数 Web 部署 领导力记住：代码是写给人看的，顺便给机器执行简单优于复杂，明确优于隐晦现在偷懒，将来还债；现在严谨，将来从容最后送大家一句话："The Zen of Python: Simple is better than complex. Complex is better than complicated."祝编程愉快！You are now a Python Legend! 🐍👑毕业项目：用所学知识，从零开始设计并实现一个完整的系统：需求分析、架构设计TDD开发、类型提示Docker部署、CI/CD性能优化、监控告警技术文档、用户手册这是你成为传说的证明！课程结束，但学习永不停止！
🐍 Python 第四课：王者之路——高阶特性与实战开发
课程目标：掌握生成器与迭代器，写出省内存的高效代码学会装饰器，给函数"开挂"理解上下文管理器，让资源管理更优雅初探并发编程，让程序快如闪电数据库操作，让数据有个家Web 开发入门，做个能访问的网站1) 回顾上节课上节课我们学会了：文件读写与 JSON 处理模块、包与虚拟环境面向对象编程：类、继承、封装、魔术方法实战：学生管理系统常见问题解答：Q：self 到底是个啥？A：self 就是对象自己。调用 dog.bark() 时，Python 自动把 dog 作为第一个参数传给 bark(self)，所以 self.name 就是 dog.name。Q：继承时 super() 是干嘛的？A：调用父类的方法。比如子类构造方法里 super().__init__() 就是执行父类的构造，避免重复写代码。2) 生成器与迭代器：省内存的神器迭代器（Iterator）迭代器是"懒加载"的代表，一次只给你一个元素，而不是一次性全塞给你。# 列表是一次性生成所有元素，占内存 big_list = [i for i in range(10000000)] # 内存爆炸！ # 迭代器：用的时候再生成 big_iter = iter(range(10000000)) # 几乎不占内存 print(next(big_iter)) # 0 print(next(big_iter)) # 1 # ... 要一个给一个自定义迭代器：class CountDown: def __init__(self, start): self.start = start def __iter__(self): return self def __next__(self): if self.start <= 0: raise StopIteration # 迭代结束的信号 self.start -= 1 return self.start + 1 # 使用 for num in CountDown(5): print(num) # 5, 4, 3, 2, 1生成器（Generator）：更简单的迭代器写 __iter__ 和 __next__ 太麻烦？用 yield！def countdown(start): while start > 0: yield start # 暂停在这里，返回 start，下次从这里继续 start -= 1 # 使用 for num in countdown(5): print(num) # 5, 4, 3, 2, 1 # 本质 gen = countdown(3) print(next(gen)) # 5 print(next(gen)) # 4 # ...生成器表达式（类比列表推导式）：# 列表推导式：一次性生成所有，占内存 squares_list = [x**2 for x in range(1000000)] # 生成器表达式：惰性求值，省内存 squares_gen = (x**2 for x in range(1000000)) # 使用时才计算 for sq in squares_gen: if sq > 100: break # 后面的根本不生成，省了大量计算实际应用：读取大文件def read_large_file(file_path): """逐行读取大文件，不会内存溢出""" with open(file_path, 'r', encoding='utf-8') as f: for line in f: yield line.strip() # 一次只处理一行 # 处理 10GB 的日志文件也毫无压力 for line in read_large_file('huge_log.txt'): process(line) # 处理每一行3) 装饰器：给函数"穿装备"什么是装饰器？装饰器就是一个接收函数作为参数，返回函数的高阶函数。用来在不修改原函数的情况下，增加额外功能。import time def timer(func): """计算函数运行时间的装饰器""" def wrapper(*args, **kwargs): start = time.time() result = func(*args, **kwargs) # 执行原函数 end = time.time() print(f"{func.__name__} 耗时：{end - start:.4f}秒") return result return wrapper # 使用装饰器 @timer # 等价于：slow_function = timer(slow_function) def slow_function(): time.sleep(1) print("函数执行完毕") slow_function() # 输出： # 函数执行完毕 # slow_function 耗时：1.0012秒执行流程：@timer 把 slow_function 传给 timertimer 返回 wrapper调用 slow_function() 实际是调用 wrapper()wrapper 里先计时，再执行原函数，再计时，返回结果带参数的装饰器def repeat(n): """重复执行 n 次的装饰器""" def decorator(func): def wrapper(*args, **kwargs): for i in range(n): print(f"第 {i+1} 次执行：") result = func(*args, **kwargs) return result return wrapper return decorator @repeat(3) def greet(name): print(f"你好，{name}！") greet("张三") # 输出3次"你好，张三！"常用内置装饰器class MyClass: @staticmethod def static_method(): """静态方法：与类和实例都无关，就是个普通函数放在类里""" print("我是静态方法") @classmethod def class_method(cls): """类方法：第一个参数是类本身，可以访问类属性""" print(f"我是类方法，属于 {cls}") @property def value(self): """属性装饰器：把方法变成属性访问""" return self._value @value.setter def value(self, val): """设置属性时的验证""" if val < 0: raise ValueError("不能为负数") self._value = val # 使用 MyClass.static_method() # 不用实例化 MyClass.class_method() # 不用实例化 obj = MyClass() obj.value = 100 # 像属性一样赋值，实际调用 setter print(obj.value) # 像属性一样访问，实际调用 getter类装饰器用类来实现装饰器，更灵活：class CountCalls: """统计函数被调用次数""" def __init__(self, func): self.func = func self.count = 0 def __call__(self, *args, **kwargs): self.count += 1 print(f"{self.func.__name__} 被调用了 {self.count} 次") return self.func(*args, **kwargs) @CountCalls def say_hello(): print("Hello!") say_hello() # 次数：1 say_hello() # 次数：24) 上下文管理器：with 语句的秘密自定义上下文管理器我们已经知道 with open(...) as f: 好用，现在自己实现：from contextlib import contextmanager class DatabaseConnection: """模拟数据库连接""" def __init__(self, config): self.config = config def __enter__(self): """进入 with 时执行""" print(f"连接数据库：{self.config}") self.conn = "连接对象" # 模拟连接 return self.conn def __exit__(self, exc_type, exc_val, exc_tb): """退出 with 时执行，无论是否异常""" print("关闭数据库连接") # 返回 True 表示异常已处理，不向外传播 # 返回 False 或不返回，异常会继续抛出 # 使用 with DatabaseConnection("localhost:3306") as conn: print(f"使用 {conn} 执行查询") # 输出： # 连接数据库：localhost:3306 # 使用 连接对象 执行查询 # 关闭数据库连接用装饰器快速创建from contextlib import contextmanager @contextmanager def managed_resource(name): """更简洁的写法""" print(f"获取资源：{name}") resource = f"资源-{name}" try: yield resource # 这里返回给 as 变量 finally: print(f"释放资源：{name}") # 使用 with managed_resource("数据库") as res: print(f"使用 {res}")实际应用：计时、锁、事务等from contextlib import contextmanager import time @contextmanager def timer_context(name): start = time.time() yield end = time.time() print(f"{name} 耗时：{end - start:.4f}秒") with timer_context("数据处理"): time.sleep(1) print("处理中...")5) 并发编程：让程序快如闪电多线程（Threading）：I/O 密集型任务适合网络请求、文件读写等等待时间长的场景。import threading import time def download(url): print(f"开始下载 {url}") time.sleep(2) # 模拟下载 print(f"{url} 下载完成") urls = ["url1", "url2", "url3", "url4"] # 串行下载：8秒 # for url in urls: # download(url) # 多线程下载：约2秒 threads = [] for url in urls: t = threading.Thread(target=download, args=(url,)) threads.append(t) t.start() for t in threads: t.join() # 等待所有线程完成 print("全部下载完成！")线程池（推荐）：from concurrent.futures import ThreadPoolExecutor def fetch_data(url): # 模拟网络请求 time.sleep(1) return f"数据来自 {url}" urls = ["api1", "api2", "api3", "api4", "api5"] with ThreadPoolExecutor(max_workers=3) as executor: # 最多3个线程 results = executor.map(fetch_data, urls) # 自动分配任务 for result in results: print(result)注意： Python 有 GIL（全局解释器锁），多线程不能真正并行计算，只是并发（交替执行）。多进程（Multiprocessing）：CPU 密集型任务适合计算密集型，利用多核 CPU。from multiprocessing import Pool import os def heavy_computation(n): """耗时计算""" print(f"进程 {os.getpid()} 处理 {n}") result = sum(i * i for i in range(n)) return result numbers = [1000000, 2000000, 3000000, 4000000] with Pool(processes=4) as pool: # 4个进程 results = pool.map(heavy_computation, numbers) print(results)异步 IO（Asyncio）：高性能并发Python 3.4+ 引入，真正的异步非阻塞，性能极高。import asyncio async def fetch_data(url): print(f"开始获取 {url}") await asyncio.sleep(1) # 模拟异步等待，不会阻塞其他任务 print(f"完成 {url}") return f"数据-{url}" async def main(): # 创建多个任务并发执行 tasks = [ fetch_data("url1"), fetch_data("url2"), fetch_data("url3") ] results = await asyncio.gather(*tasks) # 等待所有完成 print(results) # 运行 asyncio.run(main())异步 Web 请求（aiohttp）：import aiohttp import asyncio async def fetch(session, url): async with session.get(url) as response: return await response.text() async def main(): urls = ["http://api1", "http://api2", "http://api3"] async with aiohttp.ClientSession() as session: tasks = [fetch(session, url) for url in urls] results = await asyncio.gather(*tasks) print(f"获取了 {len(results)} 个页面") asyncio.run(main())选择指南：场景方案原因网络请求、文件 I/O多线程 / 异步等待时间长，切换开销小大量计算多进程绕过 GIL，利用多核超高并发（万级）异步 IO资源占用最少，性能最高6) 数据库操作：SQLite 与 SQLAlchemySQLite：零配置数据库Python 内置，无需安装，适合小型应用。import sqlite3 # 连接数据库（不存在则创建） conn = sqlite3.connect('school.db') cursor = conn.cursor() # 创建表 cursor.execute(''' CREATE TABLE IF NOT EXISTS students ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, age INTEGER, score REAL ) ''') # 插入数据 cursor.execute("INSERT INTO students (name, age, score) VALUES (?, ?, ?)", ("张三", 20, 85.5)) cursor.execute("INSERT INTO students (name, age, score) VALUES (?, ?, ?)", ("李四", 21, 92.0)) # 查询 cursor.execute("SELECT * FROM students WHERE score > ?", (90,)) rows = cursor.fetchall() for row in rows: print(row) # (2, '李四', 21, 92.0) # 提交并关闭 conn.commit() conn.close()使用上下文管理器：with sqlite3.connect('school.db') as conn: cursor = conn.cursor() cursor.execute("SELECT * FROM students") print(cursor.fetchall()) # 自动 commit 和 closeSQLAlchemy：ORM 神器用 Python 代码操作数据库，不用写 SQL。from sqlalchemy import create_engine, Column, Integer, String, Float from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import sessionmaker Base = declarative_base() class Student(Base): __tablename__ = 'students' id = Column(Integer, primary_key=True) name = Column(String(50), nullable=False) age = Column(Integer) score = Column(Float) # 连接数据库 engine = create_engine('sqlite:///school.db', echo=True) Base.metadata.create_all(engine) # 创建表 # 创建会话 Session = sessionmaker(bind=engine) session = Session() # 增 new_student = Student(name="王五", age=22, score=88.5) session.add(new_student) session.commit() # 查 students = session.query(Student).filter(Student.score > 85).all() for s in students: print(f"{s.name}: {s.score}") # 改 student = session.query(Student).filter_by(name="张三").first() student.score = 90.0 session.commit() # 删 session.delete(student) session.commit() session.close()关系示例：一对多from sqlalchemy import ForeignKey from sqlalchemy.orm import relationship class Class(Base): __tablename__ = 'classes' id = Column(Integer, primary_key=True) name = Column(String(50)) students = relationship("Student", back_populates="class_") class Student(Base): __tablename__ = 'students' id = Column(Integer, primary_key=True) name = Column(String(50)) class_id = Column(Integer, ForeignKey('classes.id')) class_ = relationship("Class", back_populates="students") # 使用 cls = session.query(Class).first() print(f"班级 {cls.name} 有 {len(cls.students)} 名学生")7) Web 开发入门：Flask 框架快速开始from flask import Flask, jsonify, request app = Flask(__name__) @app.route('/') def home(): return "Hello, Flask!" @app.route('/api/students', methods=['GET']) def get_students(): students = [ {"id": 1, "name": "张三", "score": 85}, {"id": 2, "name": "李四", "score": 92} ] return jsonify(students) @app.route('/api/students', methods=['POST']) def add_student(): data = request.get_json() # 这里应该保存到数据库 return jsonify({"message": "添加成功", "data": data}), 201 @app.route('/api/students/<int:id>', methods=['GET']) def get_student(id): return jsonify({"id": id, "name": "张三", "score": 85}) if __name__ == '__main__': app.run(debug=True, port=5000)运行：python app.py # 访问 http://localhost:5000/集成数据库的完整示例from flask import Flask, request, jsonify from flask_sqlalchemy import SQLAlchemy app = Flask(__name__) app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///students.db' db = SQLAlchemy(app) class Student(db.Model): id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(80), nullable=False) age = db.Column(db.Integer) score = db.Column(db.Float) def to_dict(self): return { 'id': self.id, 'name': self.name, 'age': self.age, 'score': self.score } # 初始化数据库 with app.app_context(): db.create_all() @app.route('/api/students', methods=['GET']) def list_students(): students = Student.query.all() return jsonify([s.to_dict() for s in students]) @app.route('/api/students', methods=['POST']) def create_student(): data = request.get_json() student = Student( name=data['name'], age=data.get('age'), score=data.get('score') ) db.session.add(student) db.session.commit() return jsonify(student.to_dict()), 201 @app.route('/api/students/<int:id>', methods=['GET']) def get_student(id): student = Student.query.get_or_404(id) return jsonify(student.to_dict()) @app.route('/api/students/<int:id>', methods=['PUT']) def update_student(id): student = Student.query.get_or_404(id) data = request.get_json() student.name = data.get('name', student.name) student.age = data.get('age', student.age) student.score = data.get('score', student.score) db.session.commit() return jsonify(student.to_dict()) @app.route('/api/students/<int:id>', methods=['DELETE']) def delete_student(id): student = Student.query.get_or_404(id) db.session.delete(student) db.session.commit() return '', 204 if __name__ == '__main__': app.run(debug=True)测试 API：# 获取所有学生 curl http://localhost:5000/api/students # 添加学生 curl -X POST -H "Content-Type: application/json" \ -d '{"name":"王五","age":22,"score":88}' \ http://localhost:5000/api/students # 更新学生 curl -X PUT -H "Content-Type: application/json" \ -d '{"score":95}' \ http://localhost:5000/api/students/1 # 删除学生 curl -X DELETE http://localhost:5000/api/students/18) 实战项目：异步爬虫 + Web 展示做一个天气数据爬虫，存储到数据库，用 Web 展示。项目结构：weather_app/ ├── crawler.py # 异步爬虫 ├── models.py # 数据库模型 ├── app.py # Flask Web └── templates/ └── index.html # 网页模板crawler.py：import aiohttp import asyncio from datetime import datetime async def fetch_weather(city): """模拟爬取天气（实际应调用天气 API）""" async with aiohttp.ClientSession() as session: # 这里应该是真实的天气 API # async with session.get(f"https://api.weather.com/{city}") as resp: # return await resp.json() # 模拟数据 await asyncio.sleep(0.5) # 模拟网络延迟 return { "city": city, "temperature": 20 + hash(city) % 15, # 伪随机温度 "humidity": 40 + hash(city) % 40, "updated_at": datetime.now().isoformat() } async def crawl_cities(cities): """并发爬取多个城市""" tasks = [fetch_weather(city) for city in cities] return await asyncio.gather(*tasks) # 使用 if __name__ == "__main__": cities = ["北京", "上海", "广州", "深圳", "杭州", "成都"] results = asyncio.run(crawl_cities(cities)) for r in results: print(r)models.py：from flask_sqlalchemy import SQLAlchemy db = SQLAlchemy() class WeatherRecord(db.Model): id = db.Column(db.Integer, primary_key=True) city = db.Column(db.String(50), nullable=False) temperature = db.Column(db.Float) humidity = db.Column(db.Integer) updated_at = db.Column(db.DateTime) def to_dict(self): return { 'city': self.city, 'temperature': self.temperature, 'humidity': self.humidity, 'updated_at': self.updated_at.strftime('%Y-%m-%d %H:%M:%S') }app.py：from flask import Flask, render_template, jsonify from models import db, WeatherRecord from crawler import crawl_cities import asyncio app = Flask(__name__) app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///weather.db' db.init_app(app) with app.app_context(): db.create_all() @app.route('/') def index(): return render_template('index.html') @app.route('/api/weather') def get_weather(): records = WeatherRecord.query.order_by(WeatherRecord.updated_at.desc()).all() return jsonify([r.to_dict() for r in records]) @app.route('/api/update', methods=['POST']) def update_weather(): cities = ["北京", "上海", "广州", "深圳", "杭州"] data = asyncio.run(crawl_cities(cities)) # 保存到数据库 for item in data: record = WeatherRecord( city=item['city'], temperature=item['temperature'], humidity=item['humidity'], updated_at=datetime.fromisoformat(item['updated_at']) ) db.session.add(record) db.session.commit() return jsonify({"message": "更新成功", "count": len(data)}) if __name__ == '__main__': app.run(debug=True)templates/index.html： 天气看板 🌤️ 实时天气看板 更新数据 9) 下节课预告：王者到传说下节课我们将学习：测试驱动开发（TDD）：pytest 单元测试代码质量：类型提示、代码格式化、linting部署上线：Docker 容器化、云服务器部署性能优化：Profiling、缓存策略设计模式：单例、工厂、观察者等大型项目架构：MVC、分层架构、微服务入门10) 学习建议异步是难点：多写多练，理解事件循环机制数据库要熟练：SQL 是基础，ORM 是效率Web 开发是必经之路：理解 HTTP、RESTful API读框架源码：Flask、FastAPI 的源码并不复杂，学习优秀设计做完整项目：从零开始做一个能用的东西，遇到问题解决问题记住： 高阶特性是工具，不是炫技。选择合适的工具解决合适的问题，才是高手。遇到问题？ 百度一下，或者看官方文档。Python 的文档是业界标杆，非常详细。祝编程愉快！May your code be bug-free! 🐍课后作业：用生成器重写文件读取功能，处理 10GB 日志文件写个装饰器，自动重试失败的数据库操作（最多3次）用 asyncio 并发请求 100 个 URL，统计成功率完善天气应用：添加历史趋势图、城市搜索、邮件预警下节课见！
🐍 Python 第三课：从白银到黄金——文件操作、模块与面向对象
课程目标：学会读写文件，让数据不再"随风而逝"掌握模块和包，告别"把所有代码写在一个文件里"的野蛮时代初探面向对象编程（OOP），理解"类"和"对象"这对好基友认识常用标准库，站在 Python 的肩膀上完成一个实战小项目，把所学知识串起来1) 回顾上节课上节课我们学会了：条件判断（if/elif/else）：让程序有脑子循环（while/for）：让程序有毅力函数：代码的打包艺术数据结构：列表、字典、元组、集合错误处理：try/except/finally常见问题解答：Q：为什么我的列表修改后，另一个变量也变了？a = [1, 2, 3] b = a b.append(4) print(a) # [1, 2, 3, 4] WTF？！A：因为 b = a 不是复制，而是给同一个列表起了两个名字！要复制用 b = a.copy() 或 b = list(a)。Q：函数里修改传入的列表，外面的列表也会变？A：是的！因为传的是引用（地址）。如果不想影响原列表，先在函数里 copy() 一份。2) 文件操作：让数据"永生"程序关闭后，变量里的数据就消失了。要持久保存，得写入文件。读取文件# 方法1：传统方式（记得手动关闭！） f = open("story.txt", "r", encoding="utf-8") content = f.read() # 读取全部 print(content) f.close() # 别忘了关！否则资源泄露 # 方法2：with 语句（推荐！自动关闭） with open("story.txt", "r", encoding="utf-8") as f: content = f.read() # 出了这个缩进，文件自动关闭，就算出错也会关打开模式：模式说明"r"只读（默认），文件不存在报错"w"只写，会清空原有内容！ 文件不存在则创建"a"追加，在末尾添加，不清空"r+"读写"b"二进制模式（如 "rb"、"wb"），用于图片、视频等各种读取方法with open("data.txt", "r", encoding="utf-8") as f: # 方法1：read() 读取全部，大文件会卡死 all_content = f.read() # 方法2：readline() 读一行 first_line = f.readline() # 方法3：readlines() 读取为列表，每行一个元素 lines = f.readlines() # 方法4：直接遍历（最推荐，省内存） for line in f: print(line.strip()) # strip() 去掉行末的换行符写入文件# 写入会覆盖原内容！ with open("output.txt", "w", encoding="utf-8") as f: f.write("Hello, World!\n") f.write("第二行\n") lines = ["第一行\n", "第二行\n", "第三行\n"] f.writelines(lines) # 写入多行，注意要自己加换行符追加模式：with open("log.txt", "a", encoding="utf-8") as f: f.write("2024-01-01: 用户登录\n") # 在文件末尾添加，不覆盖文件和目录操作（os 模块）import os # 文件操作 os.rename("old.txt", "new.txt") # 重命名 os.remove("trash.txt") # 删除文件 os.path.exists("file.txt") # 判断是否存在 os.path.getsize("file.txt") # 获取文件大小（字节） # 目录操作 os.mkdir("newfolder") # 创建目录 os.rmdir("empty_folder") # 删除空目录 os.listdir(".") # 列出目录内容 os.getcwd() # 获取当前工作目录 os.chdir("/path/to/dir") # 切换目录 # 路径拼接（跨平台！Windows 用 \，Linux/Mac 用 /） path = os.path.join("folder", "subfolder", "file.txt") # 结果：Windows 下是 "folder\subfolder\file.txt" # Linux/Mac 下是 "folder/subfolder/file.txt"JSON 数据处理JSON 是网络数据交换的标准格式，Python 有内置支持：import json data = { "name": "张三", "age": 25, "hobbies": ["编程", "游戏", "睡觉"], "is_student": False } # Python 对象 → JSON 字符串 json_str = json.dumps(data, ensure_ascii=False, indent=2) print(json_str) # 保存到文件 with open("data.json", "w", encoding="utf-8") as f: json.dump(data, f, ensure_ascii=False, indent=2) # 从 JSON 字符串 → Python 对象 parsed = json.loads(json_str) # 从文件读取 with open("data.json", "r", encoding="utf-8") as f: loaded_data = json.load(f)注意： ensure_ascii=False 让中文正常显示，不然会变成 \u4e2d\u6587。3) 模块和包：代码的"乐高积木"什么是模块？一个 .py 文件就是一个模块。你可以把功能相关的代码放在一个文件里，其他地方导入使用。文件结构：myproject/ ├── main.py # 主程序 ├── utils.py # 工具函数模块 └── data/ └── students.jsonutils.py 内容：# 工具函数模块 def add(a, b): """加法""" return a + b def multiply(a, b): """乘法""" return a * b PI = 3.14159 # 模块级别的变量main.py 中使用：# 导入整个模块 import utils print(utils.add(2, 3)) print(utils.PI) # 导入特定函数 from utils import add, multiply print(add(2, 3)) # 可以直接用，不用加 utils. 前缀 # 导入并起别名（名字太长时有用） import utils as u print(u.multiply(4, 5)) # 导入所有（不推荐，容易命名冲突） from utils import *包（Package）：模块的文件夹当项目变大，需要把模块组织成文件夹：myproject/ ├── main.py ├── math_tools/ │ ├── __init__.py # 必须有这个文件，哪怕为空 │ ├── basic.py # 基础运算 │ └── advanced.py # 高级运算 └── utils/ ├── __init__.py └── helpers.py__init__.py 的作用：Python 3.3+ 之前，必须有这个文件才算包现在可以没有，但建议保留，用来控制导入行为导入包中的模块：# 方式1 from math_tools import basic print(basic.add(1, 2)) # 方式2 from math_tools.basic import add print(add(1, 2)) # 方式3 import math_tools.basic as mb print(mb.add(1, 2))标准库和第三方库常用标准库（自带，无需安装）：模块用途os操作系统接口sys系统相关datetime日期时间jsonJSON 处理re正则表达式random随机数math / statistics数学运算collections高级数据结构itertools迭代器工具第三方库（需要安装）：# 使用 pip 安装 pip install requests # HTTP 请求 pip install numpy # 科学计算 pip install pandas # 数据分析 pip install matplotlib # 绘图 pip install flask # Web 框架使用虚拟环境（重要！）：# 创建虚拟环境 python -m venv myenv # 激活（Windows） myenv\Scripts\activate # 激活（Mac/Linux） source myenv/bin/activate # 退出 deactivate为什么用虚拟环境？不同项目依赖不同版本的库避免污染全局 Python 环境方便打包和部署4) 面向对象编程（OOP）：从"过程"到"对象"为什么需要 OOP？面向过程： 把问题拆成步骤，按顺序执行。# 面向过程写法 def create_student(name, age): return {"name": name, "age": age} def student_say_hello(student): print(f"你好，我是{student['name']}") s1 = create_student("张三", 20) student_say_hello(s1)面向对象： 把数据和操作数据的方法打包成"对象"。# 面向对象写法 class Student: def __init__(self, name, age): self.name = name self.age = age def say_hello(self): print(f"你好，我是{self.name}") s1 = Student("张三", 20) s1.say_hello()好处： 代码更有组织性，易于扩展和维护，适合大型项目。类和对象的基本概念类（Class）：对象的"设计图纸"，定义了对象有什么属性、能做什么对象（Object）：根据类创建的"实例"，是具体的存在属性（Attribute）：对象的数据（姓名、年龄等）方法（Method）：对象的行为（打招呼、学习等）类比：类 = 汽车设计图纸对象 = 根据图纸造出来的一辆辆具体汽车属性 = 颜色、品牌、排量方法 = 加速、刹车、转弯定义类和创建对象class Dog: """狗狗类""" # 类属性：所有实例共享 species = "Canis familiaris" def __init__(self, name, age): """构造方法：创建对象时自动调用""" self.name = name # 实例属性 self.age = age def bark(self): """实例方法""" print(f"{self.name}说：汪汪！") def get_info(self): return f"{self.name}今年{self.age}岁" # 创建对象（实例化） my_dog = Dog("旺财", 3) your_dog = Dog("来福", 5) # 访问属性和方法 print(my_dog.name) # 旺财 my_dog.bark() # 旺财说：汪汪！ print(Dog.species) # Canis familiaris（通过类访问） print(my_dog.species) # Canis familiaris（通过实例访问）关键概念：__init__：构造方法，创建对象时自动执行，self 代表当前对象self：必须作为第一个参数，但调用时不用传，Python 自动处理实例属性（self.xxx）：每个对象独立类属性（直接定义在类里）：所有对象共享继承：站在巨人的肩膀上class Animal: """动物基类""" def __init__(self, name): self.name = name def speak(self): raise NotImplementedError("子类必须实现这个方法") class Cat(Animal): # Cat 继承 Animal """猫咪类""" def speak(self): # 重写父类方法 print(f"{self.name}说：喵喵~") class Dog(Animal): """狗狗类""" def __init__(self, name, breed): super().__init__(name) # 调用父类构造方法 self.breed = breed # 新增属性 def speak(self): print(f"{self.name}说：汪汪！") def fetch(self): """狗狗特有的方法""" print(f"{self.name}去捡球了") # 使用 cat = Cat("咪咪") dog = Dog("旺财", "金毛") cat.speak() # 咪咪说：喵喵~ dog.speak() # 旺财说：汪汪！ dog.fetch() # 旺财去捡球了继承的好处：代码复用：共同的代码写在父类扩展性：子类可以新增属性和方法多态：不同子类对同一方法有不同实现封装：保护数据class BankAccount: """银行账户""" def __init__(self, owner, balance=0): self.owner = owner self.__balance = balance # 双下划线：私有属性，外部无法直接访问 def deposit(self, amount): """存款""" if amount > 0: self.__balance += amount print(f"存入{amount}元，当前余额：{self.__balance}") else: print("存款金额必须大于0") def withdraw(self, amount): """取款""" if 0 < amount <= self.__balance: self.__balance -= amount print(f"取出{amount}元，当前余额：{self.__balance}") return amount else: print("余额不足或金额无效") return 0 def get_balance(self): """获取余额（只读）""" return self.__balance account = BankAccount("张三", 1000) account.deposit(500) account.withdraw(200) print(account.get_balance()) # 1300 # print(account.__balance) # 报错！AttributeError命名约定：__xxx：双下划线，私有属性/方法，外部无法访问（ name mangling 机制）_xxx：单下划线，约定俗成的"内部使用"，但技术上可以访问xxx：公有，随意访问特殊方法（魔术方法）class Book: def __init__(self, title, author, pages): self.title = title self.author = author self.pages = pages def __str__(self): """print() 时调用""" return f"《{self.title}》by {self.author}" def __repr__(self): """交互式环境直接显示对象时调用""" return f"Book('{self.title}', '{self.author}', {self.pages})" def __len__(self): """len() 时调用""" return self.pages def __eq__(self, other): """== 比较时调用""" if not isinstance(other, Book): return False return self.title == other.title and self.author == other.author book = Book("三体", "刘慈欣", 300) print(book) # 《三体》by 刘慈欣 print(repr(book)) # Book('三体', '刘慈欣', 300) print(len(book)) # 300 book2 = Book("三体", "刘慈欣", 300) print(book == book2) # True常用魔术方法：方法触发时机__init__创建对象__str__str(obj)，print(obj)__repr__repr(obj)，交互式显示__len__len(obj)__eq__obj1 == obj2__lt__obj1 < obj2__getitem__obj[key]__call__obj() 把对象当函数调用5) 常用标准库速览datetime：时间处理from datetime import datetime, timedelta now = datetime.now() print(now) # 2024-01-15 14:30:00.123456 print(now.year, now.month, now.day) # 格式化 print(now.strftime("%Y-%m-%d %H:%M:%S")) # 2024-01-15 14:30:00 # 解析字符串 birth = datetime.strptime("1990-05-20", "%Y-%m-%d") # 时间计算 future = now + timedelta(days=7) print(f"一周后：{future}")random：随机数import random print(random.randint(1, 100)) # 随机整数 [1, 100] print(random.random()) # 随机浮点数 [0, 1) print(random.choice(["苹果", "香蕉", "橙子"])) # 随机选择 random.shuffle([1, 2, 3, 4, 5]) # 随机打乱（原地修改）re：正则表达式import re text = "我的邮箱是 test@example.com，电话是 138-1234-5678" # 查找邮箱 email_pattern = r'\w+@\w+\.\w+' email = re.search(email_pattern, text) if email: print(email.group()) # test@example.com # 替换 new_text = re.sub(r'\d{3}-\d{4}-\d{4}', '***-****-****', text)6) 实战项目：学生管理系统（OOP 版）把前面学的串起来，做一个完整的小项目。功能需求：添加学生（姓名、年龄、成绩）删除学生查询学生信息显示所有学生保存到文件 / 从文件加载统计平均分、最高分、最低分项目结构：student_system/ ├── main.py ├── student.py # Student 类 └── manager.py # 管理逻辑student.py：class Student: def __init__(self, name, age, score): self.name = name self.age = age self.score = score def __str__(self): return f"{self.name}({self.age}岁): {self.score}分" def to_dict(self): """转为字典，用于 JSON 序列化""" return { "name": self.name, "age": self.age, "score": self.score } @classmethod def from_dict(cls, data): """从字典创建对象""" return cls(data["name"], data["age"], data["score"])manager.py：import json from student import Student class StudentManager: def __init__(self, data_file="students.json"): self.students = [] self.data_file = data_file self.load_data() def add_student(self, name, age, score): student = Student(name, age, score) self.students.append(student) print(f"添加成功：{student}") def remove_student(self, name): for s in self.students: if s.name == name: self.students.remove(s) print(f"已删除：{name}") return print(f"未找到：{name}") def show_all(self): if not self.students: print("暂无学生") return print(f"\n共有 {len(self.students)} 名学生：") print("-" * 30) for s in self.students: print(s) print("-" * 30) def get_stats(self): if not self.students: return None scores = [s.score for s in self.students] return { "avg": sum(scores) / len(scores), "max": max(scores), "min": min(scores) } def save_data(self): data = [s.to_dict() for s in self.students] with open(self.data_file, "w", encoding="utf-8") as f: json.dump(data, f, ensure_ascii=False, indent=2) print("数据已保存") def load_data(self): try: with open(self.data_file, "r", encoding="utf-8") as f: data = json.load(f) self.students = [Student.from_dict(d) for d in data] print(f"已加载 {len(self.students)} 名学生") except FileNotFoundError: print("暂无数据文件，将创建新文件")main.py：from manager import StudentManager def main(): manager = StudentManager() while True: print("\n=== 学生管理系统 ===") print("1. 添加学生") print("2. 删除学生") print("3. 显示全部") print("4. 统计信息") print("5. 保存并退出") choice = input("请选择：") if choice == "1": name = input("姓名：") age = int(input("年龄：")) score = float(input("成绩：")) manager.add_student(name, age, score) elif choice == "2": name = input("要删除的姓名：") manager.remove_student(name) elif choice == "3": manager.show_all() elif choice == "4": stats = manager.get_stats() if stats: print(f"平均分：{stats['avg']:.2f}") print(f"最高分：{stats['max']}") print(f"最低分：{stats['min']}") elif choice == "5": manager.save_data() print("再见！") break else: print("无效选择") if __name__ == "__main__": main()7) 下节课预告：从黄金到王者下节课我们将学习：高级 Python 特性：生成器、装饰器、上下文管理器异常处理进阶：自定义异常、异常链并发编程：多线程、多进程、异步 IO数据库操作：SQLite、SQLAlchemyWeb 开发入门：Flask 框架，做个真正的网站项目实战：开发一个完整的 Web 应用8) 学习建议理解 OOP 思想：类、对象、继承、封装、多态，不只是语法，更是思维方式多读优秀代码：GitHub 上的开源项目，学习别人的设计画类图：复杂项目先画 UML 类图，理清关系再写代码不要过度设计：小项目用函数就够了，大项目才需要 OOP坚持重构：代码是写给人看的，顺便给机器执行，不断优化可读性记住： 从面向过程到面向对象，是一次思维跃迁。一开始觉得别扭很正常，写多了就顺了。遇到问题？ 百度一下，或者放一放。有时候大脑需要后台编译。祝编程愉快！Code is poetry! 🐍课后作业：完善学生管理系统：添加修改学生信息、按成绩排序、搜索功能用 OOP 实现一个图书管理系统学习使用 requests 库，写个爬取天气信息的脚本尝试用 Flask 做个简单的 Web 版学生管理（预习）下节课见！
🐍 Python 第二课：让程序有脑子——控制流与函数
课程目标：让程序学会做判断（if/else）：不再是傻乎乎地从上往下执行让程序学会循环（for/while）：重复的事情交给它，你去喝咖啡学会写函数：把代码打包，一次编写，到处使用认识基本数据结构：列表、字典、元组——数据的"收纳盒"学会处理错误：让程序优雅地崩溃（或者不要崩溃）1) 回顾上节课上节课我们学会了：安装 Python（如果还没装好，请回去补课）写第一个程序 print("Hello, Python!")变量和数据类型（int、float、str、bool）输入输出（input() 和 print()）常见问题解答：Q：为什么我的 input() 得到的数字不能加减？A：因为 input() 得到的是字符串！需要用 int() 或 float() 转换。就像你问朋友"你多大了"，他回答"二十"，这是文字；你要做算术，得把它理解成数字 20。Q：运行程序时提示 "IndentationError" 是什么意思？A：缩进错误！Python 用缩进表示代码块，就像用括号表示段落。缩进不一致，Python 会抓狂。检查你是不是混用了空格和 Tab。2) 条件判断：if/else——让程序有脑子现实生活中，我们经常要做判断：如果下雨，就带伞；否则，就不带如果年满 18 岁，可以投票；否则，回家写作业程序也需要这种能力！基本语法age = int(input("请输入你的年龄：")) if age >= 18: print("你已经成年了，可以投票！") else: print("未成年，回家写作业去吧！")注意：if 后面加冒号 :，表示"我要开始说条件成立时要做的事了"下一行要缩进（4 个空格），表示这些是"条件成立时"的代码else 后面也要加冒号 :，表示"否则的话"多条件判断：elif如果不止两种情况呢？用 elif（else if 的缩写）：score = int(input("请输入你的成绩：")) if score >= 90: print("优秀！") elif score >= 80: print("良好") elif score >= 60: print("及格") else: print("不及格，准备补考吧...")执行逻辑：先检查 score >= 90，成立就打印"优秀"，然后跳过其他所有条件不成立，检查 score >= 80，成立就打印"良好"，然后跳过其他以此类推...如果都不成立，执行 else比较运算符运算符含义例子==等于5 == 5 为 True!=不等于5 != 3 为 True>大于5 > 3 为 True<小于5 < 3 为 False>=大于等于5 >= 5 为 True<=小于等于3 <= 5 为 True注意： == 是两个等号，表示"判断是否相等"；= 是一个等号，表示"赋值"。写错的话，Python 会报错或者产生奇怪的结果。逻辑运算符：and、or、not有时候需要组合多个条件：age = 25 income = 5000 # 申请信用卡的条件：年满 18 且收入超过 3000 if age >= 18 and income > 3000: print("信用卡申请通过！") else: print("抱歉，条件不满足") # 或者：VIP 用户或者消费满 1000 可以打折 is_vip = True spent = 800 if is_vip or spent >= 1000: print("可以享受折扣！") else: print("原价支付") # not：取反 is_raining = False if not is_raining: print("今天不下雨，可以出去玩")优先级： not > and > or，不确定就加括号！嵌套 if：if 里面套 ifis_member = True purchase_amount = 200 if is_member: if purchase_amount >= 100: print("会员且满 100，打 8 折") else: print("会员但不满 100，打 9 折") else: print("非会员，原价")建议： 嵌套不要超过 3 层，否则代码会像迷宫一样难读。如果逻辑太复杂，考虑拆分或者优化。3) 循环：重复的事情，让程序干while 循环：当...的时候，就一直做# 倒计时程序 count = 5 while count > 0: print(count) count = count - 1 # 或者写成 count -= 1 print("发射！🚀")执行过程：检查 count > 0，5 > 0 成立，执行循环体打印 5，count 变成 4检查 count > 0，4 > 0 成立，执行循环体打印 4，count 变成 3...直到 count 变成 0，0 > 0 不成立，退出循环⚠️ 警告： 如果忘记写 count = count - 1，count 永远是 5，循环永远不停，这就是死循环！程序会卡死，需要按 Ctrl+C 强制终止。for 循环：遍历神器while 适合"不知道要循环多少次"的情况，for 适合"遍历一堆东西"：# 遍历字符串 for char in "Python": print(char) # 输出： # P # y # t # h # o # nrange() 函数：生成数字序列# 打印 0 到 4 for i in range(5): print(i) # 打印 1 到 5 for i in range(1, 6): print(i) # 打印 0, 2, 4, 6, 8（步长为 2） for i in range(0, 10, 2): print(i)range() 的三种用法：range(5)：0, 1, 2, 3, 4（从 0 开始，到 5 结束，不包括 5）range(1, 6)：1, 2, 3, 4, 5（从 1 开始，到 6 结束，不包括 6）range(0, 10, 2)：0, 2, 4, 6, 8（从 0 开始，到 10 结束，步长为 2）循环控制：break 和 continuebreak：立即退出循环# 找第一个能被 7 整除的数 for i in range(1, 100): if i % 7 == 0: # % 是取余数 print(f"找到了：{i}") break # 找到了，不找了，退出循环continue：跳过当前这次，继续下一次# 打印 1-10 中的奇数 for i in range(1, 11): if i % 2 == 0: # 如果是偶数 continue # 跳过，不执行下面的 print print(i)区别：break：循环结束，彻底走人continue：这次算了，继续下一轮循环嵌套：循环里面套循环# 打印乘法表 for i in range(1, 10): for j in range(1, i + 1): print(f"{j}×{i}={i*j}", end="\t") print() # 换行输出：1×1=1 1×2=2 2×2=4 1×3=3 2×3=6 3×3=9 ...注意： 嵌套循环效率较低，大数据量时要小心。另外，内层循环的变量名不要和外层重复（虽然 Python 允许，但容易混淆）。4) 函数：代码的打包艺术为什么要用函数？场景一： 你需要在程序的 10 个不同地方计算圆的面积。不写函数的话，你要复制粘贴 10 次同样的代码。万一要改公式（比如从 πr² 改成 3.14*r*r），你要改 10 处，漏改一处就 bug 了。场景二： 你的主程序有 200 行，读起来像天书。如果把功能拆成函数，主程序可能只有 20 行，清晰明了。定义函数def greet(name): """ 这是一个打招呼的函数 name: 对方的名字 """ print(f"你好，{name}！很高兴认识你！") # 调用函数 greet("张三") greet("李四")语法解析：def：define 的缩写，表示"我要定义一个函数了"greet：函数名，要小写，用下划线连接多个单词（Python 风格）(name)：参数，函数需要的外部数据"""..."""：文档字符串（docstring），说明函数是干嘛的，好习惯！函数体要缩进返回值：return函数可以处理数据，然后把结果"交出来"：def add(a, b): """计算两个数的和""" result = a + b return result # 把结果返回给调用者 # 使用返回值 sum = add(3, 5) print(sum) # 输出 8 # 或者直接 print(add(10, 20)) # 输出 30重要： return 执行后，函数立即结束，后面的代码不会执行：def test(): print("这行会执行") return "结束了" print("这行不会执行") # 永远不会执行 test()多个参数和默认参数def make_coffee(coffee_type, size="中杯", sugar=True): """ 制作咖啡 coffee_type: 咖啡类型（必填） size: 杯型，默认中杯 sugar: 是否加糖，默认 True """ sugar_str = "加糖" if sugar else "不加糖" print(f"制作完成：{size}的{coffee_type}，{sugar_str}") # 调用方式 make_coffee("拿铁") # 中杯拿铁，加糖 make_coffee("美式", "大杯") # 大杯美式，加糖 make_coffee("卡布奇诺", "小杯", False) # 小杯卡布奇诺，不加糖 make_coffee("摩卡", sugar=False) # 中杯摩卡，不加糖（指定参数名）参数传递方式：按位置传：make_coffee("美式", "大杯")按名字传：make_coffee("摩卡", sugar=False)混合传：位置参数在前，命名参数在后变量作用域：全局 vs 局部# 全局变量：在函数外面定义的 global_var = "我是全局的" def test(): # 局部变量：在函数里面定义的，外面访问不到 local_var = "我是局部的" print(global_var) # 可以访问全局变量 print(local_var) test() print(global_var) # 正常 # print(local_var) # 报错！NameError: name 'local_var' is not defined如果想在函数里修改全局变量：count = 0 def increment(): global count # 声明：我要用全局的 count，不是新建一个局部变量 count += 1 print(f"当前计数：{count}") increment() # 输出：当前计数：1 increment() # 输出：当前计数：2建议： 尽量少用 global，容易混乱。可以通过参数传递和返回值来交换数据。5) 数据结构：数据的"收纳盒"列表（List）：有序、可变、啥都能装# 创建列表 fruits = ["苹果", "香蕉", "橙子"] mixed = [1, "hello", 3.14, True, [1, 2, 3]] # 啥类型都能放，甚至列表里套列表 # 访问元素（索引从 0 开始！） print(fruits[0]) # 苹果 print(fruits[1]) # 香蕉 print(fruits[-1]) # 橙子（-1 表示最后一个） # 修改元素 fruits[1] = "葡萄" print(fruits) # ['苹果', '葡萄', '橙子'] # 常用操作 fruits.append("西瓜") # 末尾添加 fruits.insert(1, "芒果") # 在索引 1 处插入 fruits.remove("葡萄") # 删除第一个匹配的元素 popped = fruits.pop() # 删除并返回最后一个元素 print(len(fruits)) # 获取长度 # 切片：获取子列表 numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] print(numbers[2:5]) # [2, 3, 4]（从索引 2 到 5，不包括 5） print(numbers[:3]) # [0, 1, 2]（从头开始到 3） print(numbers[7:]) # [7, 8, 9]（从 7 到末尾） print(numbers[::2]) # [0, 2, 4, 6, 8]（步长为 2） print(numbers[::-1]) # [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]（反转！）遍历列表：for fruit in fruits: print(f"我喜欢吃{fruit}") # 带索引的遍历 for index, fruit in enumerate(fruits): print(f"{index}: {fruit}")字典（Dictionary）：键值对，查东西快列表是按位置找元素，字典是按"名字"找：# 创建字典 student = { "name": "张三", "age": 20, "major": "计算机", "gpa": 3.8 } # 访问 print(student["name"]) # 张三 print(student.get("phone", "未知")) # 安全获取，不存在返回默认值"未知" # 修改和添加 student["age"] = 21 # 修改 student["phone"] = "13800138000" # 添加新键值对 # 删除 del student["gpa"] # 遍历 for key in student: print(f"{key}: {student[key]}") # 或者同时遍历键和值 for key, value in student.items(): print(f"{key}: {value}")使用场景： 存储对象信息、配置项、计数统计等。元组（Tuple）：不可变的列表# 创建元组（圆括号） coordinates = (10, 20) rgb = (255, 128, 0) # 可以访问，不能修改 print(coordinates[0]) # 10 # coordinates[0] = 15 # 报错！TypeError: 'tuple' object does not support item assignment什么时候用元组？数据不应该被修改（比如坐标、RGB 颜色）作为字典的键（列表不能当键，因为可变）函数返回多个值（其实就是返回元组）def get_min_max(numbers): return min(numbers), max(numbers) # 返回元组 minimum, maximum = get_min_max([3, 1, 4, 1, 5, 9]) print(minimum, maximum) # 1 9集合（Set）：去重和交集并集# 创建集合（花括号，或者 set()） s = {1, 2, 3, 3, 3, 3} # 自动去重 print(s) # {1, 2, 3} # 常用操作 a = {1, 2, 3, 4} b = {3, 4, 5, 6} print(a & b) # 交集：{3, 4} print(a | b) # 并集：{1, 2, 3, 4, 5, 6} print(a - b) # 差集：{1, 2} print(a ^ b) # 对称差集（只在其中一个集合中的元素）：{1, 2, 5, 6}6) 错误处理：让程序优雅地犯错常见错误类型错误类型说明例子SyntaxError语法错误漏了冒号、括号不匹配NameError名字错误使用了未定义的变量TypeError类型错误字符串和整数相加ValueError值错误int("abc")IndexError索引错误访问列表不存在的索引KeyError键错误访问字典不存在的键ZeroDivisionError除零错误10 / 0try/except：捕获错误try: num = int(input("请输入一个数字：")) result = 10 / num print(f"10 除以 {num} 等于 {result}") except ValueError: print("输入的不是有效数字！") except ZeroDivisionError: print("不能除以零！") except Exception as e: print(f"发生了未知错误：{e}")执行流程：先执行 try 块里的代码如果出错，根据错误类型匹配 except 块匹配成功，执行对应的 except 块，然后继续没有错误，跳过所有 exceptelse 和 finallytry: f = open("data.txt", "r") content = f.read() except FileNotFoundError: print("文件不存在") else: print("文件读取成功！") print(content) finally: print("无论成功与否，这行都会执行") # 通常用来关闭文件、释放资源等7) 综合练习练习 1：猜数字游戏计算机随机生成一个 1-100 的数字，用户来猜，告诉用户猜大了还是小了，直到猜中。提示： 用 random.randint(1, 100) 生成随机数，需要 import random参考答案：import random target = random.randint(1, 100) attempts = 0 print("猜数字游戏！我想了一个 1-100 之间的数字。") while True: try: guess = int(input("请输入你的猜测：")) attempts += 1 if guess < target: print("太小了！") elif guess > target: print("太大了！") else: print(f"恭喜你猜中了！答案就是 {target}，你用了 {attempts} 次。") break except ValueError: print("请输入一个有效的数字！")练习 2：学生成绩管理系统用字典存储学生信息（姓名、成绩），实现以下功能：添加学生删除学生查询学生成绩显示所有学生计算平均分提示： 用字典存储，key 是学生姓名，value 是成绩。用循环显示菜单。参考答案（框架）：students = {} def add_student(): name = input("请输入学生姓名：") try: score = float(input("请输入成绩：")) students[name] = score print(f"已添加学生：{name}，成绩：{score}") except ValueError: print("成绩必须是数字！") def show_all(): if not students: print("暂无学生数据") return print("\n学生成绩列表：") print("-" * 20) for name, score in students.items(): print(f"{name}: {score}") print("-" * 20) # 主循环 while True: print("\n1. 添加学生 2. 删除学生 3. 查询成绩 4. 显示全部 5. 计算平均分 6. 退出") choice = input("请选择操作：") if choice == "1": add_student() elif choice == "4": show_all() # ... 其他选项自己实现 elif choice == "6": print("再见！") break else: print("无效的选择")8) 下节课预告：从白银到黄金下节课我们将学习：文件操作：读写文件，数据持久化模块和包：组织代码，使用第三方库面向对象编程（OOP）：类与对象，封装继承多态常用标准库：os、sys、datetime、json 等实战小项目：综合运用所学知识9) 学习建议多写代码，少看视频：看懂了不等于会了，手指会了才是真的会从模仿到创新：先抄示例代码，理解后尝试修改，最后独立写善用百度：复制报错信息搜索，99% 的问题都有答案读别人的代码：GitHub 上有很多优秀项目，学习别人的写法坚持打卡：每天写一点，保持手感记住： 编程是一门手艺，熟能生巧。你现在觉得难的，三个月后回头看会觉得"就这？"。遇到问题？ 百度一下，或者放一放，明天再看。有时候大脑需要后台编译。祝编程愉快！Keep calm and code on! 🐍课后作业：完成上面的两个练习用循环打印各种图案（金字塔、菱形等）写一个函数，判断一个数是否是素数尝试用列表推导式重写一些循环（进阶）下节课见！
课程目标：搞明白 Python 到底是啥（不是蟒蛇，放心）在你的电脑上"养"一条 Python（安装）学会跟它说第一句话写出一个能跑的程序，而不是能跑的 bug1) Python 是什么？能吃吗？Python 是一种高级编程语言，诞生于 1989 年。创始人 Guido van Rossum 在圣诞节闲得无聊（是的，大神也和我们一样），于是创造了这门语言。为什么叫 Python？不是因为创始人喜欢蟒蛇，而是因为他喜欢 BBC 的喜剧《Monty Python's Flying Circus》。所以如果你学 Python 时感到欢乐，那很符合创始人的初衷！Python 的特点：语法简洁：像读英语一样读代码，不像某些语言（说的就是你，C++）需要写一堆分号和花括号解释型语言：边写边跑，不用等编译。就像外卖 vs 自己做饭，即点即食跨平台：Windows、Mac、Linux 通吃，写一次，到处跑（Java 听了想打人）生态丰富：你想做的事，大概率有人已经写好了库，直接"拿来吧你"应用领域：数据分析（Excel 看了会沉默）人工智能（让机器学会摸鱼）Web 开发（做个网站唬人）自动化脚本（让电脑帮你干活，你负责喝茶）爬虫（合法地在网上"爬"数据）2) 为什么学 Python？因为人生苦短！Python 社区有句名言："Life is short, you need Python"（人生苦短，我用 Python）。理由一：上手快别的语言写个"Hello World"需要 5 行，Python 只需要 1 行。少写的代码，都是少掉的头发。理由二：社区庞大遇到问题？百度一下，Stack Overflow 一下，GitHub 一下。如果还解决不了……那可能是你的问题太前沿了，恭喜你要发论文了。理由三：就业面广从月薪 5k 的脚本小子到年薪 50w 的算法工程师，Python 都能陪你走。当然，工资高低取决于你学得多深，以及头发还剩多少。3) 安装 Python：给你的电脑注入灵魂第一步：下载去 python.org 下载最新版 Python 3.x。注意：不要下载 Python 2.x！ 那已经是"上古神器"了，现在用它会被人当成出土文物。第二步：安装（Windows 用户看这里！）安装时，一定要勾选 "Add Python to PATH"！不勾这个选项，以后在命令行输入 python，电脑会一脸懵逼地问："python 是什么？能吃吗？"第三步：验证安装打开命令行/终端，输入：python --version或者python3 --version如果看到类似 Python 3.x.x 的输出，恭喜你！Python 已经住进你的电脑了。如果看到 "'python' 不是内部或外部命令"，请回到第二步，重新安装并勾选那个该死的选项。运行 Python 的两种方式方式一：交互式解释器（REPL）在命令行输入 python 或 python3，你会看到 >>> 这个提示符。这表示 Python 在听候你的差遣，输入一行，执行一行。>>> print("我是一条 Python 程序") 我是一条 Python 程序 >>> 1 + 1 2 >>> exit() # 输入这个退出，或者直接按 Ctrl+D/Ctrl+Z方式二：写脚本文件创建一个文件叫 hello.py（注意后缀是 .py，不是 .txt，虽然 Windows 很喜欢把后缀藏起来），然后用编辑器打开。推荐编辑器：IDLE：Python 自带的，朴素但能用，就像你奶奶织的毛衣VS Code：微软出品，免费好用，插件丰富，程序员的主流选择PyCharm：JetBrains 出品，功能强大，专业版收费（但社区版免费且够用）Jupyter Notebook：数据分析必备，可以一边写代码一边写笔记，适合装X4) 第一个程序：Hello, Python!打开你的编辑器，输入以下"神级代码"：# 这是我的第一个 Python 程序 # 虽然它很简单，但每一个大神都是从这里开始的 print("Hello, Python!")保存为 hello.py，然后在命令行运行：python hello.py见证奇迹的时刻！ 如果看到 Hello, Python!，说明你的程序跑起来了。如果看到满屏红色报错……检查一下是不是把 print 拼成了 prnit。小知识点：# 开头的是注释，Python 会无视它，但人类需要它来理解代码print() 是一个函数，作用是把括号里的内容输出到屏幕引号里的内容是字符串，就是一串文字5) 变量：给数据起个名字在 Python 里，变量就像是贴标签的盒子。你把数据放进去，贴个标签，以后通过标签就能找到数据。name = "张三" # 字符串，用引号包起来 age = 25 # 整数，不用引号 height = 175.5 # 浮点数（小数），也不用引号 is_cool = True # 布尔值，只有 True 和 False 两个值 print(name, age, height, is_cool)Python 是动态类型语言，意思是：你不需要告诉 Python "name 是字符串"Python 看到你给 name 赋值 "张三"，自己就明白了："哦，这是个字符串"常见数据类型：类型例子说明int42, -7整数，没有小数点float3.14, -0.001浮点数，有小数点str"你好", 'Python'字符串，用单引号或双引号包起来boolTrue, False布尔值，只有真假两个值查看类型：print(type(age)) # 输出: <class 'int'>小贴士：Python 的 type() 函数就像 X 光机，能透视变量的类型。6) 输入输出：跟用户聊起来程序如果只是自己玩，那就太无聊了。让我们学会接收用户的输入：# 获取用户输入 name = input("请输入你的名字：") print("你好，", name, "！很高兴认识你！") # 但是注意！input() 得到的是字符串 age = input("请输入你的年龄：") print("明年你就", age + 1, "岁了") # 报错！字符串不能和整数相加报错！ 因为 input() 得到的是字符串 "20"，而不是数字 20。字符串加数字，Python 会懵圈。解决方案：类型转换age = input("请输入你的年龄：") age = int(age) # 把字符串转成整数 print("明年你就", age + 1, "岁了") # 正常工作！常用转换函数：int()：转成整数（"42" → 42）float()：转成浮点数（"3.14" → 3.14）str()：转成字符串（42 → "42"）完整示例：# 一个正经的自我介绍程序 name = input("请输入你的名字：") age = int(input("请输入你的年龄：")) # 直接转换，省一行代码 hobby = input("请输入你的爱好：") print("=" * 30) # 打印 30 个等号，装饰一下 print(f"大家好，我是{name}，今年{age}岁。") print(f"我喜欢{hobby}，很高兴认识大家！") print("=" * 30)注意：上面用了 f-string（格式化字符串），在引号前加 f，就可以在字符串里嵌入变量，用 {} 包起来。这是 Python 3.6+ 的特性，好用得飞起！7) Python 的语法要点：缩进是命！Python 最独特的地方：用缩进表示代码块。其他语言：if (x > 0) { print("正数"); }Python：if x > 0: print("正数") # 注意前面的四个空格！重要规则：缩进必须统一：要么都用 4 个空格，要么都用 Tab（推荐用 4 个空格）不要混用空格和 Tab，否则 Python 会抓狂同一代码块的缩进必须相同错误的例子：if x > 0: print("正数") print("大于零") # 缩进不一致，报错！注释规范：# 这是单行注释 """ 这是多行注释（其实是字符串，但没人用它） 可以用来写函数说明文档 """ # 代码要写得像诗一样优美（虽然程序员大多没有诗意） # 变量名要有意义：用 user_name 而不是 x # 适当加空行，让代码有呼吸感8) 练习时间：动手吧，少年！练习 1：自我介绍生成器写一个程序，输入名字和年龄，输出自我介绍。示例运行：请输入你的名字：小美 请输入你的年龄：22 你好，我是 小美，今年 22 岁。参考答案：name = input("请输入你的名字：") age = input("请输入你的年龄：") print(f"你好，我是 {name}，今年 {age} 岁。")练习 2：简易计算器输入两个数字，输出它们的和、差、积、商。示例运行：请输入第一个数字：10 请输入第二个数字：3 和： 13 差： 7 积： 30 商： 3.3333333333333335参考答案：a = float(input("请输入第一个数字：")) b = float(input("请输入第二个数字：")) print("和：", a + b) print("差：", a - b) print("积：", a * b) print("商：", a / b) # 注意：除法结果总是浮点数进阶挑战：如果输入的第二个数字是 0，程序会报错（除零错误）。你能想办法处理吗？（提示：下节课学 if 语句就能解决了）9) 下节课预告：从青铜到白银下节课我们将学习：变量进阶：更多数据类型和运算控制流：让程序有脑子，能判断（if/else）和循环（for/while）函数：把代码打包，重复使用数据结构：列表、字典、元组——数据的"收纳盒"错误处理：让程序优雅地犯错（而不是直接崩溃）10) 学习资源与生存指南官方资源：Python 官方文档（虽然对初学者有点硬核，但权威）学习建议：多动手，少看书：看十遍不如写一遍，手指的记忆比脑子靠谱从错误中学习：报错信息是 Python 在跟你说话，学会读报错（虽然它有时候说人话，有时候不说）善用搜索引擎：复制报错信息到百度，99% 的问题别人都遇到过加入社区：CSDN、知乎、Stack Overflow、GitHub，找到组织坚持：编程就像健身，三天打鱼两天晒网是练不出肌肉的最后送大家一句话："The only way to learn a new programming language is by writing programs in it." —— Dennis Ritchie（C语言之父）翻译：学编程的唯一方法就是写代码。所以，关掉这个教程，去写代码吧！课后作业：安装 Python 并成功运行 hello.py完成上面的两个练习试着修改练习 2，让它能计算平方、开方（提示：需要导入 math 模块）遇到问题？ 恭喜你，这是编程的常态。百度一下，或者先放一放，明天再看，也许就豁然开朗了。祝编程愉快！May the Python be with you! 🐍