抱歉,您的浏览器无法访问本站

本页面需要浏览器支持(启用)JavaScript


了解详情 >

第11章 函数:返回值与进阶用法

第十一章 函数:返回值与进阶用法

11.1 函数的返回值

11.1.1 向函数外部传递信息:返回值

函数返回值的用法

需求:函数并非总是直接通过print()来显示结果。有时,希望函数在执行完成后,将有用的处理结果,返回给调用者,以便做后续的处理

知识点:

返回值:在函数执行完后,返回给它的调用者一个结果

如果把函数看作是一个黑箱的话,那么输入就是函数的参数,输出就是函数的返回值

通过使用函数的返回值,可以让将程序的大部分繁重工作都移到函数子程序中去完成,从而简化主程序

知识点:

在函数中,可使用return关键字来返回结果

外部调用函数之处,可以使用变量接收函数的返回

任务一:改进带返回的求和函数

例子:带返回的两个数求和程序

1
2
3
4
5
6
7
8
9
def sum_2_num(num1, num2):
"""对两个数字的求和"""

return num1 + num2

# 调用函数,并使用 result 变量接收计算结果
result = sum_2_num(10, 20)

print(f"计算结果是 {result}")

任务二:返回之后的代码将不被执行

注意return 表示返回,当执行return 后,它后续代码都会不被执行

例子:

1
2
3
4
5
6
7
8
9
10
def sum_2_num(num1, num2):
"""对两个数字的求和"""
return num1 + num2
# return之后的代码永远不被执行
num = 1000
print(num)

# 调用函数,并使用 result 变量接收计算结果
result = sum_2_num(10, 20)
print(f"计算结果是 {result}")

11.1.2 利用元组函数返回多个值

任务一:函数多个返回值的需求分析

问题:在Python中,一个函数执行后能否返回多个结果

下面将通过一个例子来分析函数多个返回值的实际需求

例子:温度和湿度测量程序。假设要开发一个函数能够同时返回当前的温度和湿度。首先,实现函数的单个返回值(温度)功能

1
2
3
4
5
6
7
8
9
def measure():
"""返回当前的温度"""
print("开始测量...")
temp = 39
print("测量结束...")
return temp

result = measure()
print(result)

任务二:使用元组传递函数多个返回值

知识点(回顾): 函数传递多个返回值

在C++中,函数允许最多传回一个返回值,否则会出错

在Python中,通过元组可以实现传递函数的多个返回值功能。对函数的多个返回值使用元组进行打包,然后在函数调用之处进行解包赋值

1
2
3
4
5
6
7
#观察divmod()函数
# 返回值:商和余数,被打包在一个元组
print(divmod(15, 2))

# 通过元组解包,接受函数的多个返回值
x, y = divmod(15, 2)
print('divmod(15, 2),商:{},余数:{}'.format(x, y))

下面,将利用元组在返回温度的同时,也能够返回湿度信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def measure():
"""测量温度和湿度"""

print("测量开始...")
temp = 39
wetness = 50
print("测量结束...")

# 元组-可以包含多个数据,因此可以使用元组让函数一次返回多个值
# 如果函数返回的类型是元组,小括号可以省略
# return (temp, wetness)
return temp, wetness

# 元组
result = measure()
print(result)

知识点:

如果一个函数返回值是元组,括号可以省略

Python 中,可以将一个元组通过赋值语句解包给给多个变量

注意:解包时,变量数量需要与元组元素数量保持一致

1
2
3
4
5
6
7
8
9
10
11
# 需要单独的处理温度或者湿度 - 不方便
print(result[0])
print(result[1])

# 如果函数返回的类型是元组,同时希望单独的处理元组中的元素
# 可以使用多个变量,一次接收函数的返回结果
# 注意:使用多个变量接收结果时,变量的个数应该和元组中元素的个数保持一致
gl_temp, gl_wetness = measure()

print(gl_temp)
print(gl_wetness)

