关于参数|关于参数 (上)

本文为只字不差打字版 原文链接:https://github.com//the-craft-of-selfteaching 作者:李笑来
之前就提到过,从结构上来看,每个函数都是一个完整的程序,因为一个程序,核心构成部分就是输入、处理、输出:

  • 它可以有输入——即,它能接收外部通过参数传递的值;
  • 它可以有**处理——即,内部有能够完成某一特定任务的代码;尤其是,它可以根据“输入”得到输出;
  • 它可以有输出——即,它能向外部输送返回值……
所以,在我看来,有了一点基础知识之后,最早应该学习的是“如何写函数”——这个起点会更好一些。
这一章的内容,看起来会感觉与 Part1.E.4函数的那一章部分重合。但这两章的出发点不一样:
  • Part1.E.4函数的那一章,只是为了让读者有“阅读”函数说明文档的能力;
  • 这一章,是为了让读者能够开始动手写函数给自己或别人用……
为函数取名 哪怕一个函数内部什么都不干,它也得有个名字,然后名字后面要加上圆括号(),以明示它是个函数,而不是某个变量。
定义一个函数的关键字是def,以下代码定义了一个什么都不干的函数:
def do_nothing(): passdo_nothing()

为函数取名(为变量取名也一样)有些基本的注意事项:
  • 首先不能以数字开头。能用在名称开头的有,大小写字母和下划线_;
  • 其次,名称中,不能有空格,要么使用下划线连接词汇,如,do_nothing,要么使用Camel Case,如 doNothing——更推荐使用下划线;
  • 再次,名称不能与关键字重合——以下是Python的Keyword List:
- Python Keyword List -
and as assert async await
break class continue def del
elif else except False finally
for from global if import
in is lambda None nonlocal
not or pass raise return
True try while with yield
你随时可以用以下代码查询关键字列表:
from IPython.core.interactivershell import InteractiveShell InteractiveShell.ast_node_interactivity ="all"import keyword keyword.kwlist keyword.iskeyword('if')

['False',
'None',
'True',
'and',
'as',
'assert',
'async',
'await',
'break',
'class',
'continue',
'def',
'del',
'elif',
'else',
'except',
'finally',
'for',
'from',
'global',
'if',
'import',
'in',
'is',
'lambda',
'nonlocal',
'not',
'or',
'pass',
'raise',
'return',
'try',
'while',
'with',
'yield']
关于更多为函数、变量取名所需要的注意事项,请参阅:
  • PEP 8 -- Style Guide for Python Code:
    Naming Conventions
  • PEP 526 -- Syntax for Variable Annotations
    注: PEPs,是Python Enhancement Proposals的缩写:https://www.python.org/dev/peps/
不接受任何参数的函数 在定义函数的时候,可以定义成不接受任何参数;但调用函数的时候,依然需要写上函数名后面的圆括号():
def do_nothing(): print('This is a hello message from do_something().')do_nothing()

This is a hello message from do_something ().
没有 return 语句的函数 函数内部,不一定非要有 return 语句——上面 do_something()函数就没有 return 语句。但如果函数内部并未定义返回值,那么,该函数的返回值是None,当None 被当做布尔值对待的时候,相当于False。
这样的设定,使得函数调用总是可以在条件语句中被当做判断依据:
def do_something(): print('This is a hello message from do_something().')if not do_something():#由于该函数名称的缘故,这一句代码的可读性很差…… print("The return value of 'do_something()'is None.")

This is a hello message from do_something().
The return value of 'do_something()' is None.
if not do_something(): 翻译成自然语言,应该是,“如果 do_something() 的返回值是 ‘非真’,那么:……”
接收外部传递进来的值 让我们写个判断闰年年份的函数,取名is_leap(),它接收一个年份为参数,若是闰年,则返回True,否则返回False。
根据闰年的定义:
  • 年份应该是4的倍数;
  • 年份能被100整除但不能被400整除的,不是闰年。
所以,相当于要在能被4整除的年份中,排除那些能被100整除却不能被400整除的年份。
def is_leap(year): leap = False if year % 4 == 0: leap = True if year % 100 == 0 and year % 400 != 0: leap = False return leapis_leap(7) is_leap(12) is_leap(100) is_leap(400)

#另外一个更为简洁的版本,理解它还练脑子的 #cpython/Lib/datetime.py def _is_leap(year): return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0) _is_leap(300)

函数可以同时接受多个参数。比如,我们可以写个函数,让它输出从大于某个数字到小于另外一个数字的斐波那契数列;那就需要定义两个参数,调用他的时候也需要传递两个参数:
def fib_between(start,end): a,b = 0,1 while a < end: if a >= start: print(a,end='') a,b=b,a+bfib_between(100, 10000)

[144, 233, 377, 610, 987, 1597, 2584, 4181, 6765]
变量的作用域 下面的代码,经常会让初学者迷惑:
def increase_one(n): n += 1 return nn =1 print(increase_one(n)) #print(n)

2
当increase_one(n)被调用之后,n的值究竟是多少呢?
输出结果是1.
在程序执行过程中,变量有全局变量(Global Variables)和局域变量(Local Variables)之分。
首先,每次某个函数被调用的时候,这个函数会开辟一个新的区域,这个函数内部所有的变量,都是局域变量。也就是说,即便那个函数内部某个变量的名称与它外部的某个全局变量——只是名称相同而已。
其次,更为重要的是,当外部调用一个函数的时候,准确地将,传递的不是变量,而是那个变量的值。也就是说,当 increase_one(n) 被调用的时候,被传递给那个恰好名称也叫n的局域变量,是全局变量n的值,1.
而后,increase_one(n) 函数的代码开始执行,局域变量n经过n+=1之后,其中存储的值是2,而后这个值被 return 语句返回,所以,print( increase_one(n) )所输出的值势函数被调用之后的返回值,即,2.
然而,全局变量n的值并没有改变,因为局部变量n(它的值还是1)只不过是名字相同而已,但它们并不是同一个变量。
以上的文字,可能需要反复阅读若干遍;几遍下来,消除疑惑,以后就彻底没问题了;若是这个疑惑并未消除,或者关键点并未消化,以后则会反复被这个疑惑所坑害,浪费无数时间。
不过,有一种情况要格外注意——在函数内部处理被传递进来的值是可变的容器(比如,列表)的时候:
def be_careful(a,b): a = 2 b[0]='What?!'a = 1 b = [1, 2, 3] be_careful(a, b) a, b

(1, ['What?!', 2, 3])
所以,一个比较好的习惯是,如果传递进来的值是列表,那么在函数内部对其操作之前,先创建一个它的拷贝:
def be_careful(a,b): a = 2 b_copy = b.copy() b_copy[0]='What?!'a = 1 b = [1, 2, 3] be_careful(a, b) a, b

【关于参数|关于参数 (上)】(1, [1, 2, 3])

    推荐阅读