四、Python基础(变量与内存进阶篇)

四、Python基础(变量与内存进阶篇)


一、变量的引用
1.引用的定义和概念

在Python中

  • 变量和数据是分开存储的
  • 数据保存在内存中的一个位置
  • 变量中保存着数据在内存中的地址,称为引用
  • 数据在出现之后,数据所占的内存不会自动释放,在程序结束后会全部释放
  • 下述数据在出现之后,内存中的数据不可被修改
    int、bool、float、complex、str、tuple
  • 下述数据在出现之后,内存中的数据可以被修改
    list、dict(在内存中分配的空间)

id(变量)

可以查看变量的数据在内存中的地址

  • 在计算机中,通常使用十六进制表示内存地址
  • 用 %x 可以以十六进制的方式输出 id()

如:

num1 = 1
print(id(num1))
print(id(1))

num1 = 2
print(id(num1))
print(id(1))
  • 第1行:num1 在栈内存中出现,1 在堆内存中出现
  • 第5行:2 在堆内存中出现,num1 引用 2 的地址,但 1 仍然存在于堆内存中不被释放,且地址不变
  • 如果这个时候再定义一个 num2 = num1,则 num2 在栈内存中出现, num1 保存的数据的地址就会被 num2 所引用,相当于 1 同时被 num1 和 num2 引用
    在这里插入图片描述
    num1 是中的内存,而 1 和 2 是中的内存

如:

list_name = [1, 2, 3, 4]
print("定义时列表的首地址为:%s" % id(list_name))

"""向列表首插入一个新的数字"""
list_name.insert(0, 10)
print(list_name)
print("插入新数据后列表的首地址为:%s" % id(list_name))

在这里插入图片描述

  • 发现列表的首地址并没有发生改变,因此符合了列表在内存中的数据可以被修改,而首地址不变
  • 所以这个列表所分配的内存空间就在放在这片内存下,并不改变

如:

dictionary = {"name": "张三",
              "sex": "男",
              "age": 18}
print("原字典的地址为:%s" % id(dictionary))

dictionary["height"] = 175
dictionary.pop("name")
print(dictionary)
print("修改后字典的地址为:%s" % id(dictionary))

在这里插入图片描述

  • 发现字典的首地址并没有发生改变,因此符合了字典在内存中的数据可以被修改,但首地址不变
  • 所以这个字典所分配的内存空间就在放在这片内存下,并不改变
  • :字典中的 key 只能使用不可变类型的数据,即 key 不能是列表或字典key 可以是元组,因为元组是不可变类型
2.哈希函数 hash()

哈希函数 hash() 可以接收一个不可变类型的数据作为参数,返回结果是一个整数;哈希 是一种算法,其作用就是提取数据的特征码(如:指纹),相同的内容会得到相同的结果,而不同的内容会得到不同的结果

  • 在Python中设置字典的键值对时,会首先对 key 进行 hash 来决定怎样在内存中保存字典的数据,以方便后续对字典的操作(增、删、改、查)
  • 因为键值对的 key 必须是不可变类型数据

例:

dictionary = {"name": "张三",
              "sex": "男",
              "age": 18}
print(hash(dictionary["name"]))
print(hash("张三"))

在这里插入图片描述
例:不允许是可变类型数据

hash([1, 2, 3])
hash({"name": "老王"})

报错类型:
在这里插入图片描述 在这里插入图片描述
例:可以是不可变类型数据

info_tuple = "张三", "男"

print(hash(info_tuple))
print(hash(("张三", "男")))

在这里插入图片描述

3.局部变量与全局变量

在C语言中我们知道,在函数中定义的变量是局部变量,其作用域只在函数内部,即在调用函数时局部变量才会在栈中申请临时内存,而在函数执行结束后局部变量所占的内存就会被释放,即无法被其他函数和外部所调用

  • 全局变量:定义在函数外部的变量,函数外部和所有函数内部都可以使用这个变量,这和其他编程语言的全局变量一样;主要是因为 Python 没有 main() 函数
  • 局部变量:定义在函数内部的变量,作用域在函数内部

例:

def function():
    num = 10
    print(id(num))


function()
print(id(num))

报错类型:
在这里插入图片描述
这是因为函数在执行完之后,num 的内存就被释放掉了,所以 num 在函数外部无法使用

知识点大前提:在函数内部是无法改变全局变量引用的地址的

