学为所用

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::

详解Python函数参数定义及传参(必备参数、关键字参数、默认可省略参数、可变不定长参数、*args、**kwargs)

Python函数参数传参的种类

  Python中函数参数定义及调用函数时传参大体可分必备参数、关键字参数、默认可省略参数、不定长元组参数、不定长关键字参数等,下面通过函数定义及调用上的不同情况深入解析这些中参数的不同及应用场景。

  为了更好的理解不同参数的具体意义,所以下面演示代码中,使用的参数数量较多。具体是一个调用MySQL数据库配置参数的函数所需要的参数,我们用这个来演示不同类型的特点及适用方法,了解每种类型的应用场景及优缺点。

必备参数__仅赋值传参

  必备参数,就是在调用函数的时候,定义的参数要全部都有赋值,否则执行的时候代码会报错。

#!/usr/bin/env python
# -*- coding: utf-8 -*-


# 演示获得数据库配置参数,使用必备参数
def demo_get_conf1(user, pw, host, port, db, charset):
    "打印得到的数据库配置"
    print('host: ', host)
    print('port: ', port)
    print('user: ', user)
    print('pw: ', pw)
    print('db: ', db)
    print('charset: ', charset)


demo_get_conf1('root', '1234', '127.0.0.1', '3306', 'tests', 'utf8')

  上述代码中,调用demo_get_conf1函数的时候,定义的所有参数都必须传递,并且要按照规定的顺序传递,否则函数体内得到的也是错误的。以上代码控制台输出:

host:  127.0.0.1
port:  3306
user:  root
pw:  1234
db:  tests
charset:  utf8

  这也是我们希望得到的正确结果。

  下面我们把上述代码最后一行调用的前两个参数("root"和"1234")对调一下:

demo_get_conf1('1234', 'root', '127.0.0.1', '3306', 'tests', 'utf8')

  执行后控制台输出:

host:  127.0.0.1
port:  3306
user:  1234
pw:  root
db:  tests
charset:  utf8

  很显然,得到的 user 变成了 1234,pw 变成了 root,也就是说是完全按照位置来对应函数定义时的参数变量,所以传参的时候,顺序不能错,参数比较多的时候,就不容易记住顺序了。那么Python还给大家一种传递方式,同样是上面的函数,可以不用按顺序传参,请看下节“必备参数__键值对传参”。

必备参数__键值对传参(关键字参数)

  同样是必备参数,但是在代用函数传参的时候,可以直接使用键值对的方式,看下面演示代码:

#!/usr/bin/env python
# -*- coding: utf-8 -*-


# 演示获得数据库配置参数,使用必备参数
def demo_get_conf1(user, pw, host, port, db, charset):
    "打印得到的数据库配置"
    print('host: ', host)
    print('port: ', port)
    print('user: ', user)
    print('pw: ', pw)
    print('db: ', db)
    print('charset: ', charset)


demo_get_conf1(
    charset='utf8',
    pw='1234',
    user='root',
    host='127.0.0.1',
    port='3306',
    db='tests')

  控制台输出:

host:  127.0.0.1
port:  3306
user:  root
pw:  1234
db:  tests
charset:  utf8

  函数定义还是与上例一样,只是调用函数的时候,参数传递使用了键值对,键名就是参数定义时的变量名,这样就可以不用理会顺序,只要记住键名(参数变量名)就可以了。

  但是这毕竟是必备参数,所有的参数都要传递,否则会报错,例如将调用修改成:

demo_get_conf1(
    pw='1234',
    user='root',
    host='127.0.0.1',
    port='3306',
    db='tests')

  控制台会输出:

TypeError: demo_get_conf1() missing 1 required positional argument: 'charset'

  提示缺少'charset'参数,代码不能正常运行。那么有没有可以缺省参数,用就传递,不用就不传递的,Python肯定也有这种参数,继续看下一节“默认可省略参数”。

默认可省略参数

  默认可省略参数,就是在定义的时候就给了默认值,如果在函数调用的时候给这个参数传值了,那么就使用传递的值,如果没有传递就使用定义时候的默认值。

#!/usr/bin/env python
# -*- coding: utf-8 -*-