11.1.3 实例:交换两个数字

要求:有两个整数变量 a = 6, b = 100,如果不使用其他变量,如何交换两个变量的值

解法 1 —— 使用其他变量

1
2
3
4
5
6
7
8
9
# 解法 1 - 使用临时变量
a = 6
b = 100
print(a,b)

c = b
b = a
a = c
print(a,b)

解法 2 —— 不使用临时变量

1
2
3
4
5
6
7
8
9
# 解法 2 - 不使用临时变量
a = 6
b = 100
print(a,b)

a = a + b
b = a - b
a = a - b
print(a,b)

解法 3 —— Python 专有,利用元组

1
2
3
4
5
6
7
# 通过元组的封包和解包功能
a = 6
b = 100
print(a,b)

a, b = b, a
print(a,b)

11.1.4 实例:人物信息构建

任务一:带返回的姓名拼接函数

需求:接受姓和名,并返回首字母大写的全名

1
2
3
4
5
6
7
def get_formatted_name(first_name, last_name):
"""返回整洁的姓名"""
full_name = first_name + ' ' + last_name
return full_name.title()

name = get_formatted_name('jimi', 'hendrix')
print(name)

程序解析:

函数get_formatted_name()接受两个参数first_name名和last_name

函数体中,名和姓进行拼接(中间加1空格),并将结果存储在变量full_name

然后,将full_name字符串调用**.title()**方法转换为首字母大写格式,并将其作为返回值

主程序中,使用变量name接受函数**get_formatted_name()**的返回值,并打印输出结果

任务二:使用默认值实参改进姓名函数

需求:有些人还拥有中间名

因此,需要扩展函数get_formatted_name(),来满足中间名需求

1
2
3
4
5
6
7
def get_formatted_name(first_name, middle_name, last_name):
"""返回整洁的姓名"""
full_name = first_name + ' ' + middle_name + ' ' + last_name
return full_name.title()

name = get_formatted_name('john', 'lee', 'hooker')
print(name)

程序解析:

在调用函数时,只需提供名、中间名和姓,**get_formatted_name()**函数就能正确地运行

它根据三个实参,创建一个以空格为分隔符的拼接full_name字符串,并将结果转换为首字母大写格式

需求:

并非所有的人都有中间名。如果调用函数时,只提供了名和姓两个实参,它将不能正确地运行

为让中间名变成可选的,可给实参middle_name指定一个默认值——空字符串,并在用户没有提供中间名时不使用这个实参

函数体中,需要对2个参数(名+姓)和3个参数(名+中间名+姓)进行分别处理,可以借助if语句实现流程控制

注意:带默认值的参数,必须要在非默认值参数之后

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def get_formatted_name(first_name, last_name, middle_name=''):
"""返回整洁的姓名"""
if middle_name:
full_name = first_name + ' ' + middle_name + ' ' + last_name
else:
full_name = first_name + ' ' + last_name
return full_name.title()


name = get_formatted_name('jimi', 'hendrix')
print(name)

name = get_formatted_name('john', 'hooker', 'lee')
print(name)

程序解析:

由于每个人都拥有名和姓,因此在函数定义中首先给出这两个形参。中间名是可选的,因此在函数定义中最后列出该形参,并将其默认值设置为空字符串

在函数体中,通过if判断检查参数middle_name是否为空。然后使用两种full_name的拼接方式,最后再将其修改为首字母大写格式,并返回给外部

位置参数调用函数时,必须要确保每个位置实参能正确地关联到相应的形参。调用函数时,如果只指定名和姓,调用起来将非常简单。如果需要指定中间名,就必须确保中间名传递给middle_name(最后个参数)

任务三:改进函数返回值为字典

知识点:函数可返回任何类型的值,包括列表和字典等较复杂的数据结构

例子:人物构建函数,接受名和姓,返回一个表示人属性的字典:

1
2
3
4
5
6
7
def build_person(first_name, last_name):
"""返回一个字典,其中包含有关一个人的信息"""
person = {'first': first_name, 'last': last_name}
return person

name = build_person('三', '张')
print(name)

程序解析:

函数**build_person()**接受名和姓,并将这些值封装到字典中

存储first_name的值时,使用的键为**’first’;而存储last_name的值时,使用的键为‘last’**。最后,返回表示人属性的字典

在主程序中,打印返回值(字典),此时原来的两项文本信息存储在一个字典中:**{‘first’: ‘三’, ‘last’: ‘张’}**

任务四:改进函数接受年龄参数

扩展函数功能,让它能接受更多的参数,如中间名、年龄、职业或你要存储的其他任何信息。例如,下面程序可以储存姓名和年龄:

1
2
3
4
5
6
7
8
9
def build_person(first_name, last_name, age=''):
"""返回一个字典,其中包含有关一个人的信息"""
person = {'first': first_name, 'last': last_name}
if age:
person['age'] = age
return person

name = build_person('jimi', 'hendrix', age=27)
print(name)

程序解析:

在函数定义中,新增一个可选形参age,并将其默认值设置为空字符串

如果函数调用中包含这个形参的值,就将该值将存储到字典中;否则,直接返回无年龄信息的字典

任务五:循环接受外部输入储存用户信息

需求:

使用while哨兵式循环,不断接受用户循环输入,直到输入q为止

调用之前姓名构造函数,将用户输入作为函数的参数,并返回完整的姓名

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
def get_formatted_name(first_name, last_name):
"""返回整洁的姓名"""
full_name = first_name + ' ' + last_name
return full_name.title()


# 循环条件为True
while True:
print("\nPlease tell me your name:")
print("(enter 'q' at any time to quit)")

# 遇到用户输入q,则退出循环
f_name = input("First name: ")
if f_name == 'q':
break

l_name = input("Last name: ")
if l_name == 'q':
break

# 打印输出结果
formatted_name = get_formatted_name(f_name, l_name)
print("\nHello, " + formatted_name + "!")

在程序中,添加了一条消息来告诉用户如何退出,然后在每次提示用户输入时,都检查他输入的是否是退出值,如果是,就退出循环。现在,这个程序将不断地问候,直到用户输入的姓或名为**’q’**为止

11.2(重要)进阶函数

11.2.1 分析函数参数和返回值的作用

任务一:四种组合形式

知识点:函数根据有没有参数以及有没有返回值,可以相互组合,一共有4 种组合形式。

无参数,无返回值

无参数,有返回值

有参数,无返回值

有参数,有返回值

知识点:

定义函数时,是否接收参数,或者是否返回结果,是根据实际的功能需求来决定的

如果函数内部处理的数据不确定,就可以将外界的数据以参数传递到函数内部。

如果希望一个函数 执行完成后,向外界汇报执行结果,就可以增加函数的返回值

任务二:无参数,无返回值

知识点:

此类函数,不接收参数,也没有返回值

应用场景:只是单纯地做一件事情,例如显示菜单、打印提示语等

在函数内部针对全局变量进行操作,例如:新建名片,将最终结果记录在全局变量

注意:

如果全局变量的数据类型是一个可变类型,在函数内部可以使用方法修改全局变量的内容,变量的引用不会改变

在函数内部,使用赋值语句才会修改变量的引用

任务三:无参数,有返回值

知识点:

此类函数,不接收参数,但是有返回值

例如,采集数据程序(温度计)。返回结果就是当前的温度,而不需要传递任何的参数

任务四:有参数,无返回值

知识点:

此类函数,接收参数,没有返回值

函数内部的代码保持不变,针对不同的参数处理不同的数据

例如,名片管理系统针对找到的名片修改、删除操作

任务五:有参数,有返回值

知识点:

此类函数,接收参数,同时有返回值

应用场景:函数内部的代码保持不变,针对不同的参数 处理 不同的数据,并且返回期望的处理结果