提示

  • 函数内部无法改变全局变量的引用,即无法改变不可变类型全局变量的值;但可以改变可变类型全局变量的值——列表、字典重要),这不违背无法改变引用的前提
  • 在程序中,如果全局变量和局部变量同名,函数会优先使用局部变量,如果在函数内部没有定义某局部变量却使用了它,Python就会寻找全局变量
  • 函数内部仍然可以改变可变类型全局变量的值,即可以改变 列表字典 的值,虽然无法改变它们的引用(其实它们的引用本来就是不变的,1.引用的定义和概念 里已经将得很清楚了),但由于 列表字典堆内存中的数据是可以改变的,因此它们不受 “无法改变全局变量的引用” 的束缚
    这就导致了虽然在函数内部无法改变全局变量的引用,但是却可以改变它们的数据

例:这里并非改变了全局变量 num 的值,而是在函数内部重新定义了一个局部变量 num

def function():
    num = 100
    print("这个num是重新定义的局部变量:%d" % num)


num = 10
function()
print("这个才是真正的全局变量num:%d" % num)

在这里插入图片描述

  • 我们以为在函数中成功地改变了全局变量 num 的值,殊不知函数里的 num 已经是局部变量
  • 因此,为了代码维护的方便性,我们可以在函数中使用全局变量的数据,避免试图修改不可变类型的值,以防混淆;但是我们可以修改可变类型(列表、字典)的值
  • 为了代码的清晰与可读性,在使用了全局变量时我们应尽量把全局变量定义在所有函数的上方

例:

def function():
    num = num + 1
    print(num)


num = 10
function()

错误类型:
在这里插入图片描述
错误在于,由于赋值符的缘故,num 已经是局部变量了,由于函数内部优先使用局部变量,因此这里的局部变量 num 被提前引用就是错误的了,因为它还没有被赋值

例:我们试图在函数内部改变可变变量 (列表、字典) 的值

def function():
    num1.insert(4, 5)
    num2["sex"] = "男"
    print("我们可以尝试在函数内部改变可变变量的值(列表)", num1)
    print("我们可以尝试在函数内部改变可变变量的值(字典)", num2)


num1 = [1, 2, 3, 4]
num2 = {"name": "张三"}
function()
print("可变变量的值已经发生了改变(列表)", num1)
print("可变变量的值已经发生了改变(字典)", num2)

在这里插入图片描述

4.函数的形参和返回值的引用

例:

def test(temp_num1):
    print("temp_num1引用的地址是:%s" % id(temp_num1))

    temp_num2 = 30
    print("temp_num2引用的地址是:%s" % id(temp_num2))
    return temp_num2


num1 = 12
print("num1引用的地址是:%s" % id(num1))
num2 = test(num1)
print("num2引用的地址是:%s" % id(num2))

在这里插入图片描述

(1)显然,外部变量 num1 和 形参 temp_num1 的引用是相同的,因此:

在Python中,向函数传递参数的实质是向函数传递外部变量的引用

(2)其次,返回值 temp_num2 和 外部变量 num2 的引用是相同的,因此:

在Python中,函数返回值的实质是向外部传递返回值的引用

(3)此外,在函数中出现的数据 30 ,内存中并未被释放