# 演示获得数据库配置参数,使用默认可省略参数
def demo_get_conf2(user, pw, host, port, db, charset='utf8'):
    "打印得到的数据库配置"
    print('host: ', host)
    print('port: ', port)
    print('user: ', user)
    print('pw: ', pw)
    print('db: ', db)
    print('charset: ', charset)


demo_get_conf2('root', '1234', '127.0.0.1', '3306', 'tests')

  在上述代码中,调用demo_get_conf2函数的时候,我们并没有传递第六个参数charset,但是代码没有报错,控制台输出:

host:  127.0.0.1
port:  3306
user:  root
pw:  1234
db:  tests
charset:  utf8

  可见参数"charset"虽然在调用的时候没有传递,但是依然得到了值"utf8",这个值就是在函数定义时候,参数"charset"赋值的默认值。

  但是这个参数必须在后面,否则调用的时候按照顺序赋值的时候,少传递一个就不是这个有默认值的了,所以Python不允许那样做,看下面的例子:

#!/usr/bin/env python
# -*- coding: utf-8 -*-


# 演示获得数据库配置参数,使用默认可省略参数
def demo_get_conf1(user, pw, host, port, db='tests', charset):
    "打印得到的数据库配置"
    print('host: ', host)
    print('port: ', port)
    print('user: ', user)
    print('pw: ', pw)
    print('db: ', db)
    print('charset: ', charset)


demo_get_conf1('root', '1234', '127.0.0.1', '3306', 'tests')

  控制台输出:

SyntaxError: non-default argument follows default argument

  提示非默认参数不能在默认参数之后,代码不能正常执行。

  有些时候,我们不能确定要具体传递几个参数,应用场景需要的参数数量差异较大,那么是否可以动态传递不同数量的参数呢,继续看下一节“可变长元组参数”

不定长元组参数(*args)

  不定长元组参数,就是不确定数量的参数,定义一个参数把传入的参数组合成元组,来接收函数调用时传递过来的N个参数,在函数体内以元组形式按顺序读取。为了演示更多是使用场景,下面没有使用网络中通常使用的循环方式来取可变长元组参数。

#!/usr/bin/env python
# -*- coding: utf-8 -*-


# 演示获得数据库配置参数,使用可变长元组参数
def demo_get_conf3(host, port, *cnf):
    "打印得到的数据库配置"
    print('host: ', host)
    print('port: ', port)
    print('user: ', cnf[0])
    print('pw: ', cnf[1])
    print('db: ', cnf[2])
    print('charset: ', cnf[3])


demo_get_conf3('127.0.0.1', '3306', 'root', '1234', 'tests', 'utf8')

控制台输出:

host:  127.0.0.1
port:  3306
user:  root
pw:  1234
db:  tests
charset:  utf8

  可见这是我们正常需要得到的结果,可以多传更多的参数,只要里面取值的数量没有超过传递过来可变参数的数量,就不会报错。

  在上例中,host 和 port 是必备参数,函数调用的时候,在这两个参数后面所传递的,就都是对应到函数定义时的变长参数元组里了。

  在Python中,函数参数是可以使用元组的,那么这总定义与直接使用元组参数有什么区别呢,下面看使用元组参数的示例:

#!/usr/bin/env python
# -*- coding: utf-8 -*-


# 演示获得数据库配置参数,使用元组参数
def demo_get_conf4(host, port, cnf):
    "打印得到的数据库配置"
    print('host: ', host)
    print('port: ', port)
    print('user: ', cnf[0])
    print('pw: ', cnf[1])
    print('db: ', cnf[2])
    print('charset: ', cnf[3])


demo_get_conf4('127.0.0.1', '3306', ('root', '1234', 'tests', 'utf8'))

  控制台输出:

host:  127.0.0.1
port:  3306
user:  root
pw:  1234
db:  tests
charset:  utf8

  与上一例比,函数体一模一样,参数 cnf 只是少了个 “*”。重点是调用的时候不同,可以看出明显的区别,可变长元组参数在调用的时候,可以与必备参数一样依次传递,而定义元组类型参数,调用函数传递参数时,需要传递元组类型的数据才可以。

  这种参数传递的时候,元组里面的元素也是要强调顺序的,如果是累加一类的函数,顺序不重要,如果是每个元素都代表不同具体含义的,那顺序就十分重要,不可以搞错,否则与必备参数一样,会在函数体内取值错误。

  在上两例中,只能算是元组参数,还不能算不定长,因为函数体内的取值规定了元组的元素数量,那接下来看一个网络上通常写法的例子:

#!/usr/bin/env python
# -*- coding: utf-8 -*-


# 演示获得数据库配置参数,使用可变长元组参数
def demo_get_conf5(x, y, *nums):
    "得到累加和"
    res = x + y
    for i in nums:
        res += i
    return res


print(demo_get_conf5(10, 20,))
print(demo_get_conf5(10, 20, 15, 25, 30))
print(demo_get_conf5(10, 20, 15, 25, 30, 50, 30))

  控制台输出:

30
100
180

  上例中,前两个参数是必须传的,后面的参数可传可不传,传的数量也不固定,根据需要由外部调用决定,所以这是可变长参数。但是这种应用适合元组内参数是相同类型和作用,如果回到上面的配置参数应用中,是否可以不定长的呢,看下面的代码:

#!/usr/bin/env python
# -*- coding: utf-8 -*-


# 演示获得数据库配置参数,使用可变长元组参数
def demo_get_conf6(host, port, *cnf):
    "打印得到的数据库配置"
    arr = ['root', '1234', 'tests', 'utf8']     # 可变参数的默认值
    len_arr = len(arr)
    len_cnf = len(cnf)
    if len_cnf > len_arr:
        len_cnf = len_arr                       # 取变长参数最多不超过默认列表中的数量,多余的忽略

    for i in range(len_cnf):
        arr[i] = cnf[i]

    print('host: ', host)
    print('port: ', port)
    print('user: ', arr[0])
    print('pw: ', arr[1])
    print('db: ', arr[2])
    print('charset: ', arr[3])


print('\n', '传递5个变长参数')
demo_get_conf6('127.0.0.1', '3306', 'new_user', '5678', 'tests', 'utf8mb4', 'abc')
print('\n', '传递4个变长参数')
demo_get_conf6('127.0.0.1', '3306', 'new_user', '5678', 'tests', 'utf8mb4')
print('\n', '传递2个变长参数')
demo_get_conf6('127.0.0.1', '3306', 'new_user', '5678')
print('\n', '不传递变长参数')
demo_get_conf6('127.0.0.1', '3306')

  控制台输出:

 传递5个变长参数
host:  127.0.0.1
port:  3306
user:  new_user
pw:  5678
db:  tests
charset:  utf8mb4

 传递4个变长参数
host:  127.0.0.1
port:  3306
user:  new_user
pw:  5678
db:  tests
charset:  utf8mb4

 传递2个变长参数
host:  127.0.0.1
port:  3306
user:  new_user
pw:  5678
db:  tests
charset:  utf8

 不传递变长参数
host:  127.0.0.1
port:  3306
user:  root
pw:  1234
db:  tests
charset:  utf8

  可以看出,传递5个变长参数的,多出那个“abc”参数被忽略掉了,余下的四个参数都按照传递的值取到了;传递4个变长参数的,完全吻合,得到的都是传递的参数;传递2个变长参数的,前两个变长参数是调用传输时传递的值,后两个则是使用的默认值;不传递变长参数的,变长参数全部使用了默认值。这就实现了不同用途的变长参数取值并都可以设置默认值的目的,在一定范围内实现了不定长。

  但是这种变长参数,都还是要保证传递顺序的,元组里的顺序如果传递错误,对于后面例子那获取的数据就是错误的。是否可以不定长有不用理会顺序呢,继续看下一节“不定长字典参数”。

不定长字典参数(**kwargs)

  不定长字典参数,就是不确定数量的参数,定义一个字典,按键值对形式来接收函数调用时传递过来的N个参数,在函数体内以字典形式按键值对读取。这样在传递的时候,就可以不用在意顺序问题了,看下面的代码:

#!/usr/bin/env python
# -*- coding: utf-8 -*-