例如,名片管理系统使用字典默认值提示信息提示用户输入内容:如果输入,返回输入内容;如果没有输入,返回字典默认值

11.2.2 函数的嵌套调用

任务一:嵌套的函数流程分析

知识点:所谓的函数嵌套调用就是在一个函数里面又调用了另外的函数

例子:函数的嵌套使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def test1():
print("*" * 50)
print("test 1")
print("*" * 50)


def test2():
print("-" * 50)
print("test 2")
#先执行完的嵌套函数test1()再继续外层函数
test1()
print("-" * 50)


test2()

程序解析:如果函数 test2 中,调用了另外一个函数 test1

那么执行到调用 test1 函数时,会先把函数 test1 中的任务都执行完

才会回到 test2 中调用函数 test1 的位置,继续执行后续的代码

任务二:百度网站解读函数嵌套调用

例子:通过百度网站列举说明现实应用中的嵌套调用,仅为了直观解释什么是函数的嵌套

实例:打印分隔符

知识点:

程序往往会因需求而不停地调整更新

通过函数可以实现对功能代码的统一管理

当需求发生变化时,只需对相应函数的内部进行更新,而其他外部代码不变

需求 1:定义一个 print_line 函数能够打印 ***** 组成的一条分隔线

1
2
def print_line(char):
print("*" * 50)

需求 2:定义一个函数能够打印由任意字符组成的分隔线

1
2
def print_line(char):
print(char * 50)

需求 3:定义一个函数能够打印任意重复次数的分隔线

1
2
def print_line(char, times):
print(char * times)

需求 4:定义一个函数能够打印5行的分隔线,分隔线要求符合需求3

注意:在面对实际问题变化时,应该冷静思考,不要轻易修改之前已经完成的,能够正常执行的函数

1
2
3
4
5
6
7
8
9
def print_line(char, times):
print(char * times)


def print_lines(char, times):
row = 0
while row < 5:
print_line(char, times)
row += 1

11.2.3 模块中的函数

任务一:认识模块的功能

知识点:

模块是Python程序架构的一个核心概念,将多个拥有功能类似的函数打包封装到模块中。它是比函数更高一级的程序集

模块就好比是拥有某种功能的工具包,如果要想使用工具包中的工具(函数),就需要提前通过import导入该模块。

每一个以扩展名 py 结尾的 Python 源代码文件都是一个模块

模块名就是文件名。在其他程序中,通过import 模块名来导入该模块

实例:体验模块

步骤一:新建 py_seplines.py

步骤二:复制打印5行的分隔线程序,并添加一个额外的字符串变量name

注意:复制代码时小心代码块的缩进,建议先放cell中用小锤子插件格式化后,再复制到**.py**文件中

1
2
3
4
5
6
7
8
9
10
11
def print_line(char, times):
print(char * times)


def print_lines(char, times):
row = 0
while row < 5:
print_line(char, times)
row += 1

name = '张三的模块'

步骤三:在Jupyter中,新建一个cell,并添加如下代码

1
2
3
4
5
# 模块名为文件名
import py_seplines

py_seplines.print_line("-", 80)
print(py_seplines.name)

或者使用as关键字对import模块进行重命名:

1
2
3
4
import py_seplines as py_sep

py_sep.print_line("-", 80)
print(py_sep.name)

总结:

可以在一个Python 文件定义变量或者函数

然后在另外一个文件中使用 import 导入该模块

导入之后,就可以使用 模块名.变量 / 模块名.函数 的方式,调用该模块中定义的变量或函数

模块可以让曾编写过的代码集方便的被复用

函数是多行代码块的复用,而模块是多个函数和变量的复用

任务二:模块名也是一个标识符

知识点:

模块名、函数名、变量都属于标识符

标示符的命名可由字母、下划线数字组成

不能以数字开头

不能与关键字或保留字重名

注意:如果在给Python文件起名时,以数字开头导入模块会出错

