Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >怎样才能写好一个 Python 函数

怎样才能写好一个 Python 函数

作者头像
咸鱼学Python
发布于 2020-03-02 07:29:39
发布于 2020-03-02 07:29:39
58000
代码可运行
举报
文章被收录于专栏:咸鱼学Python咸鱼学Python
运行总次数:0
代码可运行

出处:乾明(编译)@量子位

链接:https://hackernoon.com/write-better-python-functions-c3a9a36382a6

Python 虽然好用,但用好真的很难。

尤其是函数部分,只要写不好,后面的一连串人都会遭殃。

看又看不懂,测试起来也麻烦,维护又维护不动,真是让人头疼。

那怎么写好一个 Python 函数呢?

《Writing Idiomatic Python》一书的作者在 Medium 上发表了一篇文章,给出了 6 个建议。

希望能够给你带来帮助。

0. 怎样算是好函数?

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

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

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

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

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

1. 注意命名

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

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

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
def getknn(from_df):

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

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

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

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

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

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

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
def k_nearest_neighbors(dataframe):

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

2. 单一功能

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

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

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

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

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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。

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

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

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

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

3. 文档字符串(Docstrings)

虽然每个人似乎都知道 PEP - 8,它定义了 Python 的样式指南,但是很少有人知道 PEP - 257,它是关于文档字符串的。我再这里不简单地重复 PEP - 257 的内容了,你可以在闲暇时读一下。其中的关键内容是:

  • 每个函数都需要有一个文档字符串
  • 使用适当的语法和标点符号;用完整的句子写
  • 首先对函数的作用进行一句话的总结
  • 使用说明性语言而不是描述性语言

在编写函数时,要养成写文档字符串的习惯,并在编写函数代码之前尝试写一下。

如果你不能写一个清晰的文档字符串来描述函数做什么,就说明你需要再考虑考虑为什么要写这个函数了。

4. 返回值

函数可以被认为是一些独立的程序。它们以参数的形式接受一些输入,并返回一些结果。

