1.修改参数
在def语句中,位于函数名后面的变量通常称为形参,而调用函数时提供的值称为实参。
>>>def try_to_change(n):
n = 'Mr.Gumby'
>>>name = 'Mrs.Entity'
>>>try_to_change(name)
>>>name
'Mrs.Entity'
#在try_to_change内,将新值赋给了参数n,对参数name没有影响
#传递并修改参数的效果类似于下面👇
>>>name='Mrs.Entity'
>>>n=name
n='Mr.Gumby'
>>>name
'Mrs.Entity'
结果显而易见:变量n变了,但变量name没变。同样,在函数内部重新关联参数(即给它赋值时)时,函数外部的变量不受影响。
字符串,数,元组是不可变的,这意味着它们是不可修改的(即只能替换为新值)。
参数存储在局部作用域中。
将同一个列表赋给两个变量时,这两个变量将同时指向这个列表。而想要避免这个结果,就必须创建列表的副本。对序列执行切片操作时,返回的切片都是副本。因此,如果你创建覆盖整个列表的切片,得到的将是列表的副本
>>>names=['Mr.Li','Mr.Mi']
>>>n=names[:]#这时,n和name包含两个相等但不同列表
>>>>n is names
False
>>>n==names
True#现在如果修改n,将不会影响names
>>>n[0]='Mr.Ni'
>>>n
['Mr.Ni', 'Mr.Mi']
>>>names
['Mr.Li', 'Mr.Mi']
>>>change(names[:])
>>>names
['Mr.Li','Mr.Mi']
(1)为什么要修改参数(参数可变)
在提高程序的抽象程度方面,使用函数来修改数据结构是一种不错的方式。
数据结构storage:它是一个字典,包含三个键:first、middle、last。每个键下
都存储一个字典。
storage={}
storage['first']={}
storage['middle']={}
storage['last']={}
抽象的关键在于隐藏所有的更新细节,为此可使用函数。
初始化数据结构的函数:
def init(data):
data['first']={}
data['middle']={}
data['last']={}
也可以像下面这样使用这个函数:
storage={}
init(storage)
storage
{'middle':{},'last':{},'first':{}}
如你所见,代码的可读性高了许多。
def lookup(data,label,name):
return data[label].get(name)
函数lookup接受参数label和name,并返回一个由全名组成的列表。
下面是将人员存储到数据结构中的函数。
def store(data.full_name):
name=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]
函数store执行如下操作:
- 将参数data和full_name提供给这个函数。这些参数被设置为从外部获得的值。
- 通过拆分full_name创建一个,名为names的列表‘
- 若names的长度为2(只有名字和姓),就将中间名设置为空字符串。
- 将’first’、‘middle’和‘last’存储在元组labels(也可使用列表,这里使用元组为了省略方括号。)
(2)参数不可变
在Python中,是没有办法通过给参数赋值并让这种改变影响函数外部的变量的;只能修改参数本身。(C++、Pascal、Ada等语言可以 )
应从函数返回所有需要的值(若需要返回多个值,就以元组的方式返回它们。)
例如:
可以像下面这样编写将变量的值加一的函数:
>>>def inc(x):return x+1
...
>>>foo=10
>>>foo=inc(foo)
>>>foo
11
如果一定要修改函数,可以像下面这样编写:
>>>def inc(x):x[0]=x[0]+1
...#将值放入列表内
>>>foo=[10]
>>>inc(foo)
>>>foo
[11]
2.关键字函数和默认值
前面使用的参数都是位置参数,因为它们的位置至关重要——比名称还重要。
而使用名称指定的参数称为关键字参数。主要的优点是:有助于澄清各个参数的作用。
def hello_1(greeting,name):
print('{}.{}!'.format(greeting,name))
#功能相同,参数的排列顺序相反。
def hello_2(name,greeting):
print('{}.{}!'.format(name,greeting))
有时,参数的排列顺序难以记住,尤其是参数很多的时候。为简化调用工作,可指定参数的名称。
>>>hello_1(name='world',greeting='hello,')
>hello,world
>>>hello_2(greeting='world',name='hello,')
>hello,world
关键字参数最大的优点在于:可以指定默认值。
>>>def hello_3(greeting='hello',name='world')
> print('{},{}!'.format(greeting,name))
像这样指定关键值后,调用函数时可不提供参数值!也可根据需求提供部分参数值或全部参数值。
>>>hello_3()
>hello,world!
>>>hello_3('Greetings')
>Greetings,world!
>>>hello_3('Greetings','Liming')
>Greetings,Liming.
如你所见,仅使用位置参数就很好,只不过若要提供参数name,必须同时提供参数greeting。如果只想提供参数name,并让greeting使用默认值该如何做呢?
>>>hello_3(name='Liming')
>hello,Liming!
你可以结合使用位置参数和关键字参数,但必须指定所有的位置参数,否者解释器将不知道它们是哪个函数(即不知道参数对应的位置)。
⚠️⚠️⚠️注意:通常不建议这样做。
3.收集参数
有时候,允许用户提供任意数量的参数很有用。
>>>def print_params(*params):
print(params)
...
>>>print_params('testing')
('testing',)
#注意到打印的是一个元组,因为里面有一个逗号。
>>>print_params(1,2,3)
(1, 2, 3)
#参数前面的星号将提供的值都放在一个元组里。
赋值时带星号的变量收集多余的值。若没有可供收集的参数,params将是一个空元组。
>>>def print_params_2(title,*params):
print(title)
print(params)
>>>print_params_2('Params:',1,2,3)
Params:
(1, 2, 3)
>>>print_params_2('Nothing:')
Nothing:
()
和赋值时相同,带星号的参数可以放在任意位置。不同的是,需要使用名称来指定后续参数。
>>>def in_the_middle(x,*y,z):
print(x,y,z)
>>>in_the_middle(1,2,3,4,5,6,7,z=9)
1 (2, 3, 4, 5, 6, 7) 9
>>>in_the_middle(1,2,3,4,5,6,7,9)
Traceback (most recent call last):
File "<pyshell#5>", line 1, in <module>
in_the_middle(1,2,3,4,5,6,7,9)
TypeError: in_the_middle() missing 1 required keyword-only argument: 'z'
星号不会收集关键值函数,若要收集关键字参数,可使用两个星号。
>>>def print_params_3(**params):
print(params)
>>>print_params_3(a=1,b=2,c=3)
{'a': 1, 'b': 2, 'c': 3}
如你所见,这样得到的是一个字典而不是元组。
4.分配参数
调用参数时使用运算符(*/**)实现。
>>>def agg(x,y):
return x+y
>>>p=(1,2)
>>>agg(*p)
3
这种做法也可用于参数列表的一部分,条件是这部分位于参数列表末尾。使用运算符**,可以将字典中的值分配给关键字参数。
>>>def hello_6(名字='黎明',问候语='吃了吗'):
print('{},{}!'.format(名字,问候语))
>>>hello_6()
黎明,吃了吗!
>>>P={'名字':'离宁','问候语':'晚安'}
>>>hello_6(**P)
离宁,晚安!
若在定义和调用函数时都使用*/**,将只传递元组或字典。因此还不如不使用它们,还可以省些麻烦。
>>>def with_stars(**kwds):
print(kwds['名字'],'今年有',kwds['年龄'],'岁了')
...
>>>def without_stars(kwds):
print(kwds['名字'],'今年有',kwds['年龄'],'岁了')
...
>>>W={'名字':'黎明','年龄':'22'}
>>>with_stars(**W)
黎明 今年有 22 岁了
>>>without_stars(W)
黎明 今年有 22 岁了
如你所见,对于函数with_stars,在定义和调用它时都使用了星号,而对于函数without_stars,则什么操作都没做,但两者的效果相同。所以,只有在定义函数(允许可变数量的参数)或调用函数时(拆分字典或序列)使用,星号才会发挥作用。
提示:使用这些拆分运算符来传递参数很有用,因为这样无需操心参数个数的问题。
>>>def foo(a,b,c,d=1,e=0):
print(a,b,c,d,e)
...
>>>def call_foo(*P,**W):
print("Calling foo!")
foo(*P,**W)
这在调用超类的构造函数时特别有用