2.4 可变数据 #109
Replies: 18 comments 9 replies
|
看不懂 |
|
约束传递实现看的一头雾水😥 |
|
2.4.4 局部状态 中的 “以下运行环境图展示了多次调用由 make_withdraw 创建的函数的效果”不是对应的make_withdraw代码 |
|
约束传递这部分我理解应该是 将整个计算过程,拆解成许多子模块。 |
|
在2.4.4最后一段第一句话这样用中文表达是不是更加合理,“当你在函数内部给一个变量赋值时,Python 会将这个变量视为局部变量。如果在赋值之前尝试引用这个变量,并且它在当前帧中还没有被定义,Python 会抛出一个 UnboundLocalError 异常。” |
|
凌晨三点给我头都看大了 😢 |
|
看的头大 |
|
看得确实头大... |
|
虽然很抽象,但是内容很好啊 |
|
看的头大 |
|
这一章的内容虽然有点难啃,但是关于变量的查值顺序(scope)等真的帮助很大🥲(让我们感谢ChatGPT) 不过一次看完真的有点头大。。。分两部分看吧,实现的感觉可以放在第二部分再看! |
|
疑问: >>> def outer():
x = "outer-x"
def middle():
x = "middle-x"
def inner():
nonlocal x
x = "inner-x"
inner()
print("middle:", x)
middle()
print("outer:", x)
outer()
#输出
middle: inner-x
outer: outer-x如果是这种情况,相应的变化并不是更新到x第一次被声明的地方,更严谨的说法应该是改变的是最近的,非全局的那个作用域中存在的相应变量 |
|
简单的总结,约束器与连接器的主要作用是进行非线性的运算,在已知等式关系的情况下,给定某一值进行运算,求解等式关系的其他值。 |
|
2.4.5 更深层的意义:创建“对象”的雏形 这其实就是面向对象编程(OOP) 中“对象”的雏形!一个对象将数据和对数据的操作捆绑在一起。———— 来自deepseek |
|
deepseek: 好的,CS61A 的 2.4.9 节“约束传递”(Constraint Propagation)是一个非常有趣但也确实容易让人困惑的话题。它引入了一种全新的编程范式。 我会用一个清晰的类比和分解来帮你理解它。 核心思想:什么是约束传递? 给 a 赋值 1 a = 1 给 b 赋值 2 b = 2 计算 c = a + b,得到 3 约束传递则反其道而行之。它的理念是: 你可以: 告诉系统:c = a + b(这是一个“约束”)。 然后告诉系统:a 是 1。系统会知道 a 变了,但因为它还不知道 b 和 c,所以什么也不计算。 再告诉系统:b 是 2。系统检测到变化,发现现在 a 和 b 都知道了,于是自动计算出 c = 3 并设置好。 你甚至可以反过来,先告诉系统 c 是 10 和 a 是 4,系统会自动推导出 b 必须是 6。 一个绝佳的类比就是 Excel 表格: 你在单元格 A1 输入 1 在单元格 B1 输入 2 在单元格 C1 输入公式 =A1+B1 (这就是一个“约束”) Excel 会自动显示 3。 如果你后来把 A1 改成 5,C1 会自动变成 7。 约束传递系统就是一个用 Python 实现的、迷你版的、可编程的 Excel。 系统的三个核心组成部分
关键特性: 它不知道自己怎么被计算出来,它只关心 “我是谁?” 和 “我的值变了,需要通知谁?”。 它有三个核心方法: set_value(source, new_value): 某个人(source)试图给我赋予一个新值。 get_value(): 获取我当前存储的值。 connect(constraint): 告诉连接器,某个约束关心你的值的变化。当你的值改变时,需要通知这个约束。
关键特性: 它知道如何计算,但通常不自己存储数据。它关心 “我的输入是谁?” 和 “如果我的某个输入变了,我该如何更新其他输入输出以维持关系?”。 它的核心方法: new_value(): 当某个连接的连接器值发生变化时,会调用这个方法。约束在这个方法里决定该做什么(例如,计算新的值并设置到另一个连接器)。 forget_value(): 当某个连接的连接器值被撤销时,会调用这个方法。约束需要处理“未知”状态。
它做什么? 它创建一个约束对象(比如一个加法器约束),然后调用 a.connect(约束)、b.connect(约束)、c.connect(约束),告诉这三个连接器:“以后你们的值有任何变化,都要通知这个加法器约束”。 关系图: text 工作流程示例 (以加法器 adder 为例) python a.set_value('user', 10) 设置第一个值 (a.set_value('user', 10)): a 连接器收到指令,要把自己的值设置为 10。 a 连接器说:“好的,我的值现在是10了。另外,我记得有个加法约束关心我,我得去通知它一下。” a 连接器调用加法约束的 new_value() 方法,说:“嗨,我(a)变了!” 加法约束被唤醒,它检查所有输入:a 是10,但 b 还不知道(是None),c 也不知道。所以它什么也做不了,只能等待。 设置第二个值 (b.set_value('user', 5)): b 连接器收到指令,把值设置为 5。 b 连接器更新自己的值,然后也去通知加法约束。 加法约束再次被唤醒。它检查所有输入:a 是10,b 是5,都知道了!而 c 还不知道。 约束开始履行它的职责:它根据规则 c = a + b 计算出 c 应该是 15。 它调用 c.set_value('adder', 15)。注意这里的 source 是 'adder' 而不是 'user',意思是这个值是由加法器计算得出的,不是用户直接设置的。 c 连接器收到指令,将自己的值设置为15。它也会通知所有关心它的约束(比如可能还有一个乘法约束也连着 c)。 至此,整个系统通过“值变化 -> 通知约束 -> 约束计算 -> 设置新值 -> 触发新的通知...”的链条,自动地将信息(约束)传递(Propagation) 开来,保证了声明的所有关系始终得到满足。 为什么要学这个? 模拟物理世界:很多系统天生就是由约束驱动的(比如电路、物理结构、报表)。用约束系统来建模它们非常直观。 理解计算模型:它展示了数据流(Dataflow)的一种实现方式,即计算是由数据(值)的可用性所驱动的。 总结一下: 希望这个解释能帮你打开思路!如果对具体的代码实现有疑问,可以再提出。 |
|
typo: 2.4.4 "回想一下,在没有 noncal 声明之前,所有对 balance 的重新赋值操作都会在当前环境的第一帧中绑定。" noncal -> nonlocal |
|
这一章写的真好啊,配合课程跟AI感觉学到了很多东西(赞美ChatGPT😭😭) |
|
让 ds 帮忙写了一个 oop 版本约束传递,感觉比字典+高阶函数的实现可读性稍好一些,代码贴在这里了: """constraints_oop.py
面向对象版约束传播系统 —— 与 constraints.py 功能完全一致的 OOP 实现。
原版用「装函数的字典」当对象,本版改成真正的类,对照关系:
dict 里的函数 -> 类的方法
字典字段 -> 实例属性(self.xxx)
闭包变量 -> self 属性
运行:python3 constraints_oop.py(演示双向换算)
"""
from operator import add, sub, mul, truediv
class Connector:
"""连接器:存放一个值,并把"值变了/值被撤销"广播给相连的约束。"""
def __init__(self, name=None):
self.name = name # 打印用的名字;None 表示匿名
self.value = None # 当前值,None 表示还不知道
self.informant = None # 这个值是谁设置的
self.constraints = [] # 与它相连的所有约束
def has_value(self):
return self.value is not None
def connect(self, constraint):
self.constraints.append(constraint)
def set_value(self, source, value):
"""source 声称 value 是新的输入。"""
if self.value is None:
# 第一次赋值:记下来源、存值、通知除 source 外的约束
self.informant = source
self.value = value
if self.name is not None:
print(self.name, '=', value)
for c in self.constraints:
if c is not source:
c.new_value()
elif self.value != value:
print('Contradiction detected:', self.value, 'vs', value)
# 值已存在且相同:什么都不做(防止消息无限循环)
def forget_value(self, source):
"""source 想撤销它当初设置的值。"""
# 只有"当初设值的人"才能撤销,防止误删别人的结果
if self.informant is source:
self.informant = None
self.value = None
if self.name is not None:
print(self.name, 'is forgotten')
for c in self.constraints:
if c is not source:
c.forget()
class Constraint:
"""约束基类:约束之间只通过 new_value / forget 两个消息协作。"""
def new_value(self):
pass # 子类定义:收到"某个连接器有新值"时该做什么
def forget(self):
pass # 子类定义:收到"某个值被撤销"时该做什么
class TernaryConstraint(Constraint):
"""三元约束:连接 a, b, c 三个 Connector。
规则:其中任意两个有值,就能算出第三个。
ab(a, b) -> c 如 c = a + b / c = a * b
ca(c, a) -> b 如 b = c - a / b = c / a
cb(c, b) -> a 如 a = c - b / a = c / b
这就是"约束可双向求值"的来源:一个类同时支持正向与反向推算。
"""
def __init__(self, a, b, c, ab, ca, cb):
self.a, self.b, self.c = a, b, c
self.ab, self.ca, self.cb = ab, ca, cb
for conn in (a, b, c): # 出生时就与三方相连
conn.connect(self)
def new_value(self):
av, bv, cv = (conn.has_value() for conn in (self.a, self.b, self.c))
if av and bv: # a, b 已知 -> 算 c
self.c.set_value(self, self.ab(self.a.value, self.b.value))
elif av and cv: # a, c 已知 -> 算 b
self.b.set_value(self, self.ca(self.c.value, self.a.value))
elif bv and cv: # b, c 已知 -> 算 a
self.a.set_value(self, self.cb(self.c.value, self.b.value))
# 只知道 0 或 1 个值时还推不出来,什么都不做
def forget(self):
# 三个连接器都申请撤销;不是本约束设的值会被连接器拒绝
for conn in (self.a, self.b, self.c):
conn.forget_value(self)
class Adder(TernaryConstraint):
"""加法约束:a + b = c"""
def __init__(self, a, b, c):
super().__init__(a, b, c,
add, # ab: 算 c,c = a + b
sub, # ca: 算 b,b = c - a
sub) # cb: 算 a,a = c - b
class Multiplier(TernaryConstraint):
"""乘法约束:a * b = c"""
def __init__(self, a, b, c):
super().__init__(a, b, c,
mul, # ab: 算 c,c = a * b
truediv, # ca: 算 b,b = c / a
truediv) # cb: 算 a,a = c / b
class Constant(Constraint):
"""常量约束:让连接器永久保持固定值。
做法:把自己作为 source 调一次 set_value。
Connector 只允许 informant(即本对象)撤销值,
而网络中没有任何约束会拿本对象去 forget —— 所以值永久生效。
注意它没有 connect:它从不接收 new_value / forget 消息,
也不需要 —— 常量只是"身份标记 + 一次性赋值"。
"""
def __init__(self, connector, value):
connector.set_value(self, value)
def make_celsius_fahrenheit_converter(c, f):
"""用约束把连接器 c 和 f 接成 摄氏 <-> 华氏 转换器。
数学关系:f = c * 9 / 5 + 32
拆成三条约束:u = c * 9 ;u = v * 5 ;f = v + 32
"""
u = Connector() # 4 个匿名中间连接器
v = Connector()
w = Connector()
x = Connector()
y = Connector()
Multiplier(c, w, u) # u = c * w
Multiplier(v, x, u) # u = v * x
Adder(v, y, f) # f = v + y
Constant(w, 9) # 常量:w = 9
Constant(x, 5) # x = 5
Constant(y, 32) # y = 32
if __name__ == '__main__':
celsius = Connector('Celsius')
fahrenheit = Connector('Fahrenheit')
make_celsius_fahrenheit_converter(celsius, fahrenheit)
# 正向:给摄氏赋值,华氏被自动算出(f = 25 * 9 / 5 + 32 = 77)
celsius.set_value('user', 25)
# 撤销 celsius,再反向:给华氏赋值,摄氏被自动算出(c = 100)
celsius.forget_value('user')
fahrenheit.set_value('user', 212) |
Uh oh!
There was an error while loading. Please reload this page.
2.4 可变数据
SICP Python 中文翻译
https://composingprograms.netlify.app/2/4
All reactions