Open xuzhengfu opened 4 years ago
我们已经见过不少函数,也自己写过一些函数。我们已经理解函数的概念来自代数:从输入参数出发,计算出函数的返回值;我们也知道可以用 def foo(): 来定义函数。其实函数的定义非常复杂,我们不太能够在第一次介绍时就讲清楚,所以之前我们就采取 “先引入用起来” 的方法,这是一种知识上的 “提前引用”。
def foo():
这一章我们就围绕函数定义深入看看。
哪怕一个函数内部什么都不干,它也得有个名字,然后名字后面要加上圆括号 (),以明示它是个函数,而不是某个变量。
()
给函数命名(给变量命名也一样)需要遵循的一些规则如下: 首先,名称不能以数字开头,能用在名称开头的只有大小写字母和下划线 _; 其次,名称中不能有空格,如果一个名字里有好几个词汇,可以用下划线来分割,也可以用所谓 Camel Case 风格(doNothing),习惯上更推荐使用下划线; 最后,绝对不能与 Python 语言的关键字(keyword)重复。
给函数命名(给变量命名也一样)需要遵循的一些规则如下:
_
关键字也叫保留字(reserved),是编程语言保护起来内部使用的,如果用这些词做变量、函数 或 类型的名字,编译器或者解释器就无法正确工作了。
Python 提供了一个模块叫 keyword 来帮助我们了解语言有哪些关键字:
keyword
import keyword keyword.kwlist
keyword.kwlist 就是当前你使用的 Python 解释器中不可使用的关键字列表,如果我们不记得这个列表,可以随时用 keyword.iskeyword('xxx') 来查询某个词是不是关键字。
keyword.kwlist
keyword.iskeyword('xxx')
在程序里给变量、函数命名是个挺重要的事情,影响到程序的可读性,最好能有一种流畅清晰、又始终一致的风格(style)。为了让全世界的 Python 程序员都有相对一致的风格,Python 社区有专门的一套建议规范,放在专门维护 Python 语言特性的社区 PEP 上: PEP 8 -- Style Guide for Python Code: Naming Conventions
在程序里给变量、函数命名是个挺重要的事情,影响到程序的可读性,最好能有一种流畅清晰、又始终一致的风格(style)。为了让全世界的 Python 程序员都有相对一致的风格,Python 社区有专门的一套建议规范,放在专门维护 Python 语言特性的社区 PEP 上:
PEP,是 Python enhancement proposal 的缩写,每当有重要的语言特性、新需求、新想法,就放在这里,经过广大 Python 用户和开发者的讨论完善,在某个版本放进 Python 中。很多 PEP 早已从 proposal 毕业变成官方特性,但也还在这里保留着。PEP 8 就是一个古老的 proposal,现在已为大多数 Python 用户采纳。
函数可以没有参数,也可以有一个或者多个参数。
没有参数就意味着这个函数执行不依赖于输入。 注意:即使没有参数,无论定义还是调用时,函数名后面的括号都是不可省略的,这是函数身份的标志。
没有参数就意味着这个函数执行不依赖于输入。
注意:即使没有参数,无论定义还是调用时,函数名后面的括号都是不可省略的,这是函数身份的标志。
调用时,输入参数的值 是严格按照 参数的顺序 去匹配的。比如我们写一个函数输出某年到某年之间的所有闰年: def leap_year(year_s, year_e): a = list(range(year_s, year_e)) for year in a: if (year%4==0 and year%100!=0) or (year%400==0): print(year) leap_year(2000, 2020) 当我们调用 leap_year(2000, 2020) 时,输入两个参数值 2000 和 2020,按照顺序匹配函数定义 leap_year(year_s, year_e),于是 year_s = 2000 year_e = 2020。所以参数的顺序是不能搞错的,有些函数参数很多,要是开发过程中还调整过顺序的话,那简直就是灾难,所以一般情况下还是保持函数参数不要乱动为好。
调用时,输入参数的值 是严格按照 参数的顺序 去匹配的。比如我们写一个函数输出某年到某年之间的所有闰年:
def leap_year(year_s, year_e): a = list(range(year_s, year_e)) for year in a: if (year%4==0 and year%100!=0) or (year%400==0): print(year) leap_year(2000, 2020)
当我们调用 leap_year(2000, 2020) 时,输入两个参数值 2000 和 2020,按照顺序匹配函数定义 leap_year(year_s, year_e),于是 year_s = 2000 year_e = 2020。所以参数的顺序是不能搞错的,有些函数参数很多,要是开发过程中还调整过顺序的话,那简直就是灾难,所以一般情况下还是保持函数参数不要乱动为好。
leap_year(2000, 2020)
leap_year(year_s, year_e)
year_s = 2000
year_e = 2020
这三个条件 year%4==0、year%100!=0、year%400==0 之间有何关系?若其中某个条件可以用其他条件表示,不就可以简化了吗? year%4!=0 成立,year%100!=0、year%400!=0 一定成立。 year%100!=0 成立,year%400!=0 一定成立。 year%100==0 成立,year%4==0 一定成立。 year%400==0 成立,year%4==0、year%100==0一定成立。
这三个条件 year%4==0、year%100!=0、year%400==0 之间有何关系?若其中某个条件可以用其他条件表示,不就可以简化了吗?
year%4==0
year%100!=0
year%400==0
year%4!=0
year%400!=0
year%100==0
和参数一样,Python 的函数可以没有返回值,也可以有一个或者多个返回值。 实际上,没有返回语句的函数,等价于在其最后有一句 return None,表示函数返回了一个空值 None,None 在 Python 中是一个合法的值,表示什么都没有,它在逻辑上等价于 False。
和参数一样,Python 的函数可以没有返回值,也可以有一个或者多个返回值。
实际上,没有返回语句的函数,等价于在其最后有一句 return None,表示函数返回了一个空值 None,None 在 Python 中是一个合法的值,表示什么都没有,它在逻辑上等价于 False。
return None
None
False
bool(None)
结果为:
所以即使没有返回值的函数,也可以用在 if 后面做逻辑表达式,不过我们并不推荐这么做,因为可读性很差。 大部分情况下函数是有返回值的,因为绝大部分情况下函数的作用都是做 “数据处理”,从输入出发得到输出。
所以即使没有返回值的函数,也可以用在 if 后面做逻辑表达式,不过我们并不推荐这么做,因为可读性很差。
大部分情况下函数是有返回值的,因为绝大部分情况下函数的作用都是做 “数据处理”,从输入出发得到输出。
一般情况下函数都只有一个返回值,但 Python 也允许多返回值,比如我们想用一个函数来计算两个整数相除的商和余数,可以这么写:
def idiv(a, b): quotient = a // b remainder = a % b return quotient, remainder q, r = idiv(50, 6) print(q, r)
结果如下:
8 2
和多参数的情况类似,多返回值的情况下,赋值也是按照顺序匹配的,上面的代码中赋值语句左边的 q 匹配到第一个返回值,r 匹配第二个。
在不同地方出现的同名变量和函数,可能是完全不同的两个东西。
函数定义体中的变量的作用域是该函数内,程序的其他部分不知道其存在,这种变量叫局部变量(local variable);函数的输入参数也是局部变量,也只在函数定义体中有效;
不在任何函数、类定义体中的变量的作用域是全局的,在任何地方都可以访问,这种变量称为全局变量(global variable);
如果局部变量和全局变量同名,函数定义体内会优先局部变量,不会把它当做全局变量。
def increase_one(n): n += 1 return n n = 1 print(increase_one(n)) print(n)
第一个 print() 打印的是 函数调用 increase_one(n) 的返回值,increase_one(n) 这个语句不在任何函数定义体中,所以它里面用到的变量都是全局变量: 在调用 increase_one() 时,参数 n,按照作用域原理,是全局变量 n 当时的值,也就是 1; 在 increase_one() 函数定义内,参数 n 是输入参数即局部变量,带着传进来的值 1,经过加 1 之后返回,返回值是 2; print 打印这个返回值,输出 2; 这个过程中处理的都是局部变量,完全不影响全局变量 n 的值; 第二个 print() 打印的是全局变量 n 的值,输出 1。 以上的文字,可能需要反复阅读若干遍;几遍下来,消除了疑惑,以后就彻底没问题了;若是这个疑惑并未消除,或者关键点并未消化,以后则会反复被这个疑惑所坑害,浪费无数时间。 顺便说一句,上面这个例子用来说明作用域的概念很有用,但是平时写程序最好别这么写,减少重名的变量可以提升代码的清晰度和可读性。
print()
increase_one(n)
increase_one()
print
以上的文字,可能需要反复阅读若干遍;几遍下来,消除了疑惑,以后就彻底没问题了;若是这个疑惑并未消除,或者关键点并未消化,以后则会反复被这个疑惑所坑害,浪费无数时间。
顺便说一句,上面这个例子用来说明作用域的概念很有用,但是平时写程序最好别这么写,减少重名的变量可以提升代码的清晰度和可读性。
在函数定义中可以在某个参数后面用等号 = 给它一个缺省值,调用时可以省略传入这个参数的值,直接采用缺省值;当然也可以在调用时传入这个参数的值来覆盖掉缺省值。这种特性相当于给了这个函数两个版本,一个带某个参数,一个不带,不带的版本就当该参数是某个缺省值。
=
一个函数可以有多个带缺省值的参数,但有一个限制:所有这些带缺省值的参数只能堆在参数表的最后,也就是说你定义的参数表里,出现一个带缺省值的参数,则它后面的都必须带缺省值。如果在函数定义时不遵守此规则,则会扔出一个 SyntaxError: non-default argument follows default argument 的异常。
SyntaxError: non-default argument follows default argument
例如下面这个函数:
def greeting(name, msg='Hi', punc='!'): print(f'{msg}, {name}{punc}')
在这个版本的 greeting() 函数中,包含一个普通参数 name 和两个带缺省值的参数 msg punc,如果我们想跳过 msg 只传入 name(这个是必须的,因为没有缺省值)和 punc 的值,那么就可用下面的语法:
greeting()
name
msg
punc
greeting('zhengfu', punc='.')
在上面这个例子中,第一个值按照顺序位置匹配到参数变量 name,这叫 “positional argument”(即 “按照位置顺序匹配的参数” ),而按照位置下一个是 msg,是我们想跳过的,所以要注明参数变量名,说明下一个传入的值 '.' 是给 punc 参数变量的,这叫 “keyword argument”(即 “按照参数名匹配的参数” )。
由于所有带缺省值的参数都在普通参数的后面,所以我们只要记住: 调用函数时先传入所有 不带缺省值的参数的值,严格按照函数定义的位置顺序(positional); 然后想指定哪些带缺省值参数的值,就用 变量名=值 这样的格式在后面列出(keyword),未列出的就还用缺省值了。
由于所有带缺省值的参数都在普通参数的后面,所以我们只要记住:
所谓 变长参数 就是函数定义时名字前面带个星号 * 的参数变量,这表示这个变量其实是一组值,多少个都可以。我们先来看个简单的例子:
*
def say_hi(*names): for name in names: print('Hi,', name)
在这个例子里,*names 是一个变长参数(arbitrary argument),调用时可以传入一个或者多个值,函数会把这些值看做一个列表,赋给局部变量 names —— 后面我们会知道,其实不是列表(list),而是一个元组(tuple)—— 然后我们在函数体中可以用 for...in 来对这个 names 做循环。
*names
names
for...in
在使用 arbitrary argument 的场合,有几点需要注意: 参数变量名最好用复数单词,一看就知道是一组数据;这个变量在函数里通常都会被 for...in 循环处理,用复数名词在写类似 for name in names 的循环语句时会很舒服、很地道(idiomatic); 这种参数变量只能有一个,因为从它开始后面的输入值都会被当做它的一部分,多了就不知道怎么分了,显然,如果有这种参数,必须放在参数表的最后。 上面的第二点,有一个不太常见的例外,那就是一个函数既有 arbitrary arguments 又有 arguments with default values 的情况,那么可以有两个 arbitrary arguments,其中第二个必须带缺省值,然后参数表排列成这样: def monstrosity(*normal arguments*, *normal arbitrary argument*, *arguments with defaults*, *arbitrary argument with default*) 这样是完全符合语法要求的,调用时 传入参数值 还是按照前面讲的规则,先按照位置顺序匹配前两部分,多出来的都归 normal arbitrary argument;然后按照参数变量名指定对应值,没指定的都用缺省值。不过这实在是太麻烦了,不知道什么情况下才必须用这么可怕的函数! 当然,只有上面列出的前三个部分的情况还是有的,比如下面的例子:
在使用 arbitrary argument 的场合,有几点需要注意:
for name in names
上面的第二点,有一个不太常见的例外,那就是一个函数既有 arbitrary arguments 又有 arguments with default values 的情况,那么可以有两个 arbitrary arguments,其中第二个必须带缺省值,然后参数表排列成这样:
def monstrosity(*normal arguments*, *normal arbitrary argument*, *arguments with defaults*, *arbitrary argument with default*)
这样是完全符合语法要求的,调用时 传入参数值 还是按照前面讲的规则,先按照位置顺序匹配前两部分,多出来的都归 normal arbitrary argument;然后按照参数变量名指定对应值,没指定的都用缺省值。不过这实在是太麻烦了,不知道什么情况下才必须用这么可怕的函数!
当然,只有上面列出的前三个部分的情况还是有的,比如下面的例子:
def say_hi(msg, *names, punc='!'): for name in names: print(f'{msg}, {name}{punc}') say_hi('hello', 'b', 'c', 'd', punc='.')
hello, b. hello, c. hello, d.
函数定义四要素:函数名、参数表、函数体 和 返回值,本章对每一个部分都进行了更深入的说明,尤其是一些特殊的用法; 函数定义内外是两个不同的 “作用域(scope)”,区分出 全局变量 和 局部变量,需要充分理解其运作原理; 参数表可以分为四段(正常情况下最多只会用到前三段),需要充分理解每一段的特点,如何定义和使用,以及为什么。
2020-03-03 14:39:19 initialize
1. 再探函数定义的原因
2. 为函数命名
3. “没有、一个和多个参数” 的情况
4. “没有、一个和多个返回值” 的情况
结果为:
结果如下:
5. 函数内与函数外:变量的作用域
6. 带缺省值的参数
7. 指定参数名来调用函数
例如下面这个函数:
8. 变长参数
结果如下:
小结
Logging
2020-03-03 14:39:19 initialize