CS1602计算导论
第 10 讲Part 4 抽象与组织AI Level 1

类(二)与模块

Class II and Modules

复用与重写类的行为,理解 Python 的运算和容器协议。用实际多文件例子分析导入、名字绑定和入口,再区分运行环境、模块缓存与编译缓存。

本讲结束后你应当能

  • 分析继承方法中的接收者、重写与必需状态
  • 解释 super 沿方法查找顺序继续查找
  • 为表示、算术、比较和下标规定明确协议
  • 区分模块对象、导入名字与共享引用
  • 用真实文件核对导入副作用及 main 入口
  • 区分解释器环境、进程模块缓存与磁盘字节码缓存
本页目录

I. 继承与行为接口

基类与子类

L09 已经说明对象状态、方法接收者和公开接口。现在几个类可以复用共同实现,并定义不同的行为。

继承建立基类与子类的关系。程序是否适合采用继承,还要看子类能否满足调用者依赖的接口,不由现实概念的分类自动决定。

class Animal:
    def __init__(self, name: str) -> None:
        self.name = name
    def speak(self) -> str:
        return "..."
    def introduce(self) -> str:
        return self.name + ": " + self.speak()

class Dog(Animal):
    def speak(self) -> str:
        return "woof"

class Cat(Animal):
    def speak(self) -> str:
        return "meow"

for animal in [Animal("A"), Dog("D"), Cat("C")]:
    print(animal.introduce())

Dog 没有新定义初始化,使用继承的初始化建立自己的 name。introduce 的函数来自 Animal,但执行时 self 仍为实际实例;self.speak 可以选择重写的方法。普通 Python 类的继承链最终到 object,这不表示任何对象都支持 len 等操作。

重写与初始化

重写是子类提供继承方法的另一实现。若重写 init,就不会因此自动执行基类初始化。以下例子故意展示缺字段:

class Base:
    def __init__(self, value):
        self.value = value

class Child(Base):
    def __init__(self, value):
        self.extra = value

print(Child(3).value)

继承查找能找到方法,不会复制父实例的数据。需要基类字段时,应协调初始化:

class Person:
    def __init__(self, name: str, age: int) -> None:
        self.name = name
        self.age = age
    def describe(self) -> str:
        return f"{self.name}, {self.age}"

class Student(Person):
    def __init__(self, name: str, age: int, school: str) -> None:
        super().__init__(name, age)
        self.school = school
    def describe(self) -> str:
        return super().describe() + ", " + self.school

s = Student("Alex", 18, "SJTU")
print(s.describe())

这里是单继承,super 找到 Person 的方法。一般规则是:在实际类型的方法查找顺序 MRO中,从当前类之后继续查找。它不是永远等同直接父类,也不自动执行所有父类方法。

调用时机取决于状态依赖,不是每个子类都机械地第一行调用 super。重写签名、返回、异常或状态规则后,仍需回归检查。

父方法内的 self 调用

class Base:
    def word(self):
        return "base"
    def report(self):
        return self.word()

class Child(Base):
    def word(self):
        return "child"

print(Child().report())

输出 child。Base.report 的函数代码仍然通过 Child 实例调用 word。执行父方法不会将 self 换成一个新 Base 对象。

多态与鸭子类型

同一操作可以由不同类型提供实现;调用方依赖的是行为接口。先保留有共同基类的完整版本,此例约定传入合法尺寸,不提供实验所需的全部验证:

import math

class Shape:
    def area(self) -> float:
        raise NotImplementedError("area")

class Circle(Shape):
    def __init__(self, r: float) -> None:
        self.r = r
    def area(self) -> float:
        return math.pi * self.r ** 2

class Square(Shape):
    def __init__(self, side: float) -> None:
        self.side = side
    def area(self) -> float:
        return self.side ** 2

class Rectangle(Shape):
    def __init__(self, w: float, h: float) -> None:
        self.w, self.h = w, h
    def area(self) -> float:
        return self.w * self.h

shapes = [Circle(1), Square(2), Rectangle(2, 3)]
total = 0.0
for shape in shapes:
    total += shape.area()
print(f"{total:.4f}")

总面积实际为13.1416。共同基类记录接口,但下面相同调用也可不要求共同基类:

class Circle:
    def area(self) -> float:
        return 3.141592653589793
class Square:
    def area(self) -> float:
        return 4.0

total = 0.0
for shape in [Circle(), Square()]:
    total += shape.area()
print(f"{total:.4f}")

