程序设计与计算思维¶

Computer Programming and Computational Thinking

第 12 讲:用类表示随机变量¶

2025—2026学年度春季学期

清华大学 韩文弢

准备工作¶

In [1]:
from abc import ABC, abstractmethod

import numpy as np
import matplotlib.pyplot as plt
from ipywidgets import interact, FloatSlider, IntSlider

plt.rcParams['font.sans-serif'] = ['Noto Sans CJK SC']

问题引入¶

对于随机变量:

  • 有不同的分布
    • 离散分布:离散均匀分布、伯努利分布、……
    • 连续分布:连续均匀分布、正态分布、卡方分布、……
  • 分布可能会带参数
  • 有相关的方法
    • 均值和方差
    • 随机抽样
    • ……

如何优雅地编写代码来表示随机变量?

核心概念¶

在编程语言中,可以使用类型系统与面向对象编程来更好地组织代码。

  • 抽象:利用类型表示概念
  • 封装:提供对外接口,隐藏实现细节
  • 继承:描述类型之间的逻辑关系,复用公共代码
  • 多态:同样的接口,对于不同的类型呈现不同的行为

命名空间与作用域¶

命名空间 (Namespace):从名称到对象的映射

Python 的 4 层嵌套作用域:

  1. 最内层:局部名称(当前函数)
  2. 外层闭包:非局部、非全局名称,例如函数及 lambda 表达式的嵌套定义情况
  3. 全局层:当前模块的全局名称
  4. 最外层:内置名称(builtins 模块)
In [2]:
def scope_demo():
    x = "局部变量"
    
    def inner():
        nonlocal x
        x = "修改后的局部变量"
    
    inner()
    print(f"函数内: {x}")

x = "全局变量"
scope_demo()
print(f"函数外: {x}")
函数内: 修改后的局部变量
函数外: 全局变量

类定义语法¶

class ClassName:
    <语句-1>
    ...
    <语句-N>

类定义创建一个新命名空间,所有局部变量赋值都在此命名空间中。

类对象:两种操作¶

  1. 属性引用:obj.name
  2. 实例化:ClassName() 创建实例
In [3]:
class MyClass:
    """一个简单的示例类"""
    i = 12345
    
    def f(self):
        return 'hello world'

print(f"类属性 i: {MyClass.i}")
print(f"类方法 f: {MyClass.f}")
print(f"文档字符串: {MyClass.__doc__}")
类属性 i: 12345
类方法 f: <function MyClass.f at 0x10c260930>
文档字符串: 一个简单的示例类

实例化与 __init__ 方法¶

In [4]:
class Complex:
    """复数类"""
    
    def __init__(self, realpart, imagpart):
        self.r = realpart
        self.i = imagpart

z = Complex(3.0, -4.5)
print(f"实部: {z.r}, 虚部: {z.i}")
实部: 3.0, 虚部: -4.5

实例对象:数据属性与方法¶

数据属性:实例变量,无需声明,首次赋值时创建

方法:从属于对象的函数

In [5]:
class Student:
    """学生类"""
    
    def __init__(self, name, age):
        self.name = name
        self.age = age
        self.courses = []
    
    def add_course(self, course):
        self.courses.append(course)
        print(f"{self.name}加入了课程: {course}")

student = Student("张三", 20)
student.add_course("概率论")
student.add_course("统计学")
print(f"{student.name}的课程: {student.courses}")
张三加入了课程: 概率论
张三加入了课程: 统计学
张三的课程: ['概率论', '统计学']

动态添加数据属性¶

In [6]:
x = Complex(1, 2)

x.counter = 1
while x.counter < 10:
    x.counter = x.counter * 2
print(f"动态属性 counter: {x.counter}")
del x.counter

print(f"实例类型: {x.__class__}")
动态属性 counter: 16
实例类型: <class '__main__.Complex'>

方法对象:self 的魔法¶

