python基础(函数关键字参数、收集参数、分配参数)

1.关键字参数和默认值
请看下面两个函数:
def hello_1(greeting, name): 
    print('{}, {}!'.format(greeting, name)) 
def hello_2(name, greeting): 
    print('{}, {}!'.format(name, greeting)) 

这两个函数的功能完全相同,只是参数的排列顺序相反。
>>> hello_1('Hello', 'world') 
Hello, world! 
>>> hello_2('Hello', 'world') 
Hello, world! 
有时候,参数的排列顺序可能难以记住,尤其是参数很多时。为了简化调用工作,可指定参数的名称。
>>> hello_1(greeting='Hello', name='world') 
Hello, world! 
在这里,参数的顺序无关紧要。
>>> hello_1(name='world', greeting='Hello') 
Hello, world!
不过名称很重要。
>>> hello_2(greeting='Hello', name='world') 
world, Hello! 
像这样使用名称指定的参数称为关键字参数,主要优点是有助于澄清各个参数的作用。这样,函数调用不再像下面这样怪异而神秘:
>>> store('Mr. Brainsample', 10, 20, 13, 5) 
可以像下面这样做:
>>> store(patient='Mr. Brainsample', hour=10, minute=20, day=13, month=5) 
虽然这样做的输入量多些,但每个参数的作用清晰明了。另外,参数的顺序错了也没关系。然而,关键字参数最大的优点在于,可以指定默认值。
def hello_3(greeting='Hello', name='world'): 
     print('{}, {}!'.format(greeting, name)) 
像这样给参数指定默认值后,调用函数时可不提供它!可以根据需要,一个参数值也不提供、提供部分参数值或提供全部参数值。
>>> hello_3() 
Hello, world! 
>>> hello_3('Greetings') 
Greetings, world! 
>>> hello_3('Greetings', 'universe') 
Greetings, universe! 
如你所见,仅使用位置参数就很好,只不过如果要提供参数name,必须同时提供参数greeting。如果只想提供参数name,并让参数greeting使用默认值呢?
>>> hello_3(name='Gumby') 
Hello, Gumby! 
很巧妙吧?还不止这些。你可结合使用位置参数和关键字参数,但必须先指定所有的位置参数,否则解释器将不知道它们是哪个参数(即不知道参数对应的位置)。

通常不应结合使用位置参数和关键字参数,除非你知道这样做的后果。一般而言,除非必不可少的参数很少,而带默认值的可选参数很多,否则不应结合使用关键字参数和位置参数。
例如,函数hello可能要求必须指定姓名,而问候语和标点是可选的。
def hello_4(name, greeting='Hello', punctuation='!'): 
    print('{}, {}{}'.format(greeting, name, punctuation)) 
调用这个函数的方式很多,下面是其中的一些:
>>> hello_4('Mars') 
Hello, Mars! 
>>> hello_4('Mars', 'Howdy') 
Howdy, Mars! 
>>> hello_4('Mars', 'Howdy', '...') 
Howdy, Mars... 
>>> hello_4('Mars', punctuation='.') 
Hello, Mars. 
>>> hello_4('Mars', greeting='Top of the morning to ya') 
Top of the morning to ya, Mars! 
>>> hello_4() 
Traceback (most recent call last): 
 File "<stdin>", line 1, in <module> 
TypeError: hello_4() missing 1 required positional argument: 'name'

如果给参数name也指定了默认值,最后一个调用就不会引发异常。

 

2.收集参数
有时候,允许用户提供任意数量的参数很有用。例如,在姓名存储示例中,每次只能存储一个姓名。如果能够像下面这样同时存储多个姓名就好了:
>>> store(data, name1, name2, name3)
为此,应允许用户提供任意数量的姓名。实际上,这实现起来并不难。
尝试使用下面这样的函数定义:
def print_params(*params): 
    print(params) 
这里好像只指定了一个参数,但它前面有一个星号。这是什么意思呢?尝试使用一个参数来调用这个函数,看看结果如何。

>>> print_params('Testing') 
('Testing',) 

注意到打印的是一个元组,因为里面有一个逗号。这么说,前面有星号的参数将被放在元组中?复数params应该提供了线索。
>>> print_params(1, 2, 3) 
(1, 2, 3)
参数前面的星号将提供的所有值都放在一个元组中,也就是将这些值收集起来。:赋值时带星号的变量收集多余的值。它收集的是列表而不是元组中多余的值,但除此之外,这两种用法很像。下面再来编写一个函数:
def print_params_2(title, *params): 
    print(title) 
    print(params)