提示

  • 现在,我们已经对局部变量的概念已经很清晰了,很显然,形参(形式参数)实际上就是一个局部变量,它实际上只是接收的是外部变量传递进来的地址引用
  • 改变不可变类型形参的值只是创建了一个新的局部变量,没有改变外部变量的引用,更不可能改变外部变量的值了
  • 所以我们不能通过形参来修改不可变类型外部变量的值;但我们能通过形参来修改可变类型外部变量的值(形参也符合了 3.局部变量与全局变量 中引用不可变的原理,虽然引用不可变,但可变类型的在内存中的数据是可以发生变化的,利用这个原理就可以通过形参来修改可变类型外部变量的值

例:这里并不是改变 temp_num 的值,而是定义了一个新的局部变量 temp_num ,由于它们是同名的,从此函数内形参的名称被覆盖(因为在函数里,局部变量的优先级大)

def function(temp_num):
	temp_num = 100


num = 10
function(num)

例:和上面一样,这里的赋值语句还是创建了一个局部变量 temp_list,从此函数内形参的名称被覆盖

def function(temp_list):
    temp_list = [4, 5, 6]
    print(temp_list)


list1 = [1, 2, 3]
function(list1)
print(list1)

在这里插入图片描述
例:可以改变可变类型的外部变量,但引用是无法改变的
在这里插入图片描述在这里插入图片描述

  • 所以在之前的名片管理系统中,我们给函数传递的参数要么是列表,要么是字典,这样才得以在函数里面修改外部变量的值
特别地:列表的 += 的特别规则(只针对列表,其他变量不是如此)

在Python中,+= 并不会创建局部变量, += 相当于 list.extend(other_list) 的作用

def function(temp_list1, temp_list2):
    temp_list1 += temp_list2
    print("利用+=作为extend修改了外部变量list1的值:", temp_list1, "引用是:%s" % id(temp_list1))
    temp_list1 = temp_list1 + temp_list2
    print("这种方式相当于定义了一个局部变量temp_list1,引用是:%s" % id(temp_list1))


list1 = [1, 2, 3]
list2 = [4, 5, 6]
print("list1的引用是:%s" % id(list1))
function(list1, list2)
print("list1被修改:", list1)

在这里插入图片描述

  • 显然,使用 += 后,temp_list1 的引用没有发生变化,没有定义局部变量
  • 发现 temp_list1 的引用发生了变化,它创建了一个局部变量
5.global 关键字声明全局变量

在函数内部,可以使用 global 声明全局变量声明后,在函数内部就可以修改全局变量的引用地址了

例:同样是前面的例子

def function():
    global num
    num = 100
    print("num此时是全局变量,被修改:%d" % num)


num = 10
function()
print("num被修改:%d" % num)

在这里插入图片描述

  • 由于已经声明了 num 是全局变量,因此不会定义一个局部变量 num
6.代码结构
#! (shebang)
import 模块
全局变量
函数定义
主代码
  • 为了代码的清晰与可读性,在使用了全局变量时我们应尽量把全局变量定义在所有函数的上方
  • 为了进一步的可读性,有些公司会要求在全局变量前增加 g_gl_ 前缀,见名知意

二、高级变量类型作为函数的返回值或参数

三、Python基础(高级变量类型篇) 中,我们也提到过使用高级变量类型作为函数的返回值或参数来使用,现在我们展开说说

1.元组作为返回值

当我们需要函数返回多个数据的时候,我们就可以使用高级变量类型来作为返回值

例:

def info_function():
	"""元组作为函数的返回值使用"""
    temporary_tuple = ("老王", "男", 36, 19991111)

    return temporary_tuple


new_tuple = info_function()
print(info_function())

实际上可以直接返回一个(元组)集合

def info_function():
	"""假设下列几组数据是处理后得到的数据"""
	name = "老王"
	sex = "男"
	age = 35
	number = 19991111
	"""则可以直接返回"""
    return name, sex, age, number


print(info_function())

在这里插入图片描述
在函数返回元组后,直接通过返回的元组的下标访问对应的数据即可,但这样需要准确的知道相应数据所对应的下标,有时候并不是很好

实际上,我们可以直接用多个变量来作为元组的返回赋值

例:

def info_function():
    temp_name = "张三"
    temp_sex = "男"
    temp_age = 36
    temp_number = 19991111

    return temp_name, temp_sex, temp_age, temp_number


name, sex, age, number = info_function()
for information in (name, sex, age, number):
    print(information)

在这里插入图片描述
:使用多个变量接收结果时,变量的个数应该和元组中元素的个数保持一致

2.函数的缺省参数

回忆 三、Python基础(高级变量类型篇)中讲到的对列表的降序排序操作:
list.sort(reverse=True),实际上这个方法就使用了缺省参数

作用:实际上,在日常生活中使用得更多的是应该是升序的排序,因此设置排序方法的默认排序类型的升序,而一旦需要降序排序,就再声明即可,这样做简化了升序排序方法的使用(因为升序排序方法使用得更多,所以应该设置得更简单,最好有一个默认值)

例:在某些场景中,如:文科班的女生较多,因此在录入文科班的学生信息时,我们可以设置默认值为女生,那么就可以大大简化性别输入时的操作

def student_information(temp_name, temp_gender=True):
    if not temp_gender:
        print("姓名:%s,性别:男" % temp_name)
    else:
        print("姓名:%s,性别:女" % temp_name)


student_information("小蕾")
student_information("小明", False)

在这里插入图片描述
因此,在默认情况下输入便是女生,如果写入False,那么就是男生

:在使用缺省参数时

  • 带有默认值的缺省参数应该放在函数其他参数的末尾

如果所带默认值有多个的时候:就跟 list.sort(reverse=True) 十分相像了

例:

def student_information(temp_name, temp_gender=True, temp_evaluate=True):
    if temp_gender:
        if temp_evaluate:
            print("姓名:%s,性别:女,评价:优" % temp_name)

        else:
            print("姓名:%s,性别:女,评价:良" % temp_name)

    else:
        if temp_evaluate:
            print("姓名:%s,性别:男,评价:优" % temp_name)

        else:
            print("姓名:%s,性别:男,评价:良" % temp_name)


student_information("小蕾")
student_information("小花", temp_evaluate=False)

注意观察 student_information(“小花”, temp_evaluate=False)
在这里插入图片描述

3.函数的多值参数

学习Linux时,我们知道,给 main 函数传递参数值时:int main(int *argc, char *argv[ ])
我们不知道要传递多少个参数,每次传递的参数个数是不定的,在Python中,也可以定义这种传递参数值不确定的函数——多值参数

一般在给多值参数命名,习惯使用以下两个名字:

  • *args ——即 arguments(参数),存放元组参数,前面有一个 *
  • **kwargs ——即 keyword arguments(键值对参数) 存放字典参数,前面有一个 **
  • 其中 * 和 ** 是标识符,因此也可以直接用 * 和 ** 命名不同上述名字的元组参数字典参数
  • 这种传递方式就不需要把多个参数用 (){} 阔起来了

例:

def function(num, *args, **kwargs):
    print("共有 %d 门科目:班级排名:%d,年级排名:%d" % (num, args[0], args[1]))
    for item in ("科目", "成绩"):
        print("%s" % item.ljust(10, " "), end="")

    print()
    for course in kwargs:
        print("%s" % course.ljust(10, " "), "%s" % kwargs[course].ljust(3, " "))


function(4, 1, 2, 计算机组成原理="94", 数据库与数据挖掘="92", 编译原理="86", 操作系统原理="90")
  • 分数用 " " 表示字符串,是为了使用字符串的对齐方法,分数直接用 float 或 int 也是可以的
    在这里插入图片描述
元组和字典的拆包操作

上述例子显示,向函数传递的数据是以逗号 “,” 分隔单独的数据,但如果要传递整个元组字典要怎么传递呢?

  • 在传递元组字典时,只需在相应的元组字典前加上 *** 标识即可

仍然以前面的例子为例:这里演示拆包操作

def function(num, *args, **kwargs):
    print("共有 %d 门科目:班级排名:%d,年级排名:%d" % (num, args[0], args[1]))
    for item in ("科目", "成绩"):
        print("%s" % item.ljust(10, " "), end="")

    print()
    for course in kwargs:
        print("%s" % course.ljust(10, " "), "%s" % kwargs[course].ljust(3, " "))


rank = (1, 2)
dictionary = {"计算机组成原理": "94",
              "数据库与数据挖掘": "92",
              "编译原理": "86",
              "操作系统原理": "90"}

"""进行拆包操作"""
function(4, *rank, **dictionary)

在这里插入图片描述
未进行拆包操作的错误示例:

def demo(*args, **kwargs):
    print(args)
    print(kwargs)


gl_nums = (1, 2, 3)
gl_dict = {"name": "张三", "age": 18}
demo(gl_nums, gl_dict)

在这里插入图片描述


三、交换变量的三种方法
1.使用临时变量
num1 = 5
num2 = 10

temp = num1
num1 = num2
num2 = temp
2.不使用临时变量
num1 = 5
num2 = 10

num1 = num1 + num2
num2 = num1 - num2
num1 = num1 - num2

推导过程:
在这里插入图片描述

3.Python专属——利用元组
num1 = 5
num2 = 10

"""这相当于两个元组"""
num1, num2 = num2, num1

四、常量与变量

常量——不可变的量

变量——可以变化的量

在其他语言中,如C语言中,我们可以通过 define 来定义一个常量,他一般具有以下作用:

  • 作为不可变的量,如C语言中数组的元素个数N
  • 当这个常量的值需要更改时(如:产品的需求发生变化),我们只需在 define 中把数值更改,即可把代码中所有应用到该常量的数值来进行更改,而不需要一个个地更改重要
  • 在开发时,不建议直接用数字来代替常量,一方面,如果数字需要更改,则需要一个个地进行更改,极不方便;另一方面,代码的可阅读性降低,谁知道你这个数字是用来干什么的

不过在Python中,并没有常量的定义,但是Python开发者通过命名方法的约定,用变量来代替常量,从而达到见名知意——告诉你这是一个常量

:Python没有常量,只是通过命名方式的约定来用变量代替常量

1.常量的定义
  • 定义常量和定义变量的语法完全一致,都是使用赋值语句
  • 常量应该在定义 import 导入下方定义,即代码的开头位置
  • 常量的命名约定:常量的命令应该所有字母都使用大写,单词与单词之间使用下划线 “_” 来分隔重要

例:

STUDENT_AMOUNT = 100

还可以定义其他特殊的类型

import pygame

"""定义一个矩形区域对象常量"""
SCREEN_RECT = pygame.Rect(0, 0, 480, 700)

这里的返回值是一个 pygame 中一个矩形区域的对象


上一篇文章
下一篇文章
  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值