【Easy Python】第二话:映射——输入、输出与函数的纽带

从dict开始说起

学python的时候,我们一定会接触到dict(字典)这个数据结构。

dict结构展示了数据间(key与value)一一对应的关系,key作为一个查询索引,是不允许有重复的,而不同key所对应的value,则允许重复值的存在。

比如说,我们定义一群boys&girls,打出整个dict,再打出girls有哪些,可以这样操作:

1
2
3
4
5
6
7
8
import pprint
d = dict()
d['girl'] = ['迪丽热巴', '王鸥', '鬼鬼']
d['boy'] = ['大碗宽面']
# 用pprint.pprint函数打印数据结构,使得其排版更加pretty~
pprint.pprint(d, indent=2)
# 单用pprint.pformat,可以返回一个排版过后的字符串,打印出来要额外print操作~
print('Girls are: %s' % pprint.pformat(d['girl']))

打出来的效果是:

1
2
{'boy': ['大碗宽面'], 'girl': ['迪丽热巴', '王鸥', '鬼鬼']}
Girls are: ['迪丽热巴', '王鸥', '鬼鬼']

我们可以很直观地看到这种对应关系

映射

像dict数据结构给我们展现的一样,数据间的对应关系,我们可以统称为:映射(Mapping)

如同第一话所说,程序的本质即为输入->函数->输出。输入和输出,就是一种映射关系,而实现这种映射的规则,就是函数。在dict里面,实现映射的函数,可以简化如下:

1
2
3
def get_value(key):
memory_address_of_value = hash(key)
return get_data_from_address(memory_address_of_value)

为了让我们便捷地从一个key访问到对应的value,dict不可能把所有的key都过一遍找,那样费时费力。

取而代之,dict可以直接通过一种算法函数,称之为hash,就可以把key计算(映射)为一个数值,即value的内存地址。我们通过这个内存地址,就可以取出对应的value数据了,非常简单粗暴。

数据处理小需求

现在,让我们看这样一个需求。我们的输入是:

1
input_list = [11, 2, 12, 15, 5, 10, 7, 14, 6, 9, 1, 3, 13, 8, 4]

也就是1~15的乱序。现在我们想输出这样的结果:

  • 如果大于10, 输出字符串——这个数字大于10
  • 如果小于等于10,输出字符串——这个数字小于等于10
  • 被3整除的奇数不录入

这个需求并不难,我们可以轻易地打出以下的代码:

1
2
3
4
5
6
7
output_list = []
for i in input_list:
if i % 2 == 0 or i % 3 != 0:
if i > 10:
output_list.append('%d: 大于10!' % i)
else:
output_list.append('%d: 小于等于10!' % i)

打出来的结果是:

1
2
3
4
5
6
7
8
9
10
11
12
11: 大于10!
2: 小于等于10!
12: 大于10!
5: 小于等于10!
10: 小于等于10!
7: 小于等于10!
14: 大于10!
6: 小于等于10!
1: 小于等于10!
13: 大于10!
8: 小于等于10!
4: 小于等于10!

但是我把这个代码给我敬爱的领导看了之后,领导居然看不懂,觉得我的层次不够清晰。唔妈呀,代码都这么短了,那该怎么办呀

于是,只好改成了一个逻辑层次清晰一点的结构,顺带给排了个序:

1
2
3
4
5
6
7
output = map(
lambda x: '%d: 大于10!' % x if x > 10 else '%d: 小于等于10!' % x,
filter(
lambda x: x % 2 == 0 or x % 3 != 0,
sorted(input_list),
)
)

打出来这个结果:

1
2
3
4
5
6
7
8
9
10
11
12
1: 小于等于10!
2: 小于等于10!
4: 小于等于10!
5: 小于等于10!
6: 小于等于10!
7: 小于等于10!
8: 小于等于10!
10: 小于等于10!
11: 大于10!
12: 大于10!
13: 大于10!
14: 大于10!