这里两个类都提供返回面积的无参数方法。名字相同还不足够,参数、结果和副作用也必须符合约定。没有合适的 area 实现时,普通基类方法可抛 NotImplementedError;这种普通类仍能实例化,只在调用该方法时失败,并没有自动成为不可实例化的抽象类。

II. Python 操作协议

显示与调试表示

__str__ 和 __repr__ 都须返回字符串。前者服务于可读显示,后者帮助观察对象信息;repr 可争取无歧义或可重建,但不保证所有对象都能由该字符串重建。

class Point:
    def __init__(self, x: float, y: float) -> None:
        self.x, self.y = x, y
    def __str__(self) -> str:
        return f"({self.x}, {self.y})"
    def __repr__(self) -> str:
        return f"Point({self.x}, {self.y})"

p = Point(3, 4)
print(p)
print(repr(p))
print([p])

默认 str 可以退回 repr,反向不成立。列表显示元素使用 repr。默认表示中的地址与模块名不是固定答案。不要把未知 repr 文本交给 eval。

算术、反射与不支持的操作数

NotImplemented 是返回给分派机制的哨兵,表示这组操作数不支持;NotImplementedError 是异常,二者不同。不要用哨兵作布尔条件,也不要将它视为 False。下例的 math.hypot(x,y) 求两坐标组成向量的欧氏长度。

import math

class Vector:
    def __init__(self, x: float, y: float) -> None:
        self.x, self.y = x, y
    def __repr__(self) -> str:
        return f"Vector({self.x}, {self.y})"
    def __add__(self, other: "Vector") -> "Vector":
        if not isinstance(other, Vector):
            return NotImplemented
        x = self.x + other.x
        y = self.y + other.y
        return Vector(x, y)
    def __sub__(self, other: "Vector") -> "Vector":
        if not isinstance(other, Vector):
            return NotImplemented
        return Vector(self.x - other.x, self.y - other.y)
    def __mul__(self, scalar: float) -> "Vector":
        if not isinstance(scalar, (int, float)):
            return NotImplemented
        return Vector(self.x * scalar, self.y * scalar)
    def __rmul__(self, scalar: float) -> "Vector":
        return self * scalar
    def __eq__(self, other: object) -> bool:
        if not isinstance(other, Vector):
            return NotImplemented
        return self.x == other.x and self.y == other.y
    def __abs__(self) -> float:
        return math.hypot(self.x, self.y)

a=Vector(1,2)
b=Vector(3,4)
print(a+b, b-a)
print(a*3, 3*a)
print(abs(b), a==Vector(1,2))

本例加减返回新 Vector,不改输入;乘法支持 Vector×标量及标量×Vector,abs 返回长度。右侧参与反射分派,不能把运算符总说成简单调用左侧同名方法。右操作数子类型优先、同类型反射限制等细节见参考。

__eq__ 对不支持类型返回哨兵,最终 == 可以回退身份比较;不支持的排序或算术通常产生 TypeError。示例给出了有限、尺度适中的数值运算,并非完整数值库。

增强赋值与别名

class Value:
    def __init__(self, n: int) -> None:
        self.n = n
    def __add__(self, other: "Value") -> "Value":
        return Value(self.n + other.n)
    def __iadd__(self, other: "Value") -> "Value":
        self.n += other.n
        return self

a = Value(1)
b = a
a += Value(2)
print(a.n, b.n, a is b)
a = a + Value(3)
print(a.n, b.n, a is b)

第一步得到 3 3 True,第二步得到 6 3 False。本例 __iadd__ 修改 self 并返回 self;普通 __add__ 创建新对象。__iadd__ 也可以返回新对象,缺失或返回哨兵时可能回退普通加法,因此 += 不普遍保证原地或身份不变。

长度、下标、赋值与成员关系

class Playlist:
    def __init__(self) -> None:
        self._songs: list[str] = []
    def add(self, song: str) -> None:
        self._songs.append(song)
    def __len__(self) -> int:
        return len(self._songs)
    def __getitem__(self, index: int) -> str:
        return self._songs[index]
    def __setitem__(self, index: int, value: str) -> None:
        self._songs[index] = value
    def __contains__(self, song: str) -> bool:
        return song in self._songs

p = Playlist()
p.add("first")
p.add("second")
p[1] = "changed"
print(len(p), p[-1], "first" in p)
for song in p:
    print(song)

长度须为非负整数;下标协议规定返回值和失败行为。这里委托列表,负下标有效,越界抛 IndexError,赋值返回 None,成员关系检查字符串。每个 Playlist 有独立列表。

