文章目录
十一、变量进阶
1、变量的引用
变量 和 数据 都是保存在 内存 中的;
在python中函数的 参数传递 以及 返回值 都是靠 引用 传递的。
2、引用的概念
在python中,变量 和 数据 是分开存储的;数据保存在内存中的一个位置,变量 保存着数据在内存中的地址。
变量记录着数据的地址,这就叫引用。使用id()
函数可以查看变量中保存数据所在的 内存地址 。
注意:如果变量已经被定义,当给一个变量赋值的时候,本质上是 修改了数据的引用 。变量 不再 对之前的数据引用,改为对新赋值的数据的引用。
#看到赋值语句,首先把注意力放到等号的右侧
#把 数据 看成 小格子,变量 看成 便签纸
应用:
在函数中,实参是通过 引用 获取数据的。调用函数时,本质上传递的是实参保存数据的引用,而不是实参保存的数据本身。
def test(num):
print("在函数内部%d对应的内存地址是%d" % (num, id(num)))
# 定义一个字符串变量
result = "Hello"
print("函数返回数据'Hello'的内存地址是%d" % id(result))
# 函数返回的是数据的引用,而不是数据本身。
return result
# 1、定义一个变量
a = 10
# 数据的地址本质上就是一个数字
print("a变量保存数据的内存地址是%d" % id(a))
# 2、调用test函数。
# 如果函数有返回值,但是没有定义变量接收,程序不会报错,但是无法获得返回结果。
r = test(a) # 函数的返回结果是result,因此r = result。
print("%s的内存地址是%d" % (r, id(r)))
输出:
3、可变和不可变类型
不可变类型:内存(小格子)中的数据不允许被修改:
数字类型:int, bool, float, complex, long(2,x)
字符串:str
元组:tuple
例如:
a = 1
a = "Hello"
a = [1, 2, 3]
#当给一个已有变量赋予新值时,是在修改变量a的地址,而不是在修改 小格子中原有的数据内容。
a = [4, 5, 6]
可变类型:内存(小格子)中的数据可以被修改:
列表list
字典dict
#字典的
key
只能使用不可变类型的数据,不能使用列表、字典这两个可变类型数据。
哈希(hash)
python中内置有一个名字叫做
hash(0)
的函数,接受一个 不可变类型的数据作为 参数,返回结果是一个整数。
哈希是一种算法,其作用就是提取数据的 特征码(指纹),
相同的内容 得到 相同的结果,
不同的内容得到 不同的结果。
在python中,设置字典的 键值对时,会首先对key
进行hash
,以决定如何在内存中保存字典的数据,以方便后续对字典的操作:增、删、改、查
键值对的key
必须是不可变类型数据,
键值对的value
可以是任意类型的数据
可以看到 哈希报错情况 与上述 对字典d操作时的报错情况 是一样的。
可变类型的数据变化,是通过 方法 来实现的。如果给一个可变类型的变量赋值一个新的数据,引用 会修改。
变量 不再 对之前的数据引用
变量 改为 对新赋值的数据的引用
例如:
4、局部变量和全局变量
局部变量 :是在 函数内部 定义的变量,只能在函数内部使用
函数执行结束后,函数内部的局部变量会被系统回收
不同的函数,可以定义相同名字的局部变量,互相之间不会产生影响。
例如:
def demo1():
num = 10
print("在demo1函数内部定义的变量是%d" % num)
print("%d" & num) # 会报错:在函数内部定义的变量不能在其它位置使用。
局部变量的生命周期 :指变量从 被创建 到 被系统回收 的过程。局部变量在函数执行时才会被创建,函数执行结束后 局部变量会被系统回收。在局部变量的生命周期内,可以用来存储函数内部临时使用到的数据。
全局变量 :是在 函数外部 定义的变量(没有定义在某一个函数内),所有函数 内部 都可以使用这个变量。
在开发时,应该把模块中所有的全局变量都定义在函数上方,这样就可以保证所有的函数都能够正常访问每一个全局变量了。不允许直接修改全局变量的引用——不能使用赋值语句修改全局变量的值。
注 :在其它语言中,大多 不推荐使用全局变量。因为可变范围太大,导致程序不好维护。
全局变量命名 :
全局变量命名应该增加
g_
或gl_
前缀。例如g_num
或gl_num
。
如果局部变量的命名与全局变量的名字相同,pycharm会在局部变量下方显示灰色的虚线。
例如:
num = 20 # 全局变量
def demo1():
num = 10
print("num=%d" % num)
demo1()
def demo2():
print("num=%d" % num)
demo2() # 从输出可以看出,全局变量num的数据并没有被修改
# 因此,从demo1的输出可以看出,函数内部的num赋值语句只是新建了一个局部变量。
输出:
如果想要在函数内部修改全局变量的值,只需要使用global
关键字。例如,global num
表示声明num为全局变量。global关键字告诉解释器“后面的变量是一个全局变量”,再使用赋值语句时,就不会再创建局部变量。
例如:在上述例子中增加一句global num
num = 20
def demo1():
global num
num = 10
print("num=%d" % num)
demo1()
def demo2():
print("num=%d" % num)
demo2()
输出:
5、函数参数和返回值的作用
函数根据 有没有参数 以及 有没有返回值,可以 相互组合,一共有 4种 组合形式。具体是否接收参数,是否有返回结果,都是根据实际需求而定的。如果 函数内部处理的数据不确定,就可以将外界的数据以参数传递到函数内部。如果希望一个函数执行完成后向外界汇报执行结果,就可以增加函数的返回值。
无参数,无返回值
无参数,有返回值
有参数,无返回值
有参数,有返回值
一个函数执行后能否返回多个结果?of course!
def measure:
...
return (a, b) # 利用元组,返回多个值。
#将上句改写为:
return a, b # 如果函数返回的类型是元组,小括号()可以省略
#输出结果:
result = measure()
print(result)
如果希望单独处理函数返回值的元组元素,可以使用多个变量,一次接受函数的返回结果。变量的个数可以与元组中元素的个数相同。
gl_a, gl_b = measure()
print(gl_a)
print(gl_b)
交换两个数字
有两个整数变量a = 6
,b = 100
,不使用其他变量,交换两个变量的值。
方法一:使用临时变量
c = a
a = b
b = c
方法二:不使用临时变量 ·
a = a + b
b = a - b
a = a - b
方法三:python独有,利用元组
(b, a) = (a, b)
6、函数参数
(1)不可变和可变的参数
在函数内部,针对参数使用赋值语句,会不会影响调用函数时传递的 实参变量?——不会。只要传递的参数使用赋值语句,会在函数内部修改局部变量的引用,不会影响到外部变量的引用。
def demo(num,num_list):
num = 100
num_list = [1, 2, 3]
print(num)
print(num_list)
gl_num = 99
gl_list = [4, 5, 6]
demo(gl_num,gl_list) # 100, [1, 2, 3]
print(gl_num) # 99
print(gl_list) # [4, 5, 6]
输出:
在函数内部,针对 可变类型 的数据,使用 方法 修改了数据,会影响
def demo(num_list):
num_list.append(9)
gl_list = [1, 2, 3]
demo(gl_list)
print(gl_list) # gl_list发生了变化
输出:
在python中,列表变量调用+=
本质上是在执行列表变量的extend
方法,不是先相加再赋值,不会修改变量的引用。
def demo(num, num_list):
num += num
num_list += num_list
print(num)
print(num_list)
gl_num = 9
gl_list = [4, 5, 6]
demo(gl_num,gl_list)
print(gl_num)
print(gl_list)
(2)缺省(读作:sheng 三声)参数
定义函数时,可以给 某个参数 指定一个 默认值,具有默认值的参数就叫做 缺省参数。
调用函数时,如果没有传入 缺省参数 的值,则在函数内部使用定义函数时就指定的 参数默认值 。
函数的缺省参数:将常见的值设置为参数的缺省值,从而简化函数的调用。
例如:
a_list.sort() # 默认reverse = False,为升序排序
a_list.sort(reverse = True) # 如果想降序排序,就需要指定参数。
指定函数的缺省参数:
在参数后使用赋值语句,如
gender = True
,可以指定参数的缺省值。
注意:
①应该使用最常见的值作为默认值。如果某个参数的值不能确定,则不能设置默认值,应当在具体使用时直接调用。
②在函数参数定义时,缺省参数,也就是有默认值的参数,应当位于参数列表的末尾。否则会报错。
def print_info(name, gender = True): # 指定`gender`的默认值为True
gender_text = "男生"
if not gender:
gender_text = "女生"
print("%s 是 %s" %(name, gender_text))
(3)多值参数
有时候 一个函数 能够处理的参数个数是不确定的,这个时候就应该使用多值参数。
①python中有两种多值参数:
参数名前增加一个*
可以接收 元组;
参数名前增加两个*
可以接收 字典。
②一般在给多值参数命名时,习惯使用以下两个名字
*args
——是argumrnt
的缩写,有变量的含义,前面有一个*
**kwargs
——kw
是keyword
的缩写,kwarg
可以记忆 键值对参数。存放 字典 参数,前面有两个**
。
例:
def demo(num, *nums, **person):
print(num)
print(nums)
print(person)
demo(1)# 调用函数时,不需要指定参数
demo(1, 2, 3, 4, 5, name="Yilia", age=18)
输出:
demo(1)时:
demo(1, 2, 3, 4, 5, name=“Yilia”, age=18)时:
def sum_number(*args):
num = 0
print(args)
# 使用循环遍历进行累加
for i in args:
num += i
return num
result = sum_number(1, 2, 3, 4, 5)
print(result)
输出:
def sum_number(args): # 如果不使用*
num = 0
print(args)
# 使用循环遍历进行累加
for i in args:
num += i
return num
result = sum_number((1, 2, 3, 4, 5))
print(result)
result_1 = sum_number(1, 2, 3, 4, 5)
print(result_1)
输出:会看到,如果直接以元组的形式(1, 2, 3, 4, 5)
传进5个参数,会报错。如果将整体打包成元组(1, 2, 3, 4, 5)
,会成功输出结果。
元组和字典的拆包
在调用带有多值参数的函数时,如果希望:
将一个 元组变量,直接传递给
args
;
将一个 字典变量,直接传递给kwargs
就可以使用拆包,简化参数的传递。拆包的方式是:
在 元组变量前,增加一个
*
;
在字典变量前,增加 两个*
def demo(*args, **kwargs):
print(*args)
print(**kwargs)
gl_nums = {1, 2, 3}
gl_dict = {"name": "Yillia", "age": 18}
demo(gl_nums, gl_dict)
demo(*gl_nums, **gl_dict)
输出:如果不使用拆包,demo中的内容会作为一个整体传给args。
如果想不使用拆包也可以正确传递,需要这样输入:
demo(1, 2, 3,“name”: “Yillia”, “age”: 18)
(4)递归
在函数内部,让函数自己调自己。
①函数内部的 代码 是相同的,只是针对 参数 不同,处理的结果不同。
②当参数满足一个条件时,函数不再执行。这个非常重要,通常被称为递归的出口,否则会 出现死循环。
③在处理 不确定的循环条件时 格外有用。例如:遍历整个文件目录的结构 。
例:
def sum_number(num):
print(num)
# 递归的出口
if num == 1:
return # 执行完return后,下方的代码不会执行
# 自己调自己
sum_number(num - 1)
sum_number(3)
输出:
应用:定义一个函数sum_numbers
,能够接受num
的整数参数,计算1+2+...+num
的结果。
def sum_number(num):
# 出口
if num == 1:
return 1
# 只要不满足出口条件,就一直递归
temp = sum_number(num - 1) # 这里sum_number()的调用非常关键
return num + temp
a = int(input("请输入num:"))
result = sum_number(a)
print(result)
输出: