Python程序设计 实验6:函数式编程

Posted 上山打老虎D

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Python程序设计 实验6:函数式编程相关的知识,希望对你有一定的参考价值。

1. 函数的参数传递

定义一个简单的函数 sum 如下

def sum(a, b, c):
print("a=%d, b=%d, c=%d"%(a,b,c)) 
print(a+b+c)

以下哪些语句是合法的,哪些是不合法的?分别输出什么?解释原因。
a) sum(*(1, 2, 3))
答:
语句合法。
输出如下:

三个参数均通过*param形式实现了可变数量参数,并作为a,b,c三个参数调用函数进行运算。

b) sum(1, *(2, 3))
答:
语句合法。
输出如下:

第一个参数a直接传参,后两个参数通过*param形式实现了可变数量参数,并作为b,c两个参数调用函数进行运算。

c) sum(*(1,),b=2, 3)
答:语句非法。并报错:
SyntaxError: positional argument follows keyword argument。即关键字参数必须跟随在位置参数后面。由于如果使用关键字赋值就需要都使用,这里只使用了一个关键字参数赋值,因此报错。

d) sum(*(1,),b=2, c=3)
语句合法。
输出如下:

具体同上。如果使用了关键字参数赋值,则都需进行赋值。代码中都对关键字进行了赋值。因此语句合法。

e) sum(*(1, 2),c=3)
语句合法。
输出如下:

同c题,本题中只是多了一个可变数量参数,少了一个关键字赋值。仍然满足“如果使用了关键字参数赋值,则都需进行赋值”的原则,故语句合法。

f) sum(a=1, *(2, 3))
语句非法。并报错:
TypeError: sum() got multiple values for argument ‘a’。即参数‘a’被多次赋值。这是由于首先通过关键字赋值对a进行了赋值后,又通过可变数量参数进行了赋值从而导致错误发生。

g) sum(b=1, *(2, 3))
语句非法。并报错:
TypeError: sum() got multiple values for argument ‘b’。原因同上。即参数‘b’被多次赋值。这是由于首先通过关键字赋值对b进行了赋值后,又通过可变数量参数进行了赋值从而导致错误发生。

h) sum(c=1, *(2, 3))
语句合法。
输出如下:

与f题,g题原因相同。首先利用关键字赋值对变量c进行了赋值。其次通过可变数量参数对‘a’和‘b’进行了赋值,三个变量都被赋值且未被重复赋值,故语句合法。

2. Lambda

思考以下句子的输出,解释每句话意思,并通过实际测试验证自己想法。

2.1 (lambda val: val ** 2)(5)

①输出结果: 25
②代码分析: 求输入值的平方。根据Lambda表达式,可以看出,该表达式的值为输入数据的2次方
③进行测试:
a)输入-4:输出16
b)输入0:输出0
c)输入9:输出81
结合数据测试以及理论分析,可以得出,该Lambda表达式的意思为求一个数的平方。

2.2 (lambda x, y: x * y)(3, 8)

①输出结果: 24
②代码分析: 求输入两数之积。根据Lambda表达式,可以看出,该表达式的值为输入两个数字的乘积。
③进行测试:
a)输入-4,-6:输出24
b)输入9,0:输出0
c)输入9,5:输出45
结合数据测试以及理论分析,可以得出,该Lambda表达式的意思为求一个数的平方。

2.3 (lambda s: s.strip().lower()[:2])(’ PyTHon’)

①输出结果: py
②代码分析: 求输入字符串中前两个非空格非换行小写字母(如原来为大写,则转换为对应小写字母)
首先通过string.strip()函数从头部丢弃空格以及换行符。再利用string.lower()函数将对应大写字母转为小写后利用切片得到前两个字符
③进行测试:
a)输入 I love :输出i (i后有一空格)
b)输入 456:输出45
c)输入 @aa:输出@a
结合数据测试以及理论分析,可以得出,该Lambda表达式的值为第一个非非空格非换行(大写对应)小写字母,以及其后的一个(大写对应)小写字母或其他字符。

3. Map

使用 map 语句将以下输入,分别转化为指定的输出。

3.1 [‘12’, ‘-2’, ‘0’] --> [12, -2, 0]

①大致思路:
使用map将对应字符串型转换为整型即可。

②编写代码:

print(list(map(lambda x: int(x), ['12','-2','0'])))

此处借助了Lambda表达式,将字符型转成整形,并借助list完成输出。

③运行测试:

3.2 [‘hello’, ‘world’] --> [5, 5]

①大致思路:
使用map获得每个字符串的对应长度即可。
②编写代码:

print(list(map(lambda x: len(x), ['hello', 'world'])))

③运行测试:

3.3 [‘hello’, ‘world’]` --> [‘olleh’, ‘dlrow’]

①大致思路:
使用map将字符串翻转即可。

②编写代码:

print(list(map(lambda x: x[::-1], ['hello', 'world'])))

此处借助了Lambda表达式并利用字符串切片获取反向字符串,并借助list完成输出。

③运行测试:

3.4 range(2, 6) --> [(2, 4, 8), (3, 9, 27), (4, 16, 64), (5, 25, 125)]

①大致思路:
使用map依次求出给定数字的一次方,二次方,以及三次方。

②编写代码:

print(list(map(lambda x: (x,x**2,x**3), range(2,6))))

此处借助了Lambda表达式,利用range获得范围并获取一次方,二次方,三次方的数值,并借助list完成输出。
③运行测试:

3.5 zip(range(2, 5), range(3, 9, 2)) --> [6, 15, 28]

①大致思路:
通过zip收集后即为[(2, 3), (3, 5), (4, 7)],因此函数的大意为求两个数字的乘积。由于zip返回的是元组打包成的列表,因此要对元组进行操作,而不能对两个数字进行操作。

②编写代码:

print(list(map(lambda x: x[0]*x[1], zip(range(2, 5), range(3, 9, 2)))))

此处首先借助Lambda表达式,并利用zip收集两个range获得范围。由于zip返回的是由元组构成的列表,因此Lambda中的变量为元组,故返回元组第一个值与第二个值的乘积即可。
③运行测试:

4. Filter

使用 filter 语句将以下输入,分别转化为指定的输出。

4.1 [‘12’, ‘-2’, ‘0’] --> [‘12’, ‘0’]

①大致思路:
观察题目,可以知道,filter保留下转成整型后大于等于零的字符串。因此使用filter并借助Lambda表达式可以比较简单又有效的完成这个任务。

②编写代码:

print(list(filter(lambda x: int(x)>=0 , ['12','-2','0'])))

利用Lambda作为函数,对给定列表中每个元素进行转成0后是否大于等于0进行判断并过滤。
③运行测试:

4.2 [‘hello’, ‘world’] --> [‘world’]

①大致思路:
观察题目,可以知道,filter保留下转成首字母为w的字符串。因此使用filter并借助Lambda表达式可以比较简单又有效的完成这个任务。

②编写代码:

print(list(filter(lambda x: x[0]=='w' , ['hello', 'world'])))

③运行测试:

4.3 [‘technology’, ‘method’, ‘technique’] --> [‘technology’, ‘technique’]

①大致思路:
观察题目,可以知道,filter保留下转成首字母为t的字符串。因此使用filter并借助Lambda表达式可以比较简单又有效的完成这个任务。

②编写代码:

print(list(filter(lambda x: x[0]=='t' , ['technology', 'method', 'technique'])))

通过借助Lambda表达式对字符串第一个字符直接访问并判断是否为t从而进行过滤。

③运行测试:

4.4 range(20) --> [0, 3, 5, 6, 9, 10, 12, 15, 18]

①大致思路:
观察题目,可以知道,filter保留三或五的倍数。因此使用filter并借助Lambda表达式对数字进行取余判断余数是否为零以完成这个任务。

②编写代码:

print(list(filter(lambda x: x%3==0 or x%5==0 , range(20))))

③运行测试:

5. Reduce

使用 reduce 语句编写函数 lcm(*nums),计算任意数量个正整数的最小公倍数,要求只写一句 python 语句 (提示:可使用 math 模块的 gcd 函数先求出最大公约数)。
例子:

lcm(3, 5) # 15
lcm(41, 106, 12) # 26076
lcm(1, 2, 6, 24, 120, 720) # 720
lcm(3) # 3
lcm() # 如果没有向函数提供数字,可以返回值1。

①大致思路:
最小公倍数等于两数乘积除以最大公约数。因此可以借助Lambda表达式进行运算。此处需要注意,必须要给函数赋初始值1,否则当函数的输入参数个数为零时,将报错。

②编写代码:

def lcm(*nums):
    return int(reduce(lambda x, y: x*y/math.gcd(int(x), int(y)), nums, 1))

使用Lambda表达式,将每两个元素的返回值设置为两数之积与两数最大公约数的商。在reduce内,以Lambda作为函数,以nums作为范围,并设置默认函数返回值1。需要注意的是,此处必须设置返回值为1,否则当nums中参数个数为0时,将报错。
③运行测试:
使用几个数字进行测试结果如下:
a)参数全部缺省:

b)参数缺省一个:

c)拥有两个参数

d)拥有很多参数

6. Iterator

运行以下代码,观察输出并解释输出的原因。

it = iter(range(100))
67 in it
print(next(it))
print(37 in it)
print(next(it))

①运行代码:

即print(next(it))输出了68,print(37 in it)输出了False。print(next(it))发生了StopIteration迭代结束异常。

②分析原因:
首先,定义了it为从0到100范围内的迭代器。第二行即查找67是否在迭代器中,此时迭代器位置在67处。然后第三行将迭代器后移一个并输出,因此此时应输出68。
第四行查查37是否在迭代器中,由于此时迭代器位置在68且迭代器不能向前移动,故迭代器将一直移动到末尾,并返回False。
由于第四行中对37的查找使迭代器移动到迭代对象末尾,因此再调用next函数对迭代器进行后移时,将报错StopIteration。

7. Generator

7.1 编写一个生成器 generate_triangles(),连续地产生三角数 1,3,6,10,… 三角数通过连续的正整数相加来生成(如 1=1,3=1+2,6=1+2+3,10=1+2+3+4,…)。

例子:
g=generate_triangles()
for _ in range(5):
print(next(g)) #输出 1,3,6,10,15

①大致思路:
由于三角数每一个数都等于前一个数加上第几个数。因此可以借助生成器实现快速生成。即在yield中定义,每一个数都等于前面的数字加上计数器再加一即可。

②编写代码:

# 定义生成器
def generate_triangles():
    a,  n = 1, 1
    while True:
        yield a
        # 每一个元素都等于上一个元素加第几个元素再加一
        a += n+1
        n += 1


# 进行测试
it = generate_triangles()
for _ in range(5):
    print(next(it))

首先定义生成器,由于每一个数都等于前面的数字加上计数器再加一。因此只需定义当前值与计数器,首先,将当前值加上计数器再加一。然后将计数器加一即可。

③运行测试:

7.2 使用生成器 generate_triangles(),编写函数generate_triangles_under (n),返回小于 n 的所有三角数。

①大致思路:
使用生成器进行迭代生成,并依次进行判断,如果小于n则加入到结果列表中,否则直接返回即可。

②编写代码:

# 定义生成器
def generate_triangles():
    a,  n = 1, 1
    while True:
        yield a
        # 每一个元素都等于上一个元素加第几个元素再加一
        a += n+1
        n += 1


# 定义小于n的三角数函数
def generate_triangles_under(n):
    res = []
    it = generate_triangles()
    temp = next(it)
    # 如果大于则直接退出循环
    while temp < n:
        # 如果小于则加入到列表中
        res.append(temp)
        temp = next(it)
    return res


print(generate_triangles_under(10))

调用生成器生成每一个值,并依次对每个值进行判断,如果生成的值小于n,则加入到结果列表中,否则直接返回。

③运行测试:

实验结论

本次实验主要是对函数式编程的理解以及使用。分别学习了函数的传参,Lambda表达式,Map,Filter,Reduce,迭代器和生成器的使用。通过这些连续,我初步掌握了这些函数式编程技巧的使用。
本次实验还算比较顺利,仅有一些地方遇到了困难,在查阅资料后也都得到解决。

  1. 在可变数量参数函数中,需要注意函数的初始值。在本实验中,lcm函数求最小公倍数时,就必须注意给函数赋初始值,否则如果参数个数为0,则将报错。
  2. Lambda可以减少代码量并提高可读性:在本实验进行过程中,很多代码都是使用Lambda表达式进行完成,最后编写的代码不仅比较短,而且可读性都比较高。因此,在进行函数式编程时,可以采用Lambda表达式来减少代码量并提高可读性。
  3. 定义生成器时,务必要搞清楚生成数值的过程。本实验中,生成器一部分是相对耗时比较长的,究其原因是不能正确理解生成器生成对应数据时的过程。因此,搞清楚生成数据的过程,才能进行实验。

以上是关于Python程序设计 实验6:函数式编程的主要内容,如果未能解决你的问题,请参考以下文章

python进阶一(函数式编程)2-6 python中闭包

Python函数式编程,范围和变量。我哪里错了?

Python3函数式编程小结

python 函数式编程

python基础-函数式编程

《On Java 8》中文版 第十三章 函数式编程