没有 __iter__ 时,迭代可回退为从0开始连续请求 __getitem__,遇 IndexError 结束:

class Values:
    def __init__(self):
        self.items = ["A", "B"]
    def __getitem__(self, index):
        print("index", index)
        return self.items[index]

print(list(Values()))

实际请求0、1、2,第三次越界后停止。显式 __iter__=None 不使用这种回退。完整迭代器在 L13 讲,本讲不提前使用 yield。

相等与身份

class Item:
    def __init__(self, value):
        self.value = value
    def __eq__(self, other):
        if not isinstance(other, Item):
            return NotImplemented
        return self.value == other.value

a = Item(2)
b = Item(2)
c = a
print(a == b, a is b, a is c)

输出 True False True:eq 比字段,a/b 是不同实例,c 是 a 的别名。is 可检查任意对象身份,不仅用于 None;数值或字符串的值比较用 ==,不依赖驻留/缓存。None 通常写 is None,不在没有测量时声称它必然最快。

type 的精确类型检查与 isinstance 的接受子类行为对应不同需求。内省可以辅助调试,不能代替公开接口文档。

III. 模块、绑定与入口

实际文件与模块对象

本讲自写模块是 .py 文件;Python 也有内置、扩展、冻结等模块,不把全部模块等同源文件。模块是具有命名空间的对象,import 绑定模块名,from 绑定选定对象的本地引用。

以下代码须保存为真实文件,在该目录以 Python 3.13 运行 main.py。网页单代码框不构成完整工程,不要求先运行另一页建立环境。

geometry.py

"""Geometry operations."""
def area(w, h):
    return w*h

report.py

"""Formatting operations."""
def line(value):
    return f"area={value}"

main.py

import geometry
import report
value = geometry.area(3, 4)
print(report.line(value))

实际 Python 3.13 输出:

area=12

文件职责与导入关系分开;一个模块被导入,不表示它的每个函数体同时执行。

显式导入与名字来源

import math
import math as m
from math import sqrt
from math import sqrt as root
print(math.sqrt(16), m.sqrt(16), sqrt(16), root(16))

as 改变导入者的访问名字,不修改原模块名。通常按标准库、第三方、本地分组并留空行;普通课程代码避免星号导入,以免覆盖名字、来源不明。星号仍有合法语法和 __all__ 等导出规则,不能描述为无条件引入所有名称。

导入引用不复制对象

config.py

count = 1
items = []

main.py

import config
from config import count, items
config.count = 2
config.items.append("x")
print(count, config.count, items)

实际 Python 3.13 输出:

1 2 ['x']

输出1、2、含x的列表。重新绑定 config.count 不会重绑已 from 导入的本地 count;列表仍是同一个对象,修改可见。把 from 当作对象拷贝或实时字段链接都不正确。

初始化与进程缓存

helper.py

print("module body")
def action():
    print("function body")

main.py

import sys
import helper
import helper
same = sys.modules["helper"]
print("cached", same is helper)
helper.action()

实际 Python 3.13 输出:

module body
cached True
function body

初次普通导入运行模块顶层,def 建立函数而不执行函数体。相同进程同名普通重复导入通常复用 sys.modules;显式重载或新进程需另外分析。模块总数、目录列表与预加载状态随环境改变,不作为固定预期输出。

直接运行与被导入

tools.py

print("load")
if __name__ == "__main__":
    print("demo")

main.py

import tools
import tools
print("main")

实际 Python 3.13 输出:

load
main

此处 tools 被导入时 __name__ 为tools,guard内demo不执行;顶层load仍执行。直接运行tools.py则进入该块。guard只控制自己的语句块,不能自动消除其他顶层副作用。

普通包与启动方式

mypackage/init.py

"""A regular teaching package."""

mypackage/geometry.py

def area(w, h):
    return w*h

main.py

from mypackage import geometry
print(geometry.area(3, 4))

实际 Python 3.13 输出:

12

普通包使用 __init__.py;namespace package 是另一机制,不能把“可以省略”当作所有包的通用样板。包内相对导入须在合适的包上下文运行,常用 python -m 包.模块,直接执行文件路径可能失败。

IV. 搜索、环境与执行

找到的究竟是哪个模块

导入首先考虑缓存和查找器;顶层的路径式查找使用 sys.path,包的后续搜索也受包路径影响。普通脚本常将脚本目录放入搜索路径,但当前目录永远第一不是规则。启动方式、-I、环境变量、内置/冻结模块和自定义查找器都会影响结果。

