【Python】Basics - 函数参数

Posted by 西维蜀黍 on 2019-10-20, Last Modified on 2024-01-09

定义函数

>>> def fib(n):    # write Fibonacci series up to n
...     """Print a Fibonacci series up to n."""
...     a, b = 0, 1
...     while a < n:
...         print(a, end=' ')
...         a, b = b, a+b
...     print()
...
>>> # Now call the function we just defined:
... fib(2000)
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597

函数的参数位置问题

在通常的情况下,Python 会根据在调用函数时,参数传入的位置顺序为参数变量赋值,比如:

def test(a,b,c):
    print a
    print b
    print c

test(1, 2, 3)

这其实默认包含了以下的这次含义:在调用 test()时,传入的第一个值会被赋值给变量 a,传入的第二个值会被赋值给变量 b…。

这种情况普遍存在于各种编程语言中,因此也非常好理解。Python 将这个函数参数的赋值模式称为位置参数(positional argument),即根据相对的位置顺序来为参数赋值。

Python 之所以搞这么麻烦,为这种被广泛应用于各种编程语言的函数参数赋值模式还特意取个名字,是因为 Python 还提供了另外一种函数参数的赋值模式,称为关键字参数(keyword argument),即通过指定参数的名称来为参数列表中的参数赋值。

test(a=1, b=2, c=3)
test(b=2, a=1, c=3)
test(c=3, a=1, b=3)

上面三种对于test()的调用,就是基于关键字参数(keyword argument),从调用后都能产生完全相同的副作用(side effect)角度来说,它们是等价的(equivalent)。

你可能会觉得,为什么 Python 要把一个函数参数的传值搞这么麻烦。或者说,目前,我们还没有看到引入**关键字参数(keyword argument)**有什么好处。我们在介绍完可选的函数参数后就会进行解释。

可选提供的函数参数(为函数参数设置默认值)

Common Situation

在最一般的情况下,如果我们在调用函数时,没有按照函数的参数列表提供参数,则会报错,比如下面的例子:

def test(a,b,c):
    print a
    print b
    print c

test(2)

错误:

Traceback (most recent call last):
  File "/Users/wei.shi/Desktop/performance/main.py", line 26, in <module>
    test(2)
TypeError: test() takes exactly 3 arguments (1 given)

说明:

test()需要两个参数,而我们只提供了一个参数。

因此,我们需要一种”可选提供特定参数“的机制,或者说,在定义函数的时候,就为某些参数定义好了默认值,如果这些参数没有被调用者提供,则将这些参数用它们的默认值来进行赋值。

函数的可选提供参数的定义

def ask_ok(prompt, retries=4, reminder='Please try again!'):
    while True:
        ok = input(prompt)
        if ok in ('y', 'ye', 'yes'):
            return True
        if ok in ('n', 'no', 'nop', 'nope'):
            return False
        retries = retries - 1
        if retries < 0:
            raise ValueError('invalid user response')
        print(reminder)

这个函数可以通过以下任何一种方式来调用:

  • 仅仅提供必需(mandatory)参数: ask_ok('Do you really want to quit?')
  • 提供一个可选参数:ask_ok('OK to overwrite the file?', 2)
  • 提供所有的可选参数: ask_ok('OK to overwrite the file?', 2, 'Come on, only yes or no!')

注意

需要注意的是,在定义包含可选提供参数的函数参数列表时,可选参数必需声明在非可选参数之后,换句话说,在声明了一个可选提供参数之后,接下来就不能再申明一个非可选提供参数了。

以下是一个错误例子:

def a(a=1, b):
	pass

执行后会提示:

  File ".../main.py", line 13
    def a(a=1, b):
SyntaxError: non-default argument follows default argument

可选参数 + 关键字参数(keyword argument)

有了可选参数之后,我们可以调用test()函数的方式又更多了。

假设 test2()被这样定义:

def test2(a, b, c=0):
    print a
    print b
    print c

我们可以这样来调用它:

test2(1,2)							# positional(a, b) and default(c)
test2(1,2,3)						# positional(a, b)
test2(b=2, a=1)				  # named(b=2, a=1) and default
test2(c=3,b=2,a=1)		  # named(b=2, a=1, c=3)
test2(1,c=3,b=2)				# positional(a) and named(b=2,c=3)

Keyword Arguments的意义

当可选参数不止一个时,我们就能看出Keyword Arguments的意义了。

def test3(a, b, c=3, d=4):
    print a
    print b
    print c
    print d

可以看到,必需提供的参数只有 a 和 b,因此我们可以这样调用:

test3(1, 2)
test3(1, 2, d=1000)

在这个例子中,我们就能看出Keyword Arguments的意义了,即当有多个可选传入的函数参数时,我们不需要像在 Java 中那样通过test3(1, 2, Null, 1000)来调用,而是直接指定d=1000

试想,当可选参数非常多时,简单的通过d=1000来指定我们想要传的可选参数,这确实能够缩短在编写函数调用时所花的时间。

传入任意数量的参数

有时候,在定义函数时,我们并不能提前知道到底需要多少个参数,比如:

def make_pizza(title, *toppings):
    """Print the list of toppings that have been requested.""" 
    print(title)
    print(toppings)

