简介
函数是程序的可复用片段,允许你为语句块赋予名字之后在程序的任何地方运行它们任意次,这称做函数调用。
我们已经使用过一些内建函数,例如len和range等。
函数也许是任何有意义的软件中最重要的构件,所以我们将在本章探究函数的方方面面。
函数以关键字def定义,其后紧跟函数名,由一对小括号闭合的形参,最后以冒号结束定义行,
定义行下面的是函数体,它是一个语句块。
听着有点复杂,其实定义起来是很简单的,见下面的例子:
范例:
#!/usr/bin/python
# Filename: function1.py
def sayHello():
print('Hello World!') # 语句块也就是函数体
# 结束函数定义
sayHello() # 调用函数
sayHello() # 再次调用
输出:
$ python function1.py
Hello World!
Hello World!
工作流程:
我用使用上面讲解的函数定义语法定义了一个名叫sayHello的函数,这个函数没有形参因此小括号里也就没有
定义变量。函数形参只是提供给函数的输入,所以我们可以为函数传递不同的形参而后得到相应的结果。
注意我们调用了相同的函数两次,这也意味着我们无需多次编写相同的代码(注:函数体)。
函数形参
一个函数可以拥有形参,它们是你提供给函数的值,因此函数就可以利用这些值进行一些操作了。
函数形参非常类似变量,只是这些变量是在我们调用函数时定义的并在函数运行前被赋值。
形参在函数定义中的小括号内指定,并以逗号分隔。当我们调用函数时以同样的方式提供形参值。
注意下面两个术语的区别 – 函数定义中给定的参数叫做形参,而在函数调用提供的值叫做 – 实参
范例:
#!/usr/bin/python
# Filename: func_param.py
def printMax(a, b):
if a > b:
print(a, 'is maximum')
elif a == b:
print(a, 'is equal to', b)
else:
print(b, 'is maximum')
printMax(3, 4) # 直接提供字面值
x = 5
y = 7
printMax(x, y) # 以变量作为实参
输出:
$ python func_param.py
4 is maximum
7 is maximum
工作流程:
我们定义的函数叫做printMax,它需要两个形参,分别叫做a和b。
函数利用简单的if…else…语句找出两者中较大的数并将其打印。
第一次调用printMax时我们直接使用数字作为实参,而第二次调用时我们使用变量。
printMax(x, y)促使实参x的值赋给形参a, y赋给b。
两次调用中,pringMax的工作方式完全相同。
局部变量
在函数内声明的变量与在函数外的同名变量没有任何关系,即变量名对于函数是局部的。
这被称作变量的作用域,变量的作用域开始于它们所在块中定义它们的定义点处。
范例:
#!/usr/bin/python
# Filename: func_local.py
x = 50
def func(x):
print('x is', x)
x = 2
print('Changed local x to', x)
func(x)
print('x is still', x)
输出:
$ python func_local.py
x is 50
Changed local x to 2
x is still 50
如何工作:
在函数内部,我们首先使用变量x的值时,python引用的是函数形参x的值。
接下来,我们为x赋值2,因为x是这个函数的局部名字,所以当我们在函数中使用x的时候,
不会影响到主块中的x ,在最后的print调用中我们打印了主块中x的值也证明了这点。
使用global语句
当你希望为程序的顶级名字赋值时(没有定义在任何其他作用域中的变量,比如函数或类作用域),
(注: python有名字空间的概念,名字空间建立起名字与实际对象(/数据)的映射关系,
这里的”名字”指的是名字所对应的对象)。这时你必须告诉python,名字不是局部而是全局的。
通过global语句可以做到这点,否则是不可能对一个定义在函数外的变量赋值的。
你可以使用定义在函数外的变量的值(但要假设函数内没有同名变量),不过并不鼓励这种用法而是应该
尽量避免,这会让代码的读者难以弄清变量到底定义在哪?
范例:
#!/usr/bin/python
# Filename: func_global.py
x = 50
def func():
global x
print('x is', x)
x = 2
print('Changed global x to', x)
func()
print('Value of x is', x)
输出:
$ python func_global.py
x is 50
Changed global x to 2
Value of x is 2
工作流程:
global语句用于声明x是一个全局变量 – 因此我们在函数内为x赋值后变化也会反映在主块中对x的使用中。
你也可以使用一个global语句指定多个全局变量,比如global x, y, z。
使用nonlocal语句
我们已经看到如何存取局部和全局变量。还有另一类叫做”非局部”的作用域,它介于局部和全局之间。
当在函数内定义函数时你会注意到非局部作用域。
因为python中的一切只是可执行代码,所以你可以在任何地方定义函数。让我们看一个例子:
#!/usr/bin/python
# Filename: func_nonlocal.py
def func_outer():
x = 2
print('x is', x)
def func_inner():
nonlocal x
x = 5
func_inner()
print('Changed local x to', x)
func_outer()
输出:
$ python func_nonlocal.py
x is 2
Changed local x to 5
工作流程:
当我们处于func_inner函数中时,func_outer第一行定义的变量x既不是局部也不是全局变量。
这时通过nonlocal x我们声明这个x是非局部的,所以我们才能够存取它。
试着将nonlocal x改为global x观察这两种用法有什么不同。
默认实参值
对于一些函数,你可能希望它们的形参是可选的,并当用户没有为这些形参提供值的时候给它们一个默认值。
这需要借助默认实参值。默认实参值在函数定义时通过为形参名赋一个默认值实现。
注意默认实参值应该是一个常量,更确切的应该是一个不可变类型 – 后面的章节会有具体解释,现在只要记住这
点就可以了。
范例:
#!/usr/bin/python
# Filename: func_default.py
def say(message, times = 1):
print(message * times)
say('Hello')
say('World', 5)
输出:
$ python func_default.py
Hello
WorldWorldWorldWorldWorld
工作流程:
函数say用于以指定次数打印指定字符串. 如果我们没有指定次数则默认的字符串只被打印1次。
我们通过为形参times指定一个默认实参值1实现这个功能。
第一次调用say时,我们只提供了被打印的字符串然后函数只打印一次。
而第二次调用时,我们不仅提供了被打印的字符串还提供了实参5以表明我们需要字符串被打印5次。
重点:
只有在实参列表靠后的参数才能拥有默认实参值,即你不能先声明带有默认实参值的形参再声明不带有默认实参值的参数。
这是因为实参值是根据形参的位置赋给形参的,例如:def func(a, b = 5)合法,但def func(a = 5, b)就非法了。
关键实参
如果你有一些函数拥有许多参数,但你只想使用其中的几个,这时你可以通过形参名为其赋值。
这被称做关键实参- 使用形参名(关键字)为函数指定实参而不是我们一直使用的通过位置指定实参。
这样做有两个优点,首先函数用起来更简单,因为我们不用操心实参的顺序了。
其次,可以只为我们感兴趣的形参赋值,如果其它参数带有默认实参值的话。
范例:
#!/usr/bin/python
# Filename: func_key.py
def func(a, b=5, c=10):
print('a is', a, 'and b is', b, 'and c is', c)
func(3, 7)
func(25, c=24)
func(c=50, a=100)
输出:
$ python func_key.py
a is 3 and b is 7 and c is 10
a is 25 and b is 5 and c is 24
a is 100 and b is 5 and c is 50
工作流程:
函数func拥有一个不带有默认实参值的参数,后跟两个带有默认实参值的参数。
第一次调用func(3, 7),形参a得到值3,b为7,c为默认值10。
第二次调用func(25, c=24), 因为a在参数表中所处的位置,它得到25。
而c通过引用其名字得到24也就是关键字实参的功能。b为默认值5。
第三次调用func(c=50, a=100), 我们只使用关键字实参指定形参值。
注意尽管在函数定义中a比c更早定义,但我们仍然可以先指定c再指定a。
可变参数(VarArgs)
TODO
因为我们还没有讨论列表和字典,我是不是应该将这部分放到后面的章节?
有时你可能希望编写一个可以接受任意多个形参的函数,使用星号可以帮你做到:
#!/usr/bin/python
# Filename: total.py
def total(initial=5, *numbers, **keywords):
count = initial
for number in numbers:
count += number
for key in keywords:
count += keywords[key]
return count
print(total(10, 1, 2, 3, vegetables=50, fruits=100))
输出:
$ python total.py
166
工作流程:
当我们以星号声明一个形参比如*param,那么这个参数点之后的所有实参会被收集成一个列表,
本例中这个列表叫做param。与之类似如果我们以双星号声明一个形参,它会被收集成一个关键字实参字典。
后面的章节我们会研究列表和字典。
只能以关键字赋值的形参(Keyword-only Parameters)
(注:为方便理解和翻译,以后直接使用英文术语 keyword-only)
如果我们希望某些关键字形参只能通过关键字实参得到而不是按照实参的位置得到,可以将其声明在星号形参后面:
#!/usr/bin/python
# Filename: keyword_only.py
def total(initial=5, *numbers, vegetables):
count = initial
for number in numbers:
count += number
count += vegetables
return count
print(total(10, 1, 2, 3, vegetables=50))
print(total(10, 1, 2, 3))
# 引发错误,因为我们没有为vegetables提供默认实参值
输出:
$ python keyword_only.py
66
Traceback (most recent call last):
File "test.py", line 12, in <module>
print(total(10, 1, 2, 3))
TypeError: total() needs keyword-only argument vegetables
工作流程:
在星号形参后面声明的形参导致它成为keyword-only实参。
如果没有为这些实参提供一个默认值,那么必须在调用函数时以关键字实参为其赋值,否则将引发错误。
如果你只需要keyword-only实参但不需要星号实参,那么可以简单的省略星号实参的实参名。
例如def total(initial=5, *, vegetables)。
return语句
return用于从函数返回,即跳出函数。也可以利用return语句从函数返回一个值。
范例:
#!/usr/bin/python
# Filename: func_return.py
def maximum(x, y):
if x > y:
return x
else:
return y
print(maximum(2, 3))
输出:
$ python func_return.py
3
工作流程:
maximum函数用于返回参数中的最大值,本例中我们以数字作为参数调用它。
函数使用一个简单的if…else语句比较出最大值并使用return语句将其返回。
注意一个不带有返回值的return语句相当于返回return None。
None是python的一个特殊类型,代表空。例如如果一个变量的值为None则代表它不存在值。
每个函数的末尾都隐含的包含一个return None语句除非你编写了自己的return语句。
你可以通过print(someFunction())证实这点,someFunction是一个没有显式使用return语句的函数。例如:
def someFunction():
pass
其中pass语句用来指示一个空语句块。
提示
python已经包含了一个被称作max的内建函数,它的功能即是寻找最大值,所以尽可能的使用这个函数吧。
DocStrings
python拥有一个俏皮的特性被称作文档字符串,通常它被简称为docstrings。
文档字符串是一个你应该利用的重要的工具,因为它帮助你更好的注释程序使得程序更易于理解。
更神奇的是你甚至可以在程序运行时取得文档字符串!
范例:
#!/usr/bin/python
# Filename: func_doc.py
def printMax(x, y):
'''Prints the maximum of two numbers.
The two values must be integers.'''
x = int(x) # convert to integers, if possible
y = int(y)
if x > y:
print(x, 'is maximum')
else:
print(y, 'is maximum')
printMax(3, 5)
print(printMax.__doc__)
输出:
$ python func_doc.py
5 is maximum
Prints the maximum of two numbers.
The two values must be integers.
工作流程:
一个函数的第一个逻辑行的字符串将成为这个函数的文档字符串。
注意类和模块同样拥有文档字符串,在后面相应的章节我们会学到它们。
根据惯例,文档字符串是一个多行字符串,其中第一行以大写字母开头,并以句号结尾。
接下来的第二行为空行,从第三行开始为详细的描述。
我强烈建议你在你的正规函数中遵循这个编写文档字符串的惯例。
我们可以通过使用函数的__doc__属性(注意双下划线)存取printMax的文档字符串。
记住python中的一切都是对象,其中也包括函数。在后面的类一章我们会学到更多。
如果你在python使用过help(),其实你已经看到过文档字符串的应用了!
help()只是取出函数的__doc__属性,然后以一种整洁的方式显示给你。
你可以用上面的函数作个实验 – 在你的程序中包含help(printMax)即可,记住按q键退出help。
函数注解(Annotations)
函数还拥有另一个被称作函数注解的高级特性,对于附加额外的形参和返回值信息非常有用。
因为python语言本身并不提供这样的功能(甩给了第三方库,具体实现方式第三方库说了算)。
所以在我们的讨论中决定跳过这一特性。如果你对函数注解有兴趣可以参见python增强提议No.3107
(http://www.python.org/dev/peps/pep-3107/)(注:python3已经支持这个特性了)
小结
我们已经看到了函数的方方面面,但注意这些不是函数的所有方面。
不过现有的关于python函数的知识已经足够应付日常应用了。
接下来我们将学习如何使用和创建pthon模块。
![](https://i-blog.csdnimg.cn/blog_migrate/d248848a88eecd2cd8454b625393d298.jpeg)
![](https://i-blog.csdnimg.cn/blog_migrate/509dda39d1cff72cdd4fd1da6ee20d52.jpeg)