如何写一个更好的Python函数?(上)

简介: Python虽然好用,但用好真的很难。尤其是函数部分,只要写不好,后面的一连串人都会遭殃。看又看不懂,测试起来也麻烦,维护又维护不动,真是让人头疼。那怎么写好一个Python函数呢?

什么样的函数是一个好函数?


“好”的Python函数和“差”的Python函数之间有什么差别呢?每个人都有自己的理解。基于我的理解,如果一个Python函数能够符合下面的大部分条件,我会认为它是一个“好”函数:

  • 命名合理
  • 单一功能
  • 包括文档字符串
  • 返回一个值
  • 不超过50行
  • 是幂等函数或纯函数

对许多人来说,这些要求可能显得过于苛刻了。

不过,我保证,如果你的函数遵循这些规则,你的代码会非常漂亮,会让其他的程序员都“馋哭”的。

下面,我将一一讨论这些规则,然后总结它们是如何创造“好”函数的。


命名

在这个问题上,我最喜欢的一句话是:

计算机科学中只有两件事很让人头疼:缓存失效和命名。

尽管这听起来很莫名其妙,但给一个事情命名太难了。下面是一个反面案例:

def get knn(from_df):

原文中这个代码没有放上去,量子位根据上下文信息进行了补充。

这个函数命名的第一个问题是它使用了缩写。

对于那些并不出名的缩略词来说,使用完整的英语单词会更好。缩写单词的唯一原因是为了节省打字时间,但是每个现代编辑器都有自动填充功能,所以你只需要键入一次全名就可以了。

缩写通常是特定领域的。在上面的代码中,KNN指的是“K-Nearest Neighbors”,df指的是“DataFrame”,这是一个数据结构。如果另一个不熟悉这些首字母缩写的程序员正在阅读代码,几乎很难看懂。

关于这个函数的名字还有另外两个小瑕疵:

  • “get”这个词是无关紧要的。对于大多数命名比较好的函数来说,很明显有一些东西会从函数中返回,它的名字将反映这一点。
  • from_df也不是必要的。如果没有明确的参数名称,函数的文档字符串或类型注释会描述参数的类型。

那么我们如何重命名这个函数呢?很简单:

def k_nearest_neighbors(dataframe):

即使是外行,这个函数要计算的内容也很清楚,参数的名称(dataframe)也清楚地表明了参数类型。


单一功能

单一功能原则不仅适用于类和模块,也同样适用于函数。

一个函数应该只有一个功能。也就是说,它应该只做一件事。

一个重要的原因是,如果每个函数只做一件事,只有这件事发生了变化,才需要改变这个函数。

此外,如果这个函数的单个功能不再需要了,直接把它删了就行了。

还是用例子来说明吧。下面这个函数,可以做不止一件“事情”:

defcalculate_andprint_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。

如果需要计算新的或不同的统计数据,或者需要改变输出的格式,就需要对这个函数进行调整。

所以,这个函数最好写成两个独立的函数:一个用来执行并返回计算结果,另一个用来获取这些结果并打印出来。

这种处理方式,不仅能让测试函数更容易,并且还允许这两个部分有了迁移性,如果合适的话,还可能一起应用到不同的模块中。

在编程中,你会发现好多函数都可以做很多很多事情。同样,为了可读性和可测试性,这些函数应该被分解成更小的函数,每个函数只有一个功能。


文档字符串(Docstrings)

虽然每个人似乎都知道PEP - 8,它定义了Python的样式指南,但是很少有人知道PEP - 257,它是关于文档字符串的。我再这里不简单地重复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.

>>> defadd(a, b):

...   print(a + b)

...

>>> b = add(1, 2)

3

>>> b

>>> b is None

True

你会发现 b 的返回值实际上是 None。即使你写的函数没有返回语句,它仍然会返回一些东西。而且,每个函数都应该返回一个有用的值,测试起来也会更方便。毕竟,你写的代码应该能够被测试。

试想一下,测试上面的add函会有多艰难。遵循这个概念,我们应该这样写代码:

with open('foo.txt', 'r') as input_file:

   for line in input_file:

       if line.strip().lower().endswith('cat'):

           # ... do something useful with these lines

