Python基础-15 函数

十五、函数

1. 函数概述

- 概述

  • 所谓函数,就是把 具有独立功能的代码块 组织为一个小模块,在需要的时候 调用
  • 函数的使用包含两个步骤:
    1. 定义函数 —— 封装 独立的功能
    2. 调用函数 —— 享受 封装 的成果
  • def 是英文 define 的缩写
  • 函数名称 的命名应该 符合 标识符的命名规则
    • 可以由 字母下划线数字 组成
    • 不能以数字开头
    • 不能与关键字重名

- 函数的作用:

  • 提高代码的开发效率
  • 使代码更加简洁
  • 提高代码的复用率
  • 实现代码的一致性
    • 一致性指的是,只要修改函数的代码,则所有调用该函数的地方都能得到体现。

- 分类

  1. 内置函数:我们前面使用的 str()、list()、len()等这些都是内置函数,我们可以拿来直接使用。

  2. 标准库函数:我们可以通过 import 语句导入库,然后使用其中定义的函数

  3. 第三方库函数:Python 社区也提供了很多高质量的库。下载安装这些库后,也是通过 import 语句导 入,然后可以使用这些第三方库的函数

  4. 用户自定义函数:用户自己定义的函数,显然也是开发中适应用户自身需求定义的函数。今天我们学习的 就是如何自定义函数。

2. 函数的定义和调用(重点)

1. 定义与调用

函数的定义格式:

def 函数名([参数列表...]):
    '''文档字符串'''
	函数功能代码...
	函数功能代码...

定义空函数

def def 函数名(参数列表...):
	pass  # 占位符的作用

函数使用(调用):

函数名(参数...)

参数列表

  • 圆括号内是形式参数列表,有多个参数则使用逗号隔开
  • 形式参数不需要声明类型,也不需要指定函数返回值类型
  • 无参数,也必须保留空的圆括号
  • 实参列表必须与形参列表一一对应

注意

  • 函数定义必须在函数调用的上方
    • 内置函数对象会自动创建
    • 标准库和第三方库函数,通过 import 导入模块时,会执行模块中的 def 语句
  • Python 执行 def 时,会创建一个函数对象,并绑定到函数名变量上。

2. 函数调用过程(重点)

  1. 函数在定义时,函数体中的代码是不执行的
  2. 当产生函数调用时,程序才会跳转到函数体中执行函数的代码
  3. 函数体中的代码执行完成后,回到调用处 (重点理解)

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 中参数的传递都是“引用传递”,不 是“值传递”。具体操作时分为两类:

- 参数的作用

函数的参数,增加函数的 通用性,针对 相同的数据处理逻辑,能够 适应更多的数据

  1. 在函数 内部,把参数当做 变量 使用,进行需要的数据处理
  2. 函数调用时,按照函数定义的参数顺序,把 希望在函数内部处理的数据通过参数 传递

- 参数的使用

  • 在函数名的后面的小括号内部填写 参数
  • 多个参数之间使用 , 分隔

- 参数传递中是否会影响原参数

无论传递的参数是可变还是不可变

  • 只要 针对参数 使用 赋值语句,会在 函数内部 修改 局部变量的引用不会影响到 外部变量的引用

如果传递的参数是 可变类型

  • 在函数内部,使用 方法 修改了数据的内容,同样会影响到外部的数据

传递不可变对象时。不可变对象里面包含的子对象是可变的。

  • 则方法内修改了这个可变对象,源对象也发生了变化

  • 传递不可变对象时,如果发生拷贝,用的是浅拷贝(见本章《浅拷贝,深拷贝》)

#传递不可变对象时。不可变对象里面包含的子对象是可变的。则
方法内修改了这个可变对象,源对象也发生了变化。
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)

- 多值参数案例 —— 计算任意多个数字的和

需求

  1. 定义一个函数 sum_numbers,可以接收的 任意多个整数
  2. 功能要求:将传递的 所有数字累加 并且返回累加结果
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 文件(了解)

Ccompiled 编译过 的意思

操作步骤

  1. 浏览程序目录会发现一个 __pycache__ 的目录
  2. 目录下会有一个 hm_10_分隔线模块.cpython-35.pyc 文件,cpython-35 表示 Python 解释器的版本
  3. 这个 pyc 文件是由 Python 解释器将 模块的源码 转换为 字节码
    • Python 这样保存 字节码 是作为一种启动 速度的优化

- 字节码

  • Python 在解释源程序时是分成两个步骤的

    1. 首先处理源代码,编译 生成一个二进制 字节码
    2. 再对 字节码 进行处理,才会生成 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. 设置断点:
    1. (1) 在行号后面单击即可增加断点
    2. (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 PointAlt+F10
单步调试: 遇到函数跳过step overF8若当前执行的是一个函数,则会把这个 函数当做整体一步执行完。不会进入这 个函数内部
单步调试: 遇到函数进入step intoF7若当前执行的是一个函数,则会进入这 个函数内部
跳出函数step outShift+F8当单步执行到子函数内时,用 step out 就可以执行完子函数余下部分,并返回 到上一层函数
执行的光标处run to cursorAlt+F9一直执行,到光标处停止,用在循环内 部时,点击一次就执行一个循环
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值