第五章 函数
5.1 函数的简介
- 函数也是一个对象
- 对象是内存中专门用来存储数据的一块区域
- 函数可以用来保存一些可执行的代码,并且可以在需要时,对这些语句进行多次的调用
- 创建函数(定义函数):
def 函数名([形参1, 形参2, ..., 形参n]) :
代码块
- 函数名必须要符和标识符的规范
(可以包含字母、数字、下划线、但是不能以数字开头)
- 函数中保存的代码不会立即执行,需要调用函数代码才会执行
- 调用函数:
函数对象()
- 定义函数一般都是要实现某种功能的
函数的简介–示例代码:
# 比如有如下三行代码,这三行代码是一个完整的功能:
# print('hello')
# print('你好')
# print('再见')
# 定义一个函数
def fn() :
print('这是我的第一个函数!')
print('hello')
print('今天天气真不错!')
# 打印 fn
# print(fn) # <function fn at 0x03D2B618>
# print(type(fn)) # <class 'function'>
# fn 是函数对象 fn() 是调用函数
# print 是函数对象 print() 是调用函数
# fn()
# 定义一个函数,可以用来求任意两个数的和
# def sum() :
# a = 123
# b = 456
# print(a + b)
# sum()
# 定义函数时指定形参
def fn2(a, b) :
# print('a =', a)
# print('b =', b)
print(a, "+", b, "=", a + b)
# 调用函数时,来传递实参
fn2(10, 20)
fn2(123, 456)
5.2 函数的参数
- 在定义函数时,可以在函数名后的()中定义数量不等的形参。
多个形参之间使用,隔开
- 形参(形式参数),定义形参就相当于在函数内部声明了变量,但是并不赋值
- 实参(实际参数)
- 如果函数定义时,指定了形参,那么在调用函数时也必须传递实参,实参将会赋值给对应的形参,简单来说,有几个形参就得传几个实参。
练习1:
定义一个函数,可以用来求任意三个数的乘积。
练习2:
定义一个函数,可以根据不同的用户名显示不同的欢迎信息。
示例代码:
# 求任意三个数的乘积
def mul(a, b, c) :
print(a * b * c)
# 根据不同的用户名显示不同的欢迎信息
def welcome(username) :
print('欢迎', username, '光临')
# mul(1, 2, 3)
# welcome('孙悟空')
5.3 函数参数传递的方式
# 定义一个函数
# 定义形参时,可以为形参指定默认值
# 指定了默认值以后,如果用户传递了参数则形参默认值没有任何作用,如果用户没有传递参数,则形参默认值就会生效。
def fn(a = 5, b = 10, c = 20) :
print('a =', a)
print('b =', b)
print('c =', c)
# fn(1, 2, 3)
# fn(1, 2)
# fn()
# 实参的传递方式
# 位置参数
# 位置参数:就是将对应位置的实参赋值给对应位置的形参
# 第一个实参赋值给第一个形参,第二个实参赋值给第二个形参,......
# fn(1, 2, 3)
# 关键字参数
# 关键字参数:可以不按照形参定义的顺序去传递,而直接根据参数名去传递参数
# fn(b=1, c=2, a=3)
# print('hello', end='') # end 就是关键字参数
# 位置参数和关键字参数可以混合使用
# 混合使用位置参数和关键字参数时,必须将位置参数写到前面,且位置参数和关键字参数指定的位置不能相同。
# fn(1, c=30) # a = 1, b = 10, c = 30
# fn(1, a=30) # 报错
def fn2(a) :
print('a =', a)
# 函数在调用时,解析器不会检查实参的类型
# 实参可以传递任意类型的对象
b = 123
b = True
b = 'hello'
b = None
b = [1, 2, 3]
# fn2(b)
# fn2(fn) # 没问题,实参可以是函数对象
def fn3(a , b) :
print(a + b)
# fn3(123, "456") # 报错
def fn4(a) :
# 在函数中对形参进行重新赋值,不会影响其他的变量
# a = 20
# a 是一个列表,我们尝试修改列表中的元素
# 如果形参执行的是一个对象,当我们通过形参去修改对象的值时,会影响到所有指向该对象的变量。
a[0] = 30
print('a =', a, id(a))
c = 10
c = [1, 2, 3]
# fn4(c)
# fn4(c.copy()) # 列表副本,新的对象
# fn4(c[:]) # 列表副本,新的对象
# print('c =', c, id(c))
5.4 函数的不定长参数
# 不定长的参数
# 定义一个函数,可以求任意个数字的和
def sum(*nums) :
# 定义一个变量,来保存结果
result = 0
# 遍历元组,并将元组中的数进行累加
for n in nums :
result += n
print(result)
# sum(123, 456, 789, 10, 20, 30, 40)
# 在定义函数时,可以在形参前边加上一个*,这样这个形参将会获取到所有的实参
# 它将会将所有的实参保存到一个元组中
# a, b, *c = (1, 2, 3, 4, 5, 6) # 元组的解包(解构)
# *a 会接受所有的位置实参,并且会将这些实参统一保存到一个元组中(参数的装包)
def fn(*a) :
print("a =", a, type(a)) # a = (1, 2, 3, 4, 5) <class 'tuple'>
fn(1, 2, 3, 4, 5)
# 带星号的形参只能有一个
# 带星号的参数,可以和其他参数配合使用
# 第一个参数给 a,第二个参数给 b,剩下的都保存到 c 的元组中
def fn2(a, b, *c) :
print('a =', a) # a = 1
print('b =', b) # b = 2
print('c =', c) # c = (3, 4, 5)
fn2(1, 2, 3, 4, 5)
# 可变参数不是必须写在最后,但是注意,带*的参数后的所有参数,必须以关键字参数的形式传递
# 第一个参数给 a,剩下的位置参数给 b 的元组,c 必须使用关键字参数
def fn2(a, *b, c) :
print('a =', a) # a = 1
print('b =', b) # b = (2, 3, 4)
print('c =', c) # c = 5
fn2(1, 2, 3, 4, c=5)
# 所有的位置参数都给a,b 和 c 必须使用关键字参数
def fn2(*a, b, c) :
print('a =',a) # a = (1, 2, 3)
print('b =',b) # b = 4
print('c =',c) # c = 5
fn2(1, 2, 3, b=4, c=5)
# 如果在形参的开头直接写一个*,则要求我们的所有的参数必须以关键字参数的形式传递
def fn2(*, a, b, c) :
print('a =', a) # a = 3
print('b =', b) # b = 4
print('c =', c) # c = 5
fn2(a=3, b=4, c=5)
# *形参只能接收位置参数,而不能接收关键字参数
def fn3(*a) :
print('a =', a) # a = (1, 2, 3, 4, 5)
fn3(1, 2, 3, 4, 5)
# **形参可以接收其他的关键字参数,它会将这些参数统一保存到一个字典中
# 字典的 key 就是参数的名字,字典的 value 就是参数的值
# **形参只能有一个,并且必须写在所有参数的最后
def fn3(b, c, **a) :
print('a =', a, type(a)) # a = {'d': 2, 'e': 10, 'f': 20} <class 'dict'>
print('b =', b) # b = 1
print('c =', c) # c = 3
fn3(b=1, d=2, c=3, e=10, f=20)
# 参数的解包(拆包)
def fn4(a, b, c) :
print('a =', a)
print('b =', b)
print('c =', c)
# 创建一个元组
t = (10, 20, 30)
# 创建一个列表
# t = [10, 20, 30]
# 传递实参时,也可以在序列类型的参数前添加星号,这样它会自动将序列中的元素依次作为参数传递
# 这里要求序列中元素的个数必须和形参的个数的一致
# fn4(t[0], t[1], t[2]) # 此种方式太麻烦了
fn4(*t) # 解包的方式
# 创建一个字典
d = {
'a':100, 'b':200, 'c':300}
# 通过 ** 来对一个字典进行解包操作
fn4(**d)
5.5 函数的返回值
# 返回值,返回值就是函数执行以后返回的结果
# 可以通过 return 来指定函数的返回值
# 可以直接使用函数的返回值,也可以通过一个变量来接收函数的返回值
def sum(*nums) :
# 定义一个变量,来保存结果
result = 0
# 遍历元组,并将元组中的数进行累加
for n in nums :
result += n
print(result)
# sum(123, 456, 789)
# return 后边跟什么值,函数就会返回什么值
# return 后边可以跟任意的对象,返回值甚至可以是一个函数
def fn() :
# return 'hello'
# return [1, 2, 3]
# return {'k':'v'}
def fn2() :
print('hello')
return fn2 # 返回值也可以是一个函数
r = fn() # 这个函数的执行结果就是它的返回值
# r()
# print(fn())
# print(r)
# 如果仅仅写一个 return 或者不写 return,则相当于 return None
def fn2() :
a = 10
return
# 在函数中,return 后的代码都不会执行,return 一旦执行,则函数自动结束
def fn3():
print('hello')
return
print('abc')
# r = fn3()
# print(r)
def fn4() :
for i in range(5) : # 生成一个这样的自然数序列:[0, 1, 2, 3, 4]
if i == 3 :
# break 用来退出当前循环
# continue 用来跳过当次循环
return # 用来结束函数
print(i)
print('循环执行完毕!')
# fn4()
def sum(*nums) :
# 定义一个变量,来保存结果
result = 0
# 遍历元组,并将元组中的数进行累加
for n in nums :
result += n
return result
r = sum(123, 456, 789)
# print(r + 778)
def fn5():
return 10
# fn5 和 fn5() 的区别
print(fn5) # fn5 是函数对象,打印 fn5 实际是在打印函数对象:<function fn5 at 0x05771BB8>
print(fn5()) # fn5() 是在调用函数,打印 fn5() 实际上是在打印 fn5() 函数的返回值:10
5.6 函数的文档字符串–函数的说明
# help() 是 Python 中的内置函数
# 通过 help() 函数可以查询 python 中的函数的用法
# 语法:help(函数对象)
# help(print) # 获取 print() 函数的使用说明
# 文档字符串(doc str)
# 在自定义函数时,可以在函数内部编写文档字符串,文档字符串就是【函数的说明】,建议使用英文编写,哈哈!
# 当我们编写了文档字符串时,就可以通过 help() 函数来查看自定义函数的说明
# 文档字符串非常简单,其实直接在函数的第一行写一个字符串就是文档字符串,单引号和双引号均可,但是一般使用 三重引号,因为三重引号可以换行,并且会保留字符串中的格式。
def fn(a: int, b: bool, c: str='hello') -> int :
'''
这是一个文档字符串的示例
函数的作用:......
函数的参数:
a,作用,类型,默认值......
b,作用,类型,默认值......
c,作用,类型,默认值......
'''
return 10
help(fn)
5.7 变量的作用域与命名空间
# 作用域(scope)
# 作用域指的是变量生效的区域
b = 20 # 全局变量
def fn() :
a = 10 # a定义在了函数内部,所以它的作用域就是函数内部,函数外部无法访问
print('函数内部:', 'a =', a)
print('函数内部:', 'b =', b)
# fn()
print('函数外部:', 'a =', a)
print('函数外部:', 'b =', b)
# 在 Python 中一共有两种作用域:
# 全局作用域
# - 全局作用域在程序执行时创建,在程序执行结束时销毁
# - 所有函数以外的区域都是全局作用域
# - 在全局作用域中定义的变量,都属于全局变量,全局变量可以在程序的任意位置被访问
# 函数作用域
# - 函数作用域在函数调用时创建,在调用结束时销毁
# - 函数每调用一次就会产生一个新的函数作用域
# - 在函数作用域中定义的变量,都是局部变量,它只能在函数内部被访问
#
# 变量的查找--就近原则
# - 当我们使用变量时,会优先在当前作用域中寻找该变量,如果有则使用,
# 如果没有,则继续去上一级作用域中寻找,如果有则使用,
# 如果依然没有,则继续去上一级作用域中寻找,以此类推
# 直到找到全局作用域,依然没有找到,则会抛出异常:NameError: name 'a' is not defined
def fn2() :
def fn3() :
print('fn3中:', 'a =', a)
fn3()
# fn2()
a = 20
def fn3() :
# a = 10 # 在函数中为变量赋值时,默认都是为局部变量赋值
# 如果希望在函数内部修改全局变量,则需要使用 global 关键字,来声明变量
global a # 声明在函数内部使用的 a 是全局变量,此时再去修改 a 时,就是在修改全局的 a
a = 10 # 修改全局变量
print('函数内部:', 'a =', a)
# fn3()
# print('函数外部:', 'a =', a)
-------------------------------------------------------------------------------------
# 命名空间(namespace)
# 命名空间指的是变量存储的位置,每一个变量都需要存储到指定的命名空间当中
# 每一个作用域都会有一个它对应的命名空间
# 全局的命名空间,用来保存全局变量
# 函数的命名空间,用来保存函数中的变量(局部变量)
# 命名空间实际上就是一个字典,是一个专门用来存储变量的字典
# locals() 用来获取当前作用域的命名空间
# 如果在全局作用域中调用 locals(),则获取的是全局命名空间
# 如果在函数作用域中调用 locals(),则获取的是函数的命名空间
# 返回的是一个字典
a = 10
scope = locals() # 获取当前命名空间
print(scope)
print(type(scope)) # <class 'dict'>
# print(a) # 10
# print(scope['a']) # 10
# 向 scope 中添加一个 key-value
scope['c'] = 1000 # 向字典中添加 key-value 就相当于在全局中创建了一个全局变量(一般不建议这么做)
print(c)
def fn4() :
a =