for 循环中使用 updater 的注意事项

例如,我想让三个圆及其圆心绕着原点旋转,其中圆通过 updater 移动到圆心上:
mobs = VGroup()
for i in range(3):
    dot = Dot()
    distance = 0.5 + np.random.random()*1.3
    angle = np.random.random()*2*PI
    dot.shift([distance*np.cos(angle), distance*np.sin(angle), 0])
    dot.add_updater(lambda z, dt: z.rotate(0.5*PI*dt, about_point=ORIGIN))
    circle = Circle().move_to(dot)
    circle.add_updater(lambda z: z.move_to(dot))
    mobs.add(dot, circle)
self.add(mobs)
self.wait(3)

但是实际上,看起来的效果好像是三个圆都围绕着同一个圆心,而不是各自的圆心

 

这其实是 for 循环中使用闭包而导致的,这是一个例子:

fs = []
for i in range(1, 4):
    def f():
         return i*i
    fs.append(f)

f1, f2, f3 = fs

print(f1(), f2(), f3())

你可能认为结果是 1, 4, 9,但实际上结果为 9, 9, 9

我的看法是,闭包的结果是返回一个函数,如果这个函数没有传入参数,那么就会产生问题。上面的例子中 f() 没有传入任何的参数,等到循环结束后,i 已经变成了 3,这时候 f1, f2, f3 其实是等价的,均返回 3*3

解决方法也很简单,只需要在 f() 中传入参数即可:

fs = []
for i in range(1, 4):
    def f(n):
        def g():
            return n*n  # 这里不能写成 i*i
        return g
    fs.append(f(i))

f1, f2, f3 = fs

print(f1(), f2(), f3())

 

我们回到最初的问题,lambda 本质上也是一个函数,我们需要改写一下,使得每个 updater 的函数均绑定到各自的对象上:

mobs = VGroup()
for i in range(3):
    def generate_updater(mob1, mob2):
        def updater(mob1):
            mob1.move_to(mob2)
        return updater  # 这里不用传入参数
    dot = Dot()
    distance = 0.5 + np.random.random()*1.3
    angle = np.random.random()*2*PI
    dot.shift([distance*np.cos(angle), distance*np.sin(angle), 0])
    dot.add_updater(lambda z, dt: z.rotate(0.5*PI*dt, about_point=ORIGIN))
    circle = Circle().move_to(dot)
    circle.add_updater(generate_updater(circle, dot))
    mobs.add(dot, circle)
self.add(mobs)
self.wait(3)

 

posted @ 2023-03-11 09:50  树叶本子  阅读(19)  评论(0编辑  收藏  举报