栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Python

什么样的函数才叫 Pythonic

Python 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

什么样的函数才叫 Pythonic

python视频教程栏目介绍Pythonic。

在机器学习中,我们经常需要使用类和函数定义模型的各个部分,例如定义读取数据的函数、预处理数据的函数、模型架构和训练过程的函数等等。那么什么样的函数才是漂亮的、赏心悦目的代码呢?本期教程,会从命名到代码量等六方面探讨如何养成美妙的函数。文末有给大家录的视频教程,大家可以按需学习,不清楚的地方也可以留言!

与多数现代编程语言一样,在 Python 中,函数是抽象和封装的基本方法之一。你在开发阶段或许已经写过数百个函数,但并非每个函数都生而平等。写出「糟糕的」函数会直接影响代码的可读性和可维护性。那么,什么样的函数是「糟糕的」函数呢?更重要的是,要怎么写出**「好的」**函数呢?

简单回顾

数学中充满了函数,尽管我们可能记不住它们。首先来回忆一下大家**喜欢的话题——微积分。你可能记得这个方程式:f(x) = 2x + 3. 这是一个叫做「f」的函数,含有一个未知数 x,「返回」2*x+3。这个函数可能和我们在 Python 中看到的不一样,但它的基本思想和计算机语言中的函数是一样的。

函数在数学中历史悠久,但在计算机科学中更加神通广大。尽管如此,函数还是存在一些缺陷。接下来我们将讨论一下什么是「好的」函数,以及在出现什么样的征兆时我们需要重构函数。

决定函数好坏的关键

好的 Python 函数与蹩脚 Python 函数的区别是什么?「好」函数的定义之多让人惊讶。从我们的目的出发,我会把好的 Python 函数定义为符合以下清单中大部分规则的函数(有些比较难实现):

  • 命名合理
  • 具有单一功能
  • 包含文档注释
  • 返回一个值
  • 代码不超过 50 行
  • 幂等,尽可能是纯函数

对很多人来说,这个列表可能有些过于严格。但我保证,如果你的函数符合这些规则,你的代码看起来会非常漂亮。下面我将分步讲解各个规则,然后总结这些规则如何构成一个「好」函数。

命名

关于这个问题,我**喜欢的一句话(出自 Phil Karlton,总被误以为是 Donald Knuth 说的)是:

在计算机科学中只有两个难题:缓存失效和命名问题。

听起来有点匪夷所思,但整个不错的命名真的很难。下面就有一个糟糕的函数命名:

def get_knn(from_df):复制代码

我基本上在任何地方都见过糟糕的命名,但这个例子来自数据科学(或者说,机器学习),从业者总是在 Jupyter notebook 上写代码,然后尝试将那些不同的单元变成一个可理解的程序。

该函数命名的第一个问题是使用首字母缩写/缩略词。比起缩略词和并未普及的首字母缩写,完整的英语单词会更好。使用缩写的**原因是为了节省打字时间,但现代的编辑器都有自动补全功能,所以你只需键入一次全名。之所以说缩写是一个问题,是因为它们通常只能用于特定领域。在上面的代码中,knn 是指「K-Nearest Neighbors」,df 指的是「DataFrame」——无处不在的 Pandas 数据结构。如果另外一个不太熟悉这些缩写的编程人员正在阅读代码,那 TA 就会一头雾水。

关于这个函数名称,还有另外两个小问题:单词「get」无关紧要。对于大多数命名比较好的函数,很明显函数会返回一些东西,其名字会反映这一点。from_df 也是不必要的。如果参数的名称描述不够清楚的话,函数的文档注释或者类型注释将描述参数类型。

那我们如何重新命名这个函数呢?例如:

def k_nearest_neighbors(dataframe):复制代码

现在,即使是外行也知道这个函数在计算什么了,参数的名称(dataframe)也清楚地告诉我们应该传递什么类型的参数。

单一功能原则