并尝试调用它:
>>> print_params_2('Params:', 1, 2, 3) 
Params: 
(1, 2, 3) 
因此星号意味着收集余下的位置参数。如果没有可供收集的参数,params将是一个空元组。
>>> print_params_2('Nothing:') 
Nothing: 
() 
与赋值时一样,带星号的参数也可放在其他位置(而不是最后),但不同的是,在这种情况下你需要做些额外的工作:使用名称来指定后续参数。
>>> def in_the_middle(x, *y, z): 
...          print(x, y, z) 
... 
>>> in_the_middle(1, 2, 3, 4, 5, z=7) 
1 (2, 3, 4, 5) 7 
>>> in_the_middle(1, 2, 3, 4, 5, 7) 
Traceback (most recent call last): 
 File "<stdin>", line 1, in <module> 
TypeError: in_the_middle() missing 1 required keyword-only argument: 'z' 
星号不会收集关键字参数。
>>> print_params_2('Hmm...', something=42) 
Traceback (most recent call last): 
 File "<stdin>", line 1, in <module> 
TypeError: print_params_2() got an unexpected keyword argument 'something' 
要收集关键字参数,可使用两个星号。
>>> def print_params_3(**params): 
... print(params) 
... 
>>> print_params_3(x=1, y=2, z=3) 
{'z': 3, 'x': 1, 'y': 2} 
如你所见,这样得到的是一个字典而不是元组。可结合使用这些技术。
 def print_params_4(x, y, z=3, *pospar, **keypar): 
       print(x, y, z) 
       print(pospar) 
       print(keypar) 
其效果与预期的相同。
>>> print_params_4(1, 2, 3, 5, 6, 7, foo=1, bar=2) 
1 2 3 
(5, 6, 7) 
{'foo': 1, 'bar': 2} 
>>> print_params_4(1, 2) 
1 2 3 
() 
{} 
通过结合使用这些技术,可做的事情很多。不管在函数定义中是否使用了*和**,都可在函数调用中使用它们。
现在回到最初的问题:如何在姓名存储示例中使用这种技术?解决方案如下:
def store(data, *full_names): 
    for full_name in full_names: 
         names = full_name.split() 
         if len(names) == 2: names.insert(1, '') 
         labels = 'first', 'middle', 'last' 
         for label, name in zip(labels, names): 
              people = lookup(data, label, name) 
              if people: 
                  people.append(full_name) 
              else: 
                  data[label][name] = [full_name] 
这个函数调用起来与只接受一个姓名的前一版一样容易。
>>> d = {} 
>>> init(d) 
>>> store(d, 'Han Solo') 
但现在你也可以这样做:
>>> store(d, 'Luke Skywalker', 'Anakin Skywalker') 
>>> lookup(d, 'last', 'Skywalker') 
['Luke Skywalker', 'Anakin Skywalker'] 
>>> print_params('Testing') 
('Testing',) 

 

3.分配参数
与收集参数相反的操作是什么呢?模块operator提供了这个函数的高效版本。假设有如下函数:
def add(x, y): 
    return x + y 

同时假设还有一个元组,其中包含两个你要相加的数。
params = (1, 2) 
这与前面执行的操作差不多是相反的:不是收集参数,而是分配参数。这是通过在调用函数(而不是定义函数)时使用运算符*实现的。
>>> add(*params) 

这种做法也可用于参数列表的一部分,条件是这部分位于参数列表末尾。通过使用运算符**,可将字典中的值分配给关键字参数。如果你像前面那样定义了函数hello_3,就可像下面这样做:
>>> params = {'name': 'Sir Robin', 'greeting': 'Well met'} 
>>> hello_3(**params) 
Well met, Sir Robin! 
如果在定义和调用函数时都使用*或**,将只传递元组或字典。因此还不如不使用它们,还可省却些麻烦。
>>> def with_stars(**kwds): 
...        print(kwds['name'], 'is', kwds['age'], 'years old') 
... 
>>> def without_stars(kwds): 
...        print(kwds['name'], 'is', kwds['age'], 'years old') 
... 
>>> args = {'name': 'Mr. Gumby', 'age': 42} 
>>> with_stars(**args) 
Mr. Gumby is 42 years old 
>>> without_stars(args) 
Mr. Gumby is 42 years old 
如你所见,对于函数with_stars,我在定义和调用它时都使用了星号,而对于函数without_stars,我在定义和调用它时都没有使用,但这两种做法的效果相同。因此,只有在定义函数(允许可变数量的参数)或调用函数时(拆分字典或序列)使用,星号才能发挥作用。

使用这些拆分运算符来传递参数很有用,因为这样无需操心参数个数之类的问题,如下所示:
def foo(x, y, z, m=0, n=0): 
    print(x, y, z, m, n) 
def call_foo(*args, **kwds): 
    print("Calling foo!") 
    foo(*args, **kwds) 
这在调用超类的构造函数时特别有用。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值