1、关键字参数(positional argument)和位置参数(keyword argument)

Python函数的参数根据函数在调用时(注意,不是函数定义时)传参的形式分为关键字参数和位置参数。

(1)关键字参数:

关键字参数是指在函数调用传参时,由标识符(如name=)引导的参数,或者放在一个由**引导的字典里进行传递。如下所示:

complex(real=3, imag=5)
complex(**{'real': 3, 'imag': 5})

(2)位置参数

不是关键字参数的参数就是位置参数。它除了单独传递之外,还可以放在一个由*引导的可迭代序列(列表、元组等)里进行传递。如下所示:

complex(3, 5)
complex(*(3, 5))

位置参数总是放在函数的参数列表最前方,关键字参数必须要放在位置参数后面。它们之间的位置关系如下所示:

def func(arg1, arg2, kwarg1, kwarg2):
func(1, 2, kwarg1=3, kwarg2=4)

这里arg1、arg2是位置参数,kwarg1,kwarg2是关键字参数。关键字参数的key(也就是这里的'kwarg1=3'中的'kwarg1','kwarg2=4'中的'kwarg2')要保证和形参名称一致

2、接受任意数量的参数

(1)接受任意数量的位置参数

"*"号表达式除了上一章我们讲的用于对任意长度可迭代对象进行拆分之外, 还能在函数定义中使用,用于定义一个可以接受任意数量位置参数的函数,如下所示:

def avg(first, *rest):
print(rest)
return (first + sum(rest)) / (1 + len(rest))
print(avg(1, 2, 3, 4, 5))
# (2, 3, 4, 5)
# 1. 5

"*"开头的参数必须做为最后一个位置参数使用,且"*"开头的参数传进来后是元组数据结构。

(2)接受任意数量的关键字参数

想接受任意数量的关键字参数,我们可以类似地使用"**"开头的参数。如下所示:

import html
def make_element(name, value, **attrs) -> str:
key_values = [ ' %s="%s"' % item for item in attrs.items()]
attr_str = ''.join(key_values)
# Perform a string formatting operation.
element = '<{name} {attrs}>{value}</{name}>'.format(name=name, attrs=attr_str, value=html.escape(value))
return element res_1 = make_element('item', 'Albatross', size='large', quantity=6)
res_2 = make_element('p', '<spam>') # escape会把这里'<spam>'中的'<'和'>'替代成安全的序列&lt; &gt;
print(res_1) # <item size="large" quantity="6">Albatross</item>
print(res_2) # <p >&lt;spam&gt;</p>

"**"开头的参数必须做为最后一个关键字参数使用,且"**"开头的参数传进来后是字典数据结构。

(3)同时接受任意数量的位置参数和关键字参数

如果想要函数同时接受任意数量的位置参数和关键字参数,只要联合使用"*"和"**"即可。

def anyargs(*args:tuple, **kwargs:dict):
print(args)
print(kwargs)
anyargs(2, 3, 4, 5, time=1, data=2)
# (2, 3, 4, 5)
# {'time': 1, 'data': 2}

3、keyword-only参数

前面说过,"*"打头的参数只能做为最后一个位置参数,"**"打头的参数只能做为最后一个关键字参数(自然也是最后一个参数),而依此推断"*"打头的参数后的参数就必然是关键字参数了。

# 出现在*args之后的参数称为keyword-only参数
# 这两个例子中y都只能是关键字参数,在传参时要保证key和形参的一致性
def a(x, *args, y):
print(y)
def b(x, *args, y, **kwargs):
print(y) a(4, 6, 7, 8, y=1)
b(4, 6, 7, 3, y=1, data=2, year=3) # 1
# 1

这样的参数称为keyword-only参数,即出现在*args之后的参数只能做为关键字参数使用。

我们可以充分利用这一性质,将关键字参数放在以*打头的参数后,或者一个单独的*之后,强迫函数的调用者必须传关键字参数,比如下面这样:

def recv(max_size, *, block):
'Receives a message'
pass recv(1024, True) # recv2() takes 1 positional argument but 2 were given
# and missing 1 required keyword-only argument: 'block'
recv(1024, block=True) # OK

这项技术在实际项目中,可以用来为接受任意数量的位置参数的函数来指定关键字参数,比如下面这个带截断功能的求最小值函数。这里的clip参数被强迫为必须按照关键字参数传入,而且设定了一个默认值None, 使参数为可选的。如下所示:

def mininum(*values, clip=None):
m = min(values)
if clip is not None:
m = clip if clip > m else m
return m res1 = mininum(1, 5, 2, -5, 10)
res2 = mininum(1, 5, 2, -4, 10, clip=0)
print(res1, res2) # -5, 0

除此之外,keyword-only参数可以提高代码可读性,像下面这种函数写法:

msg = recv(1024, False)

如果代码的阅读者不熟悉recv函数的工作方式,那么可能不太明白这里的False参数有什么作用,如果这个函数的调用可以写成下面这样的话,那就清晰多了(当然,需要这个函数的编写者最开始就强制函数的使用者这样写):

msg = recv(1024, block=False)

最后,如果函数定义的的时候强制使用了keyword-only参数,那么当用户请求帮助信息时,参数信息可以很自然地显现出来:

print(help(recv))
# Help on function recv in module __main__: # recv(max_size, *_, block)
# Receives a message

3、可选参数(带默认值的参数)

要想定义一个可选参数,需要在函数定义中为参数赋值,并保证默认参数出现在参数列表最后。像下面这样:

def spam(a, b=42):
print(a, b)
spam(1) # 1, 42
spam(1, 2) # 1, 2

如果默认值是可变容器,比如说列表、集合、字典等,需要把None做为默认值:如下所示:

def spam(a, b=None):
if b is None:
b = []

警示1: 千万不能直接像下面这样写:

def spam(a, b=[]):

如果像上面那样写,那么就会发生一些你所不期望看到的现象:如果默认值在函数体之外被修改了,那么这种修改在之后的函数调用中仍然阴魂不散,如下面所示:

def spam(a, b=[]):
print(b)
return b
x = spam(1)
x.append('oh!')
x.append('no!')
print(x)
spam(1)
# []
# ['oh!', 'no!']
# ['oh!', 'no!']

警示2: 在函数体中,我们常常需要判断参数是否为None,此处需要使用is运算符,千万不能直接像下面这样写:

def spam(a, b=None):
if not b:
b = []

这里的问题在于:尽管None会被判定为False,可还有其他许多对象(比如长度为0的字符串、列表、元组、字典等)也存在这样的行为。这样,有很多其他的特定输入也会被判定为False,然后本该是用户传进来的值直接被默认的[]覆盖掉了。如下所示:

def spam(a, b=None):
if not b:
b = []
spam(1) # OK
x = []
spam(1, x) # Oops! x will be overwritten by default []
spam(1, 0) # Oops! 0 will be overwritten by default []
spam(1, '') # Oops! '' will be overwritten by default []

最后,我们再来讨论一个非常棘手的问题。我们想要在函数中检测调用者是否对可选参数提供了某个特定值(可以是任意值,None也算)这样,我们自然就不能用None,0, False当做默认值然后再来做检测了,因为用户本身就可能拿它们当做参数。

要解决这个问题,可以像下面这样写:

_no_value = object()
def spam(a, b=_no_value):
if b == _no_value:
print("No b value supplied")
return
print(a, b)
spam(1) # No b value supplied
spam(1, 2) # 1 2
spam(1, None) # 1 None

这里_no_value是基于object()类创建的一个独有对象,可以用这个来对用户提供的参数做检测,因为用户几乎不可能把_no_value对象做为参数输入(除非用户传入的是相同的对象,否则哪怕是object类型的另一个对象都和_no_value对象是不同的)。

这里简要说明一下object类,object是Python中几乎所有对象的基类,object对象没有任何数据(底层缺少__dict__字典,甚至没办法设置任何属性),它唯一的作用就是用来检测相等性。

参考文献

  • [1] https://www.python.org/
  • [2] Martelli A, Ravenscroft A, Ascher D. Python cookbook[M]. " O'Reilly Media, Inc.", 2005.