「单一功能原则」来自 Bob Martin「大叔」的一本书,不仅适用于类和模块,也同样适用于函数(Martin **初的目标)。该原则强调,函数应该具有「单一功能」。也就是说,一个函数应该只做一件事。这么做的一大原因是:如果每个函数只做一件事,那么只有在函数做那件事的方式必须改变时,该函数才需要改变。当一个函数可以被删除时,事情就好办了:如果其他地方发生改动,不再需要该函数的单一功能,那么只需将其删除。

举个例子来解释一下。以下是一个不止做一件「事」的函数:

def calculate_and print_stats(list_of_numbers):
 sum = sum(list_of_numbers) 
 mean = statistics.mean(list_of_numbers) 
 median = statistics.median(list_of_numbers) 
 mode = statistics.mode(list_of_numbers) 
 print('-----------------Stats-----------------') 
 print('SUM: {}'.format(sum) print('MEAN: {}'.format(mean)
 print('MEDIAN: {}'.format(median) 
 print('MODE: {}'.format(mode)复制代码

这一函数做两件事:计算一组关于数字列表的统计数据,并将它们打印到 STDOUT。该函数违反了只有一个原因能让函数改变的原则。显然有两个原因可以让该函数做出改变:新的或不同的数据需要计算或输出的格式需要改变。**好将该函数写成两个独立的函数:一个用来执行并返回计算结果;另一个用来接收结果并将其打印出来。函数有多重功能的一个致命漏洞是函数名称中含有单词「and」

这种分离还可以简化针对函数行为的测试,而且它们不仅被分离成一个模块中的两个函数,还可能在适当情况下存在于不同的模块中。这使得测试更加清洁、维护更加简单。

只做两件事的函数其实非常罕见。更常见的情况是一个函数负责许多许多任务。再次强调一下,为可读性、可测试性起见,我们应该将这些「多面手」函数分成一个一个的小函数,每个小函数只负责一项任务。

文档注释

很多 Python 开发者都知道 PEP-8,它定义了 Python 编程的风格指南,但很少有人了解定义了文档注释风格的 PEP-257。在这里并不会详细介绍 PEP-257,读者可详细阅读该指南所约定的文档注释风格。

  • PEP-8:https://www.python.org/dev/peps/pep-0008/
  • PEP-257:https://www.python.org/dev/peps/pep-0257/

首先文档注释是在定义模块、函数、类或方法的第一段字符串声明,这一段字符串应该需要描述清楚函数的作用、输入参数和返回参数等。PEP-257 的主要信息如下:

  • 每一个函数都需要一个文档描述;
  • 使用合适的语法和标点,书写完整的句子;
  • **开始需要用一句话总结函数的主要作用;
  • 使用规定性的语言而不是描述性的语言。

在编写函数时,遵循这些规则很容易。我们只需要养成编写文档注释的习惯,并在实际写函数主体之前完成它们。如果你不能清晰地描述这个函数的作用是什么,那么你需要更多地考虑为什么要写这个函数。

返回值

函数可以且应该被视为一个独立的小程序。它们以参数的形式获取一些输入,并返回一些输出值。当然,参数是可选的,但是从 Python 内部机制来看,返回值是不可选的。即使你尝试创建一个不会返回值的函数,我们也不能选择不在内部采用返回值,因为 Python 的解释器会强制返回一个 None。不相信的读者可以用以下代码测试:

❯ python3
Python 3.7.0 (default, Jul 23 2018, 20:22:55)
[Clang 9.1.0 (clang-902.0.39.2)] on darwin
Type "help", "copyright", "credits" or "license" *for *more information.
>>> def add(a, b):
... print(a + b)
...
>>> b = add(1, 2)
3
>>> b
>>> b is None
True复制代码

运行上面的代码,你会看到 b 的值确实是 None。所以即使我们编写一个不包含 return 语句的函数,它仍然会返回某些东西。不过函数也应该要返回一些东西,因为它也是一个小程序。没有输出的程序又会有多少用,我们又如何测试它呢?

我甚至希望发表以下

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/816823.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号