任务三:模块的Pyc文件

知识点:

为了提高模块的运行效率,Python对模块进行预编译

预编译后的模块名文件为**.pyc。放在与.py**模块源代码同级目录的__pycache__文件夹中

这里的ccompiled 编译过的程序

知识点:查看**.pyc**文件

浏览与**.py**模块源代码同级目录的__pycache__文件夹

该目录下会有一个 py_seplines.cpython-37.pyc 文件,cpython-37 表示 Python 解释器的版本是3.7

这个 pyc 文件是由Python解释器将模块的源码转换为字节码Python 通过字节码来优化代码运行效率

知识点:字节码

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

首先处理源代码,编译生成一个二进制字节码

再对字节码进行处理,才会生成 CPU 能够识别的机器码

有了模块的字节码文件之后,下一次运行程序时,如果在上次保存字节码之后没有修改过源代码,Python将会直接加载 .pyc文件并跳过编译这个步骤。

Python 重编译时,它会自动检查源文件和字节码文件的时间戳。如果发现源代码被修改,那么下次程序运行时,字节码将被重新创建

模块是Python程序架构的一个核心概念

11.2.4 函数中的变量引用

任务一:变量与数据

知识点:函数通过引用传递数据

变量和数据都是保存在内存中的

Python函数的参数传递以及返回值都是靠引用传递的

知识点:变量与数据

变量数据 是分开存储的

数据保存在内存中的一个位置

变量中保存着数据在内存中的地址

变量记录数据的地址,就叫做引用

使用 id() 函数可以查看变量中保存数据所在的内存地址

知识点:如果变量已经被定义,当给一个变量赋值的时候,本质上是 修改了数据的引用

变量不再对之前的数据引用

变量改为对新赋值的数据引用

任务二:数据的赋值

Python 中,变量类似于 贴在数据上的标签

定义一个整数变量 a,并且赋值为 1a=1

将变量 a 赋值为 2a = 2

定义一个整数变量 b,并且将变量 a 的值赋值给 bb = a

变量 b 是第 2 个贴在数字 2 上的标签

任务三:函数的参数和返回值的传递

知识点:在 Python 中,函数的实参/返回值都是是靠引用来传递来的

函数的参数是通过引用来传递

函数的返回值也是通过引用来传递

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def test(num):
print("-" * 50)
print(f"{num} 在函数内的内存地址是 {id(num)}")
result = 100
print(f"返回值 {result} 在内存中的地址是 {id(result)}")
print("-" * 50)

return result

a = 10
print(f"调用函数前 内存地址是 {id(a)}")

r = test(a)

print(f"调用函数后 实参内存地址是 {id(a)}")
print(f"调用函数后 返回值内存地址是 {id(r)}")

11.2.5 不可变类型与哈希函数 (重要)

任务一:基本概念解析

知识点:不可变类型,内存中的数据不允许被修改

数字类型 int, bool, float, complex, long(2.x)

字符串 str

元组 tuple

知识点:可变类型,内存中的数据可以被修改

列表 list

字典 dict

1
2
3
4
a = 1
a = "hello"
a = [1, 2, 3]
a = [3, 2, 1]

任务二:赋值与可变类型修改

知识点:

初学者很容易将赋值误认为是对变量对应内存内容修改

通过赋值修改变量时,原内存内容不变,而内存地址变化。重新把标签贴在另个空间上

可变类型数据,通过方法修改数据时,原内存内容变化,而内存地址不变化

任务三:列表和字典的修改和赋值

知识点:

可变类型的数据修改,是通过方法来实现的

如果给一个可变类型的变量,赋值了一个新的数据,引用会修改

变量不再对之前的数据引用。

变量改为对新赋值的数据引用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
demo_list = [1, 2, 3]
print(f"定义列表后的内存地址 {id(demo_list)}")