调用 x.f() 等价于 MyClass.f(x)

实例对象作为第一个参数自动传入

In [7]:
class Counter:
    def __init__(self):
        self.count = 0
    
    def increment(self):
        self.count += 1
        return self.count

c = Counter()
print(f"调用前: count={c.count}")

method = c.increment
print(f"方法对象: {method}")
print(f"绑定实例: {method.__self__}")
print(f"底层函数: {method.__func__}")

print(f"\n调用结果: {method()}")
print(f"调用后: count={c.count}")
调用前: count=0
方法对象: <bound method Counter.increment of <__main__.Counter object at 0x10c18b4d0>>
绑定实例: <__main__.Counter object at 0x10c18b4d0>
底层函数: <function Counter.increment at 0x10c260ca0>

调用结果: 1
调用后: count=1

类变量与实例变量¶

类变量:所有实例共享 实例变量:每个实例独有

In [8]:
class Dog:
    kind = 'canine'
    
    def __init__(self, name):
        self.name = name

d = Dog('Fido')
e = Dog('Buddy')

print(f"d.kind: {d.kind} (共享)")
print(f"e.kind: {e.kind} (共享)")
print(f"d.name: {d.name} (独有)")
print(f"e.name: {e.name} (独有)")
d.kind: canine (共享)
e.kind: canine (共享)
d.name: Fido (独有)
e.name: Buddy (独有)

可变类变量的陷阱¶

In [9]:
class DogBad:
    tricks = []
    
    def add_trick(self, trick):
        self.tricks.append(trick)

d = DogBad()
e = DogBad()
d.add_trick('roll over')
e.add_trick('play dead')

print(f"d.tricks: {d.tricks}")
print(f"e.tricks: {e.tricks}")
d.tricks: ['roll over', 'play dead']
e.tricks: ['roll over', 'play dead']
In [10]:
class DogGood:
    def __init__(self, name):
        self.name = name
        self.tricks = []
    
    def add_trick(self, trick):
        self.tricks.append(trick)

d = DogGood('Fido')
e = DogGood('Buddy')
d.add_trick('roll over')
e.add_trick('play dead')

print(f"{d.name} 的技能: {d.tricks}")
print(f"{e.name} 的技能: {e.tricks}")
Fido 的技能: ['roll over']
Buddy 的技能: ['play dead']

特殊方法:运算符重载¶

Python 通过双下划线方法(魔术方法)实现运算符自定义

In [11]:
class Vector:
    """二维向量"""
    
    def __init__(self, x, y):
        self.x = x
        self.y = y
    
    def __add__(self, other):
        return Vector(self.x + other.x, self.y + other.y)
    
    def __mul__(self, scalar):
        return Vector(self.x * scalar, self.y * scalar)
    
    def __repr__(self):
        # 对象的内部表示(调试用)
        return f"Vector({self.x}, {self.y})"
    
    def __str__(self):
        # 对象的可读表示(给人看)
        return f"({self.x}, {self.y})"

v1 = Vector(1, 2)
v2 = Vector(3, 4)
print(f"v1 + v2 = {v1 + v2}")
print(f"v1 * 3 = {v1 * 3}")
print(f"repr: {repr(v1)}")
print(f"str: {str(v1)}")
v1 + v2 = (4, 6)
v1 * 3 = (3, 6)
repr: Vector(1, 2)
str: (1, 2)

继承:代码复用¶

class DerivedClassName(BaseClassName):
    <语句>

属性查找:先在派生类中查找,找不到则沿继承链向上搜索

In [12]:
class Animal:
    def __init__(self, name):
        self.name = name
    
    def speak(self):
        return "发出声音"

class Dog(Animal):
    def speak(self):
        return "汪汪汪"

class Cat(Animal):
    def speak(self):
        return "喵喵喵"

dog = Dog("旺财")
cat = Cat("小黑")

