python默认参数调用方法解析

1. Python 默认参数介绍

默认参数在 Python 函数中常用到,它为函数提供了缺省值,使得调用函数时可以不传递该参数。声明一个默认参数的语法如下:

def foo(a, b=2, c=3):

pass

foo(10) # 等价于 foo(10, 2, 3)

foo(10, c=30) # 等价于 foo(10, b=2, c=30)

在函数定义中,函数可以有任意数目的默认参数。默认参数的位置顺序必须与函数定义相同,即先是非默认参数,紧接着是默认参数。

1.1 使用默认参数的好处

默认参数的使用可以缩短函数的定义,并且简化函数参数的调用。

假如有一个取平均数的函数,有一个参数决定是否对输入的数字进行四舍五入。如果默认情况下进行四舍五入,那么函数定义如下:

def mean(numbers, round_result=True):

"""

Compute the mean of a list of numbers.

:param numbers: A list of numbers

:param round_result: Round the result to the nearest integer (default=True)

"""

n = sum(numbers)

result = n/len(numbers)

if round_result:

return round(result)

else:

return result

这样,当用户不关心是否对结果进行四舍五入时,即 round_result 参数传递 False,简化调用:

>>> mean([1.0, 2.0, 3.0])

2

>>> mean([1.0, 2.0, 3.0], False)

2.0

1.2 注意点

需要注意的是,默认参数的值只会在 Python 程序中调用了函数让其诞生后,“评估”一次。这和函数对象本身不同,函数对象是在解释 Python 模块时被创建的。

为了更好地理解这个问题,看看下面的代码:

i = 5

def f(arg=i):

print(arg)

i = 6

f() # 输出 5

2. Python 默认参数负责问题

当默认参数使用可变对象时,可能会导致一些不易发现的问题。

下面举一个典型的例子。假如有一个函数,要将接收到的字符串插入到列表中,然后返回该列表。

def func(item, items=[]):

items.append(item)

return items

如果连续多次调用该函数,如下所示:

print(func(1))

print(func(2))

print(func(3))

输出结果是:

[1]

[1, 2]

[1, 2, 3]

这并不是我们所期待的结果,因为我们每次调用 func 时都创建了一个新的空列表,然后将传递给它的参数添加到该列表中。但实际情况是,列表 items 被指定为默认参数,这意味着它实际上是一个全局变量。

默认参数 items 在函数定义时评估一次,因此在第一次调用函数时,items 将被创建并初始化为 []。因后续调用共享同一列表,所以会导致多次调用导致列表被逐渐扩充。

很多初学 Python 的程序员都会陷入类似这样的状况,所以在做函数设计时,应格外注意参数的传递方式。

2.1 怎样避免出现负责问题

为了解决这个问题,我们可以遵循一些相对比较好的实践经验。

1) 将不可变类型作为默认参数传入函数

不可变类型包括整数、浮点数和字符串。这样的变量不能被修改,并且在多次调用函数时保持不变。例如,下面的函数将数字添加到一个列表中,但默认参数是一个空元组,这样就保证了列表 items 只是一个新列表。

def func(item, items=()):

items += (item,)

return items

2) 在函数内创建默认参数的副本,而非对该变量进行就地修改

当我们有可变参数作为默认参数时,我们应该在函数内部新建一个副本,从而避免全局的可变对象被就地更改。例如,下面的函数接收一个字符串和一个列表作为参数,并将字符串添加到该列表的副本中:

def func(item, items=None):

if items is None:

items = []

items.append(item)

return items

这样,在每次调用该函数时,都会在函数内部新建一个空列表。

2.2 使用可变参数作为默认参数的示例

那么,如果我们确实需要使用可变参数作为默认参数,应该怎么做呢?下面是一种示例,它使用时尽可能地保护了可变对象:

def func(item, items=None):

if items is None:

items = []

items += [item]

return items

这里使用了默认参数,但将列表对象替换为 None。当函数被调用时,如果没有传递列表 items,则会在函数内部创建一个新的空列表,并将其添加到列表中。

这种方法可以更好地防止在调用函数时共享可变对象,并且它使用列表的加法运算符而不是 append 方法,这使得在函数内创建一个副本变得更为安全。

3. 使用默认参数注意事项

在使用默认参数时,需要注意以下几个问题:

3.1 默认参数的值在函数定义后评估一次

当 Python 解释器加载模块并定义函数时,函数的默认参数将被评估一次。这意味着所有的默认参数都将被创建并分配了值,而不是在函数被调用时动态创建。例如:

import datetime

def log(message, timestamp=datetime.datetime.now()):

print(message, timestamp)

log("Hello World!")

log("Hello again!") # 这里的时间是函数定义时的时间

由于默认参数是 datetime.datetime.now(),因此该函数将在加载模块时首先被评估。

3.2 默认参数应该使用不可变对象的值

默认参数的值在函数定义时仅被评估一次。如果默认参数是可变对象的话,那么实际上是多次使用了同一个对象。这样会导致许多问题,因为函数调用可能会修改这个默认参数。例如:

def add_item(name, quantity, unit, grocery_list=[]):

grocery_list.append(name)

grocery_list.append(quantity)

grocery_list.append(unit)

return grocery_list

print(add_item("apple", 5, "kg"))

print(add_item("bread", 2, "pieces"))

上述函数的默认参数为 []。这意味着该函数的所有调用都共享同一列表对象。由于列表是可变的,因此 append 调用将会直接修改共享的列表对象,从而导致不正确的行为。

修复这个问题有多种方法。其中一种方法是使用 None 作为默认值来避免使用可变对象:

def add_item_v2(name, quantity, unit, grocery_list=None):

grocery_list = [] if grocery_list is None else grocery_list

grocery_list.append(name)

grocery_list.append(quantity)

grocery_list.append(unit)

return grocery_list

print(add_item_v2("apple", 5, "kg"))

print(add_item_v2("bread", 2, "pieces"))

3.3 避免在调用函数时使用可变对象作为默认参数

任何时候都不应该在函数调用中共享可变对象。因为这样的代码可能会出现令人费解的错误。学习使用非可变对象作为默认参数是种好习惯。如果确实需要使用可变对象(例如列表或字典)作为默认参数,则必须小心处理。

在编写代码时,请务必考虑到这些注意事项,以避免出现隐藏的错误并使代码更易于阅读和调试。

后端开发标签