random.py

print("local random")

main.py

import random
print(random.randint(1, 3))

实际 Python 3.13 输出:

local random
AttributeError: module 'random' has no attribute 'randint' (consider renaming '/privaterandom.py' since it has the same name as the standard library module named 'random' and prevents importing that standard library module)

这个受控目录与全新进程找到了本地random,因此没有randint,产生预期 AttributeError。不在学生真实项目里留下遮蔽文件。诊断可看 sys.executable、模块存在时的 __file__ 及 python -m pip —version;属性错误也可能另有原因。

虚拟环境和安装

python3 -m venv .venv
# macOS/Linux:
source .venv/bin/activate
# Windows PowerShell:
# .venv\Scripts\Activate.ps1
python -m pip --version
python -m pip install PACKAGE
python -m pip list
python -m pip install --upgrade PACKAGE
python -m pip uninstall PACKAGE
python -m pip install compatible_file.whl

PACKAGE与wheel名为占位示例,不实际安装或卸载未知包。venv隔离依赖,不是安全沙箱。wheel兼容Python/ABI/平台,旧NumPy 1.26.0不能当作本课3.13的正确pin。需要固定版本时选择与环境兼容的发行包,不要求学生一次安装所有库。

CPython与字节码

CPython是Python的一种实现,包含编译与执行过程。编译器可能产生机器码或中间表示,解释器与JIT也可结合,不能把两类简单等同一次转汇编与逐条转机器码。

import dis

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

dis.dis(add)

按取值、运算、返回理解即可。具体指令和偏移是CPython版本细节,不要求背诵。

两层不同的缓存

sys.modules保存本进程已经初始化的模块;磁盘pyc保存编译代码缓存。新进程使用有效缓存可能减少编译,但仍运行顶层初始化:

helper.py

print("helper")
value = 3

main.py

import helper
print(helper.value)

实际 Python 3.13 输出:

Process 1:
helper
3
Process 2:
helper
3

两个新进程都打印helper及3。通常直接运行main.py不会为自身写缓存,导入helper可以产生带版本标签的缓存;权限、-B、有效性与实现都会改变缓存行为。首次导入大库慢可能包含依赖、加载和初始化成本,不只编译。

字节码不是跨全部版本通用格式,也不是源码保密手段。exec不直接把pyc二进制当源码。

参考:完整机制与往年例子

Person与Manager

class Person:
    def __init__(self, name: str, job: str, pay: float) -> None:
        self.name, self.job, self.pay = name, job, pay
    def get_last_name(self) -> str:
        return self.name.split()[-1]
    def give_raise(self, fraction: float) -> None:
        self.pay *= 1 + fraction
    def __repr__(self) -> str:
        return f"Person({self.name!r}, {self.job!r}, {self.pay:.2f})"

class Manager(Person):
    def __init__(self, name: str, pay: float) -> None:
        super().__init__(name, "manager", pay)
    def give_raise(self, fraction: float, bonus: float = 0.1) -> None:
        super().give_raise(fraction + bonus)

m = Manager("Alex Chen", 100)
m.give_raise(0.05)
print(m.get_last_name(), f"{m.pay:.2f}")

保留往年的name/job/pay、姓氏查询、薪资更新和Manager重写。这里的fraction和bonus约定用于教学,业务有效域应另外定义。同一命名空间第二个同名def会重新绑定名称,不是按参数重载。

合作式多继承与C3

class Root:
    def visit(self):
        return ["Root"]
class Left(Root):
    def visit(self):
        return ["Left"] + super().visit()
class Right(Root):
    def visit(self):
        return ["Right"] + super().visit()
class Combined(Left, Right):
    def visit(self):
        return ["Combined"] + super().visit()

print([c.__name__ for c in Combined.__mro__])
print(Combined().visit())

Combined/Left/Right/Root形成线性化顺序,super沿它继续,因此Left后面可以是Right而非直接父Root。只安排查找顺序不会自动调用所有方法,合作式实现必须协调super。

class A: pass
class B(A): pass
class C: pass
class D(B, C): pass
print([c.__name__ for c in D.__mro__])

结果D/B/A/C/object表明不是BFS。下面相互冲突的基类先后约束使类创建失败:

class X: pass
class Y: pass
class A(X, Y): pass
class B(Y, X): pass
class C(A, B): pass

继承选择还涉及行为替换:一个可独立更新宽高的Rectangle接口,未必适合Square子类保持相等边长。组合与委托也是有效方式。

