SICP2020(1)[CS61a资源的使用方法、纯函数]

摘要

加州伯克利大学自2011年起启用了python作为CS61a这一课程的主要编程语言,课程内容也与之前相比有了不少变化(主题内容以及中心思想并无改变),更重要的是课程资源有了极大的丰富,而网络上很少有中文版本的对相应资源的使用指导,这让我在开始学习时也遇到了不少问题;而CS61a作为编程开山课程,做到了让高中生也可以学习本课程,但是让他们克服如此障碍有些困难,于是我决定将一些问题总结汇总,希望能够帮助中文母语的编程初学者。希望你玩得愉快!!!

在终端中操作样例的常见错误

在伯克利大学2020年春季的SICP版本中,课后的script并没有仔细说明在终端中操作python的具体步骤,导致如果没有看课堂视频的同学们迟迟无法进行操作,现将其归结如下:
凡是看到代码第一行中出现>>>代表了该代码是在终端中运行的,请windows的同学启用git bash并在其中输入python,使得其转入python并显示>>>而不是原来的~$

大家看到的script中的换行好像在git bash中很难实现,其实只要正确的输入相应的指令,例如

>>>from opeartor import mul
>>>def square(x):
       return mul(x, x) 

但如果你正确输入了相关指令,再摁下回车键后,你会发现git bash中出现的不是>>>,而是...,如下图:

现在我们再来回看几个在开始编写程序时容易出现的问题:
1.注意是def不是define!!!与我们在编写scheme时是完全不一样的。
2.注意def后要加:,且一定是英文的。
3.注意缩进,python因为没有像scheme中的括号以及c或c++中的;所以它的各种语句辨识都靠缩进,一般来讲一个函数在跳入下一级定义时需要缩进,也就是常说的Tab键,是四个空格。

SICP提交作业方法

python版SICP最大的改进就是它的自动审批作业系统,我们在CS61a官网上按照课程表下载相应的lab与hw文件,不用联网就可以自动检测自己的作业以及lab是否正确,该系统叫做ok。现在介绍windows系统下的ok系统的具体使用方法。
请在电脑中预先安装Git,有关Git的介绍在另一篇博客上。在安装选项时除了图示选项

其余选项都可以使用默认。

hw的批改方法

每一次的hw作业都会在课程网站上有一个相应的压缩文件可供下载,建议建立一个属于CS61a的文件夹方便管理。在相应网站上都会有一行相应的命令用于作业检查(忽略作业提交指令,只有拥有伯克利大学账号的同学才可以提交)我们以hw01为例,如图
进入课程主页找到hw01

进入后可以下载相应压缩文件并解压

文件中包含我们的作业代码(忽略其他,只关注我们的作业代码即可),运用编辑器打开作业代码并根据网站上的作业要求完成作业,此时我们回到网站,我们会发现在每一道题的后面都会有检查指令,可是该检查指令该怎样使用呢?

