一篇文章带你了解函数声明时的优雅操作4. 错误使用默认值
在编写函数时,Python允许我们为某些参数设置一些默认值。许多内置函数也使用此功能。考虑下面的示例。我们可以使用range()函数创建一个列表对象,该函数具有常规语法range(start,stop,step)。如果省略,默认步骤参数将使用一个。但是,我们可以在以下代码中显式设置step参数(类似于第2个示例):
>>> list(range(5, 15))
[5, 6, 7, 8, 9, 10, 11, 12, 13, 14]
>>> list(range(5, 15, 2))
[5, 7, 9, 11, 13]
但是,当我们编写包含具有可变数据类型的默认值的函数时,事情会变得棘手。当我们说可变数据时,是指Python对象在创建后就可以更改,例如列表,字典和集合。要了解有关Python数据可变性的更多信息,请参阅我的上一篇文章。考虑以下有关在函数中使用默认值和可变参数的简单示例:
>>> def append_score(score, scores=[]):
... scores.append(score)
... print(scores)
...
>>> append_score(98)
[98]
>>> append_score(92, [100, 95])
[100, 95, 92]
>>> append_score(94)
[98, 94]
当我们尝试附加分数98时,将打印出预期结果,因为我们省略了分数参数,并且期望使用空列表。当我们尝试将得分92附加到[100,95]列表中时,结果[100,95,92]也符合预期。但是,当我们尝试附加分数94时,我们中的某些人可能会期望结果为[94],但事实并非如此。为什么会发生这种情况?
这是因为Python中的函数也是一等公民,并且被视为常规对象(请参阅我之前的文章,关于函数是Python中的对象)。这意味着在定义函数时,将创建一个对象,包括该函数的默认变量。让我们看一下有关这些概念的代码片段:
>>> def append_score(score, scores=[]):
... scores.append(score)
... print(f'scores: {scores} & id: {id(scores)}')
...
>>> append_score.__defaults__
([],)
>>> id(append_score.__defaults__[0])
4650019968
>>> append_score(95)
scores: [95] & id: 4650019968
>>> append_score(98)
scores: [95, 98] & id: 4650019968
我们修改了先前的功能,使其能够输出分数列表的内存地址。如您所见,在调用函数之前,我们可以找到函数参数的默认值及其访问__default__属性的内存地址。两次调用函数后,具有相同存储地址的相同列表对象已更新。
那么最好的做法是什么?我们应该使用None作为可变数据类型的默认值,这样,在声明函数时,函数不会实例化可变对象。调用函数时,我们可以根据需要创建可变对象。有关其他信息,请参见下面的代码。现在,一切都按预期工作:
>>> def append_score(score, scores=None):
... if not scores:
... scores = []
... scores.append(score)
... print(scores)
...
>>> append_score(98)
[98]
>>> append_score(92, [100, 95])
[100, 95, 92]
>>> append_score(94)
[94]
5. 滥用* args和** kwargs
Python允许我们通过支持可变数量的参数来编写灵活的函数。如果您还记得的话,您一定已经在某些库的文档中的某些地方看到了* args和* kwargs。本质上,* args指的是数量不确定的位置参数,而** kwargs指的是数量不确定的关键字参数。
在Python中,位置参数是根据其位置传递的参数,而关键字参数是根据其指定的关键字传递的参数。下面来看一个简单的例子:
>>> def add_numbers(num0, num1, num2=2, num3=3):
... outcome = num0 + num1 + num2 + num3
... print(f"num0={num0}, num1={num1}, num2={num2}, num3={num3}")
... return outcome
...
>>> add_numbers(0, 1)
num0=0, num1=1, num2=2, num3=3
6
>>> add_numbers(0, 1, num3=4, num2=5)
num0=0, num1=1, num2=5, num3=4
10
在函数add_numbers中,num0和num1是位置参数,而num2和num3是关键字参数。需要注意的一件事是,您可以更改关键字参数之间的顺序,但不能更改位置参数和关键字参数之间的顺序。
尽管* args和* kwargs的可用性使我们能够编写更灵活的Python函数,但是滥用它们可能会导致函数混乱。之前,我提到我们可以使用pandas库进行数据处理,并简要提到了read_csv函数,该函数读取CSV文件。该函数共有49个参数:一个位置参数和48个关键字参数。从理论上讲,我们可以通过执行以下操作使列表更短:
pandas.read_csv(filepath_or_buffer: Union[str, pathlib.Path, IO[~AnyStr]], **kwargs)
但是,在此功能的实际实现中,我们仍然必须拆开* kwarg文件,并弄清楚如何正确读取CSV文件。为什么这些经验丰富的Python开发人员愿意列出所有这些关键字参数?这是因为他们了解以下原则:
“Explicit is better than implicit.” —The Zen of Python
尽管使用** kwargs可以为我们在函数声明的第一行中节省一些时间,但代价是我们的代码变得不太明确。 同样的想法也适用于* args。 如上所述,如果我们在代码共享环境中工作,我们总是希望我们的代码是显式的,从而易于理解。 因此,我们尽可能避免使用* args和** kwargs编写更明确的代码。
总结
在本文中,我们回顾了Python程序员可能在其代码中犯的五个常见错误。 尽管您可以通过忽略项目中的这些错误来拥有自己的编码风格,但是您的代码可能变得难以理解,并且长期可维护性较低。 因此,如果可能的话,我们所有人都可能希望避免这些错误并提高代码的可读性,从而提高共享性。
· END ·