Skip to content

关于参数(上)

17 / 44

之前就提到过从结构上来看每个函数都是一个完整的程序因为一个程序核心构成部分就是输入处理输出

  • 它可以有输入 —— 它能接收外部通过参数传递的值
  • 它可以有处理 —— 内部有能够完成某一特定任务的代码尤其是它可以根据输入得到输出”;
  • 它可以有输出 —— 它能向外部输送返回值……

所以在我看来有了一点基础知识之后最早应该学习的是如何写函数” —— 这个起点会更好一些

这一章的内容看起来会感觉与 Part1.E.4 函数那一章部分重合但这两章的出发点不一样

  • Part1.E.4 函数那一章只是为了让读者有阅读函数说明文档的能力
  • 这一章是为了让读者能够开始动手写函数给自己或别人用……

为函数取名

哪怕一个函数内部什么都不干它也得有个名字然后名字后面要加上圆括号 ()以明示它是个函数而不是某个变量

定义一个函数的关键字是 def以下代码定义了一个什么都不干的函数

python
def do_nothing():
    pass

do_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 |

你随时可以用以下代码查询关键字列表

python
from IPython.core.interactiveshell import InteractiveShell
InteractiveShell.ast_node_interactivity = "all"

import keyword
keyword.kwlist               # 列出所有关键字
keyword.iskeyword('if')      # 查询某个词是不是关键字
plaintext
['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']

True

关于更多为函数变量取名所需要的注意事项请参阅

:PEPs, Python Enhancement Proposals 的缩写https://www.python.org/dev/peps/

不接收任何参数的函数

在定义函数的时候可以定义成不接收任何参数但调用函数的时候依然需要写上函数名后面的圆括号 ()

python
def do_something():
    print('This is a hello message from do_something().')

do_something()
plaintext
This is a hello message from do_something().

没有 return 语句的函数

函数内部不一定非要有 return 语句 —— 上面 do_somthing() 函数就没有 return 语句但如果函数内部并未定义返回值那么该函数的返回值是 None None 被当作布尔值对待的时候相当于是 False

这样的设定使得函数调用总是可以在条件语句中被当作判断依据

python
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.")
plaintext
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 整除的年份

python
def is_leap(year):
    leap = False
    if year % 4 == 0:
        leap = True
        if year % 100 == 0 and year % 400 != 0:
            leap = False
    return leap

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

函数可以同时接收多个参数比如我们可以写个函数让它输出从大于某个数字到小于另外一个数字的斐波那契数列那就需要定义两个参数调用它的时候也需要传递两个参数

python
def fib_between(start, end):
    a, b = 0, 1
    while a < end:
        if a >= start:
            print(a, end=' ')
        a, b = b, a + b

fib_between(100, 10000)
plaintext
144 233 377 610 987 1597 2584 4181 6765

当然可以把这个函数写成返回值是一个列表

python
def fib_between(start, end):
    r = []
    a, b = 0, 1
    while a < end:
        if a >= start:
            r.append(a)
        a, b = b, a + b
    return r

fib_between(100, 10000)
plaintext
[144, 233, 377, 610, 987, 1597, 2584, 4181, 6765]

变量的作用域

下面的代码经常会让初学者迷惑

python
def increase_one(n):
    n += 1
    return n

n = 1
print(increase_one(n))
# print(n)
plaintext
2

increase_one(n) 被调用之后n 的值究竟是多少呢或者更准确点问随后的 print(n) 的输出结果应该是什么呢

输出结果是 1

在程序执行过程中变量有全局变量(Global Variables)局域变量(Local Variables)之分

首先每次某个函数被调用的时候这个函数会开辟一个新的区域这个函数内部所有的变量都是局域变量也就是说即便那个函数内部某个变量的名称与它外部的某个全局变量名称相同它们也不是同一个变量 —— 只是名称相同而已

其次更为重要的是当外部调用一个函数的时候准确地讲传递的不是变量而是那个变量的也就是说 increase_one(n) 被调用的时候被传递给那个恰好名称也叫 n 的局域变量的是全局变量 n 的值1

而后increase_one() 函数的代码开始执行局域变量 n 经过 n += 1 之后其中存储的值是 2而后这个值被 return 语句返回所以print(increase(n)) 所输出的值是函数被调用之后的返回值2

然而全局变量 n 的值并没有被改变因为局部变量 n它的值是 2和全局变量 n它的值还是 1只不过是名字相同而已但它们并不是同一个变量

以上的文字可能需要反复阅读若干遍几遍下来消除了疑惑以后就彻底没问题了若是这个疑惑并未消除或者关键点并未消化以后则会反复被这个疑惑所坑害浪费无数时间

不过有一种情况要格外注意 —— 在函数内部处理被传递进来的值是可变容器比如列表的时候

python
def be_careful(a, b):
    a = 2
    b[0] = 'What?!'

a = 1
b = [1, 2, 3]
be_careful(a, b)
a, b
plaintext
(1, ['What?!', 2, 3])

所以一个比较好的习惯是如果传递进来的值是列表那么在函数内部对其操作之前先创建一个它的拷贝

python
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
plaintext
(1, [1, 2, 3])