《老鸟python 系列》视频上线了,全网稀缺资源,涵盖python人工智能教程,爬虫教程,web教程,数据分析教程以及界面库和服务器教程,以及各个方向的主流实用项目,手把手带你从零开始进阶高手之路!点击 链接 查看详情




函数参数详解

阅读:227571286    分享到

定义函数的时候,我们把参数的名字和位置确定下来,函数的接口定义就完成了,对于函数的调用者来说,只需要知道如何传递正确的参数,以及函数将返回什么样的值就够了,函数内部的复杂逻辑被封装起来,调用者无需了解。

Python 的函数定义非常简单,但灵活度却非常大,除了正常定义的必选参数外,还可以使用默认参数、可变参数和关键字参数,使得函数定义出来的接口,不但能处理复杂的参数,还可以简化调用者的代码,本节我们就来学习函数的各种参数定义。

位置参数

位置参数也叫必传参数,也就是函数的实参的个数和位置必须和形参的个数和位置一致。说明:我们把函数定义时的参数叫做形参,调用函数时给的参数叫做实参。

def myfunc(name, age):
    print(name)
    print(age)

myfunc("ruhua", 18)                # 实参 "ruhua" 对应形参 name,实参 18 对应形参 age
myfunc("ruhua", "zhaoritian", 18)  # 错误,实参和形参的参数个数不匹配

Python 是动态语言,在定义函数的时候没法给形参定义类型,所以我们需要对调用的函数参数类型说明要清楚,不然会得到错误的逻辑结果。

def myfunc(name, age):
    print(name)
    print(age)

myfunc(18, "ruhua")  # 实参 18 对应形参形参 name,实参 "ruhua" 对应形参 age

默认参数

在函数定义时,如果给某个参数提供一个默认值,这个参数就变成了默认参数,不再是位置参数了。在调用函数的时候,我们可以给默认参数传递一个自定义的值,也可以使用默认值。

def girlsschool(name, sex="f"):
    print(name)
    print(sex)

girlsschool("wanghuahua")
girlsschool("ruhua", "m")

有多个默认参数时,调用的时候,既可以按顺序提供默认参数,也可以不按顺序提供部分默认参数,当不按顺序提供部分默认参数时,需要把参数名写上。

def girlsschool(name, age=18, sex="f"):
    print(name)
    print(age)
    print(sex)

girlsschool("ruhua", sex="m", age=17)

使用默认参数时要确保位置参数在前,默认参数在后,否则 Python 的解释器会报错。其实大家可以思考一下原因(如果默认参数在位置参数前面,由于位置参数是按次序接收实参的,所有默认参数在实参中必须给出,这样的话默认参数就没法默认了)。

def girlsschool(age=18, name):   # 函数参数定义错误
    pass

def _girlsschool(name, age=18):  # ok
    pass

默认参数详解

从一个例子说起,对于以下代码,我们预期的结果是:调用 func(),L 的值是 [2],再次调用 func(),L 的值还是 [2],而结果是:[2],[2, 2]。

def func(L=[]):
    L.append(2)
    print(L)

func()
func()

我们都知道函数的默认参数也是局部变量,但 Python 的函数在第一次使用默认参数的时候,该默认参数指向的内存就确定了,这与其他语言不同(在其他语言中,默认参数每次调用都要重新分配内存),我们打印出默认参数 L 的 id 发现两次调用都是一样。

def func(L=[]):
    print(id(L))
    L.append(2)
    print(L)

func()
func()

我们通过在函数体内定义一个局部变量,来看下 Python 解释器的行为,我们发现两次调用 L 指向的内存确实不是一个(第一次结果为 [2], 第二次结果为 [2]),但是打印出的内存地址确是一样的。

def func():
    L = []
    print(id(L))
    L.append(2)
    print(L)

func()
func()

导致上面结果的原因是内存分配机制是一样的,我们可以通过下面的代码来证明,通过下面的代码,我们发现第一次调用 func 函数中 L 的 id 和 mylist 的 id 是一样的,和第二次调用 func 函数中 L 的 id 并不一样。

def func():
    L = []
    print(id(L))
    L.append(2)
    print(L)

func()
mylist = []
print(id(mylist))
func()

对于默认参数是变量的情况出现的坑,我们可以通过修改默认参数的值为常量来避免。

def func(L=None):
    if not isinstance(L, list):
        L = []
    L.append(2)
    print(L)

func()
func()

为什么要设计 str,None 这样的不变对象呢?因为不变对象一旦创建,对象内部的数据就不能修改, 这样就减少了由于修改数据导致的错误。此外,由于对象不变,多任务环境下同时读取对象不需要加锁, 同时读一点问题都没有,所以我们在编写程序时,如果默认参数可以设计一个不变对象,那就尽量设计成不变对象。