demo_list.append(999)
demo_list.pop(0)
demo_list.remove(2)
demo_list[0] = 10
print(f"修改数据后的内存地址 {id(demo_list)}")

demo_dict = {"name": "小明"}
print(f"定义字典后的内存地址 {id(demo_dict)}")

demo_dict["age"] = 18
demo_dict.pop("name")
demo_dict["name"] = "老王"
print(f"修改数据后的内存地址 {id(demo_dict)}")

任务四:字典的key是不可变类型

知识点:字典的 key 只能使用不可变类型的数据

允许:不可变类型可以作为key

1
2
3
4
5
6
7
8
9
d = {}
d["name"] = "xiaoming"
print(d)

d[1] = "整数"
print(d)

d[(1,)] = "元组"
print(d)

不允许:可变类型不可以作为keyTypeError:unhashable type错误

1
2
3
4
5
d[[1,2,3] = "列表"
print(d)

d[{"n":"xxx"}] = "字典"
print(d)

任务五:认识哈希函数

知识点:哈希 (hash)

Python 中内置有一个名字叫做 hash(o) 的函数

参数:接收一个不可变类型的数据作为参数

返回:一个整数

哈希 是一种算法,其作用就是提取数据的特征码(指纹)

相同的内容得到相同的结果

不同的内容得到不同的结果

Python中,设置字典的键值对时,会首先对key进行 hash 来决定如何在内存中保存字典的数据,以便后续对字典的操作:增、删、改、查

键值对的 key 必须是不可变类型数据

键值对的 value 可以是任意类型的数据

允许:不可变类型可以用hash函数生成它的特征码

1
2
h = hash(1)
print(h)
1
2
3
4
5
6
7
8
9
10
# 传入相同的数据,hash值永远一样
h = hash("hello")
print(h)

h = hash("hello")
print(h)

# 数据稍微变化,hash值变化很大
h = hash("hello1")
print(h)
1
2
3
4
# 元组是不可变类型
# hash函数生成元组数据特征码
h = hash((1,))
print(h)

不允许:可变类型不可以用hash函数生成它的特征码

1
2
3
4
# 列表是可变类型
# unhashable type
h = hash([1])
print(h)
1
2
3
4
# 字典是可变类型
# unhashable type
h = hash({})
print(h)

11.2.6 递归函数

任务一:递归函数的定义

知识点:

在函数体内部,可以调用其他函数也可以调用自己。将函数调用自身的编程技巧称为递归

递归函数的特点:

  • 函数内部的代码是相同的,只是针对参数不同,处理的结果不同

  • 参数满足递归终止条件时,函数不再执行。这个非常重要,通常被称为递归的出口,否则会出现死循环

例子:递归函数实现倒计时程序

1
2
3
4
5
6
7
8
9
10
def sum_numbers(num):
print(num)
# 递归的出口很重要,否则会出现死循环
if num == 1:
return

# 当不满足终止条件,则进入递归
sum_numbers(num - 1)

sum_numbers(3)

实例:递归实现计算数字累加

需求分析:

定义一个函数 sum_numbers

能够接收一个 num 的整数参数

计算 1 + 2 + … num的结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 定义一个函数 sum_numbers
# 能够接收一个 num 的整数参数
# 计算 1 + 2 + ... num 的结果
def sum_numbers(num):

# 1. 出口
if num == 1:
return 1

# 2. 数字的累加 num + (1...num -1)
# 假设 sum_numbers 能够正确的处理 1...num - 1
temp = sum_numbers(num - 1)

# 两个数字的相加
return num + temp


result = sum_numbers(100)
print(result)

提示:

递归是一个编程技巧,初次接触递归会感觉有些吃力

建议先想好递归通项递归出口条件,然后再编写递归函数

在处理不确定的循环条件时,格外的有用,例如:遍历整个文件目录的结构

递归可以等价看作是while循环+函数的流程结构

11.2.7 匿名函数lambda

11.2.8 eval()函数

任务一:eval()函数基本用法

知识点:eval()函数十分强大,它将字符串当成有效的表达式来求值 并返回计算结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 基本的数学计算
# Out: 2
print(eval("1 + 1"))

# 字符串重复
# Out: '**********'
print(eval("'*' * 10"))

# 将字符串转换成列表
# Out: list
print(type(eval("[1, 2, 3, 4, 5]")))

# 将字符串转换成字典
# Out: dict
print(type(eval("{'name': 'xiaoming', 'age': 18}")))

任务二:使用eval()函数实现计算器功能

需求分析

提示用户输入一个 加减乘除混合运算

返回计算结果

1
2
3
input_str = input("请输入一个算术题:")

print(eval(input_str))

任务三: eval()函数的危险性

危险:

在开发时千万不要使用**eval()**直接转换 input 的结果

用户就可以控制计算机做任何事情

主要的原因是因为用户可以通过eval()函数间接的执行各种终端指令

eval()函数执行终端指令来对计算机进行各种操作

1
__import__('os').system('ls')

等价代码

1
2
import os
os.system("ls")

执行成功,返回 0

执行失败,返回错误信息

11.3 局部变量和全局变量

11.3.1 局部变量

任务一:局部变量和全局变量的基本概念

知识点:

局部变量是在函数内部定义的变量,只能在函数内部使用

全局变量是在函数外部定义的变量(没有定义在函数内),所有函数内部都可以使用这个变量

区别:

  • 局部变量和全局变量的定义位置不一样

  • 局部变量和全局变量的作用域范围不一样

  • 全局变量一般用于在所有函数中共享全局信息。由于作用域范围太大,开发中慎用全局变量。如果全局变量出现错误,那么程序的错误调试将会变得非常复杂

任务二:认识局部变量

知识点:

局部变量是在函数内部定义的变量,只能在函数内部使用

作用域范围:函数执行结束后,函数内部的局部变量,会被系统回收

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def demo1():
# 局部变量出生:定义局部变量时,才会被创建
num = 10
print(f"在demo1函数内部的变量是{num}")
# 局部变量死亡:函数执行完成之后


def demo2():
num = 99
# 此num和demo1相互独立无关
print(f"demo2 ==> {num}")
pass

# 在函数内部定义的变量,不能在其他位置使用
# print(f"{num}")

demo1()
demo2()

局部变量的生命周期(重要)

知识点:

所谓生命周期就是变量从被创建被系统回收的过程。

出生:局部变量函数执行时才会被创建。定义变量时被创建。

消亡:函数执行结束后局部变量被系统回收。函数return或执行所有代码时,变量消亡。

在生命周期内,局部变量可以用来存储函数内部临时使用到的数据

任务三:不同函数中的局部变量相互不影响

知识点:

不同的函数可以定义相同的名字的局部变量,但是彼此之间不会产生影响。

在函数内部使用,局部变量用于临时保存函数内部需要使用的数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def demo1():
# 局部变量出生:定义局部变量时,才会被创建
num = 10
print(f"在demo1函数内部的变量是{num}")
# 局部变量死亡:函数执行完成之后


def demo2():
# num = 99
# 由于局部变量相互独立,此num和demo1相互独立无关
print(f"demo2 ==> {num}")

demo1()
demo2()

11.3.2 全局变量

任务一:认识全局变量

知识点:

全局变量是在函数外部定义的变量

作用域是整个程序,即所有函数内部都可以使用该变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 定义一个全局变量
num = 10

def demo1():
# 函数内部可以使用全局变量num
print(f"demo1 ==> {num}")


def demo2():
# 函数内部可以使用全局变量num
print(f"demo2 ==> {num}")


demo1()
demo2()
print("over")

任务二:PyCharm单步调试解析全局变量

知识点:

使用PyCharm调试功能,查看定义在函数和模块中的变量

注意:一个python源文件或主程序可以看作是一个模块

在模块中,可以定义多个函数和变量

在模块中定义的变量,相对于函数来说是全局变量。换言之,模块中的变量级别和函数是相同的

任务三:函数内部不能直接修改 全局变量的引用

知识点:

全局变量:在函数外部定义的变量,与函数同一个级别。因此,所有函数内部都可以使用这个变量

虽然要慎用全局变量,但是它可以在各函数中,共享数据信息。所以,对某些需要共享数据的程序开发,使用全局变量会带来很大的便利,可以减少参数个数

全局变量的只读属性,为了防止修改全局变量而影响其他函数的运行:

  • 在函数中,可以通过全局变量的引用获取对应的数据

  • 但是Python不允许直接修改全局变量的引用 ,即不能通过赋值语句来修改全局变量的值

知识点:在函数中,如果使用赋值语句尝试着来修改全局变量值,会发生什么事情

本质上,Python会定义一个新的和全局变量同名的局部变量

因此,在对该变量赋值,本质上是对同名的局部变量进行修改,并不影响原全局变量的值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
num = 10

def demo1():
print("demo1" + "-" * 50)
# 只是定义了一个局部变量,不会修改到全局变量,只是变量名相同而已
num = 100
print(num)


def demo2():
print("demo2" + "-" * 50)
print(num)

demo1()
demo2()
print("over")

任务四:函数变量查找过程的深度解析

知识点:函数执行时,需要处理变量时 会:

首先查找函数内部是否存在指定名称的局部变量如果有,直接使用

如果没有,查找函数外部是否存在指定名称 的全局变量如果有,直接使用

如果还没有,程序报错

任务五:global 关键字开启全局变量修改权限

知识点:

在函数中需要修改全局变量,需要使用 global 关键字来进行声明

通过 global 关键字开启全局变量修改权限

如果修改没有 global 前缀的全局变量,函数会新建一个同名的局部变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 全局变量
num = 10

def demo1():

# 希望修改全局变量的值 - 使用 global 声明一下变量即可
# global 关键字会告诉解释器后面的变量是一个全局变量
# 再使用赋值语句时,就不会创建局部变量
global num
num = 99
print(f"demo1 ==> {num}")


def demo2():
print(f"demo2 ==> {num}")


demo1()
demo2()

任务六:全局变量定义的位置

知识点:

全局变量可以定义在函数定义之后

全局变量必须要在函数调用之前被定义,否则会引发异常

在Python程序运行中,程序遇到函数定义时会被跳过,而真正的函数代码执行会发生在函数调用

因此,为了保障函数能够正确使用全局变量,建议将全局变量定义在函数定义之前

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 注意:在开发时,应该把模块中的所有全局变量
# 定义在所有函数上方,就可以保证所有的函数
# 都能够正常的访问到每一个全局变量了
num = 10
# 再定义一个全局变量
title = "编程爱好者"
# 再定义一个全局变量
name = "小明"


def demo():

print(num)
print(title)
print(name)

# 在函数调用之前定义全局变量,不会异常
# title = "编程爱好者"

demo()

# 由于全局变量,是在调用函数之后,才定义的。
# 在执行函数时,变量还没有定义,所以程序会报错!
# name = "小明"

常用的代码结构规范如下图。关于shebang或**#!标识,属于Linux的shell**编程,有兴趣可以课外查阅相关资料

任务七:全局变量命名规范

知识点:为了避免局部变量和全局变量出现混淆,在定义全局变量时,变量名前应该增加 g_ 或者 gl_ 的前缀

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
gl_num = 10
# 再定义一个全局变量
gl_title = "编程爱好者"
# 再定义一个全局变量
gl_name = "小明"


def demo():

# 如果局部变量的名字和全局变量的名字相同
# pycharm会在局部变量下方显示一个灰色的虚线
num = 99
# 这里的num是局部变量
print(num)

print(gl_title)
print(gl_name)

# # 再定义一个全局变量
# title = "编程爱好者"

demo()

评论