print(f"{dog.name}: {dog.speak()}")
print(f"{cat.name}: {cat.speak()}")
旺财: 汪汪汪
小黑: 喵喵喵

调用基类方法¶

In [13]:
class Animal:
    def __init__(self, name):
        self.name = name
        print(f"创建动物: {name}")

class Dog(Animal):
    def __init__(self, name, breed):
        Animal.__init__(self, name)
        #super().__init__(name)
        self.breed = breed
        print(f"品种: {breed}")

dog = Dog("旺财", "金毛")
创建动物: 旺财
品种: 金毛

多重继承¶

用来表示“既是,也是”的关系。

In [14]:
class Flyable:
    def fly(self):
        return "可以飞行"

class Swimmable:
    def swim(self):
        return "可以游泳"

class Duck(Animal, Flyable, Swimmable):
    def speak(self):
        return "嘎嘎嘎"

duck = Duck("唐老鸭")
print(f"{duck.name}: {duck.speak()}")
print(f"能力: {duck.fly()}, {duck.swim()}")
创建动物: 唐老鸭
唐老鸭: 嘎嘎嘎
能力: 可以飞行, 可以游泳

isinstance 与 issubclass¶

In [15]:
print(f"dog 是 Dog 的实例: {isinstance(dog, Dog)}")
print(f"dog 是 Animal 的实例: {isinstance(dog, Animal)}")
print(f"Dog 是 Animal 的子类: {issubclass(Dog, Animal)}")
print(f"bool 是 int 的子类: {issubclass(bool, int)}")
dog 是 Dog 的实例: True
dog 是 Animal 的实例: True
Dog 是 Animal 的子类: True
bool 是 int 的子类: True

私有变量:名称改写¶

Python 没有真正的私有变量,但支持名称改写:

  • _name:约定为私有(API 的非公有部分)
  • __name:改写为 _classname__name
In [16]:
class Secret:
    def __init__(self):
        self.public = "公开变量"
        self._private = "约定私有"
        self.__very_private = "名称改写"

s = Secret()
print(f"公开: {s.public}")
print(f"约定私有: {s._private}")
print(f"实际属性名: {s._Secret__very_private}")
公开: 公开变量
约定私有: 约定私有
实际属性名: 名称改写

抽象基类 (ABC):定义接口契约¶

使用 abc 模块定义子类必须要实现的方法。

In [17]:
from abc import ABC, abstractmethod

class Shape(ABC):
    @abstractmethod
    def area(self):
        pass
    
    @abstractmethod
    def perimeter(self):
        pass

class Rectangle(Shape):
    def __init__(self, width, height):
        self.width = width
        self.height = height
    
    def area(self):
        return self.width * self.height
    
    def perimeter(self):
        return 2 * (self.width + self.height)

rect = Rectangle(3, 4)
print(f"矩形面积: {rect.area()}")
print(f"矩形周长: {rect.perimeter()}")
矩形面积: 12
矩形周长: 14

多态:同一接口,不同实现¶

不同对象对相同方法有不同行为。

In [18]:
class Square(Rectangle):
    def __init__(self, side):
        super().__init__(side, side)

def describe_shape(shape):
    print(f"面积: {shape.area()}, 周长: {shape.perimeter()}")

shapes = [Rectangle(3, 4), Square(5)]
for shape in shapes:
    describe_shape(shape)
面积: 12, 周长: 14
面积: 25, 周长: 20

迭代器:自定义可迭代对象¶

定义 __iter__() 和 __next__() 方法。

In [19]:
class Reverse:
    """反向迭代器"""
    
    def __init__(self, data):
        self.data = data
        self.index = len(data)
    
    def __iter__(self):
        return self
    
    def __next__(self):
        if self.index == 0:
            raise StopIteration  # 表示迭代结束
        self.index -= 1
        return self.data[self.index]

rev = Reverse('spam')
print(f"反向遍历: {list(rev)}")

