文章目录
十五、函数
1. 函数概述
- 概述
- 所谓函数,就是把 具有独立功能的代码块 组织为一个小模块,在需要的时候 调用
- 函数的使用包含两个步骤:
- 定义函数 —— 封装 独立的功能
- 调用函数 —— 享受 封装 的成果
def
是英文define
的缩写- 函数名称 的命名应该 符合 标识符的命名规则
- 可以由 字母、下划线 和 数字 组成
- 不能以数字开头
- 不能与关键字重名
- 函数的作用:
- 提高代码的开发效率
- 使代码更加简洁
- 提高代码的复用率
- 实现代码的一致性
- 一致性指的是,只要修改函数的代码,则所有调用该函数的地方都能得到体现。
- 分类
-
内置函数:我们前面使用的 str()、list()、len()等这些都是内置函数,我们可以拿来直接使用。
-
标准库函数:我们可以通过 import 语句导入库,然后使用其中定义的函数
-
第三方库函数:Python 社区也提供了很多高质量的库。下载安装这些库后,也是通过 import 语句导 入,然后可以使用这些第三方库的函数
-
用户自定义函数:用户自己定义的函数,显然也是开发中适应用户自身需求定义的函数。今天我们学习的 就是如何自定义函数。
2. 函数的定义和调用(重点)
1. 定义与调用
函数的定义格式:
def 函数名([参数列表...]):
'''文档字符串'''
函数功能代码...
函数功能代码...
定义空函数
def def 函数名(参数列表...):
pass # 占位符的作用
函数使用(调用):
函数名(参数...)
参数列表
- 圆括号内是形式参数列表,有多个参数则使用逗号隔开
- 形式参数不需要声明类型,也不需要指定函数返回值类型
- 无参数,也必须保留空的圆括号
- 实参列表必须与形参列表一一对应
注意
- 函数定义必须在函数调用的上方
- 内置函数对象会自动创建
- 标准库和第三方库函数,通过 import 导入模块时,会执行模块中的 def 语句
- Python 执行 def 时,会创建一个函数对象,并绑定到函数名变量上。
2. 函数调用过程(重点)
- 函数在定义时,函数体中的代码是不执行的
- 当产生函数调用时,程序才会跳转到函数体中执行函数的代码
- 函数体中的代码执行完成后,回到调用处 (重点理解)
3. 函数文档注释(了解)
文档注释(DocString):写在程序文件开头的位置时,是程序文件的DocString,对整个程序进行说明
函数的注释:写在函数内部开头位置时,是对函数功能的说明, 函数的 DocString
- 在 连续的三对引号 之间编写对函数的说明文字
def show():
'''
这是show函数的说明文档
show函数的作用是用来显示一个字符串
:return:
'''
print('hello')
return
- 在 函数调用 位置,使用快捷键
CTRL + Q
可以查看函数的说明信息 - 或用
help(函数名)
:用来显示函数的说明文档 - 或
函数名.__doc__
来调用
# 注:函数名不加括号,因为不需要执行这个函数
eg. help(show) √
help(show()) ×
- 按住ctrl + 鼠标左键 点击函数名,跳转到函数
4. 函数的参数
- 参数的传递
函数的参数传递本质上就是:从实参到形参的赋值操作。 Python 中“一切皆对象”, 所有的赋值操作都是“引用的赋值”。所以,Python 中参数的传递都是“引用传递”,不 是“值传递”。具体操作时分为两类:
- 参数的作用
函数的参数,增加函数的 通用性,针对 相同的数据处理逻辑,能够 适应更多的数据
- 在函数 内部,把参数当做 变量 使用,进行需要的数据处理
- 函数调用时,按照函数定义的参数顺序,把 希望在函数内部处理的数据,通过参数 传递
- 参数的使用
- 在函数名的后面的小括号内部填写 参数
- 多个参数之间使用
,
分隔
- 参数传递中是否会影响原参数
无论传递的参数是可变还是不可变
- 只要 针对参数 使用 赋值语句,会在 函数内部 修改 局部变量的引用,不会影响到 外部变量的引用
如果传递的参数是 可变类型
- 在函数内部,使用 方法 修改了数据的内容,同样会影响到外部的数据
传递不可变对象时。不可变对象里面包含的子对象是可变的。
-
则方法内修改了这个可变对象,源对象也发生了变化
-
传递不可变对象时,如果发生拷贝,用的是浅拷贝(见本章《浅拷贝,深拷贝》)
#传递不可变对象时。不可变对象里面包含的子对象是可变的。则
方法内修改了这个可变对象,源对象也发生了变化。
a = (10,20,[5,6])
print("a:",id(a))
def test01(m):
print("m:",id(m))
m[2][0] = 888
print(m)
print("m:",id(m))
test01(a)
print(a)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5eHnvD9S-1610633237772)(Media/image-20210103173835181.png)]
面试题 —— 参数传递中是否会影响原参数
- 在
python
中,列表变量调用+=
本质上是在执行列表变量的extend
方法,不会修改变量的引用
def demo(num, num_list):
print("函数内部代码")
# num = num + num
num += num
# num_list.extend(num_list) 由于是调用方法,所以不会修改变量的引用
# 函数执行结束后,外部数据同样会发生变化
num_list += num_list
print(num)
print(num_list)
print("函数代码完成")
gl_num = 9
gl_list = [1, 2, 3]
demo(gl_num, gl_list)
print(gl_num)
print(gl_list)
- 形参
- 形参:形式参数,定义 函数时,小括号中的参数,接收函数调用传递过来的实参数据,在函数内部 作为变量使用
- 不占用内存空间,只有在被调用时才会占用内存空间,调用完了即被释放。
- 在定义形式参时,每个参数都可以理解成一个key
- 使用这个key,可以明确的为当前这个参数进行赋值
def say_hi(name) # name是形参,占位作用
print('hello', name)
say_hi('tom') # 实参,拥有具体的数据
# 调用函数时,传递参数:name = tom
- 形参的使用——默认参数
在定义函数时,函数中的形式参数,被赋值,这个值就是默认值
当在函数调用时,如果给定了值,那么就使用给定值,如果没有给定值,那就使用默认值
- 注意:
- 默认值参数只能出现在参数列表的最右侧
def display(a,b,c=0):
print(a,b,c)
display(1,2)
- 实参
- 实参:实际参数,调用 函数时,小括号中的参数,是用来把数据传递到 函数内部 用的
- 实参可以是常量、变量、表达式、函数等
- 实参的使用——位置参数与关键字参数
-
位置参数:即函数调用时传入的参数与定义的位置和值一一对应
- 通过test(1,2,3)调用,则参数1、2、3与x、y、z一一对应
-
关键字参数:即在函数调用时通过参数=值指定各个参数的值,此时与位置无关
- 可以无序
- 通过test(y=2,z=3,x=1)调用,此时需指定每个参数的值,个数要与定义时一致但是位置可随意
-
位置参数和关键字参数混合使用:
- 位置参数需在关键字参数前面,关键字参数按顺序赋值,
- 例如test(1,2,z=3),但是test(x=1,2,3)、test(1,2,y=3)会报错
5. 可变参数
- 不定长位置参数(可变参数)
*args
:在参数中定义了该形参后,那可接收多个不确定个数的位置参数
def 函数名(*args):
函数体内容
注意
args
可自定义名称,前边带*
即可- 此时
args
为元组类型 - 在函数中使用时需要将*去掉,即
args
,不带*
号,则相当于是用一个元组 - 而
*args
为手动解包后的数据,相当于一连串数据,可以依次传递给多个参数
def my_son(*args):
print(args)
s = 0
for i in args: # 求和功能
s += i
print(s)
my_son(1)
my_son(1, 2)
my_son(1, 2, 3)
my_son(1, 2, 3, 4)
my_son(1, 2, 3, 4, 5)
- 不定长关键字参数(可变参数)
**kwargs
:在参数中定义了该形参后,那可接收多个不确定个数的关键字参数
def 函数名(*args, **kwargs):
函数体内容
注意
kwargs
可自定义名称,前边带*
即可- 此时
kwargs
为字典类型 - 在函数中使用时需要将*去掉,即
kwargs
,不带*
号,则相当于是用一个字典 - 而
**kwargs
为手动解包后的数据,相当于一连串数据,可以依次传递给多个参数 **kwargs
需在*args
之后
def show(**kwargs):
print(kwargs)
show(a=1)
show(a=1,b=2)
show(a=1,b=2,c=3)
# 定义一个可以接收任何参数的函数
def display(*args,**kwargs):
print(f'args:{args}')
print(f'kwargs:{kwargs}')
display()
display(1,2,3)
display(a=1,b=2)
display(1,2,3,4,a=1,b=2)
- 多值参数案例 —— 计算任意多个数字的和
需求
- 定义一个函数
sum_numbers
,可以接收的 任意多个整数 - 功能要求:将传递的 所有数字累加 并且返回累加结果
def sum_numbers(*args):
num = 0
# 遍历 args 元组顺序求和
for n in args:
num += n
return num
print(sum_numbers(1, 2, 3))
- 混合参数的顺序(了解)
如果很多个值都是不定长参数,那么这种情况下,可以将缺省参数放到 *args的后面, 但如果有**kwargs的话,**kwargs必须是最后的
def func(a,b,c,d,e, *args,f=1,g=2,**kwargs):
函数体代码
eg.
def func(a,b,c,d,e, *args,f=1,g=2,**kwargs):
print(a,b,c,d,e)
print(args)
print(f,g)
print(kwargs)
func(1,2,3,4,5)
print('---------------')
func(1,2,3,4,5,6,7,8,9)
print('---------------')
func(1,2,3,4,5,6,7,8,9,f=11,g=22)
print('---------------')
func(1,2,3,4,5,6,7,8,9,f=11,g=22,h=33,i=44)
- 强制命名参数
在带星号的“可变参数”后面增加新的参数,必须在调用的时候“强制命名参数”。
def f1(*a,b,c):
print(a,b,c)
# f1(2,3,4) #会报错。由于 a 是可变参数,将 2,3,4 全部收集。造成 b 和 c 没有赋值。
f1(2,b=3,c=4)
5.可变参数二次传递(重点理解)
# 报错
def show(a, *args, **kwargs):
print(a)
display(args) # 这里是元组,因此相当于值传递了一个元组参数给a,而b,c,d缺少参数
def display(a, b, c, d):
print(a, b, c, d) # TypeError: display() missing 3 required positional arguments: 'b', 'c', and 'd'
show(1, 2, 3, 4, 5)
出错原因:只把args元组传递给了a,而b,c,d缺少数据,因此报错
# 手动解包
def show(a, *args, **kwargs):
print(a)
display(*args) # 这里用*args,手动解包,数据变成4个,依次传递给a,b,c,d
def display(a, b, c, d):
print(a, b, c, d)
show(1, 2, 3, 4, 5)
- 手动解包(序列解包)
序列解包可以用于元组、列表、字典。序列解包可以让我们方便的对多个变量赋值。
即:将一个可迭代对象数据拆分,使其能依次传递给多个数据
t = (1, 2, 3)
print(t) # (1, 2, 3)
print(*t) # 1 2 3
注意
- 手动解包只适用于传参时,赋值时会自动解包
- 用
*
可解包的对象只有可迭代对象- 字符串
- 元祖
- 字典等都可以
- 整型类的不可以。
- 用
*
解包字典,可以拿到其对应的键key
- 用
**
可以解包字典
def demo(*args, **kwargs):
print(args)
print(kwargs)
# 需要将一个元组变量/字典变量传递给函数对应的参数
gl_nums = (1, 2, 3)
gl_xiaoming = {"name": "小明", "age": 18}
# 会把 num_tuple 和 xiaoming 作为元组传递个 args
# demo(gl_nums, gl_xiaoming)
demo(*gl_nums, **gl_xiaoming)
6. 函数返回值(难点,重点)
- 概述
return 返回一个函数的结果
- 返回值 是函数 完成工作后,最后 给调用者的 一个结果
- 调用函数一方,可以 使用变量 来 接收 函数的返回结果
- 格式:
return 数据
注意
-
一个函数,无论在哪遇到
return
,那么这个函数都会直接结束执行,回到调用处,并返回值 -
return
后面可以没有数据 -
函数也可以没有
return
,函数默认返回None
-
多个return
- 一个函数中可以存在多个Return
- 但是,只能有一个语句有效
- 在执行顺序上,第一个遇到的Return 有效
7. 使用模块中的函数
模块是 Python 程序架构的一个核心概念
- 模块 就好比是 工具包,要想使用这个工具包中的工具,就需要 导入 import 这个模块
- 每一个以扩展名
py
结尾的Python
源代码文件都是一个 模块 - 在模块中定义的 全局变量 、 函数 都是模块能够提供给外界直接使用的工具
6.1 第一个模块体验
步骤
- 新建
hm_10_分隔线模块.py
- 复制
hm_09_打印多条分隔线.py
中的内容,最后一行print
代码除外 - 增加一个字符串变量
- 复制
name = "黑马程序员"
- 新建
hm_10_体验模块.py
文件,并且编写以下代码:
import hm_10_分隔线模块
hm_10_分隔线模块.print_line("-", 80)
print(hm_10_分隔线模块.name)
体验小结
- 可以 在一个 Python 文件 中 定义 变量 或者 函数
- 然后在 另外一个文件中 使用
import
导入这个模块 - 导入之后,就可以使用
模块名.变量
/模块名.函数
的方式,使用这个模块中定义的变量或者函数
模块可以让 曾经编写过的代码 方便的被 复用!
- 模块名也是一个标识符
- 标示符可以由 字母、下划线 和 数字 组成
- 不能以数字开头
- 不能与关键字重名
注意:如果在给 Python 文件起名时,以数字开头 是无法在
PyCharm
中通过导入这个模块的
- Pyc 文件(了解)
C
是compiled
编译过 的意思
操作步骤
- 浏览程序目录会发现一个
__pycache__
的目录 - 目录下会有一个
hm_10_分隔线模块.cpython-35.pyc
文件,cpython-35
表示Python
解释器的版本 - 这个
pyc
文件是由 Python 解释器将 模块的源码 转换为 字节码Python
这样保存 字节码 是作为一种启动 速度的优化
- 字节码
-
Python
在解释源程序时是分成两个步骤的- 首先处理源代码,编译 生成一个二进制 字节码
- 再对 字节码 进行处理,才会生成 CPU 能够识别的 机器码
-
有了模块的字节码文件之后,下一次运行程序时,如果在 上次保存字节码之后 没有修改过源代码,Python 将会加载 .pyc 文件并跳过编译这个步骤
-
当
Python
重编译时,它会自动检查源文件和字节码文件的时间戳 -
如果你又修改了源代码,下次程序运行时,字节码将自动重新创建
提示:有关模块以及模块的其他导入方式,后续课程还会逐渐展开!
模块是 Python 程序架构的一个核心概念
8. 函数也是对象,内存底层分析
执行 def 定义函数后,系统就创建了相应的函数 对象。
def print_star(n):
print("*"*n)
print(print_star)
print(id(print_star))
c = print_star
c(3)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pda6ayz3-1610633237780)(Media/image-20210102201647543.png)]
我们执行“c=print_star”后,显然将 print_star 变量的值赋给了变量 c,内存图变成了:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JRjmUIl9-1610633237785)(Media/image-20210102201704036.png)]
显然,我们可以看出变量 c 和 print_star 都是指向了同一个函数对象。因此,执行 c(3)和执 行 print_star(3)的效果是完全一致的。
注
- Python 中,圆括号意味着调用函数。在没有圆括 号的情况下,Python 会把函数当做普通对象。
9. 认识 Bug
bug在程序中是指让程序出现崩溃的错误
- Debug 调试程序——PyCharm 的调试工具
- F8 Step Over 可以单步执行代码,会把函数调用看作是一行代码直接执行
- F7 Step Into 可以单步执行代码,如果是函数,会进入函数内部
断点
程序运行到此处,暂时挂起,停止执行。我们可以详细在此时观察程序的运行情况,方 便做出进一步的判断。
- 设置断点:
- (1) 在行号后面单击即可增加断点
- (2) 在断点上再单击即可取消断点
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sB3iK3oY-1610633237789)(Media/image-20210107192227826.png)]
进入调试视图
我们通过如下三种方式都可以进入调试视图:
(1) 单击工具栏上的按钮:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KYwIsrSm-1610633237792)(Media/image-20210107192303289.png)]
(2) 右键单击编辑区,点击:debug ‘模块名’
(3) 快捷键:shift+F9 进入调试视图后,布局如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DrUH0WSg-1610633237793)(Media/image-20210107192314242.png)]
左侧为“浏览帧”:
调试器列出断点处,当前线程正在运行的方法,每个方法对应一个“栈帧”。最上面的 是当前断点所处的方法。
变量值观察区:
调试器列出了断点处所在方法相关的变量值。我们可以通过它,查看变量的值的变化。 也可以通过 ,增加要观察的变量。
调试操作区
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oxsz5Nut-1610633237794)(Media/image-20210107192344731.png)]
我们通过上图中的按钮进行调试操作,它们的含义如下:
中文名称 | 英文名称 | 图标和快捷键 | 说明 |
---|---|---|---|
显示当前所有断点 | show Execution Point | Alt+F10 | |
单步调试: 遇到函数跳过 | step over | F8 | 若当前执行的是一个函数,则会把这个 函数当做整体一步执行完。不会进入这 个函数内部 |
单步调试: 遇到函数进入 | step into | F7 | 若当前执行的是一个函数,则会进入这 个函数内部 |
跳出函数 | step out | Shift+F8 | 当单步执行到子函数内时,用 step out 就可以执行完子函数余下部分,并返回 到上一层函数 |
执行的光标处 | run to cursor | Alt+F9 | 一直执行,到光标处停止,用在循环内 部时,点击一次就执行一个循环 |