# 演示获得数据库配置参数,使用可变长字典参数
def demo_get_conf7(host, port, **cnf):
    "打印得到的数据库配置"
    print('host: ', host)
    print('port: ', port)
    print('user: ', cnf['user'])
    print('pw: ', cnf['pw'])
    print('db: ', cnf['db'])
    print('charset: ', cnf['charset'])


demo_get_conf7('127.0.0.1', '3306', user='new_user', pw='5678', db='tests', charset='utf8mb4')

  控制台输出:

host:  127.0.0.1
port:  3306
user:  new_user
pw:  5678
db:  tests
charset:  utf8mb4

  在上述代码中,可以看出,在函数体内实际把**cnf参数当做字典来读取,那么与把函数参数直接定义成字典来用相比较,对函数体内是没有区别的,但是在函数调用的时候,参数传递就有差别了。如果参数定义成字典,那么调用的时候就需要传递字典,否则会报错,如下面代码:

# 演示获得数据库配置参数,使用字典参数
def demo_get_conf7(host, port, cnf):
    "打印得到的数据库配置"
    print('host: ', host)
    print('port: ', port)
    print('user: ', cnf['user'])
    print('pw: ', cnf['pw'])
    print('db: ', cnf['db'])
    print('charset: ', cnf['charset'])


demo_get_conf7('127.0.0.1', '3306', {'user':'new_user', 'pw':'5678', 'db':'tests', 'charset':'utf8mb4'})

  与上一例比,函数体一模一样,参数 cnf 只是少了两个 “*”。重点是调用的时候不同,可以看出明显的区别,可变长字典参数在调用的时候,可以直接写键名,不用引号,使用“=”赋值,而定义字典类型参数,调用函数传递参数时,需要传递字典类型的数据才可以。

  在上线的例子中,函数体内的代码变相等于指定了不定长参数**cnf的长度,但参数是可以变长的是确定的,主要是函数体里面取值的代码逻辑。如果循环打印输出,就可以任意变长,但是实际项目中这样做适用场景不多。还是取数据库配置这个需求,我们改写一下函数体的代码:

#!/usr/bin/env python
# -*- coding: utf-8 -*-


# 演示获得数据库配置参数,使用字典参数
def demo_get_conf7(host, port, **cnf):
    "打印得到的数据库配置"
    arr = {'user': 'root', 'pw': '1234', 'db': 'tests', 'charset': 'utf8'}     # 可变参数的默认值

    for key, val in cnf.items():
        if key in arr:
            arr[key] = val                       # 取变长参数传递过来的键,如果在预置里面存在就更改,不存在的忽略

    print('host: ', host)
    print('port: ', port)
    print('user: ', arr['user'])
    print('pw: ', arr['pw'])
    print('db: ', arr['db'])
    print('charset: ', arr['charset'])


print('----------参数完整传递:')
demo_get_conf7('127.0.0.1', '3306', user='new_user', pw='5678', db='tests', charset='utf8mb4')
print('----------参数多余传递:')
demo_get_conf7('127.0.0.1', '3306', user='new_user', pw='5678', db='tests', charset='utf8mb4', abc='123')
print('----------参数减少传递:')
demo_get_conf7('127.0.0.1', '3306', user='new_user', pw='5678')
print('----------变参没有传递:')
demo_get_conf7('127.0.0.1', '3306')

  控制台输出:

----------参数完整传递:
host:  127.0.0.1
port:  3306
user:  new_user
pw:  5678
db:  tests
charset:  utf8mb4
----------参数多余传递:
host:  127.0.0.1
port:  3306
user:  new_user
pw:  5678
db:  tests
charset:  utf8mb4
----------参数减少传递:
host:  127.0.0.1
port:  3306
user:  new_user
pw:  5678
db:  tests
charset:  utf8
----------变参没有传递:
host:  127.0.0.1
port:  3306
user:  root
pw:  1234
db:  tests
charset:  utf8

  从控制台结果可以看出,多余传递的可变长参数被忽略掉了,少传的可变长参数使用了函数体内的默认值。可变长参数如果一个也没传递,那就完全使用了函数体内的默认值。

  以上是对Python中函数的不同类型参数的区别及应用场景例举,一点心得体会,希望对有兴趣的朋友能有所帮助!

posted on 2019-11-19 18:23  学为所用  阅读(10711)  评论(0编辑  收藏  举报