名称改写与子类签名

class Mapping:
    def __init__(self, values):
        self.items = []
        self.__update(values)
    def update(self, values):
        self.items.extend(values)
    __update = update

class Child(Mapping):
    def update(self, keys, values):
        self.items.extend(zip(keys, values))

m = Child([1, 2])
m.update(["a"], [3])
print(m.items)

基类__update实际为_Mapping__update,保留基类函数,子类不同签名的update不破坏初始化。名称改写仍可显式外部访问,不是权限控制。

内省及其适用范围

class Animal: pass
class Dog(Animal): pass
d = Dog()
d.name = "Rex"
print(isinstance(d, Animal), type(d) is Animal)
print(issubclass(Dog, Animal))
print(d.__dict__)
print([c.__name__ for c in Dog.__mro__])
print([n for n in dir(d) if not n.startswith("_")])

还可查看实例__class__、类__name__、__bases__与__mro__。不是全部对象都有__dict__,dir也不保证完整支持接口。L09普通字段的实例优先模型有数据描述符等例外;本讲只说明边界,不展开描述符实现。

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

print(add.__name__)
print(add.__code__.co_varnames)
print(add.__code__.co_names)
print(add.__code__.co_consts)

函数是对象,可绑定名字或作为参数。co_varnames为局部名,co_names为非局部引用名,co_consts为常量;它们是code object属性,不代表整个运行状态。

比较与特殊方法分派

操作方法
<、<=、==、!=、>、>=__lt__、__le__、__eq__、__ne__、__gt__、__ge__
+、-、*、abs__add__、__sub__、__mul__、__abs__及适用反射方法
+=__iadd__及普通加法回退
len、[]、[]=、in__len__、__getitem__、__setitem__、__contains__
str、repr__str__、__repr__

右操作数是子类型且提供不同反射实现时可能优先;同类型不保证再次尝试反射。eq没有__req__形式,通常可尝试对方eq,两方哨兵后回退身份。定义eq的可变类通常变不可哈希,若设计hash要与相等规则一致。

隐式特殊操作通常通过类型及继承链查找,而不是普通实例字段:

class Box: pass
def length():
    return 3
b = Box()
b.__len__ = length
print(b.__len__())
print(len(b))

直接调用b.__len__打印3,但len(b)预期TypeError。这与普通属性调用有区别。

几何、复数与同构字符串

class Triangle:
    def __init__(self, a: float, b: float, c: float) -> None:
        if min(a, b, c) <= 0 or not (a+b>c and a+c>b and b+c>a):
            raise ValueError("invalid edges")
        self.edges = [a, b, c]
    def area(self) -> float:
        a, b, c = self.edges
        s = (a+b+c)/2
        return (s*(s-a)*(s-b)*(s-c))**0.5
    def __lt__(self, other: "Triangle") -> bool:
        if not isinstance(other, Triangle):
            return NotImplemented
        return self.area() < other.area()
    def __getitem__(self, index: int) -> float:
        return self.edges[index]
    def __setitem__(self, index: int, value: float) -> None:
        candidate = self.edges.copy()
        candidate[index] = value
        Triangle(*candidate)
        self.edges = candidate

a, b = Triangle(3, 4, 5), Triangle(3, 3, 3)
print(b < a, a[0])
a[0] = 4
print(a[0])

本例明确以面积定义Triangle的<,下标选三边,非法边赋值保留旧边。不能将任意Triangle相加或大小关系称为普遍几何定义。

class ComplexNumber:
    def __init__(self, real: float, imag: float) -> None:
        self.real, self.imag = real, imag
    def __add__(self, other: "ComplexNumber") -> "ComplexNumber":
        if not isinstance(other, ComplexNumber):
            return NotImplemented
        return ComplexNumber(self.real + other.real, self.imag + other.imag)
    def __repr__(self) -> str:
        return f"ComplexNumber({self.real}, {self.imag})"

print(ComplexNumber(1, 2) + ComplexNumber(3, -1))

Point2D记录两个坐标,nDPoint可存一份标量坐标列表,维度需要明确;L09的Vector、方向角及HappyNumber保留完整定义。本讲二维Vector运算协议与上一讲n维向量接口分开命名。

def isomorphic(s: str, t: str) -> bool:
    if len(s) != len(t):
        return False
    forward, reverse = {}, {}
    for a, b in zip(s, t):
        if a in forward and forward[a] != b:
            return False
        if b in reverse and reverse[b] != a:
            return False
        forward[a], reverse[b] = b, a
    return True