这回领导终于满意啦~~

分解过程,持续映射

让一个普通的数据处理过程变得层次清晰,其实是有方法的,得益于python内置的高阶函数,如果你写过js,玩转es6或者lodash,绝对章口就莱。

上一段代码中,不难发现多了3个新函数:map、filter跟sorted。我们这就一一来看:

map,就是映射啦~它的输入参数是一个函数(映射规则)以及我们的一堆输入值,只要给定函数跟相应的一堆输入值,我们就可以构建映射输出。

然而,map的却并不是返回所有映射输出结果,而是单纯的一个可迭代(Iterable)的对象,这个对象具有一个叫迭代器(Iterator)的东东可以让我们访问输出的结果。迭代器,就像我敬爱的领导懒洋洋的那样,不去理它,它啥都不干,只有调用它,它才会屁颠屁颠地把下一个map输出值给取出来。这种“懒人操作”,其实是很有必要的。如果输入数据量大,我们需要的输出数据量小的话,先把所有输出都给出来,再过一遍输出数据,那要猴年马月呀!

filter,跟map一样,输入也是一个函数跟一堆输入值,基本机制都差不多,勉强算是一种映射的形式,但输出总有差别——顾名思义,filter是过滤、筛选之意,满足条件的选择,不满足条件的抛弃。因此,输入函数(规则)的返回值,应当是True或者False

sorted,顾名思义,就“排序”之意,其输入是任意可迭代的对象,以及每个元素要拿来比较的值(key),还有是否逆序(reverse)的选项。清晰易懂。

说完这些,回到我们的需求:

  • 如果大于10, 输出字符串——这个数字大于10
  • 如果小于等于10,输出字符串——这个数字小于等于10
  • 被3整除的奇数不录入

要解决这个需求,我们应当:

  1. 先排个序,好看点——sorted(input_list)
  2. 筛选数字——filter(函数:输入值->是or不是被3整除奇数-> False or True, 一堆输入值:第一步的输出)
  3. 根据大小输出字符串——map(函数:输入值->大于10 | 小于10 -> 输出啥啥啥, 一堆输入值:第二步的输出)

然后串起来,就是这些啦:

1
2
3
4
5
6
7
output = map(
lambda x: '%d: 大于10!' % x if x > 10 else '%d: 小于等于10!' % x,
filter(
lambda x: x % 2 == 0 or x % 3 != 0,
sorted(input_list),
)
)

是不是很清晰?那就在以后码码的时候,多试试看这种style吧~

总结

接触编程的同学们,总会听过两个词:面向对象编程(Object Oriented Programming)与函数式编程(Functional Programming),甚至有许多人鼓吹函数式编程,贬低面向对象云云,引起一番争论。

面向对象旨在把代码世界打造成我们熟悉的物以类(class)聚的现实世界。这样,整个代码架构就更容易被人理解,可维护性就更强了。因此,面向对象,是一个相对宏观的概念。

函数式编程,顾名思义,函数即为程序的基础。正如第一话所说的那样,函数,不仅是输入到输出的映射规则,而且就像活字印刷一样,是一个可复现的过程。因此,不管多长多短、多大多小的程序,都可以看做是一个或是多个函数的繁复联系,就比如说,咱们吃火龙果之后……采用函数式编程,能够极大增强程序的条理性,因此可以说,相对于面向对象,函数式编程是一个微观的概念。

面向对象与函数式编程,是你中有我、我中有你的关系,并不存在所谓的对立面。作为一个既支持面向对象又支持函数式编程的语言,Python还是很人性化滴!第二话虽然讲述的多是函数式编程相关的概念,但实际使用时,尤其代码量增大时,就需要应用面向对象的方法与设计模式,收拾一下你的项目啦~

实践出真知,希望咱们一起进步~

版权声明
本文为博客HiKariのTechLab原创文章,转载请标明出处,谢谢~~~