for char in Reverse('golf'):
    print(char, end=' ')
反向遍历: ['m', 'a', 'p', 's']
f l o g 

回顾:随机变量¶

  • 随机变量 $X$: 具有不同可能结果 $x$ 的数学对象
  • 概率: $\mathbb{P}(X = x)$
  • 概率分布: 结果与概率的映射关系

高斯分布 (正态分布)¶

自然界最常见的连续概率分布。

核心参数:

  • 均值 $\mu$ (决定位置)
  • 标准差 $\sigma$ / 方差 $\sigma^2$ (决定宽度/分散度)
In [20]:
def bell_curve(x, mu=0, sigma=1):
    """高斯分布的概率密度函数"""
    return np.exp(-((x - mu) ** 2) / (2 * sigma ** 2)) / (sigma * np.sqrt(2 * np.pi))

交互式分布可视化¶

直观感受参数变化对分布形态的影响。

In [21]:
def plot_gaussian(mu=0.0, sigma=1.0, n_samples=10000):
    data = mu + sigma * np.random.randn(n_samples)
    fig, ax = plt.subplots(figsize=(10, 6))
    ax.hist(data, bins=100, density=True, alpha=0.3, color='blue', label='样本数据')
    x = np.linspace(-6, 6, 1000)
    y = bell_curve(x, mu, sigma)
    ax.plot(x, y, 'r-', linewidth=2, label='理论分布')
    x_fill = np.linspace(mu - sigma, mu + sigma, 100)
    y_fill = bell_curve(x_fill, mu, sigma)
    ax.fill_between(x_fill, y_fill, alpha=0.3, color='purple', label='μ±σ 区域')
    ax.axvline(mu, color='white', linestyle='--', linewidth=2)
    ax.text(mu, 0.02, 'μ', color='white', fontsize=14, ha='center',
            bbox=dict(boxstyle='round', facecolor='black', alpha=0.5))
    ax.set_xlim(-6, 6)
    ax.set_ylim(0, 0.7)
    ax.set_xlabel('x', fontsize=12)
    ax.set_ylabel('概率密度', fontsize=12)
    ax.set_title(f'高斯分布: μ={mu:.2f}, σ={sigma:.2f}', fontsize=14)
    ax.legend()
    ax.grid(True, alpha=0.3)
    plt.tight_layout()
    plt.show()

interact(plot_gaussian,
         mu=FloatSlider(min=-3, max=3, step=0.1, value=0, description='均值 μ:'),
         sigma=FloatSlider(min=0.1, max=3, step=0.1, value=1, description='标准差 σ:'),
         n_samples=IntSlider(min=1000, max=100000, step=1000, value=10000, description='样本数:'));