可变参数

在 Python 函数中,实参个数可以是不固定的,就是我们调用函数时传入的实参个数是可变的(可以是任意个数,还可以是 0 个),可变参数的形参要写成 *变量名 的形式。

def func(*arg):
    print(arg)

func()
func(1)
func(1, 2, 3)

对于定义可变参数的函数,我们调用函数时,无论传入的实参参数类型是什么,个数是多少个(包括 0 个), Python 解释器都会把对应的实参组成一个 tuple 传给形参。

def func(*arg):
    print(arg)

func()                  # 0 个实参,arg 被组成 ()
func(1, 2)              # 2 个实参,arg 被组成 (1, 2)
func("hello")           # 1 个实参,arg 被组成 ("hello",)
func(1, [1, 2])         # 2 个实参,arg 被组成 (1, [1, 2])
func((1, 2))            # 1 个实参,arg 被组成 ((1, 2),)
func({1: 2})            # 1 个实参,arg 被组成 ({1: 2},)

如果传入的实参类型是集合(str, list, tuple, dict, set), Python 允许在实参前面加一个 * 号,此时 Python 解释器则会把实参中的所有成员(字典是键)展开。

def func(*arg):
    print(arg)

# 解释器会把 *"hello" 展开为 'h','e','l','l','o',实参变为('h','e','l','l','o')
func(*"hello")

# 解释器会把 *[1, 2] 展开为 1, 2,实参变为(1, 1, 2)
func(1, *[1, 2])

# 解释器会把 *(1, 2) 展开为 1, 2,实参变为(1, 2)
func(*(1, 2))

# 解释器会把 *{1: "1", 2: "2"} 展开为 1, 2 ,实参变为(1, 2)
func(*{1: "1", 2: "2"})

如果可变参数在位置参数或默认参数的前面,就变成了命名关键字参数(下面学习)。

def girlsschool(*sex, name):     # 命名关键字参数(下面讲解定义)
    pass

def _girlsschool(*sex, age=18):  # 命名关键字参数(下面讲解定义)
    pass

def _girlsschool(*sex, age, name="ruhua"):  # 命名关键字参数(下面讲解定义)
    pass

def _girlsschool(*sex, age=18, name):  # 命名关键字参数(下面讲解定义)
    pass

def _girlsschool(name, *sex, age=18):  # 命名关键字参数(下面讲解定义)
    pass

def _girlsschool(name="ruhua", *sex, age):  # 命名关键字参数(下面讲解定义)
    pass

关键字参数

可变参数允许你传入 0 个或任意个参数,我们知道这些可变参数在函数调用时自动组装为一个 tuple。而关键字参数允许你传入 0 个或任意个含参数名的参数,这些关键字参数在函数内部自动组装为一个 dict。

def func(**arg):
    print(arg)

func()
func(name="ruhua", age=18)

如果传入的实参类型是 dict,Python 允许在实参前面加一个 ** 号,此时 Python 解释器则会把字典类型的实参转为 key=value 的形式。

def func(**arg):
    print(arg)

stuents_dict = {"name": "ruhua", "age": 18}

# 解释器会把 **stuents_dict 转为 name="ruhua", age=18,最后的实参为(name="ruhua", age=18)
func(**stuents_dict)

# 解释器会把 **stuents_dict 转为 name="ruhua", age=18,最后的实参为(sex="f", name="ruhua", age=18)
func(sex="f", **stuents_dict)

要确保关键字参数在可变参数和默认参数和位置参数的后面,否则 Python 的解释器会报错。

def girlsschool(name, age=18, **beauty, *sex):   # 函数参数定义错误
    pass

def _girlsschool(name, age=18, *sex, **beauty):  # ok
    pass

命名关键字参数

命名关键字参数是在 Python 3 中新增加的一种语法,它和关键字参数 **kw 不同,命名关键字参数需要一个特殊分隔符 ** 后面的参数被视为命名关键字参数。例如,只接收 sex 和 lover 作为关键字参数,这种方式定义的函数如下。

def Human(name, age, *, sex, lover):
    print(name, age, sex, lover)

Human('ruhua', 18, sex='f', lover='tangbohu')

如果函数定义中已经有了一个可变参数,后面跟着的命名关键字参数就不再需要一个特殊分隔符 * 了。

def Human(name, age, *args, sex, lover):
    print(name, age, args, sex, lover)

Human('ruhua', 18, sex='f', lover='tangbohu')