make_pizza('myPizza', 'pepperoni') 
make_pizza('myPizza', 'mushrooms', 'green peppers', 'extra cheese')

parameters = ('myPizza', 'mushrooms', 'green peppers', 'extra cheese')
make_pizza(*parameters)

toppings其实是一个 tuple。

值得注意的是,在每一个函数中,只能有一个参数是传入任意数量的参数。比如这样的定义就会报错:

def make_pizze(*a, *b):
    pass

传入任意数量的关键字参数(keyword argument)

def build_profile(first, last, **user_info):
    """Build a dictionary containing everything we know about a user.""" 
    profile = {} 
    profile['first_name'] = first 
    profile['last_name'] = last 
    for key, value in user_info.items():
    	profile[key] = value 
    return profile

user_profile = build_profile('albert', 'einstein')
user_profile = build_profile('albert', 'einstein', location='princeton', field='physics') 
paras = {location='princeton', field='physics'}
user_profile = build_profile('albert', 'einstein', **paras) 

可变数量参数 + 关键字参数(keyword argument)

def print_params(x, y, z=3, *params, **kw):
    print(x, y, z)
    print(params)
    print(kw)
>>>print_params(1, 2, 3, 5, 6, 7, foo=1, bar=2)
1 2 3
(5, 6, 7)
{'foo':1, 'bar':2}

参数组合

在Python中定义函数,可以用必选参数(必需提供的参数)、默认参数(可选提供参数)、可变参数、关键字参数和命名关键字参数,这5种参数都可以组合使用。但是请注意,参数声明的顺序必须是:必选参数、默认参数、可变参数、命名关键字参数和关键字参数。

比如定义一个函数,包含上述若干种参数:

def f1(a, b, c=0, *args, **kw):
    print('a =', a, 'b =', b, 'c =', c, 'args =', args, 'kw =', kw)

在函数调用的时候,Python解释器自动按照参数位置和参数名把对应的参数传进去。

>>> f1(1, 2)
a = 1 b = 2 c = 0 args = () kw = {}

>>> f1(1, 2, c=3)
a = 1 b = 2 c = 3 args = () kw = {}

>>> f1(1, 2, 3, 'a', 'b')
a = 1 b = 2 c = 3 args = ('a', 'b') kw = {}

# 以 [] 形式提供 args
>>> f1(1, 2, 3, *[4, 5])
('a =', 1, 'b =', 2, 'c =', 3, 'args =', (4, 5), 'kw =', {})



# 不提供 args
>>>f1(1, 2, 3, x=99, y =100)
('a =', 1, 'b =', 2, 'c =', 3, 'args =', (), 'kw =', {'y': 100, 'x': 99})

# 这样提供 x=99 时,有可能会出现新的问题,即当你想获得 kw = {"a": 99}的结果时,这样的调用方式竟然无法搞定
>>> f1(1, 2, 3, 'a', 'b', x=99)
a = 1 b = 2 c = 3 args = ('a', 'b') kw = {'x': 99}

# 以下情况会报错
>>> f1(1, 2, 3, 'a', 'b', a=99)
# 错误:TypeError: f1() got multiple values for keyword argument 'a'
# 因此,推荐尽量以 **{} 的形式为关键字参数赋值(除非你要提供的参数非常简单)

>>> f1(1, 2, 3, 'a', 'b', x=99, y =100)
('a =', 1, 'b =', 2, 'c =', 3, 'args =', ('a', 'b'), 'kw =', {'y': 100, 'x': 99})

# 以 **{} 形式提供kw
>>> f1(1, 2, 3, 4, 5, **{"x": 99, "y": 100})
('a =', 1, 'b =', 2, 'c =', 3, 'args =', (4, 5), 'kw =', {'y': 100, 'x': 99})

# 以 [] 形式提供 args,以 **{} 形式提供kw
>>> f1(1, 2, 3, *[4, 5], **{"x": 6, "y": 7})
('a =', 1, 'b =', 2, 'c =', 3, 'args =', (4, 5), 'kw =', {'y': 7, 'x': 6})

# 这样也能执行成功,但是结果很奇怪
>>> f1(1, 2, *[4, 5], **{"x": 6, "y": 7})
('a =', 1, 'b =', 2, 'c =', 4, 'args =', (5,), 'kw =', {'y': 7, 'x': 6})

可选提供参数 + 任意数量的关键字参数(keyword argument)

可选提供参数 + 关键字参数

Example 1

def fun(a=1, b=2, c=3, **filter):
	print a
	print b
	print filter

fun(a=3, b=4, c=5, d=6)
fun(3, 4, **{"c": 5, "d": 6})

输出:

3
4
{'c': 5, 'd': 6}
3
4
{'c': 5, 'd': 6}

Example 2

def fun(a=1, b=2, c=3, d=4, **filter):
	print a
	print b
	print c
  print d
	print filter

fun(b=5, c=6, **{"x": 7, "y": 8})
# fun(b=5, c=6, **{"a": 7, "b": 8})  会报错:TypeError: fun() got multiple values for keyword argument 'b'

输出:

1
5
6
{'y': 8, 'x': 7}

Reference