摘要
本篇作为2020年春季Functional Programming总结的第二篇,内容大致包括python语言的环境(environment)框架(frame)特点,以及hw1、lab01中一些有趣题目的答案与反思。相关具体细节(例如函数的自定义等)原课程网上教科书已经编写的相当清楚,我就不再班门弄斧了,本篇目的是自己在学习过程中间的思考想法,仅供参考。希望你玩的愉快!
环境(environment)及框架(frame)
这是Founction Programming这门课程出现的第一个重要知识点,这种思考方式适用于任何编程环境、任何编程化的语言。
思考几个问题:当我们仅仅定义一个函数时发生了什么?当我们在python中定义一个函数,并在之后使用它时发生了什么?当我们从库中调用一个函数时发生了什么?如果我们定义的函数是嵌套的(nested),那我们在使用这个函数时发生了什么?
要理清这几个问题我们先搞清楚python的一个重要因素,环境(environment)
环境是指python在运行各个函数时的背景,它是由多个框架构成的。先不要着急理解它,引用课堂例子我们看如下代码:1
2from math import pi
pau = 2*pi
运行这个代码后,我们在全局下创造了一个盒子,我们首先从math
库中将pi
调用出来放进这个盒子中,然后根据我们的运算规则将相应的计算值赋予pau
,同时把它也扔进盒子中。我们取课本原图表示这一环节。
这个盒子就是我们创造的全局环境,我们不管之后再添加什么代码,只要还在全局环境下,那么pi = 3.14....
,而pau = 2pi
,我们可以直接使用则两个名字代表数据。
我们再来看我们定义函数时会发生什么。有一定python基础的人(比如我)在不知道答案的时候往往会认为,一旦函数定义,python会将函数储存起来,形成一个自我的密闭空间。但事实并不是这样。1
2
3from math import mul
def square(x):
return mul(x, x)
我们在运行以上代码后,函数square
并不会像我们想象的一样自动形成一个封闭的空间,而是跟我们之前的附值一样,将名称square
保留在全局变量下,不会管它到底怎么运行,会返回什么,计算机只是将名称静置在全局环境中并且标明:这是一个函数,等待被召唤。引用课堂原图
而当我们使用我们所定义的函数的时候,函数会被唤醒,生成一个新的盒子,也就在此时我们函数的内容才会被interpreter审视进行相关的操作。值得注意的是,我们使用函数自己创造的盒子中的附值操作(assignment)都不会传递到任何的其他盒子中!附值操作只要出了这个盒子(环境)就消失了,不要妄图在盒子外使用自己在盒子内部的附值名称。
每一个盒子(除了全局)都有一个“母盒”(parent)你可以理解为新的盒子实在其之上建立的。如果你只是单纯定义了一个函数,那么该函数的“母盒”就是全局(global),如果是嵌套环境,则内部函数的母环境就是外部函数。每一个盒子内部的附值都不能拿出这个盒子,例如:1
2
3
4def example(x):
i = 1
return x + 1
i
程序会报错,因为i
仅仅在函数内部定义,在出了函数之后就再无定义了。我们将这种数据(不光是附值,在一个自定义函数里定义的嵌套函数的名称拿到全局环境下也没有作用)称为局部变量(local variable)。
那么又有一个问题,母环境下的值可以在子环境中引用吗?可以用,但是请避免这种情况发生。在python2.x时代子环境,也就是我们说的子盒是没有权力调用除全局变量定义的附值的,而在python3.x时代这个问题似乎解决了,因为如果你尝试以下代码:1
2
3
4
5
6
7
8
9def 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
9def 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 | from operator import add, sub |
非常有意思的一道题目,它其实展现的是python关于附值也就是=
这一功能的强大之处,不但可以把名字赋给数值,还可以把名字赋给函数,答案如下:1
2
3
4
5
6
if b >= 0:
h = add
else:
h = sub
return h(a, b)
Q5
1 | def if_function(condition, true_result, false_result): |
很有趣的题目答案如下:1
2
3
4
5
6
7
8def 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
9def 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
返回相应的值,则一次完整的函数调用就完成了,但是请注意name
与name(parameater1...)
二者不是一个东西,前者是使用函数名对函数进行调用,即将函数作为参数或者返回值(在之后的heigher order function中会有涉及),在终端中输入一个函数的名称返回回来的是function<..>
括号中跟着函数寄存的内存地址,而后者则是直接使用或是运行这个函数,代表了该函数的返回值。综上在Q5中我们运行了函数f
与t
它们的返回值None
作为了参数输入了if_function
中在运行f
和t
时print
函数发挥了威力,它在函数运行时在屏幕上打印出了相应值。
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 | def double_eights(n): |
有很多类型重复的题目,在此就先拿出一道来讲,答案如下:1
2
3
4
5
6
7
8while n > 1:
if n % 10 == 8:
n = n//10
if n % 10 ==8:
return True
else:
n = n // 10
return False
唯一需要注意的点就是//
整除符号的运用,在使用整除符号后会忽略小数,是用来解决针对输入数字的每一位单独数字进行处理的必要工具。