interactive(children=(FloatSlider(value=0.0, description='均值 μ:', max=3.0, min=-3.0), FloatSlider(value=1.0, d…

实验:两个高斯分布的和¶

如果对两个独立的随机变量做相加,会发生什么?

In [22]:
n_samples = 100000
X1 = 0 + 1 * np.random.randn(n_samples)
X2 = 3 + 2 * np.random.randn(n_samples)
total = X1 + X2

fig, ax = plt.subplots(figsize=(10, 6))
ax.hist(total, bins=100, density=True, alpha=0.5, color='green', label='X1 + X2 的样本')

mu_total = 0 + 3
sigma_total = np.sqrt(1**2 + 2**2)
x = np.linspace(-10, 15, 1000)
y = bell_curve(x, mu_total, sigma_total)
ax.plot(x, y, 'r-', linewidth=2, label=f'理论分布 (μ={mu_total}, σ={sigma_total:.2f})')

ax.set_xlabel('x', fontsize=12)
ax.set_ylabel('概率密度', fontsize=12)
ax.set_title('两个高斯分布的和', fontsize=14)
ax.legend()
ax.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()

print(f"看到结果又是一个高斯分布。")
print(f"理论均值: {mu_total}, 样本均值: {np.mean(total):.4f}")
print(f"理论标准差: {sigma_total:.4f}, 样本标准差: {np.std(total):.4f}")
No description has been provided for this image
看到结果又是一个高斯分布。
理论均值: 3, 样本均值: 3.0068
理论标准差: 2.2361, 样本标准差: 2.2349

理论结论¶

两个独立高斯分布的和,仍为高斯分布:

$$ \mu_{new} = \mu_1 + \mu_2 $$ $$ \sigma_{new}^2 = \sigma_1^2 + \sigma_2^2 $$

动机:为何需要自定义类型?¶

传统面向过程的痛点(例如 C 语言或者 R 语言要定义一大堆函数,用各种字母表示不同的分布):

  • 函数分散,脱离上下文
  • 缺乏对 分布本身(底层数学对象) 的统一引用

OOP 解决方案:

  • 将“随机变量”提升为一等公民 (对象)
  • 自身携带参数,内部聚合所有相关方法 (采样、均值、方差)

定义随机变量的抽象基类¶

构建类型层次结构,制定“随机变量”必须遵守的接口契约。

In [23]:
class RandomVariable(ABC):
    """随机变量的抽象基类"""

    @abstractmethod
    def sample(self):
        """从分布中采样"""
        pass

    def mean(self):
        """理论均值"""
        raise NotImplementedError("子类必须实现 mean 方法")

    def var(self):
        """理论方差"""
        raise NotImplementedError("子类必须实现 var 方法")

    def std(self):
        """理论标准差"""
        return np.sqrt(self.var())

class ContinuousRandomVariable(RandomVariable):
    """连续随机变量的抽象基类"""
    pass

class DiscreteRandomVariable(RandomVariable):
    """离散随机变量的抽象基类"""
    pass

实践:高斯随机变量类¶

实现具体的 Gaussian 类型,并提供其特有逻辑。

In [24]:
class Gaussian(ContinuousRandomVariable):
    """高斯(正态)分布"""

    def __init__(self, mu=0.0, sigma2=1.0):
        self.mu = mu      # 均值
        self.sigma2 = sigma2  # 方差

    def sample(self):
        """从高斯分布中采样"""
        return self.mu + np.sqrt(self.sigma2) * np.random.randn()

    def mean(self):
        """返回理论均值"""
        return self.mu

    def var(self):
        """返回理论方差"""
        return self.sigma2

    def pdf(self, x):
        """概率密度函数"""
        return bell_curve(x, self.mu, np.sqrt(self.sigma2))

    def __add__(self, other):
        """两个高斯分布的和"""
        if isinstance(other, Gaussian):
            return Gaussian(self.mu + other.mu, self.sigma2 + other.sigma2)
        else:
            return MixtureSum(self, other)

    def __repr__(self):
        return f"Gaussian(μ={self.mu}, σ²={self.sigma2})"

# 测试
G = Gaussian(1, 2)
print(G)
print(f"均值: {G.mean()}")
print(f"方差: {G.var()}")
print(f"标准差: {G.std()}")
print(f"样本: {G.sample()}")
Gaussian(μ=1, σ²=2)
均值: 1
方差: 2
标准差: 1.4142135623730951
样本: 1.6434473155844853

运算符重载的魔力:直接求和¶

通过实现 __add__,就可以直接用 + 对两个分布对象进行操作。

In [25]:
G1 = Gaussian(0, 1)
G2 = Gaussian(5, 6)
G3 = G1 + G2

print(f"G1: {G1}")
print(f"G2: {G2}")
print(f"G1 + G2 = {G3}")
print(f"理论均值: {G3.mean()}")
print(f"理论方差: {G3.var()}")
G1: Gaussian(μ=0, σ²=1)
G2: Gaussian(μ=5, σ²=6)
G1 + G2 = Gaussian(μ=5, σ²=7)
理论均值: 5
理论方差: 7

伯努利分布 (Bernoulli)¶

典型的离散随机变量:

  • 取值 1,概率为 $p$
  • 取值 0,概率为 $1-p$
In [26]:
class Bernoulli(DiscreteRandomVariable):
    """伯努利分布"""

    def __init__(self, p=0.5):
        if not 0 <= p <= 1:
            raise ValueError("概率 p 必须在 [0, 1] 之间")
        self.p = p

    def sample(self):
        """从伯努利分布中采样"""
        return 1 if np.random.rand() < self.p else 0

    def mean(self):
        return self.p

    def var(self):
        return self.p * (1 - self.p)

    def __add__(self, other):
        """两个随机变量的和"""
        return MixtureSum(self, other)

    def __repr__(self):
        return f"Bernoulli(p={self.p})"

# 测试
B = Bernoulli(0.3)
print(B)
print(f"均值: {B.mean()}")
print(f"方差: {B.var()}")
print(f"10 个样本: {[B.sample() for _ in range(10)]}")
Bernoulli(p=0.3)
均值: 0.3
方差: 0.21
10 个样本: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]

混合分布求和 (Mixture Sum)¶

如何处理不同类型(如高斯 + 伯努利)随机变量的相加?

In [27]:
class MixtureSum(RandomVariable):
    """两个随机变量的和"""

    def __init__(self, X, Y):
        self.X = X
        self.Y = Y

    def sample(self):
        return self.X.sample() + self.Y.sample()

    def mean(self):
        return self.X.mean() + self.Y.mean()

    def var(self):
        # 假设独立
        return self.X.var() + self.Y.var()

    def __add__(self, other):
        """支持连续相加"""
        return MixtureSum(self, other)

    def __repr__(self):
        return f"({self.X} + {self.Y})"

# 测试
mixture = Bernoulli(0.25) + Gaussian(0, 0.1)
print(mixture)
print(f"均值: {mixture.mean()}")
print(f"方差: {mixture.var()}")
(Bernoulli(p=0.25) + Gaussian(μ=0, σ²=0.1))
均值: 0.25
方差: 0.2875

统一的可视化接口¶

有了多态和通用接口,就可以用同一套绘图代码来处理所有分布。

In [28]:
def plot_histogram(rv, n_samples=100000, bins=100, title=None):
    """绘制随机变量的直方图"""
    samples = [rv.sample() for _ in range(n_samples)]

    fig, ax = plt.subplots(figsize=(10, 6))
    ax.hist(samples, bins=bins, density=True, alpha=0.5, color='blue', edgecolor='black')
    ax.set_xlabel('值', fontsize=12)
    ax.set_ylabel('概率密度/概率', fontsize=12)
    ax.set_title(title or str(rv), fontsize=14)
    ax.grid(True, alpha=0.3)

    # 显示统计信息
    sample_mean = np.mean(samples)
    sample_std = np.std(samples)
    theory_mean = rv.mean()
    theory_std = rv.std()

    info_text = f'理论: μ={theory_mean:.3f}, σ={theory_std:.3f}\n'
    info_text += f'样本: μ={sample_mean:.3f}, σ={sample_std:.3f}'
    ax.text(0.02, 0.98, info_text, transform=ax.transAxes,
            verticalalignment='top', bbox=dict(boxstyle='round', facecolor='wheat', alpha=0.5))

    plt.tight_layout()
    plt.show()

# 测试不同的分布
plot_histogram(Gaussian(0, 1), title='标准高斯分布')
No description has been provided for this image
In [29]:
plot_histogram(Bernoulli(0.3), bins=3, title='伯努利分布 (p=0.3)')
No description has been provided for this image
In [30]:
plot_histogram(Gaussian(0, 1) + Gaussian(3, 2), title='两个高斯分布的和')
No description has been provided for this image
In [31]:
plot_histogram(Bernoulli(0.25) + Bernoulli(0.75), bins=4, title='两个伯努利分布的和')
No description has been provided for this image
In [32]:
plot_histogram(Bernoulli(0.25) + Gaussian(0, 0.1), title='伯努利 + 高斯')
No description has been provided for this image

卡方分布 ($\chi^2$)¶

数学定义:标准高斯分布的平方。

In [33]:
class ChiSquared1(ContinuousRandomVariable):
    """自由度为 1 的卡方分布"""

    def sample(self):
        Z = np.random.randn()
        return Z ** 2

    def mean(self):
        return 1.0

    def var(self):
        return 2.0

    def __add__(self, other):
        return MixtureSum(self, other)

    def __repr__(self):
        return "ChiSquared1()"

plot_histogram(ChiSquared1(), title='卡方分布 (自由度=1)')
No description has been provided for this image

构建 $\chi_n^2$ 分布¶

利用我们重载的 + 号,通过对多个独立的 $\chi_1^2$ 求和快速构建!

In [34]:
# 创建 chi-squared(4) 分布
chi4 = ChiSquared1() + ChiSquared1() + ChiSquared1() + ChiSquared1()
plot_histogram(chi4, title='卡方分布 (自由度=4)')
No description has been provided for this image

综合比较:交互式分布演示¶

In [35]:
def compare_distributions(dist_type='Gaussian', param1=0.0, param2=1.0, n_samples=10000):
    if dist_type == 'Gaussian':
        rv = Gaussian(param1, param2)
    elif dist_type == 'Bernoulli':
        rv = Bernoulli(max(0.01, min(0.99, param1)))
    elif dist_type == 'Sum of 2 Gaussians':
        rv = Gaussian(0, 1) + Gaussian(param1, param2)
    else:
        rv = Gaussian(0, 1)

    samples = [rv.sample() for _ in range(n_samples)]

    fig, ax = plt.subplots(figsize=(10, 6))
    ax.hist(samples, bins=50, density=True, alpha=0.5, color='blue', edgecolor='black')
    ax.set_xlabel('值', fontsize=12)
    ax.set_ylabel('概率密度', fontsize=12)
    ax.set_title(f'{dist_type}: {rv}', fontsize=14)
    ax.grid(True, alpha=0.3)

    sample_mean = np.mean(samples)
    sample_std = np.std(samples)
    info_text = f'样本均值: {sample_mean:.3f}\n样本标准差: {sample_std:.3f}'
    ax.text(0.02, 0.98, info_text, transform=ax.transAxes,
            verticalalignment='top', bbox=dict(boxstyle='round', facecolor='wheat', alpha=0.5))

    plt.tight_layout()
    plt.show()

interact(compare_distributions,
         dist_type=['Gaussian', 'Bernoulli', 'Sum of 2 Gaussians'],
         param1=FloatSlider(min=-5, max=5, step=0.1, value=0, description='参数1:'),
         param2=FloatSlider(min=0.1, max=5, step=0.1, value=1, description='参数2:'),
         n_samples=IntSlider(min=1000, max=100000, step=1000, value=10000, description='样本数:'));
interactive(children=(Dropdown(description='dist_type', options=('Gaussian', 'Bernoulli', 'Sum of 2 Gaussians'…

本讲小结¶

关键实现路径:

  1. 建立抽象基类 (ABC) 和类型层次结构。
  2. 实现具体分布 (Gaussian, Bernoulli 等)。
  3. 利用运算符重载 (__add__) 和多态构建 MixtureSum,实现分布的加法。

OOP 设计的优点:

  • 强制要求厘清概念之间的关系。
  • 代码高度模块化,将一个分布的属性和方法关联在一起,便于实现和使用。
  • 方便添加新的分布,可重用已有的关于分布实现的代码。
  • 对外提供统一的接口(但是不同分布有不同的行为),能够用同样的代码来处理不同的分布。