与 Python 开发人员接触足够长的时间,您就会听到有关 Tim Peter 的Zen Of Python 的所有信息。
您可以通过在 Python REPL 中执行import this
来方便地阅读Zen ,介绍了 Python 设计背后的 20 条指导原则中的 19 条。最近我开始更加欣赏一句格言:“明确胜于含蓄。”
我见过的最常见的解释——如此普遍,以至于目前在搜索该短语时它出现在谷歌的特色片段中——是冗长的代码比简洁的代码更好,因为冗长显然是可读性的关键……或者其他什么。
当然,使用更好的变量名和用命名常量(或者,在 Python 中,“常量”)替换幻数都是很棒的事情。但是,您最后一次在代码中寻找隐式输入并将其显式化是什么时候?
如何识别隐式输入和输出
以下函数有多少个输入和输出?
def find_big_numbers(): with open("numbers.txt", "r") as f: for line in f: if line.strip().isnumeric(): number = float(line) if number > 100: print(number)
find_big_numbers()
没有参数并且总是返回None
。如果您看不到函数体并且无法访问标准输出流,您还会相信这个函数会做任何事情吗?
然而, find_big_numbers()
除了None
之外还有两个输入和另一个输出:
-
numbers.txt
是隐式输入。没有它,该函数将无法运行,但如果不阅读函数体,就不可能知道该文件是必需的。 - 第 6 行的幻数
100
是隐式输入。没有它你就不能定义一个“大数”,但是如果不阅读函数体就无法知道这个阈值。 - 值可能会也可能不会打印到
stdout
,具体取决于numbers.txt
的内容。这是一个隐式输出,因为该函数不返回那些值。
隐式输出通常称为副作用。
自己试试
识别此代码段中is_adult
函数的所有输入和输出:
from datetime import date birthdays = { "miles": date(2000, 1, 14), "antoine": date(1987, 3, 25), "julia": date(2009, 11, 2), } children = set() adults = set() def is_adult(name): birthdate = birthdays.get(name) if birthdate: today = date.today() days_old = (today - birthdate).days years_old = days_old // 365 if years_old >= 18: print(f"{name} is an adult") adults.add(name) return True else: print(f"{name} is not an adult") children.add(name) return False
为什么要避免隐式输入和输出
一个很好的理由是他们喜欢违反最小意外原则。
当然,并非所有隐式输入和输出都是不好的。 Python 文件对象使用.write()
将数据写入文件的方法有一个隐式输出:文件。没有办法消除它。但这并不奇怪。写入文件是重点。
另一方面,像前面代码片段中的is_adult()
这样的函数做了很多令人惊讶的事情。不那么极端的例子比比皆是。
避免隐式输入和输出还可以提高代码的可测试性和可重用性。要了解具体方法,让我们重构之前的find_big_numbers()
函数。
如何删除隐式输入和输出
这里又是find_big_numbers()
所以你不必向上滚动:
def find_big_numbers(): with open("numbers.txt", "r") as f: for line in f: if line.strip().isnumeric(): number = float(line) if number > 100: print(number)
早些时候,我们确定了两个隐式输入,即numbers.txt
文件和数字100
,以及一个隐式输出,即打印到stdout
的值。让我们先处理输入。
您可以将文件名和阈值移动到函数的参数中:
def find_big_numbers(path, threshold=100): with open(path, "r") as f: for line in f: if line.strip().isnumeric(): number = float(line) if number > threshold: print(number)
这已经大大提高了可测试性和可重用性。如果您想在不同的文件上尝试,请将路径作为参数传递。 (作为奖励,该文件现在可以位于您计算机上的任何位置。)如果需要,您还可以更改“大数字”的阈值。
但是输出很难测试。
如果您想知道该函数产生了正确的值,您需要拦截stdout
。这是可能的。但为什么不只返回所有值的列表:
def find_big_numbers(path, threshold=100): big_numbers = [] with open(path, "r") as f: for line in f: if line.strip().isnumeric(): number = float(line) if number > threshold: big_numbers.append(number) return big_numbers
现在find_big_numbers()
有一个明确的return
语句,返回文件中找到的大数字列表。
find_big_numbers()
。你会如何清理它?您可以通过使用已知内容的文件路径调用它并将返回的列表与正确值列表进行比较来测试find_big_numbers()
:
# test_nums.txt looks like: # 29 # 375 # 84 >>> expected_nums = [375.0] >>> actual_nums = find_big_numbers("test_nums.txt") >>> assert(actual_nums == expected_nums)
find_big_numbers()
现在也更可重用了。您不仅限于将数字打印到stdout
。您可以将这些大数字发送到任何您想要的地方。
隐式输入是函数或程序使用的未作为参数显式传递的数据。您可以通过将隐式输入重构为参数来消除隐式输入。
隐式输出是发送到函数或程序外部某处但未显式返回的数据。您可以通过用合适的返回值替换它们来删除显式输出。
并非所有隐式输入和输出都可以避免,例如旨在从文件和数据库读取或写入数据或发送电子邮件的函数。不过,尽可能多地消除隐式输入和输出可以提高代码的可测试性和可重用性。
find_big_numbers()
中删除了所有隐式输入和输出?好奇 Python 之禅中的第 20 行发生了什么?互联网上流传着各种各样的理论。我觉得这很有可能。
在 Eric Normand 的优秀著作 Grokking Simplicity 中阅读有关隐式输入和输出的更多信息。从Manning * 获得即时访问或在亚马逊* 上订购。
*附属链接。有关更多信息,请参阅我的关联披露。
想要更多这样的东西吗?
每周六一封电子邮件,附上一条可行的提示。
总是少于 5 分钟的时间。
现在订阅
原文: https://davidamos.dev/stop-using-implicit-inputs-and-outputs/