一、函数参数形式
位置参数:def f(a,b),调用函数f(a,b), a和b都是位置参数
键参数:def f(a,b),调用函数f(a,b=3),a时位置参数,b是键参数
(函数参数是位置参数还是键参数是由调用函数的实参形式决定的,与定义函数的形参无关)
扩展位置参数:def f(a,b,*lst),*lst是扩展位置参数
扩展键参数:def f(a,b,**key),**key是扩展键参数
二、函数传参方式
def describe_pet(animal_type, pet_name="dawang"): #位置参数和键参数
"""显示宠物的信息"""
print("\nI have a " + animal_type + ".")
print("My " + animal_type + "'s name is " + pet_name.title() + ".")
位置实参:describe_pet('hamster', 'harry'),顺序必须一致。
关键字实参: describe_pet(pet_name='harry', animal_type='hamster')
默认值:pet_name参数指定了默认值,函数调用可以不传递该参数:
describe_pet("hamster")或describe_pet(animal_type="hamster")
有默认值的参数一定要放在没有默认值的参数后面。
def make_pizza(*toppings): #扩展位置参数,参数保存在元组toppings中
"""概述要制作的比萨"""
print("\nMaking a pizza with the following toppings:")
for topping in toppings:
print("- " + topping)
def build_profile(first, last, **user_info): #扩展键参数,参数保存在字典user_info中
"""创建一个字典, 其中包含我们知道的有关用户的一切"""
profile = {}
profile['first_name'] = first
profile['last_name'] = last
for key, value in user_info.items():
profile[key] = value
return profile
传递任意数量的实参:
make_pizza('mushrooms', 'green peppers', 'extra cheese')
user_profile = build_profile('albert', 'einstein',location='princeton',field='physics')
如果要让函数接受不同类型的实参, 必须在函数定义中将接纳任意数量实参的形参放在最后。 Python先匹配位置实参或关键字实参, 再将余下的实参都收集到最后一个形参中 。
二、函数传参原理
1、位置参数传参需要按顺序?
因为函数调用时,位置参数从左到右压入运行时栈并按序拷贝到f_localsplus指向的内存区域。在访问函数参数时,是通过索引(偏移位置)访问位置参数,所以参数的顺序十分重要。
2、有默认值的参数一定要放在没有默认值的参数后面,且要连续设置?(函数参数默认值从函数参数列表最右端开始,必须连续设置)
这要从如何函数调用开始后的PyEval_EvalCodeEx函数说起
def f(a=1,b=2):
print(a+b)
假设有这样的函数定义,可以通过f(),f(2),f(b=3)等方式进行调用。Python虚拟机需要面对判断是否需要设置默认参数,设置多少个默认参数的问题。
根据函数定义,co_argcount=2(函数参数个数),默认参数个数default=2。
(1)f()调用该函数
位置参数个数argcount=na=0,键参数个数kwcount=nk=0。
if(co->co_argcount>0||co->co_flags&(CO_VARARGS|CO_VARKEYWORDS)){
int i;
...
//n是函数调用传入的位置参数个数,即na,这里是0
int n=argcount;
//[1]如果传入的位置参数个数比函数定义的参数个数小,即虚拟机需要设置默认参数
if(argcount<co->co_argcount){
//[2]m(无默认值的参数)=函数定义的参数个数-有默认值的参数,无默认值参数必须函数调用时传入,这里m=0
int m=co->co_argcount-default;
for(i=argcount;i<m;i++){
if(GETLOCAL(i)==NULL){
goto fial;
}
}
//[3]n如果大于m,证明希望替换一些函数定义的有默认值的参数。n-m是函数调用传入得参数中,有多少个参数用于默认位置参数的,这里是0
if(n>m)
i=n-m;
else
i=0;
//[4]设置默认位置参数的默认值,这里i=0,default=2,需要设置两个默认参数
for(;i<default;i++){
if(GETLOCAL(m+i)==NULL){
//defs指向函数定义的默认值参数列表
PyObject *def=defs[i];
Py_INCREF(def);
SETLOCAL(m+i,def);
}
}
}
}
从步骤4可以看到,前面m个参数(无默认值参数)是不需要设置默认值的。需要设置得默认参数个数是default-i个,而且是连续设置的。
整段代码的设计,已经是立在非默认参数在前,默认参数在后且连续的前提上。
(1)f(b=3)调用该函数
位置参数个数argcount=na=0,键参数个数kwcount=nk=1,kws指向函数调用的键参数,kws[0]="b",kws[1]=3。
if(co->co_argcount>0||co->co_flags&(CO_VARARGS|CO_VARKEYWORDS)){
int i;
int n=argcount;
...
//[1]遍历函数调用传入的所有键参数
for(i=0;i<kwcount;i++){
PyObject *keyword=kws[2*i]; //键参数的键,这里是"b"
PyObject *value=kws[2*i+!]; //键参数的值,这里是3
int j;
//[2]遍历函数定义的所有参数(保存在变量表co->co_varnames中),查找和传入的键参数键keyword相同的默认参数,这里是b
for(j=0;j<co-co_argcount;j++){
PyObject *nm=PyTuple_GET_ITEM(co->co_varnames,j);
int cmp=PyObject_RichCompareBool(keyword,nm,Py_EQ);
if(cmp>0)
break;
else if(cmp<0)
goto faill
}
//[3]如果keyword不在变量名表,这里是(a,b)
if(j>=co-co_argcount){...}
//[4]如果keyword在变量名表
else{
if(GETLOCAL(j)!=NULL){
goto fail;
}
Py_INCREF(value);
//设置"b"的键值是3
SETLOCAL(j,value);
}
}
...
//[4]设置默认位置参数的默认值,这里i=0,default=2,需要设置两个默认参数
for(;i<default;i++){
if(GETLOCAL(m+i)==NULL){
//defs指向函数定义的默认值参数列表
PyObject *def=defs[i];
Py_INCREF(def);
SETLOCAL(m+i,def);
}
}
}
3、如果要让函数接受不同类型的实参, 必须在函数定义中将接纳任意数量实参的形参(扩展位置参数和扩展键参数)放在最后?
def f(value,*list,**key)
根据函数定义,co_argcount=1(函数参数个数),在python内部,lst是有PyTupleObject实现的局部变量,**key由PyDictObject实现的局部变量,不计入co_argcount中。如果由lst形式的位置参数,co_flag会设置CO_VARARGS标识符;而如果有**key形式的参数,co_flag会设置CO_VARKEYWORDS标识符。
假设以f(-1,1,2,a=3,b=4)的方式调用函数
位置参数个数argcount=na=3,键参数个数kwcount=nk=2。args指向函数调用的位置参数(-1,1,2)。先来看看扩展位置参数的访问;
if(co->co_argcount>0||co->co_flags&(CO_VARARGS|CO_VARKEYWORDS)){
int i;
int n=argcount;
//这里argcount=3,co->co_argcount=1
if(argcount>co->co_argcount){
n=co_co_argcount;
}
//[1]设置位置参数值,n是函数定义的位置参数个数
for(i=0;i<n;i++){
x=args[i];
SETLOCAL(i,x);
}
if(co->co_flags&CO_VARARGS){
//[2]u是扩展位置参数个数
u=PyTuple_New(argcount-n);
SETLOCAL(co->co_argcount,u);
//[3]将扩展位置参数放入到PyTupleObject中
for(i=n;i<argcount;i++){
x=arg[i];
PyTuple_SET_ITEM(u,i-n,x);
}
}
}
在[3]处,扩展位置参数一股脑的塞进了PyTupleObject对象中。在[2]处,这个存放扩展位置参数的元素放入了f_localsplus,且在位置参数后面。而后面的扩展键参数,又会放在PyTupleObject对象后面,如下:
if(co->co_argcount>0||co->co_flags&(CO_VARARGS|CO_VARKEYWORDS)){
int i=0;
int n=argcount;
PyObject *kwdict=NULL;
...
//[1]创建PyDictObject对象,放在PyTupleObject之后
if(co->co_flags&CO_VARKEYWORDS){
kwdict=PyDict_New();
i=co->co_argcount;
if(co->co_flags&CO_VARARGS)
i++;
SETLOCAL(i,kwdict);
}
//[2]遍历函数调用传入的所有键参数,确定函数的def语句中是否出现了见参数的keyword
for(i=0;i<kwcount;i++){
PyObject *keyword=kws[2*i];
PyObject *value=kws[2*i+!];
int j;
//[2]遍历函数定义的所有参数(保存在变量表co->co_varnames中),查找和传入的键参数键keyword相同的默认参数,这里是b
for(j=0;j<co-co_argcount;j++){
PyObject *nm=PyTuple_GET_ITEM(co->co_varnames,j);
int cmp=PyObject_RichCompareBool(keyword,nm,Py_EQ);
if(cmp>0)
break;
else if(cmp<0)
goto faill
}
//[3]如果keyword不在变量名表
if(j>=co-co_argcount){
PyDict_SetItem(kwdict,keyword,value);
}else{
...
}
}
f(-1,1,2,a=3,b=4)调用函数后,PyFrameObject对象中f_localsplus的结构如下