SICP2020-2[环境与框架特点、hw1、lab01]

摘要

本篇作为2020年春季Functional Programming总结的第二篇,内容大致包括python语言的环境(environment)框架(frame)特点,以及hw1、lab01中一些有趣题目的答案与反思。相关具体细节(例如函数的自定义等)原课程网上教科书已经编写的相当清楚,我就不再班门弄斧了,本篇目的是自己在学习过程中间的思考想法,仅供参考。希望你玩的愉快!

环境(environment)及框架(frame)

这是Founction Programming这门课程出现的第一个重要知识点,这种思考方式适用于任何编程环境任何编程化的语言
思考几个问题:当我们仅仅定义一个函数时发生了什么?当我们在python中定义一个函数,并在之后使用它时发生了什么?当我们从库中调用一个函数时发生了什么?如果我们定义的函数是嵌套的(nested),那我们在使用这个函数时发生了什么?
要理清这几个问题我们先搞清楚python的一个重要因素,环境(environment)
环境是指python在运行各个函数时的背景,它是由多个框架构成的。先不要着急理解它,引用课堂例子我们看如下代码:

1
2
from math import pi
pau = 2*pi

运行这个代码后,我们在全局下创造了一个盒子,我们首先从math库中将pi调用出来放进这个盒子中,然后根据我们的运算规则将相应的计算值赋予pau,同时把它也扔进盒子中。我们取课本原图表示这一环节。

这个盒子就是我们创造的全局环境,我们不管之后再添加什么代码,只要还在全局环境下,那么pi = 3.14....,而pau = 2pi,我们可以直接使用则两个名字代表数据。
我们再来看我们定义函数时会发生什么。有一定python基础的人(比如我)在不知道答案的时候往往会认为,一旦函数定义,python会将函数储存起来,形成一个自我的密闭空间。但事实并不是这样。
1
2
3
from math import mul
def square(x):
return mul(x, x)

我们在运行以上代码后,函数square并不会像我们想象的一样自动形成一个封闭的空间,而是跟我们之前的附值一样,将名称square保留在全局变量下,不会管它到底怎么运行,会返回什么,计算机只是将名称静置在全局环境中并且标明:这是一个函数,等待被召唤。引用课堂原图

而当我们使用我们所定义的函数的时候,函数会被唤醒,生成一个新的盒子,也就在此时我们函数的内容才会被interpreter审视进行相关的操作。值得注意的是,我们使用函数自己创造的盒子中的附值操作(assignment)都不会传递到任何的其他盒子中!附值操作只要出了这个盒子(环境)就消失了,不要妄图在盒子外使用自己在盒子内部的附值名称。
每一个盒子(除了全局)都有一个“母盒”(parent)你可以理解为新的盒子实在其之上建立的。如果你只是单纯定义了一个函数,那么该函数的“母盒”就是全局(global),如果是嵌套环境,则内部函数的母环境就是外部函数。每一个盒子内部的附值都不能拿出这个盒子,例如:
1
2
3
4
def example(x):
i = 1
return x + 1
i

程序会报错,因为i仅仅在函数内部定义,在出了函数之后就再无定义了。我们将这种数据(不光是附值,在一个自定义函数里定义的嵌套函数的名称拿到全局环境下也没有作用)称为局部变量(local variable)。
那么又有一个问题,母环境下的值可以在子环境中引用吗?可以用,但是请避免这种情况发生。在python2.x时代子环境,也就是我们说的子盒是没有权力调用除全局变量定义的附值的,而在python3.x时代这个问题似乎解决了,因为如果你尝试以下代码:
1
2
3
4
5
6
7
8
9
def example2(x):
i = 1
def sub():
return x + 1
return sub()

k = example2(1)
k
>>>2

计算机返还了2,也就是说子盒sub调用了在母盒中的附值i = 1,这一切看上去很完美,但是如果你尝试如下代码你会发现问题:
1
2
3
4
5
6
7
8
9
def count_cond(condition):
i , num = 1 , 0
def conts(n):
while i <= n:
if condition(n, i):
num = num + 1
i = i + 1
return num
return conts

这是一段我自己尝试的代码,系统会报错,信息显示i,num在没有被附值的情况下被调用了。现在这个问题还在各大论坛上有争论,但为例保证代码的流畅以及避免bug,在写代码时还是自动默认一个环境中只能调用自己以及全局幻境中的变量,其余的不要随意进行调用。

hw1

Q2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from operator import add, sub