命名关键字参数必须传入参数名,这和位置参数不同,如果没有传入参数名,Python 解释器则报异常。

def Human(name, age, *, sex, lover):
    print(name, age, sex, lover)

Human('ruhua', 18, 'f', 'tangbohu')  # 必须要指定参数名 sex 和 lover

命名关键字参数可以有默认值,从而简化调用,给命名关键字参数设置默认值和给默认参数设置默认值的规则不一样,命名关键字参数不限制默认值的顺序。

def Human(name, age, *, sex, lover="tangbohu"):
    print(name, age, sex, lover)

Human('ruhua', 18, sex='f')

##################我是分割线##################
def Human(name, age, *, sex="f", lover):
    print(name, age, sex, lover)

Human('ruhua', 18, lover='tangbohu')

各种类型参数组合使用

在 Python 中定义函数,可以用位置参数,默认参数,可变参数和关键字参数,这 4 种参数都可以一起使用,或者只用其中某些,但是请大家养成一个好习惯,尽量不要使用命名关键字参数,参数定义的顺序最好按照:必选参数,默认参数,可变参数和关键字参数的写法。

def func(a, b="ok", *c, **d):
    pass

参数组合混用的时候,如果形参的默认参数后面有可变参数,传给默认参数的实参最好不要指定名称。

def girlsschool(age=18, *sex):
    print(age)
    print(sex)

girlsschool(age=19, "f")   # 错误

本节重要知识点

Python 中默认参数的实现机制。

可变参数和关键字参数的实现机制

参数组合混用注意事项。

作业

著名公司(某度)为什么我们经常用到的第三方库的函数喜欢用 func(*arg, **kwarg) 的形式来定义函数参数。


如果以上内容对您有帮助,请老板用微信扫一下赞赏码,赞赏后加微信号 birdpython 领取免费视频。


登录后评论

user_image
木大木大
2020年3月14日 21:47 回复
语法是*和**。名称*args和**kwargs只是按照惯例,但没有硬性要求使用它们。

当您不确定有多少参数可以传递给函数时,可以使用*args,也就是说,它允许您向函数传递任意数量的参数。

user_image
刘雅骏
2020年2月20日 07:30 回复
我不明白这对什么编程任务有帮助。

也许吧:

我想输入列表和字典作为函数的参数,同时作为通配符,这样我就可以传递任何参数了?

有没有一个简单的例子来解释如何使用*args和**kwargs?

另外,我发现的教程只使用了"*"和变量名。

*args和**kwargs是仅仅是占位符还是在代码中使用*args和**kwargs?

user_image
多年微软MVP
2020年1月25日 10:24 回复

*arg相当于不定个数的参数元组,有时可以放在参数表的最后,把多出来的参数都归到这里。

**kwargs相当于把多余的参数以字典的形式收集起来。


user_image
纯纯
2019年8月29日 00:53 回复

对于任意函数,都可以通过类似func(args,*kw)的形式来调用它,无论它的参数如何定义的 假设func需要传入参数的形式是(a,b,c,d=1,e=2,f=3)

args =(a,b,c)

kw = {d:1,e:2,f:3}

那么以func(args,*kw)这样的参数传入时zhi ,参数列表就变成了(a,b,c,d=1,e=2,f=3) 上面的dao例子,传入参数已经很复杂了1653。基本上所有的参数组合情况都不会超出这个复杂程度。最简单的没有参数,那么直接就是args,kw为空。只有有位置参数和默认参数args不为空,kw为空。依此类推,你可以按不同的参数组合方式去比对,所有的组合都可以这么传参的。跟你定没定义可变参数和关键字参数没有关系。不管怎样,传入参数形式最复杂的方式无外乎就是上面那个例子——位置、可变、关键字都有。


user_image
Zoffy
2019年4月7日 01:03 回复
*args:可以理解为只有一列的表格,长度不固定。

            **kwargs:可以理解为字典,长度也不固定。

            1、函数调用里的*arg和**kwarg:
              (1) *arg:元组或列表“出现”
                        **kwarg:字典“出没”
              (2)分割参数
             2、函数定义时传的*arg /**kwarg:
              (1)接收参数

user_image
DCjanus
2019年3月14日 21:14 回复
实际上真正的Python参数传递语法是*和**。
*args和**kwargs只是一种约定俗成的编程实践。我们也可以写成*vars和**kvars。

*args和**kwargs一般是用在函数定义的时候。
二者的意义是允许定义的函数接受任意数目的参数。
也就是说我们在函数被调用前并不知道也不限制将来函数可以接收的参数数量。
在这种情况下我们可以使用*args和**kwargs。