if line.strip().lower().endswith(‘cat’):这一行能够工作,是因为每个字符串方法( strip ( )、lower ( )、end swith ( ) )都返回一个字符串作为调用函数的结果。

当给定函数没有返回值时,有一些常见的原因:

“它所做的只是[一些与I / O相关的事情,比如将一个值保存到数据库中]。我不能返回任何有用的东西。”

我不同意。如果操作顺利完成,函数可以返回True。

“我们修改了其中一个参数,将其用作参考参数。”

这里有两点需要注意。首先,尽最大努力避免这种做法。用好了令人惊讶,用不好非常危险。

其次,即使这样做不可行,复制某个参数的成本太高,你也可以回到上一条建议。

“我需要返回多个值。单独返回一个值是没有意义的。”

可以使用元组返回多个值。

总是返回一个有用的值,调用者总是可以自由地忽略它们。

相关文章
|
24天前
|
搜索推荐 Python
利用Python内置函数实现的冒泡排序算法
在上述代码中,`bubble_sort` 函数接受一个列表 `arr` 作为输入。通过两层循环,外层循环控制排序的轮数,内层循环用于比较相邻的元素并进行交换。如果前一个元素大于后一个元素,就将它们交换位置。
125 67
|
17天前
|
Python
Python中的函数是**一种命名的代码块,用于执行特定任务或计算
Python中的函数是**一种命名的代码块,用于执行特定任务或计算
42 18
|
9天前
|
数据可视化 DataX Python
Seaborn 教程-绘图函数
Seaborn 教程-绘图函数
40 8
|
18天前
|
Python
Python中的函数
Python中的函数
31 8
|
25天前
|
监控 测试技术 数据库
Python中的装饰器:解锁函数增强的魔法####
本文深入探讨了Python语言中一个既强大又灵活的特性——装饰器(Decorator),它以一种优雅的方式实现了函数功能的扩展与增强。不同于传统的代码复用机制,装饰器通过高阶函数的形式,为开发者提供了在不修改原函数源代码的前提下,动态添加新功能的能力。我们将从装饰器的基本概念入手,逐步解析其工作原理,并通过一系列实例展示如何利用装饰器进行日志记录、性能测试、事务处理等常见任务,最终揭示装饰器在提升代码可读性、维护性和功能性方面的独特价值。 ####
|
1月前
|
Python
Python中的`range`函数与负增长
在Python中,`range`函数用于生成整数序列,支持正向和负向增长。本文详细介绍了如何使用`range`生成负增长的整数序列,并提供了多个实际应用示例,如反向遍历列表、生成倒计时和计算递减等差数列的和。通过这些示例,读者可以更好地掌握`range`函数的使用方法。
49 5
|
1月前
|
测试技术 数据安全/隐私保护 Python
探索Python中的装饰器:简化和增强你的函数
【10月更文挑战第24天】在Python编程的海洋中,装饰器是那把可以令你的代码更简洁、更强大的魔法棒。它们不仅能够扩展函数的功能,还能保持代码的整洁性。本文将带你深入了解装饰器的概念、实现方式以及如何通过它们来提升你的代码质量。让我们一起揭开装饰器的神秘面纱,学习如何用它们来打造更加优雅和高效的代码。
|
1月前
|
弹性计算 安全 数据处理
Python高手秘籍:列表推导式与Lambda函数的高效应用
列表推导式和Lambda函数是Python中强大的工具。列表推导式允许在一行代码中生成新列表,而Lambda函数则是用于简单操作的匿名函数。通过示例展示了如何使用这些工具进行数据处理和功能实现,包括生成偶数平方、展平二维列表、按长度排序单词等。这些工具在Python编程中具有高度的灵活性和实用性。
36 2
|
2月前
|
Python
python的时间操作time-函数介绍
【10月更文挑战第19天】 python模块time的函数使用介绍和使用。
38 4
|
1月前
|
中间件 Docker Python
【Azure Function】FTP上传了Python Function文件后,无法在门户页面加载函数的问题
通过FTP上传Python Function至Azure云后,出现函数列表无法加载的问题。经排查,发现是由于`requirements.txt`中的依赖包未被正确安装。解决方法为:在本地安装依赖包到`.python_packages/lib/site-packages`目录,再将该目录内容上传至云上的`wwwroot`目录,并重启应用。最终成功加载函数列表。

热门文章

最新文章