def a_plus_abs_b(a, b):
"""Return a+abs(b), but without calling abs.

>>> a_plus_abs_b(2, 3)
5
>>> a_plus_abs_b(2, -3)
5
>>> # a check that you didn't change the return statement!
>>> import inspect, re
>>> re.findall(r'^\s*(return .*)', inspect.getsource(a_plus_abs_b), re.M)
['return h(a, b)']
"""
if b >= 0:
h = _____
else:
h = _____
return h(a, b)

非常有意思的一道题目,它其实展现的是python关于附值也就是=这一功能的强大之处,不但可以把名字赋给数值,还可以把名字赋给函数,答案如下:

1
2
3
4
5
6

if b >= 0:
h = add
else:
h = sub
return h(a, b)

Q5

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
def if_function(condition, true_result, false_result):
"""Return true_result if condition is a true value, and
false_result otherwise.

>>> if_function(True, 2, 3)
2
>>> if_function(False, 2, 3)
3
>>> if_function(3==2, 3+2, 3-2)
1
>>> if_function(3>2, 3+2, 3-2)
5
"""
if condition:
return true_result
else:
return false_result

def with_if_statement():
"""
>>> result = with_if_statement()
6
>>> print(result)
None
"""
if c():
return t()
else:
return f()

def with_if_function():
"""
>>> result = with_if_function()
5
6
>>> print(result)
None
"""
return if_function(c(), t(), f())

def c():
"*** YOUR CODE HERE ***"

def t():
"*** YOUR CODE HERE ***"

def f():
"*** YOUR CODE HERE ***"

很有趣的题目答案如下:

1
2
3
4
5
6
7
8
def c():
return False

def t():
print(5)

def f():
print(6)

这道题的问题在于为什么with_if_function在引用的时候会出现两个数字?问题的关键在于()with_if_function中返还if_function时该函数在干什么?它运行了三个函数c(),t(),f(),计算机会首先运行这三个函数并将这三个函数的返回。还记得上一章的纯函数吗?print只会在屏幕上打印出值,而返还None,所以我们运行t(),f()它们自然而然的在屏幕上打印出了5以及6。我们将代码改变一下
1
2
3
4
5
6
7
8
9
def with_if_function():
"""
>>> result = with_if_function()
5
6
>>> print(result)
None
"""
return if_function(c(), t, f)

这时你会发现with_if_function只出现一个数字了。我们在这里有必要补充一下函数的调用(call)

补充:函数调用

函数的定义如下def name(parameter1,parameter2...): body函数有一个名字,有相应的参数,以及函数的主体。当你需要一个函数的时候呼叫它的名字即可,调用函数名,由return返回相应的值,则一次完整的函数调用就完成了,但是请注意namename(parameater1...)二者不是一个东西,前者是使用函数名对函数进行调用,即将函数作为参数或者返回值(在之后的heigher order function中会有涉及),在终端中输入一个函数的名称返回回来的是function<..>括号中跟着函数寄存的内存地址,而后者则是直接使用或是运行这个函数,代表了该函数的返回值。综上在Q5中我们运行了函数ft它们的返回值None作为了参数输入了if_function中在运行ftprint函数发挥了威力,它在函数运行时在屏幕上打印出了相应值。

Lab01

Q2

WWDP。本问比较多暂且不写回答了,最主要的问题是python对于False的判定,除了False本身的字符串会被判定为“假”意外,0,[],None会被判定为“假”,注意负数,小数在判定中都认为是“真”的,只有0才被判定为假。
此外and以及or的返回值要特别注意,它们会返回最后计算的数值and在计算到有一个为假后会返还那个为假命题的数值并跳过后面所有的计算,注意是数值而不是Booleans(True、False),or则是在计算到有一个为真后就会返还该数值并结束计算。在此处需要注意的是Error具有最高等级,只要任何被判定项中出现错误,则会自动结束并返回错误类型例如:

1
2
3
4
>>>1/0 or 1
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ZeroDivisionError: division by zero

Q8

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def double_eights(n):
"""Return true if n has two eights in a row.
>>> double_eights(8)
False
>>> double_eights(88)
True
>>> double_eights(2882)
True
>>> double_eights(880088)
True
>>> double_eights(12345)
False
>>> double_eights(80808080)
False
"""
"*** YOUR CODE HERE ***"

有很多类型重复的题目,在此就先拿出一道来讲,答案如下:

1
2
3
4
5
6
7
8
while n > 1:
if n % 10 == 8:
n = n//10
if n % 10 ==8:
return True
else:
n = n // 10
return False

唯一需要注意的点就是//整除符号的运用,在使用整除符号后会忽略小数,是用来解决针对输入数字的每一位单独数字进行处理的必要工具。