print(isomorphic("egg", "add"))
print(isomorphic("foo", "bar"))

正反两张表防止不同字符映到同一字符。保留旧例的具体任务,而不是只列一个名字。

重载、循环导入与future

settings.py

print("load")
value = 1

main.py

import importlib
import settings
from settings import value
settings.value = 9
print(value, settings.value)
importlib.reload(settings)
print(value, settings.value)

实际 Python 3.13 输出:

load
1 9
load
1 1

使用importlib.reload,不使用已过时的imp。重载重新执行模块代码,先前from的引用不自动改绑;它不是生产并发热更新保证。

a.py

import b
value = 1

b.py

from a import value

main.py

import a

实际 Python 3.13 输出:

ImportError: cannot import name 'value' from 'a' (consider renaming '/privatea.py' if it has the same name as a library you intended to import)

两个模块循环访问尚未建立的字段可以报错。调整职责或延后访问比“换IDE”更有意义,不能把Notebook缓存行为当语义标准。

from __future__ import annotations

class Node:
    def copy(self) -> Node:
        return Node()

print(Node().copy().__class__.__name__)

future是编译指令,不是任意普通导入;允许合法的文档/注释等前置,不要求物理第一行。annotations从3.7可选,在3.13仍须显式启用,不提供类型校验。

金额转换的标准库规则

Lab10 的 Money 采用明确十进制舍入。先说明接口:Decimal 从字符串建立十进制值,quantize(Decimal(“1”)) 量化到整数单位;ROUND_HALF_UP 在恰好半个单位时向远离零的方向舍入。这里单位已经乘成分,不把浮点乘法再截断。

from decimal import Decimal, ROUND_HALF_UP
for yuan in [1.005, -1.005]:
    value = Decimal(str(yuan)) * 100
    cents = value.quantize(Decimal("1"), rounding=ROUND_HALF_UP)
    print(int(cents))

实际101、−101分。规则针对传入值的str,不恢复此前浮点运算丢失的信息。完整Money类仍由实验实现;账户利息使用同一量化规则,但单位为整数分。

包、路径和发行工具

保留PYTHONPATH、site/.pth、site-packages与zip路径等知识;sys.path项不全都是普通目录。__all__影响星号导出,普通包、namespace包、相对导入和-m是不同机制。

NumPy提供数组、广播及数值操作;Matplotlib绘图;SciPy提供科学算法;pandas处理表格;pytest用于测试。Scrapy、Beautiful Soup用于网页处理,Pygame用于游戏程序。官网用途不要求本讲安装这些依赖。

CPython、PyPy、Jython为不同实现;Cython和Nuitka等编译工具不全部归为解释器。旧稿Coco名称缺可靠定位,保留为待考证原稿项,不编造新介绍。CPython源码阅读使用当前仓库,不照抄旧版本文件行号。

显式编译与可信执行

source = "answer = 2 + 3"
code = compile(source, "trusted.py", "exec")
namespace = {}
exec(code, namespace)
print(namespace["answer"])

compile得到code object,再在指定命名空间exec,仅执行课程自己写的可信源码。

python -m py_compile module_a.py module_b.py
python -m compileall selected_project_directory

只选择自己的教学目录,不扫描整个机器;编译不保证缓存可写或跨版本适用。dis/缓存/源码阅读是观察工具,不是新的安全边界。

大整数:第12周的主题预告

本周按统一日历启动素数计数五法,大整数将在第12周启动。保留原表示与接口知识:从十进制字符串构造BigInt,单独符号与分组数字,str/repr、eq/lt、add/sub/mul;不能把整个输入转int来代替实现,但可以用int作为测试参考。

须考虑零、前导零、负数、不等长、负差、进位借位和竖式乘法。报告不超过一页,说明表示选择、符号处理及具体边界/随机对拍证据。旧稿20/20/20/10/15/5/10分项只作历史需求,7.5%与旧项目B时序不再作为现行评分发布。

收束与配套任务

L10实验的Shape族、账户、Money、Deck和真实模块工程使用固定接口,细节见Lab10。个人作业2素数五法占5%,第10周启动、截止来自solo2统一配置;L11继续讨论算法、复杂度、异常和测试。慢算法可以按合理输入规模计时,不换成另一算法冒充。

用继承或运算符以前要解释状态、支持类型、失败与副作用;用模块以前要分清对象引用、顶层运行和环境。组织方式不能替代验证。

来源:Python3.13 Classes、Data Model、Import。