跳至主要内容

深入理解 Python 函数定义

在 Python 中,你也可以定义包含若干参数的函数。这里有三种可用的形式,也可以混合使用。
默认参数值
最常用的一种形式是为一个或多个参数指定默认值。这会创建一个可以使用比定义时允许的参数更少的参数调用的函数,例如:
def ask_ok(prompt, retries=4, complaint='Yes or no, please!'):
while True:
ok = input(prompt)
if ok in ('y', 'ye', 'yes'):
return True
if ok in ('n', 'no', 'nop', 'nope'):
return False
retries = retries - 1
if retries < 0:
raise OSError('uncooperative user')
print(complaint)
这个函数可以通过几种不同的方式调用:
•只给出必要的参数:
ask_ok('Do you really want to quit?')
•给出一个可选的参数:
ask_ok('OK to overwrite the file?', 2)
•或者给出所有的参数:
ask_ok('OK to overwrite the file?', 2, 'Come on, only yes or no!')
这个例子还介绍了 in 关键字。它测定序列中是否包含某个确定的值。
默认值在函数 定义 作用域被解析,如下所示:
i = 5
def f(arg=i):
print(arg)
i = 6
f()
将会输出 5。
重要警告: 默认值只被赋值一次。这使得当默认值是可变对象时会有所不同,比如列表、字典或者大多数类的实例。例如,下面的函数在后续调用过程中会累积(前面)传给它的参数:
def f(a, L=[]):
L.append(a)
return L
print(f(1))
print(f(2))
print(f(3))
这将输出:
[1]
[1, 2]
[1, 2, 3]
如果你不想让默认值在后续调用中累积,你可以像下面一样定义函数:
def f(a, L=None):
if L is None:
L = []
L.append(a)
return L
关键字参数
函数可以通过 关键字参数 的形式来调用,形如 keyword = value。例如,以下的函数:
def parrot(voltage, state='a stiff', action='voom', type='Norwegian Blue'):
print("-- This parrot wouldn't", action, end=' ')
print("if you put", voltage, "volts through it.")
print("-- Lovely plumage, the", type)
print("-- It's", state, "!")
接受一个必选参数 (voltage) 以及三个可选参数 (state, action, 和 type)。可以用以下的任一方法调用:
parrot(1000) # 1 positional argument
parrot(voltage=1000) # 1 keyword argument
parrot(voltage=1000000, action='VOOOOOM') # 2 keyword arguments
parrot(action='VOOOOOM', voltage=1000000) # 2 keyword arguments
parrot('a million', 'bereft of life', 'jump') # 3 positional arguments
parrot('a thousand', state='pushing up the daisies') # 1 positional, 1 keyword
不过以下几种调用是无效的:
parrot() # required argument missing
parrot(voltage=5.0, 'dead') # non-keyword argument after a keyword argument
parrot(110, voltage=220) # duplicate value for the same argument
parrot(actor='John Cleese') # unknown keyword argument
在函数调用中,关键字的参数必须跟随在位置参数的后面。传递的所有关键字参数必须与函数接受的某个参数相匹配 (例如 actor 不是 parrot 函数的有效参数),它们的顺序并不重要。这也包括非可选参数(例如 parrot(voltage=1000) 也是有效的)。任何参数都不可以多次赋值。下面的示例由于这种限制将失败:
>>> def function(a):
... pass
...
>>> function(0, a=0)
Traceback (most recent call last):
File "<stdin>", line 1, in ?
TypeError: function() got multiple values for keyword argument 'a'
引入一个形如 **name 的参数时,它接收一个字典(参见 Mapping Types — dict ),该字典包含了所有未出现在形式参数列表中的关键字参数。这里可能还会组合使用一个形如 *name (下一小节详细介绍) 的形式参数,它接收一个元组(下一节中会详细介绍),包含了所有没有出现在形式参数列表中的参数值( *name 必须在 **name 之前出现)。 例如,我们这样定义一个函数:
def cheeseshop(kind, *arguments, **keywords):
print("-- Do you have any", kind, "?")
print("-- I'm sorry, we're all out of", kind)
for arg in arguments:
print(arg)
print("-" * 40)
keys = sorted(keywords.keys())
for kw in keys:
print(kw, ":", keywords[kw])
它可以像这样调用:
cheeseshop("Limburger", "It's very runny, sir.",
"It's really very, VERY runny, sir.",
shopkeeper="Michael Palin",
client="John Cleese",
sketch="Cheese Shop Sketch")
当然它会按如下内容打印:
-- Do you have any Limburger ?
-- I'm sorry, we're all out of Limburger
It's very runny, sir.
It's really very, VERY runny, sir.
----------------------------------------
client : John Cleese
shopkeeper : Michael Palin
sketch : Cheese Shop Sketch
注意在打印关键字参数之前,通过对关键字字典 keys() 方法的结果进行排序,生成了关键字参数名的列表;如果不这样做,打印出来的参数的顺序是未定义的。
可变参数列表
最后,一个最不常用的选择是可以让函数调用可变个数的参数。这些参数被包装进一个元组(参见 元组和序列 )。在这些可变个数的参数之前,可以有零到多个普通的参数:
def write_multiple_items(file, separator, *args):
file.write(separator.join(args))
通常,这些 可变 参数是参数列表中的最后一个,因为它们将把所有的剩余输入参数传递给函数。任何出现在 *args 后的参数是关键字参数,这意味着,他们只能被用作关键字,而不是位置参数:
>>> def concat(*args, sep="/"):
... return sep.join(args)
...
>>> concat("earth", "mars", "venus")
'earth/mars/venus'
>>> concat("earth", "mars", "venus", sep=".")
'earth.mars.venus'
参数列表的分拆
另有一种相反的情况: 当你要传递的参数已经是一个列表,但要调用的函数却接受分开一个个的参数值。这时候你要把已有的列表拆开来。例如内建函数 range() 需要要独立的 start,stop 参数。你可以在调用函数时加一个 * 操作符来自动把参数列表拆开:
>>> list(range(3, 6)) # normal call with separate arguments
[3, 4, 5]
>>> args = [3, 6]
>>> list(range(*args)) # call with arguments unpacked from a list
[3, 4, 5]
以同样的方式,可以使用 ** 操作符分拆关键字参数为字典:
>>> def parrot(voltage, state='a stiff', action='voom'):
... print("-- This parrot wouldn't", action, end=' ')
... print("if you put", voltage, "volts through it.", end=' ')
... print("E's", state, "!")
...
>>> d = {"voltage": "four million", "state": "bleedin' demised", "action": "VOOM"}
>>> parrot(**d)
-- This parrot wouldn't VOOM if you put four million volts through it. E's bleedin' demised !
Lambda 形式
出于实际需要,有几种通常在函数式编程语言例如 Lisp 中出现的功能加入到了 Python。通过 lambda 关键字,可以创建短小的匿名函数。这里有一个函数返回它的两个参数的和: lambda a, b: a+b。 Lambda 形式可以用于任何需要的函数对象。出于语法限制,它们只能有一个单独的表达式。语义上讲,它们只是普通函数定义中的一个语法技巧。类似于嵌套函数定义,lambda 形式可以从外部作用域引用变量:
>>> def make_incrementor(n):
... return lambda x: x + n
...
>>> f = make_incrementor(42)
>>> f(0)
42
>>> f(1)
43
上面的示例使用 lambda 表达式返回一个函数。另一个用途是将一个小函数作为参数传递:
>>> pairs = [(1, 'one'), (2, 'two'), (3, 'three'), (4, 'four')]
>>> pairs.sort(key=lambda pair: pair[1])
>>> pairs
[(4, 'four'), (1, 'one'), (3, 'three'), (2, 'two')]
文档字符串
这里介绍的文档字符串的概念和格式。
第一行应该是关于对象用途的简介。简短起见,不用明确的陈述对象名或类型,因为它们可以从别的途径了解到(除非这个名字碰巧就是描述这个函数操作的动词)。这一行应该以大写字母开头,以句号结尾。
如果文档字符串有多行,第二行应该空出来,与接下来的详细描述明确分隔。接下来的文档应该有一或多段描述对象的调用约定、边界效应等。
Python 的解释器不会从多行的文档字符串中去除缩进,所以必要的时候应当自己清除缩进。这符合通常的习惯。第一行之后的第一个非空行决定了整个文档的缩进格式。(我们不用第一行是因为它通常紧靠着起始的引号,缩进格式显示的不清楚。)留白“相当于”是字符串的起始缩进。每一行都不应该有缩进,如果有缩进的话,所有的留白都应该清除掉。留白的长度应当等于扩展制表符的宽度(通常是8个空格)。
以下是一个多行文档字符串的示例:
>>> def my_function():
... """Do nothing, but document it.
...
... No, really, it doesn't do anything.
... """
... pass
...
>>> print(my_function.__doc__)
Do nothing, but document it.
No, really, it doesn't do anything.
函数注解
函数注解 是关于用户自定义的函数的完全可选的、随意的元数据信息。无论 Python 本身或者标准库中都没有使用函数注解;本节只是描述了语法。第三方的项目是自由地为文档,类型检查,以及其它用途选择函数注解。
注解是以字典形式存储在函数的 __annotations__ 属性中,对函数的其它部分没有任何影响。参数注解(Parameter annotations)是定义在参数名称的冒号后面,紧随着一个用来表示注解的值得表达式。返回注释(Return annotations)是定义在一个 -> 后面,紧随着一个表达式,在冒号与 -> 之间。下面的示例包含一个位置参数,一个关键字参数,和没有意义的返回值注释:
>>> def f(ham: 42, eggs: int = 'spam') -> "Nothing to see here":
... print("Annotations:", f.__annotations__)
... print("Arguments:", ham, eggs)
...
>>> f('wonderful')
Annotations: {'eggs': <class 'int'>, 'return': 'Nothing to see here', 'ham': 42}
Arguments: wonderful spam

评论

此博客中的热门博文

【反邪 | 柳州:融安县东起乡扶贫能手勇当反邪先锋】(龙政光)#我为反邪教代言##柳州反邪#在抗击新型冠状病毒感染的肺炎疫情的战疫中,融安县东起乡驻村工作队的队员们一直战斗在疫情防控一线,他们既是扶贫能手又是反邪先锋。疫情期间他们入户确保贫困户生活能得到保障、进行外来人口摸排、对重点对象进行监测、进行产业政策宣传、动员复工复产确保脱贫成果。同时向村民普及反邪教知识,进行防疫知识宣传并发放反邪教宣传手册,告诉他们什么是邪教,邪教的危害,要做到认知邪教,拒绝邪教!提醒村民防范邪教,防止邪教组织在疫情紧张时期拉拢蛊惑群众,如有外来人员宣传邪教时马上打110报警。反邪教工作的持续开展确保了东起乡的稳定安宁。@广西反邪教 @柳州政法

【反邪 | 柳州:融安县东起乡扶贫能手勇当反邪先锋】(龙政光)#我为 反邪教 代言##柳州反邪#在抗击新型冠状病毒感染的肺炎疫情的战疫中,融安县东起乡驻村工作队的队员们一直战斗在疫情防控一线,他们既是扶贫能手又是反邪先锋。疫情期间他们入户确保贫困户生活能得到保障、进行外来人口摸排、对重点对象进行监测、进行产业政策宣传、动员复工复产确保脱贫成果。同时向村民普及 反邪教 知识,进行防疫知识宣传并发放反 邪教 宣传手册,告诉他们什么是 邪教 , 邪教 的危害,要做到认知 邪教 ,拒绝 邪教 !提醒村民防范 邪教 ,防止 邪教 组织在疫情紧张时期拉拢蛊惑群众,如有外来人员宣传 邪教 时马上打110报警。 反邪教 工作的持续开展确保了东起乡的稳定安宁。@广西 反邪教 @柳州政法

看《银翼杀手2049》前,先看看原著小说吧

电影名气这么大,原著小说也不简单,来自美国科幻界大师菲利普·迪克的《仿生人会梦见电子羊吗?》 菲利普·迪克精装套系 (美)菲利普·迪克 著 许东华 等译 译林出版社 2017年10月   韩松   1982年,雷德利·斯科特导演的电影《银翼杀手》上映,成为电影史上的科幻经典。时至今日,《银翼杀手》的魅力丝毫未减,其影响之深远已经超越了电影。35年后,电影续集《银翼杀手2049》10月27日在国内上映。电影名气这么大,原著小说也不简单,来自美国科幻界大师菲利普·迪克的《仿生人会梦见电子羊吗?》,这本有着奇怪名字的小说,是菲利普·迪克最负盛名的作品,也是各大科幻书单的必读书目,电影《银翼杀手》和《银翼杀手2049》的人物角色和灵感设想就是发源于此。   菲利普·迪克精装套系包括《仿生人会梦见电子羊吗?》《高堡奇人》《少数派报告》《尤比克》《流吧!我的眼泪》五部科幻小说。作品集中探讨“何为真实”以及“个体身份建构”,盛名经久不衰,有多部作品被改编成电影,包括《银翼杀手》《少数派报告》《全面回忆》等。以其名字命名的菲利普·K·迪克奖是美国科幻界的主要奖项之一。    生前潦倒,死后盛名   关于迪克的生平,如今人们介绍得已经很多了,大致是这么一些情况:   他生于1928年,卒于1982年,只活了54岁。他作品中的不少人物,差不多也都命不长。   他活着时,几乎就是个“撸瑟”,也就是失败者。他有一个双胞胎妹妹,但出生后五周就死了。迪克认为是母亲照料不周,因此恨他妈,家庭关系不好。而父亲在迪克四岁时,也抛家而去。迪克结过五次婚,都离了。成年迪克靠安非他命活着,还吸毒,经常陷入神经错乱中。他有严重的焦虑症,不能与人正常交往。他还患上了妄想症,认为自己被联邦调查局和中央情报局监视。他有广场恐惧症,连在公众面前吃东西,都感到困难。他还患上了抑郁症,曾尝试自杀。他在70年代后,沉湎于超自然,并一度称获得天启。他只短暂地上过一年大学,读哲学,在加大伯克利分校。他基本上是自学成才。他一生大部分时间居住在加利福尼亚。   他是一位多产的作家,从1952年开始写作,到1982年去世,创作了整整30年,写了44部长篇小说,以及120多篇中短篇小说。他常常为挣稿费养活自己而拼命写。他没有其他工作。他的个人经济状况很窘迫,在50年代,一度连图书馆借书...

糖尿病患者按这10种方法吃主食,升血糖慢,餐后血糖达标更有保障

主食的主要成分是碳水化合物,简称“糖类”,因为糖类会产生热量,所以吃了以后,就会升高血糖。但是,从古到今,人类生存,一日三餐,又离不开主食。 主食吃了升血糖,不吃又不行,那么,怎样才能吃主食让血糖升得慢一些呢?本文就告诉您10个方法。 1.吃粗粮比吃细粮升血糖慢 临床工作人员常会建议糖尿病患者适当吃点粗粮,就是因为粗粮中含有丰富的膳食纤维,膳食纤维是一种多糖类物质,不会被人体吸收,也不产生热量,所以,吃了不会明显升高血糖。 但是,有些患者就天天吃粗粮,甚至一天三餐,一次吃两三个馒头,那就失去了吃粗粮的意义。 粗粮中的膳食纤维掺合在细粮中,可起到延缓血糖升高的作用,吃多了,粗粮中的非膳食纤维成分同样会产生更多的热量。 所以,建议 糖尿病患者吃粗粮的时候,只是比同等量的细粮多吃几口而已,并不是吃得越多越好。最好粗粮细粮搭配着吃,交替着吃。 2.吃杂粮主食比吃纯粮主食升血糖慢 杂粮馒头指两种或两种以上的粮食磨成面粉做成的馒头,如将荞麦面、小麦面、玉米面、豆面等,其中的两种或三种,甚至四种,或者更多的面粉掺合起来做成的馒头,比单纯用一种面粉做成的馒头营养丰富,还升血糖慢。 如果 在杂粮馒头中加入部分菜叶,如芹菜叶等,升血糖更慢 。 杂米饭也是这个道理,将 大米、小米、豆类或其他米类中的两种或两种以上的米混杂在一起,蒸米饭,要比单纯蒸白米饭升血糖慢。 若在 蒸杂粮米饭的时候加入几块胡萝卜、南瓜等,还能补充一些维生素 等。 3.吃干饭比吃稀饭升血糖慢 现在很多患者知道吃大米稀饭升血糖快。这是因为稀饭经过反复熬煮、糊化,易于吸收,能够迅速升高血糖。 所以,有的患者不喝大米稀饭改喝小米稀饭, 小米稀饭同样能够较快升高血糖 。这不是大米和小米本身的原因,而是烹饪方式的结果。 任何一种米,只要熬烂,做成稀饭,都比干饭升血糖快。 为什么有的人喝麦片,升血糖快;有的人喝麦片,血糖升高不明显。升血糖慢的,是用开水冲了以后,及时食用;升血糖快的,也是缘于长时间的焖煮,性质变成稀饭了。 4.吃硬的比吃软的升血糖慢 同样是主食,如果选择 火烧、煎饼、窝头、烙饼 等相对干硬的的食物,需要较长时间的咀嚼、消化和吸收,就要比柔软的发糕、疙瘩汤、煮烂的面条等升血糖慢。 俗话说,有的人在人际交往方面,吃软不吃硬,糖尿病患者吃主食时...