我是小鱼,今天是2021年9月6日,记录第一次遇到匿名函数
环境:Pycharm 3.9.6, PyCharm 2021.2 community
项目:书《Python编程 - 从入门到实践》中第16 章 P332页的代码
书是翻译自外文教程,但全书的第16.2节原作者是用python2编写,内容是关于交易收盘价走势图,中文版是由一位中国人用python3的方式重新了。所以书的这一节与原来的差异非常大,格式上编辑们也没有按原来的风格统一,内容上难度突然增加了很多。当我看到P332页的代码时,突然发现这里使用到了没有学过的知识,从图灵买书的论坛(见下面链接)上看到有人提到这是匿名函数,在此做一个学习的记录。
听说《Python编程 - 从入门到实践》这本书已经出第二版了,有机会看一下第二版中这一章节的写法
我照着书上写的代码如下:
import json
from itertools import groupby
import pygal
filename = 'btc_close_2017.json'
# 将数据加载到一个列表中
with open(filename) as f:
btc_data = json.load(f)
# 创建5个列表,用来收集每一天的信息
# 打印每一天的信息
dates, months, weeks, weekdays, close = [], [], [], [], []
for btc_dict in btc_data:
dates.append(btc_dict['date'])
months.append(int(btc_dict['month']))
weeks.append(int(btc_dict['week']))
weekdays.append(btc_dict['weekday'])
close.append(int(float(btc_dict['close'])))
def draw_line(x_date, y_date, title, y_legend):
xy_map = []
for x, y in groupby(sorted(zip(x_date, y_date)), key=lambda _: _[0]):
y_list = [v for _, v in y]
xy_map.append([x, sum(y_list) / len(y_list)])
x_unique, y_mean = [*zip(*xy_map)]
line_chart = pygal.Line()
line_chart.title = title
line_chart.x_labels = x_unique
line_chart.add(y_legend, y_mean)
line_chart.render_to_file(title+'.svg')
return line_chart
idx_month = dates.index('2017-12-01')
line_chart_month = draw_line(months[:idx_month],close[:idx_month], '收盘价月日均值(¥)', '月日均值')
draw_line函数的前面5行代码,
其中第二行“ key=lambda _: _[0]”,lambda即为一个匿名函数,又称为虚拟函数
当一个函数的表达式比较简单,并且只调用一次,那么久没有必要为它单独写一个def函数,可以直接在语句中写一个lambda虚拟函数给一个变量赋值,上述例子中就是给key变量赋值。
语法也比较简单:lambda [arg1 [,arg2,.....argn]]:expression
lambda是python中预留的关键字,后面必须要加一个空格,空格后面是形参名称“arg1”,如果有多个形参,可以用逗号隔开,后面跟冒号“:”,冒号后面跟表达式,包含前面的形参。函数结果直接返回给变量key。另外,根据PEP要求,冒号后面也要有一个空格。
举个例子:
# 普通python函数
def func(a,b,c):
return a+b+c
print func(1,2,3)
# 结果为 6
# lambda匿名函数
f = lambda a,b,c: a+b+c
print f(1,2,3)
# 结果为 6
# 在代码:f = lambda a,b,c: a+b+c 中,lambda表示匿名函数,
# 冒号 “:”之前的a,b,c表示它们是这个函数的参数。
# 匿名函数不需要return来返回值,表达式本身结果就是返回值。
当调用变量 f 时,直接在 f 后面跟一个括号加上实参就可以了。
现在回到我的实例中:“ key=lambda _: _[0]”,下划线就是形参,“_[0]”就是表达式,这个表达式表达的其实是列表元素的索引.
这里简单说一下zip与groupby在这里的作用,x_date是一个列表,是全年的月份的列表,一共是334个元素,y_date是全年收盘价格的列表,也是334个元素。
zip是把两个列表合并起来,python说明文件中对zip的中文帮助说的是“创建一个聚合了来自每个可迭代对象中的元素的迭代器。它返回一个元祖的迭代器……”在这个例子中,zip返回了一个列表,列表中的每个元素都是一个2元的元祖,每个元祖中第一个参数就是x_date,第二个参数是y_date. zip返回的列表也是334个元素。可以这么理解,zip就像拉链,把两边的拉链(列表)zip到一起,这两个列表(两边的拉链)长度可以一样,也可以不一样。拉链起来的列表长度取短的那边的拉链长度(短的列表的长度),拉链上的每一个牙齿就是原来列表中的每一个元素,拉链合拢后,两边的牙齿成对合拢,每一对都是一个元祖,合拢后的拉链又是一个列表。
sorted在这句语句中可有可无。
groupby的用法在知乎上有一篇说明,写得比较清楚,请见下面链接。在我的用到的这个例子里我的个人理解是这样的,groupby中有两个参数,第一个是数据源,就是刚zip得到的一个列表,第二个是根据什么来分组,就是key,就是lambda这个虚拟函数获得的东西。“_[0]”就是根据元祖中第[0]个位置的元素来分组。刚刚的每个元祖都是(月份,收盘价),那就是月份那个元素来分组。我们就可以获得一个分组对象。这句语句“ for x,y in groupby(……)” 中我们获得的x应该就是分组的依据,其实就是月份数,而y,是分组后原来的对象,记住是原来的对象,也就是说y其实还是一个元祖(月份数,收盘价)。然后下面又用了一次for语句 “[v for _, v in y]” 来获得一个列表,这个列表就是y当中所有元素的收盘价的集合。“_”依然是表示月份数。然后,groupby函数还有一个特点,它返回的组是一个迭代器,向后迭代的过程中,前一个组会消失。我是按月份分组,那么每个月都是一个组,第一个月的分组,在操作完“xy_map.append([x, sum(y_list) / len(y_list)])”之后就消失了,然后又从第二个月开始进行y_list的列表。
最后获得的xy_map是一个列表,列表中的每个元素也是一个2元列表,第一个元素是月份,第二个元素是月日均价,就是这句语句“sum(y_list) / len(y_list)”
另外,groupby函数后面可以跟.mean()函数的,是不是上面这句求均值的代码可以用.mean()函数来替代?等以后我学得精了再回来试试。
另外,再说一下,draw_line函数的第5行代码“x_unique, y_mean = [*zip(*xy_map)]”
在我的PyCharm中,后面的方括号内的内容出现了报警,这是因为PyCharm认为等号左边有两个变量,但是等号右边只写了一个列表。是一个错误。这里可以正常运行。其实这里涉及到“*”号放在变量名前面的用法。具体见下面参考中赵继超的笔记,他解释的非常清楚。
图灵论坛: 图灵社区
lambda参考:python中lambda的用法_abyss-phospherus的博客-CSDN博客_python中lambda
groupby知乎上的参考:如何使用groupby函数对数据进行分组(1) - 知乎
groupby在CSDN上的函数详解:groupby函数详解_Yale-曼陀罗-CSDN博客_groupby
赵继超的笔记:Python 列表前加 *号_赵继超的笔记-CSDN博客