Python技法2:函数参数的进阶用法的更多相关文章

  1. Python中用format函数格式化字符串的用法

    这篇文章主要介绍了Python中用format函数格式化字符串的用法,格式化字符串是Python学习当中的基础知识,本文主要针对Python2.7.x版本,需要的朋友可以参考下   自python2. ...

  2. Python学习笔记 - 函数参数

    >>> def power(x): ... return x * x ... >>> power(5) 25 >>> def power(x, n ...

  3. Python 内置函数sorted()在高级用法

    对于Python内置函数sorted(),先拿来跟list(列表)中的成员函数list.sort()进行下对比.在本质上,list的排序和内建函数sorted的排序是差不多的,连参数都基本上是一样的. ...

  4. python基础之函数参数,名称空间,以及函数嵌套

    函数进阶内容梗概: 1. 函数参数--动态传参 2. 名称空间, 局部名称空间, 全局名称空间, 作⽤用域, 加载顺序. 3. 函数的嵌套 4. gloabal , nonlocal 关键字 1. 函 ...

  5. python学习day10 函数Ⅱ(参数&作用域)

    函数Ⅱ(参数&作用域) 知识小结: py2与py3的区别 逻辑运算()>not>and>or 字符串翻转切片翻转 is与==区别 git相关 数据类型判断 操作系统:cent ...

  6. python中的函数参数的传递

    转载自: http://winterttr.me/2015/10/24/python-passing-arguments-as-value-or-reference/ 我想,这个标题或许是很多初学者的 ...

  7. Python学习之函数参数

    上一节,我们学习了Python中是如何定义和调用函数且如何得到返回值的.在调用函数时,有的函数需要参数来启动函数,有的则无需参数.这一节我们来介绍Python中有哪些参数类型. 位置参数 在调用函数时 ...

  8. python基础之函数参数、嵌套、返回值、对象、命名空间和作用域

    函数的使用原则 函数的使用必须遵循:先定义后使用的原则 函数的定义,与变量的定义是相似的,如果没有事先定义函数而直接引用就相当于在引用一个不存在变量名 定义阶段:只检测语法,不执行代码,当出现语法错误 ...

  9. 数组名作为函数参数以及sizeof用法

    来源:https://blog.csdn.net/jay_zhouxl/article/details/51745518 int f(int *p,char *a) { printf("p[ ...

随机推荐

  1. 关于Junit中Assert已经过时

    在junit4.12之后,Assert就过时了,提供了TestCase来取代: 同样在TestCase中原本比较常见的一些方法也已经取消了,例如:assertNotEquals.assertThat. ...

  2. 关于Java for循环的注意点

    1 import java.util.ArrayList; 2 3 public class Main { 4 5 public static void main(String[] args) { 6 ...

  3. 我说Java完全面向对象,老大过来就是一jio

    哈喽,大家好,我是指北君.自从开始学Java,就知道Java是一门面向对象编程的语言,所以在指北君眼中,Java就是完全面向对象的.有一天老大问到我这个事情,我脱口而出,结果老大过来就是一jio... ...

  4. Win10 pip install augimg 报 OSError: [WinError 126] 找不到指定的模块,解决办法

    第一种Win10下python成功安装augimg的方法: 下载Shapely,地址https://www.lfd.uci.edu/~gohlke/pythonlibs/#shapely,选择对应版本 ...

  5. Qt5获取系统文件图标,文件路径

    获取系统图标: QFileIconProvider icon_provider; QIcon icon = icon_provider.icon(QFileIconProvider::Folder); ...

  6. thrift的介绍及其使用

    什么是thrift Thrift是Facebook于2007年开发的跨语言的rpc服框架,提供多语言的编译功能,并提供多种服务器工作模式:用户通过Thrift的IDL(接口定义语言)来描述接口函数及数 ...

  7. Django——ORM打印SQL

    如果想打印ORM转换过程中的SQL,需要在settings.py中进行如下配置: LOGGING = { 'version': 1, 'disable_existing_loggers': False ...

  8. Python习题集(十二)

    每天一习题,提升Python不是问题!!有更简洁的写法请评论告知我! https://www.cnblogs.com/poloyy/category/1676599.html 题目 请写一个函数fin ...

  9. Identity角色管理二(显示角色)

    需要将目前所有角色名显示出来,方法同用户管理 一.创建Index acction public async Task<ActionResult> Index() { var roles = ...

  10. Filter案例之敏感词过滤和代理模式

    一.需求分析 二 .代理模式 1.概念 2.代码实现 代理对象可以强转为真实对象,即对应的接口类: 3.通过代理增强方法 其中,方法对象invoke真实对象,反射原理: 三.过滤敏感词汇案例代码实现 ...