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 函数Ⅱ(参数&amp;作用域)

    函数Ⅱ(参数&作用域) 知识小结: 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[ ...

  10. Python基础之函数参数与返回值进阶

    参数作用:如果外界希望在函数内部处理数据,就可以将数据作为参数传入函数内部: 返回值作用:如果希望一个函数函数执行完成后,向外界报告函数的执行结果,就可以使用函数的返回值. 函数的返回值 进阶 利用元 ...

随机推荐

  1. webform(内置对象)

    一.内置对象 (一)Response - 响应请求对象1.定义:Response对象用于动态响应客户端请示,控制发送给用户的信息,并将动态生成响应.Response对象只提供了一个数据集合cookie ...

  2. gulp配置版本号 教程之gulp-rev-append

    简介: 使用gulp-rev-append给页面的引用添加版本号,清除页面引用缓存. 1.安装nodejs/全局安装gulp/项目安装gulp/创建package.json和gulpfile.js文件 ...

  3. .Net MVC4笔记之js css引用与压缩

    1.引用时,可以用即可以直接使用“~”来表示根目录. 引入js 引入js 引入css <link href="~/Content/uploadify/uploadify.css&quo ...

  4. Android开发——Fragment的简单使用总结

    前言: 之前搞项目的时候,就使用了这个Fragment,中间遇到了许多坑,把坑都解决了,现在写一篇较为简单的Fragment使用总结 Fragment的简单介绍: 简单来说,Fragment其实可以理 ...

  5. 全文索引&amp;&amp;地理空间索引

    Ⅰ.全文索引 搜索引擎的实现核心技术,搜索类似where col like '%xxx%';关键字可以出现再某个列任何位置 这种查询条件,B+ tree索引是无法使用的.如果col上创建了索引,因为排 ...

  6. flask 使用Flask-WTF处理表单

    使用Flask-WTF处理表单 扩展Flask-WTF继承了WTFforms,使用它可以在flask中更方便的使用WTForms.Flask-WTF将表单数据解析.CSRF保护.文件上传等功能与Fla ...

  7. Birthday(费用流)

    Birthday https://www.nowcoder.com/acm/contest/206/A 题目描述 恬恬的生日临近了.宇扬给她准备了一个蛋糕. 正如往常一样,宇扬在蛋糕上插了n支蜡烛,并 ...

  8. 工具篇:如何使用junit.jar进行测试

    一.网上下载:junit.jar包 下载地址:https://sourceforge.net/projects/junit/?source=typ_redirect 二.导入指定项目中 三.在指定方法 ...

  9. php json包 Services_JSON-1.0.2 1 ---one

  10. Appium 简介及工作原理

    申请:本文介绍主要是针对Android. 1.什么是Appium: Appium是一个开源.跨平台的测试框架,可以用来测试原生及混合的移动端应用.Appium支持IOS.Android及Firefox ...