回到我们储存作业的文件夹,鼠标右键,你会发现选项中多了Git Bash here这个选项(注意!!!一定是要在该文件夹内部鼠标右键!!!

我们使用从网站上拷贝下来的指令后加--local输入进git bash中就会启动ok程序对我们写的代码进行批改。注意,在命令的最后添加local的意思是将所写代码仅在本地进行校验,而不将其上传,如果忘记添加则系统会要求输入邮箱,十分麻烦。

而如果错误系统也会显示相应的错误类型,方便纠错。

总结

提交相应某一问题时只需要在相应位置打开git bash输入指令python ok -q <name> --local其中<>中的内容需要在课程网站上查询,或者在代码中查看,每一道题都有相应的名称,如果想要一次性全部验证只需要输入python ok即可(由于python版本的差别,该指令的第一个单词可以是python python3 py此项依据python版本而定)

lab的批改方法

lab共分为两个部分,课堂部分以及课下练习,其中课下联系的部分与hw相同,需要首先在lab中下载压缩文件,打开压缩文件中的lab01.py(借用第一次实验为例)根据注释中的相关要求,完成代码,并借助代码中的题目名称进行ok校验即可。
lab线上课程的操作方法则较为简单在相应的文件夹中git bash here,根据网站上直接输入网站上的指令就可以开始答题


?处输入相应的答案即可。

(新补充)关于check import error

在后续作业中会有部分题目中存在字符串检查的doctest,它在启动ok程序后会调用同文件夹下的另一个代码文件construct_check.py在极个别情况下,下载作业压缩文件时会导致该文件为空或者丢失,此时在ok检查时无论如何都会有错误import error,解决方法仅需要将相关文件补齐即可。代码如下:

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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178

from ast import parse, NodeVisitor, Name

_NAMES = {
'Add': '+',
'And': 'and',
'Assert': 'assert',
'Assign': '=',
'AugAssign': 'op=',
'BitAnd': '&',
'BitOr': '|',
'BitXor': '^',
'Break': 'break',
'Recursion': 'recursive call',
'ClassDef': 'class',
'Continue': 'continue',
'Del': 'del',
'Delete': 'delete',
'Dict': '{...}',
'DictComp': '{...}',
'Div': '/',
'Ellipsis': '...',
'Eq': '==',
'ExceptHandler': 'except',
'ExtSlice': '[::]',
'FloorDiv': '//',
'For': 'for',
'FunctionDef': 'def',
'GeneratorExp': '(... for ...)',
'Global': 'global',
'Gt': '>',
'GtE': '>=',
'If': 'if',
'IfExp': '...if...else...',
'Import': 'import',
'ImportFrom': 'from ... import ...',
'In': 'in',
'Index': '...[...]',
'Invert': '~',
'Is': 'is',
'IsNot': 'is not ',
'LShift': '<<',
'Lambda': 'lambda',
'List': '[...]',
'ListComp': '[...for...]',
'Lt': '<',
'LtE': '<=',
'Mod': '%',
'Mult': '*',
'Nonlocal': 'nonlocal',
'Not': 'not',
'NotEq': '!=',
'NotIn': 'not in',
'Or': 'or',
'Pass': 'pass',
'Pow': '**',
'RShift': '>>',
'Raise': 'raise',
'Return': 'return',
'Set': '{ ... } (set)',
'SetComp': '{ ... for ... } (set)',
'Slice': '[ : ]',
'Starred': '',
'Sub': '-',
'Subscript': '[]',
'Try': 'try',
'Tuple': '(... , ... )',
'UAdd': '+',
'USub': '-',
'While': 'while',
'With': 'with',
'Yield': 'yield',
'YieldFrom': 'yield from',
}

def check(source_file, checked_funcs, disallow, source=None):
"""Checks that AST nodes whose type names are present in DISALLOW
(an object supporting 'in') are not present in the function(s) named
CHECKED_FUNCS in SOURCE. By default, SOURCE is the contents of the
file SOURCE_FILE. CHECKED_FUNCS is either a string (indicating a single
name) or an object of some other type that supports 'in'. CHECKED_FUNCS
may contain __main__ to indicate an entire module. Prints reports of
each prohibited node and returns True iff none are found.
See ast.__dir__() for AST type names. The special node name 'Recursion'
checks for overtly recursive calls (i.e., calls of the form NAME(...) where
NAME is an enclosing def."""
return ExclusionChecker(disallow).check(source_file, checked_funcs, source)

class ExclusionChecker(NodeVisitor):
"""An AST visitor that checks that certain constructs are excluded from
parts of a program. ExclusionChecker(EXC) checks that AST node types
whose names are in the sequence or set EXC are not present. Its check
method visits nodes in a given function of a source file checking that the
indicated node types are not used."""

def __init__(self, disallow=()):
"""DISALLOW is the initial default list of disallowed
node-type names."""
self._disallow = set(disallow)
self._checking = False
self._errs = 0

def generic_visit(self, node):
if self._checking and type(node).__name__ in self._disallow:
self._report(node)
super().generic_visit(node)

def visit_Module(self, node):
if "__main__" in self._checked_funcs:
self._checking = True
self._checked_name = self._source_file
super().generic_visit(node)

def visit_Call(self, node):
if 'Recursion' in self._disallow and \
type(node.func) is Name and \
node.func.id in self._func_nest:
self._report(node, "should not be recursive")
self.generic_visit(node)

def visit_FunctionDef(self, node):
self._func_nest.append(node.name)
if self._checking:
self.generic_visit(node)
elif node.name in self._checked_funcs:
self._checked_name = "Function " + node.name
checking0 = self._checking
self._checking = True
super().generic_visit(node)
self._checking = checking0
self._func_nest.pop()

def _report(self, node, msg=None):
node_name = _NAMES.get(type(node).__name__, type(node).__name__)
if msg is None:
msg = "should not contain '{}'".format(node_name)
print("{} {}".format(self._checked_name, msg))
self._errs += 1

def errors(self):
"""Returns the number of number of prohibited constructs found in
the last call to check."""
return self._errs

def check(self, source_file, checked_funcs, disallow=None, source=None):
"""Checks that AST nodes whose type names are present in DISALLOW
(an object supporting the contains test) are not present in
the function(s) named CHECKED_FUNCS in SOURCE. By default, SOURCE
is the contents of the file SOURCE_FILE. DISALLOW defaults to the
argument given to the constructor (and resets that value if it is
present). CHECKED_FUNCS is either a string (indicating a single
name) or an object of some other type that supports 'in'.
CHECKED_FUNCS may contain __main__ to indicate an entire module.
Prints reports of each prohibited node and returns True iff none
are found.
See ast.__dir__() for AST type names. The special node name
'Recursion' checks for overtly recursive calls (i.e., calls of the
form NAME(...) where NAME is an enclosing def."""

self._checking = False
self._source_file = source_file
self._func_nest = []
if type(checked_funcs) is str:
self._checked_funcs = { checked_funcs }
else:
self._checked_funcs = set(checked_funcs)
if disallow is not None:
self._disallow = set(disallow)
if source is None:
with open(source_file) as inp:
p = parse(open(source_file).read(), source_file)
else:
p = parse(source, source_file)
self._errs = 0

self.visit(p)
return self._errs == 0

纯函数以及非纯函数

纯函数

在python终端中调用abs函数

>>> abs(-2)
2

我们说abs是一纯函数,它有一个input返回一个output,只要input相等,output一定是相等的。

非纯函数

python中的函数print是一个典型的非纯函数,我们在终端输入

>>> print(1,2,3)
1 2 3

你可能会开始纳闷,只要输入1,2,3它返回的不应该都是相同的结果吗?为什么它不是纯函数呢?想到这里可以说明你已经掌握了纯函数的奥义了,但请不要急,接着往下看。
请尝试以下输入

>>>print(print(1),print(2))
1
2
None None

你会发现,结果与你预想之中的不一样,多了两个None,我们现在来揭晓答案,print函数不论接收任何输入,其返回值都是None,该值在python中表示无,没有。而你的屏幕上显示的东西是print函数的功能:即在屏幕上打印出所输入的值。但是请十二万分的注意,它只是把东西打印在了屏幕上,其本身什么数值都没有返还!所以它是一个彻头彻尾的非纯函数。
上述例子中,括号内第二个和第三个print只是老老实实地把你交给它的数字打了出来而已,而第一个print也只是老老实实的把收到的两个print返回的None打印了出来而已。
我们再来看一个例子

>>>two = print(2)
2
>>>print(two)
None %有些人会报错

第一个式子中print接收到2,于是老老实实的把2打印了出来,而第二个例子中因为two没有给print赋予任何的数值,print会返回None,而新版python的interpreter会把two当成没有定义的函数,从而报错。

参考资料

本文基于加州伯克利大学CS61a2020年春季公开资料写成,目的在于帮助他人更好的使用相关资源,以上仅为本人经验总结及学习回顾,详细资料请自行访问加州伯克利大学CS61a课程官网https://inst.eecs.berkeley.edu/~cs61a/archives.html,如果对ok有什么问题请查阅https://cal-cs-61a-staff.github.io/ok-help/