课程是CS61A Summer 2020
Key Value Store是课程中的一个诊断测验,给定函数lens的形式,要求填空完成其功能,功能由下方的doctest可以看出来,即键值对的录入与查询功能,通过
给出的代码大致框架如下所示:
def lens(prev=lambda x: 0):
def put(k, v):
def get(k2):
if ______:
______
else:
______
return ______
return ______
然后开始进行一个分析:
首先根据doctest中put1=lens()
可知,最后是return put
然后根据get2, put2 = put1("cat", "animal")
可知,倒数第二个return是有两个返回值,并且第一个返回值是get()
函数。
第二个返回值是put()
函数,但是put()
的函数体并没有定义相关的返回变量,所以纵观上下,只有lens()
可以返回一个put()
函数。
但是开头的lens(prev = lambda x : 0)
表明要使用lens()
函数需要一个参数。虽然第一次使用lens()
用的参数是默认值,但是不代表后面的就也是默认值。
def lens(prev=lambda x: 0):
def put(k, v):
def get(k2):
if ______:
______
else:
______
return get, lens(______)
return put
截止目前可以分析出这么多。
然后在put()
函数里定义了一个get()
函数,不难发现该函数的功能是查找键值对的。根据输入的keyk2
查找之前可能存储过的值。所以这个if-else
可能跟查找(比较)有关。
既然说到将待查找的k2
与原有的数值进行比较,那么就得分析一下原来的值存储在哪,于是就得涉及一下两个概念:
“栈帧”:键值对的保存在什么位置?在为函数运行开辟的空间里。只要该函数还未运行玩,其对应的空间就还存在,存储在其中的变量就还可以获取到。
百度百科:每个栈帧对应着一个未运行完的函数。栈帧中保存了该函数的返回地址和局部变量。
“闭包”:特点是某个外包函数创建了一个内嵌函数,该内嵌函数使用了外部函数的一个变量,并且外部函数的返回值是这个内嵌函数。
看到doctest的最后一个没有查找到的情况,它返回的是0,这就和lens(prev=lambda x: 0)
对应上了,那么我们可以合理推断,这个prev(x)
就是一个查询之前未销毁的栈帧的函数,并且在本次查询失败的情况下,调用上一层的get()
函数进行查询,知道到达最初第一个get
查找失败(本例中是get2查找失败)
显然这里的prev
是要被动态指定的,在没有指定时(即查找到最起初的数据时)prev使用默认值,因为无论如何这时候查不出符合的东西,返回0。那么由于prev是一个出现在函数参数列表里的形式参数,其动态指定应当由传参时完成。看一下整个代码,也就是导数第二个return
那里
至此就填完了:
def lens(prev=lambda x: 0):
def put(k, v):
def get(k2):
if k2 == k:
return v
else:
return prev(k2)
return get, lens(get)
return put
细节是这样的:
第一次使用lens()
创建了一个put1()
函数,用于存放第一个键值对,然后这个函数自带的prev()
是一个lambda,无论如何都返回0,也就是无论如何在get2()
中查不到时返回0
。
到了get3, put3
这里,由return get, lens(get)
可知,返回得到的新的get3, put3
,并且把当前的get3
(当然,这样说不严谨,只是说get3代指的那个函数,因为这时还没有赋值过去)作为put3
里面的prev
,这样在下一次get4, put4
时,就可以在get4()
没有找到符合的键值对时,使用prev调用get3()
查找,随后的过程都是相仿的,知道找到或者返回0。