参数有没有都可以,但从 Python 内部的角度来看,返回值是必须要有的。你不可能创建一个没有返回值的函数。如果函数没有返回值,Python 会“强制”返回 None。你可以测试一下这段代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
❯ python3
Python 3.7.0 (default, Jul 232018, 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 isNone
True

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

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制

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。

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

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

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

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

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

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

5. 函数长度

让你读一个 200 行的函数,并说出它是做什么的,你是什么感受?

函数的长度直接影响可读性,从而影响可维护性。所以要保持你的函数简短。50 行是一个随意的数字,在我看来是合理的。你编写的大多数函数应该要短一些。

如果一个函数遵循单一功能原则,它很可能是相当短的。如果它是纯函数或是幂等的(下面讨论) ,它也可能是短的。

那么,如果函数太长,应该怎么做?重构。这会改变程序的结构而不改变其行为。

从一个长函数中提取几行代码,并把它们变成自己的函数。这是缩短长函数的最快、也是最常见的方式。

加上你给所有这些新函数取了合适的名称,因此生成的代码读起来也会更容易。

6. 幂等和函数纯度

不管被调用了多少次,幂等函数总是在给定相同参数集的情况下返回相同的值。

结果不依赖于非局部变量、参数的可变性或来自任何 I / O 流的数据。下面的这个 add_three(number)函数是幂等函数:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
def add_three(number):
    """Return *number* + 3."""
    return number + 3

不管一个人调用 add_three(7)多少次,答案总是 10。以下是一个非幂等函数:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
def add_three():
    """Return 3 + the number entered by the user."""
    number = int(input('Enter a number: '))
    return number + 3

这个函数的返回值取决于 I / O,即用户输入的数字。对 add_three()的每次调用都会返回不同的值。

如果它被调用两次,用户可以第一次输入 3,第二次输入 7,分别调用 add_three()返回 6 和 10。

幂等性的一个现实中例子是在电梯前点击“向上”按钮。第一次按时,电梯会被“通知”你要上去。因为按按钮是幂等的,所以反复按它都没有什么影响。结果是一样的。

6.1 为什么幂等很重要?

可维护性和可维护性。幂等函数很容易测试,因为在使用相同的参数时,它们总是返回相同的结果。

测试仅仅是检查通过不同调用返回值的预期值。更重要的是,这些测试很快,这是单元测试中一个重要且经常被忽视的问题。

而在处理幂等函数时,重构是轻而易举的事情。无论如何在函数之外更改代码,使用相同的参数调用它的结果总是一样的。

6.2 什么是纯函数?

在函数编程中,如果一个函数既幂等又没有可观察到的副作用,它就被认为是纯函数。函数外部的任何东西都不会影响这个值。

然而,这并不意味着函数不能影响非局部变量或 I / O 流之类的事情。例如,如果上面 add_three(number)的幂等版本在返回结果之前打印了结果,那么它仍然被认为是幂等的,因为当它访问 I / O 流时,这个访问与从函数返回的值无关。

调用 print()只是一个副作用:除了返回值之外,还与程序的其他部分或系统本身进行了一些交互。

让我们把我们的 add_three(number)示例再向前推进一步。我们可以编写下面的代码片段来确定调用 add_three(number)的次数:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
add_three_calls = 0
def add_three(number):
    """Return *number* + 3."""
    global add_three_calls
    print(f'Returning {number + 3}')
    add_three_calls += 1
    return number + 3
def num_calls():
    """Return the number of times *add_three* was called."""
    return add_three_calls

我们现在正在打印到控制台(一个副作用)并修改一个非局部变量(另一个副作用),但是由于这两者都不影响函数返回的值,它仍然是幂等的。

纯函数没有副作用。它不仅不使用任何“外部数据”来计算值,除了计算和返回所述值之外,它与系统/程序的其余部分都没有交互。因此,虽然我们新的 add_three(number)定义仍然是幂等的,但它不再是纯的。

纯函数没有日志语句或 print()调用。它们不使用数据库或互联网连接。它们不访问或修改非局部变量。它们不调用任何其他非纯函数。

简而言之,它们无法做到爱因斯坦所说的“远距离幽灵般的行动”(在计算机科学环境中)。它们不会以任何方式修改程序或系统的其余部分。

在命令式编程(编写 Python 代码时所做的那种)中,它们是所有函数中最安全的函数。

它们也很容易被测试和维护,甚至比只是幂等函数更重要的是,测试它们基本上可以和执行它们一样快。

测试本身很简单:没有数据库连接或其他外部资源进行模拟,也不需要安装代码,之后也没有什么需要清理的。

明确地说,幂等性和纯函数只是一种期望,不是必需的。也就是说,由于好处很多,我们可能会希望只编写纯函数或幂等函数,但这不现实。

重要的是,我们要有意识开始写代码来隔离副作用和外部依赖性。这会使得我们编写的每一行代码都更容易被测试。

Love & Share

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-02-27,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 咸鱼学Python 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
码如其人,同学你能写一手漂亮的Python函数吗
与多数现代编程语言一样,在 Python 中,函数是抽象和封装的基本方法之一。你在开发阶段或许已经写过数百个函数,但并非每个函数都生而平等。写出「糟糕的」函数会直接影响代码的可读性和可维护性。那么,什么样的函数是「糟糕的」函数呢?更重要的是,要怎么写出「好的」函数呢?
机器之心
2018/10/22
4640
码如其人,同学你能写一手漂亮的Python函数吗
写好一个函数的个人见解
在 JS 中,除了变量,用的最多的应该就是函数了,函数是 Javascript 的第一公民。
皮小蛋
2021/05/06
4790
写好一个函数的个人见解
代码整洁之道-编写 Pythonic 代码
很多新手在开始学一门新的语言的时候,往往会忽视一些不应该忽视的细节,比如变量命名和函数命名以及注释等一些内容的规范性,久而久之养成了一种习惯。对此呢,我特意收集了一些适合所有学习 Python 的人,代码整洁之道。
用户1564362
2019/10/31
1.6K0
Python 进阶指南(编程轻松进阶):十、编写高效函数
这一章探索了我们编写函数的不同方法以及不同权衡的优缺点。我们将深入研究如何在小函数和大函数之间进行权衡,参数的数量如何影响函数的复杂性,以及如何使用*和**操作符编写参数数量可变的函数。我们还将探索函数式编程范式以及根据这种范式编写函数的好处。
ApacheCN_飞龙
2023/04/09
1.4K0
如何用PEP 8编写漂亮的Python代码
Pep 8的存在是为了提高Python代码的可读性。但为什么可读性如此重要呢?为什么编写可读的代码是Python语言的指导原则之一?
Python知识大全
2020/02/13
9970
Python编程实验四:函数的使用
(1)通过本次实验,学生应掌握函数的定义与调用的基本语法,能根据需要灵活应用递归函数; (2)掌握函数的可变参数的常见用法,以及列表作为参数传递时的注意事项; (3)学习map函数和lambda表达式的应用; (4)按照实验题目要求独立正确地完成实验内容(编写、调试算法程序,提交程序清单及及相关实验数据与运行结果)
Francek Chen
2025/01/22
720
Python编程实验四:函数的使用
程序猿应该如何写好一个函数
函数是系统的元组件,组件要是写不好,整个系统的技术债就算是欠下了。而保持幂等性是系统可读性和可维护性的关键一环,关于幂等性,看这里,或者点击查看原文。
明月AI
2021/10/28
3920
作为一个Python爱好者,如何写出高可读性的代码?
推荐导读:Python的初学者,开发者都应该知道的代码可读性提高技巧,本篇主要介绍了如下内容:
Python数据科学
2018/12/28
1.3K0
Python升级之路( Lv5 ) 函数
第一章 Python 入门 第二章 Python基本概念 第三章 序列 第四章 控制语句 第五章 函数
时间静止不是简史
2022/12/02
1.2K0
Python升级之路( Lv5 ) 函数
【Python篇】Python 函数综合指南——从基础到高阶
在 Python 中,函数是构建程序的一个重要部分,它允许你封装逻辑并高效地重用代码。函数是组织良好的、可重复使用的代码块,用于执行单一的、相关的操作。函数为的应用提供了更好的模块化能力,并提高了代码的重用性。
半截诗
2024/10/09
4170
【愚公系列】2021年12月 Python教学课程 12-Python函数
函数(function)是用于完成特定任务的程序代码的自包含单元。在面向对象编程的类中,函数通常被称作方法。不同的函数在程序中扮演着不同的角色,起着不同的作用,执行不同的动作。比如 print()函数可以将对象打印到屏幕上;还有一些函数能够返回一个值以供程序使用,比如 len()将可计算长度的对象的元素个数返回给程序。
愚公搬代码
2021/12/14
5270
python中的函数
1.什么是函数 函数是组织好的,可重复使用的,用来实现单一,或相关联功能的代码段。 函数能提高应用的模块性,和代码的重复利用率。 2.定义一个函数 1.函数代码块以 def 关键词开头,后接函数标识符名称和圆括号()。 2.任何传入参数和自变量必须放在圆括号中间。圆括号之间可以用于定义参数。 3.函数的第一行语句可以选择性地使用文档字符串—用于存放函数说明。 4.函数内容以冒号起始,并且缩进。 5.return [表达式] 结束函数,选择性地返回一个值给调用方。不带表达式的return相当于返回 None。
py3study
2020/01/14
2.1K0
Python 代码风格指南谷歌版
非常感谢我们的忠实读者 shendeguize,在后台留言告诉我,已经翻译了《谷歌Python代码风格指南》 ,大家这样相互帮助,感觉真是太好。
double
2020/05/08
1.2K0
Python 代码风格指南谷歌版
网络工程师学Python-9-函数
在 Python 中,函数是一段可重复使用的代码块,可以接受一些输入(即函数参数),并根据输入执行某些操作。函数可以帮助我们组织代码、减少重复代码、实现模块化设计,并提高代码的可读性和可维护性。
网络技术联盟站
2023/04/19
2580
网络工程师学Python-9-函数
Python学习笔记(四)·函数式编程
函数是 Python 内建支持的一种封装,我们通过把大段代码拆成函数,通过一层一层的函数调用,就可以把复杂任务分解成简单的任务,这种分解可以称之为面向过程的程序设计。函数就是面向过程的程序设计的基本单元。
公爵
2022/09/28
9340
Python学习笔记(四)·函数式编程
05. 函数式编程
在《04.函数》一文中介绍了Python中的函数,以及函数的基础使用。函数是Python内建支持的一种封装,我们通过把大段代码拆成函数,通过一层一层的函数调用,就可以把复杂任务分解成简单的任务,这种分解可以称之为面向过程的程序设计。函数就是面向过程的程序设计的基本单元。而函数式编程(Functional Programming),是一种抽象程度很高的编程规范。
有一只柴犬
2024/01/25
1570
​一篇全网最详细的python之函数
调用函数需要知道函数名和参数,如果传入的参数不对,会报TypeError的错误并且给出错误信息我可以给你一些常见的Python内置函数的示例:
可以叫我才哥
2024/03/06
1740
​一篇全网最详细的python之函数
[Python] 函数与函数编程
函数体就是在调用函数时所执行的一系列语句。调用函数的方法是在函数名称后面加上参数。参数的顺序必须与函数定义匹配,否则会引发TypeError异常。可以为函数的参数设置默认值,例如:
py3study
2020/01/14
1.5K0
【深度学习】 Python 和 NumPy 系列教程(七):Python函数(基础知识、模块、n种不同形式的函数)
Python本身是一种伟大的通用编程语言,在一些流行的库(numpy,scipy,matplotlib)的帮助下,成为了科学计算的强大环境。本系列将介绍Python编程语言和使用Python进行科学计算的方法,主要包含以下内容:
Qomolangma
2024/07/29
1100
【深度学习】 Python 和 NumPy 系列教程(七):Python函数(基础知识、模块、n种不同形式的函数)
开源图书《Python完全自学教程》7.1.1基本格式
函数是编程中应用非常多的一类对象(请注意这里的宾语“对象”,在本章7.3节会专门探讨)。此前我们已经多次使用过 Python 的内置函数,现在要自己亲自动手编写更有个性的函数。这是一件很有意思的事情,也伴随着一定的挑战。相信已经自学至此的读者能够攻克本章所遇到的难关。
老齐
2022/07/06
2350
开源图书《Python完全自学教程》7.1.1基本格式
推荐阅读
相关推荐
码如其人,同学你能写一手漂亮的Python函数吗
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
查看详情【社区公告】 技术创作特训营有奖征文
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验