2.4 针对数据结构对象的常用操作
在数据分析等应用场景中,比较关键的就是数据存储与数据操作。在前文中,讲述了以列表、元组和字典的形式存储数据的方法,下面将讲述Python中常用的操作数据的方法。
2.4.1 映射函数map
通过调用map函数,就能根据指定的函数对指定序列执行映射运算,它的语法如下。
map(function, parameter)
其中,第一个参数function表示映射运算的规则,第二个参数parameter则表示待映射的对象。在下面的MapDemo.py范例程序中演示的是映射的效果。
1 # !/usr/bin/env python 2 # coding=utf-8 3 def square(x): # 计算平方数 4 return x ** 2 5 print (list(map(square, [1,3,5]))) # 输出[1, 9, 25] 6 7 def strToLowCase(str): 8 return str.lower() 9 strList=["Company","OFFICE"] 10 strList=map(strToLowCase,strList) 11 print(list(strList)) # ['company', 'office'] 12 13 def tagCustomer(num): 14 if num>5000: 15 return "VIP" 16 else: 17 return "Normal" 18 print (list(map(tagCustomer,[1000]))) # ['Normal'] 19 #print map(tagCustomer,1000) # 会报错
在第3行和第4行中通过def定义了一个计算平方数的函数square,在第5行的map方法中,第一个参数是这个函数,第二个参数则是待计算平方数的列表,从第5行的打印语句来看,输出的是1,3,5的平方数。从中可知,map方法会把用第1个参数指定的函数运用于第2个参数指定的序列,并把计算好的结果返回。
在第7行和第8行的函数中实现了“把输入参数转为小写字母”的功能,在第10行中,同样调用了map方法,把strList这个列表里的元素转成了小写字母,从第11行输出语句的结果中即可看到“转为小写字母”的效果。
通过调用map方法还可以实现基于规则的映射,比如在某个项目中有这样的定义,凡是消费金额高于5000元的客户,将加上VIP的标识,否则是一般用户。通过第13行到第18行的代码,调用map函数即可实现这一映射的效果。
不过要注意的是,map方法第二个参数需要是“可以遍历”(或可迭代)的对象,比如列表元素或字典等,如果像第19行那样,把整数数据类型作为参数,则会出现如下的错误提示,因为第二个参数不具有可迭代特性(Iteration)。
TypeError: argument 2 to map() must support iteration
2.4.2 筛选函数filter
该函数的语法为filter(function, iterable)。具体的含义是,根据第1个参数指定的函数,过滤掉第2个参数指定对象中不符合要求的元素。在下面的FilterDemo.py范例程序中来演示一下filter函数的相关用法。
1 # !/usr/bin/env python 2 # coding=utf-8 3 # 判断输入参数是否是小写字母的函数 4 def isLowCase(str): 5 return str.lower() == str 6 strlist=filter(isLowCase, ["Hello","world"]) 7 print(list(strlist)) # ['world'] 8 # 判断输入参数是否为空的函数 9 def filterNull(empNo): 10 return empNo.strip() !='' 11 dataFromFile=['101','102','103',''] 12 empList=filter(filterNull,dataFromFile) 13 print(list(empList)) # ['101', '102', '103']
在第4行和第5行中定义了isLowCase函数,在其中判断输入参数是否为小写字母。
在第6行中定义的filter方法第一个参数即为isLowCase ,指定判断的规则是“都为小写字母”,第二个参数是一个包含字符串的列表。这里filter方法的作用是,依次对"Hello"和"world"这两个字符串运行isLowCase函数,如果返回false则过滤掉,返回true则保留。这样strList对象就只包含了小写字母的字符串,从第7行打印语句的输出结果中即可看到这一过滤结果。
在第9行和第10行的filterNull方法中,用来判断输入参数是否为空,在第11行中定义的dataFromFile列表里,包含了一个空的元素,这样执行第12行的filter方法,就能把其中空元素过滤掉,从第13行print语句的输出结果中即可看到过滤掉空元素之后的效果。
2.4.3 累计处理函数reduce
Python中的reduce函数会调用指定的函数对参数序列中的元素从左到右进行累计处理,这句话看上去有些难懂,下面通过ReduceDemo.py范例程序来看看这个函数的作用。
1 # coding=utf-8 2 from functools import reduce 3 # 定义一个加法的函数add 4 def add(x, y): 5 return x + y 6 print(reduce(add, [1,2,3,4,5])) # 输出15 7 print(reduce(add, [1,2,3,4,5],100)) # 输出115 8 # 定义乘法的函数 9 def multiply(x,y): 10 return x*y 11 print(reduce(multiply, [1,2,3,4,5])) # 输出120 12 # 定义拼接数字的函数 13 def combineNumber(x, y): 14 return x * 10 + y 15 print(reduce(combineNumber, [1,2,3,4,5])) # 输出12345
在第4行和第5行中定义了一个实现加法功能的add函数(或称为方法),在第6行中,reduce的第一个参数即为add,第二个参数是一个包含1到5的序列。
这里reduce函数的含义是,先取左边的两个参数1和2,把它们作为add方法的两个输入参数,经add函数返回的结果是3,再把3和从左到右的第3个序列(也是3)作为add函数的两个输入参数,这时add函数的返回值是6,再用6和第4个序列(数字4)作为add函数的两个输入参数,以此类推。所以结果是1到5的累加和,即15,第7行的输出语句验证了这一结果。同理,通过第11行的reduce方法,实现了1到5的阶乘效果。
在第13行和第14行的combineNumber方法中,定义了拼装两个数字的函数,比如输入参数是1和2,那么会返回12。在第15行中通过调用reduce方法,从左到右拼装了1到5这个序列,返回结果是12345。
从这个范例程序的代码中可知,reduce具有“递归操作”的功能,即从左到右读取序列,把两个数值(或上一次运算的结果和下一个数值)作为参数传入指定的函数,由此完成针对整个序列的操作或运算。
2.4.4 通过Lambda表达式定义匿名函数
在之前的范例程序中,我们通过def语句定义函数时,会指定函数的名字,但在某些场合,函数内的代码非常简单,不值得“大张旗鼓”地定义函数名以及用return返回结果,这时就可以通过Lambda表达式来定义匿名函数,以此来简化代码。
在下面的LambdaSimpleDemo.py范例程序中演示了Lambda的用法,请大家注意Lambda和map等函数整合使用的编程逻辑。
1 # !/usr/bin/env python 2 # coding=utf-8 3 # 通过lambda表达式定义了一个匿名函数 4 add=lambda a,b,c:a+b+c 5 print(add(1,2,3)) # 输出6 6 # 计算奇数 7 numbers=[1,3,6,7,10,11] 8 # 与filter整合使用 9 numbers=filter(lambda input: input%2!=0, numbers) 10 print numbers #[1, 3, 7, 11] 11 numbers=[2, 3, 4] 12 # 与map整合使用 13 numbers=map(lambda x: x*x, numbers) 14 print numbers #[4, 9, 16] 15 # 与reduce整合使用 16 numbers=[1,2,3,4,5] 17 sum=reduce(lambda x, y: x + y, numbers) 18 print sum # 输出15 19 # 与sorted整合使用 20 numbers=[1,-2, 3, -4,5] 21 numbers=sorted(numbers, lambda x, y: abs(y)-abs(x)) 22 print numbers # [5, -4, 3, -2, 1]
在第4行中演示了Lambda定义匿名函数的基本做法。这里没有定义函数名,也即是定义了个匿名函数。在lambda关键字之后有3个变量,即为匿名函数的三个参数,在冒号之后则定义了该Lambda表达式(也就是匿名函数)的返回值,这个匿名函数是计算三个输入参数的和,而在等号左边的add则是这个lambda表达式的名字。第5行的程序语句用到了add来调用第4行定义的Lambda表达式。
在第9行中把Lambda表达式和filter函数整合到一起。前面介绍过,filter函数的第一个参数指定了过滤规则,这里通过Lambda表达式指定“只保留满足input%2!=0条件”的数,即只保留奇数,第10行的print语句验证了这个filter函数的效果。
在第13行中整合使用了Lambda表达式和map函数,依次对列表中的每个元素进行了“乘积”的操作,对第16行定义的numbers列表中的每个值进行了累加的操作,在第17行中整合了Lambda表达式和reduce函数。
在第21行中,在sorted函数的第二个参数中,通过Lambda表达式定义了针对numbers序列的排序规则。通过Lambda表达式定义的排序规则是:如果y的绝对值大于x的绝对值,则y排在x之前,反之则y在x之后。通过第22行的输出即可验证这一结果。
除了能定义匿名函数,Lambda表达式的另一个用法是把函数作为“输入参数”,即可以定义“高级函数”,在下面的LambdaSeniorDemo.py范例程序中示范了这种用法。
1 # !/usr/bin/env python 2 # coding=utf-8 3 # 第3个参数是Lambda表达式 4 def add(x,y,func): 5 return func(x) + func(y) 6 print(add(2,4,lambda a:a*a)) # 2的平方加4的平方等于20 7 8 print("My Stock List".find("stock")) # 输出-1,表示没找到 9 def existKey(key,words,func): 10 return func(words).find(key) 11 # 输出3,表示找到了 12 print(existKey("stock","My Stock List" ,lambda words:words.lower()))
在第4行定义的add函数中,第3个参数func其实是个函数,在第6行的调用中,我们传入的func函数是对输入参数a进行平方运算,所以add函数返回的结果是x和y的平方和。
在字符串比较的过程中,一般不会区分字母大小写,一般的做法是把目标字符串转成小写字母,而把待比较的字符串也转换成小写字母。
在第9行的existKey函数中,通过第3个参数定义了针对words输入参数的操作。而在第12行的调用时,用Lambda编写的操作是通过lower把输入参数转小写,所以在existKey的函数中,首先是调用func函数,把输入参数words转成小写,随后再看其中是否存在key(即stock),由于存在,因此第12行的print语句会输出3,表示stock在字符串中所处的位置。
从上述两个范例程序中,大家看到了Lambda作为函数输入参数的做法。请注意,一般通过Lambda表达式只会定义功能比较简单的匿名函数。
如果函数需要实现的功能比较复杂,那么应该采用比较复杂的def方式来定义函数,因为用Lambda表达式定义复杂的逻辑,第一是实现起来很难,第二则是可读性很差。