『无为则无心』Python函数 — 29、Python变量和参数传递
1、Python的变量
(1)Python变量不能独立存在
- 比如在
C++等语言中,变量的声明和赋值是可以分开的。int a;
a=343;
- 而在Python中却不行,在声明Python变量的同时必须进行赋值操作,否则会报错。
Python Console: starting.
Python 3.7.7
>>> a
Traceback (most recent call last):
File "<input>", line 1, in <module>
NameError: name 'a' is not defined
>>> a = 343 >>> a
343
如果你直接使用一个不存在的变量,就会发生错误:
NameError: name 'a' is not defined。
(2)变量是内存中数据的引用
a = 343这样代码被执行时,首先要在内存中创建出343这个对象,然后让a指向它,这便是引用。
此后,我们在程序中使用变量a时,其实都是在使用343,Python可以通过a找到343这个值,这是对引用最通俗的解释。
如下图所示:

(3)注意点
赋值语句执行过程中,有一点极容易被忽略掉,那就是这个过程中,在内存中创建了新的数据的问题。
a = [1]
b = [1]
print(a == b)
True
print(a is b)
False
两行赋值语句,分别将列表[1]赋值给a和b,表达式a==b的结果是Ture,因为他们的内容的确相同,但表达式a is b的结果是False,因为这两行赋值语句执行过程中,一共创建了两个列表,他们只是内容相同而已,但内存地址绝对不相同,下图是这两个变量的内存描述示意图。

2、了解变量的引用
在Python中,变量的值是靠引用来传递来的。
我们可以用id()函数来判断两个变量是否引用的同一个值。
id()函数返回对象的唯一标识符,标识符是一个整数,是对象的内存地址。如果两个变量的内存地址相同,说明两个变量引用的是同一个地址。
看下面综合示例:
# 1. int类型
"""
声明变量保存整型数据,把这个数据赋值到另一个变量;
id()检测两个变量的id值(内存的十进制值)
"""
a = 1
b = a
print(b) # 打印变量b的值为1
# id(a):返回a变量在内存中的十进制地址
# 变量a和变量b的内存地址一样,说明ab引用的是同一数据。
print(id(a)) # 140708464157520
print(id(b)) # 140708464157520
# 将变量a重新赋值
a = 2
print(b) # 打印变量b的值为1
# 因为修改了a的数据,内存要开辟另外一份内存取存储2,
# id检测a和b的地址不同
print(id(a)) # 140708464157552,此时得到是的数据2的内存地址
print(id(b)) # 140708464157520
# 2. 列表(可变数据类型)
aa = [10, 20]
bb = aa
# 发现a和b的id值相同的,说明引用的是同一个内存地址
print(id(aa)) # 2325297783432
print(id(bb)) # 2325297783432
# 变量aa添加数据
aa.append(30)
print(bb) # 变量bb的值为[10, 20, 30], 列表为可变类型
# 打印结果
print(id(aa)) # 2325297783432
print(id(bb)) # 2325297783432
# 继续操作
bb = bb + [666]
print(aa) # [10, 20, 30]
print(bb) # [10, 20, 30, 666]
print(id(aa)) # 30233096
print(id(bb)) # 40054280
# 这时我们会发现可变类型变量的引用也会发生改变,
# 这是为什么呢?这里就不解释了,下面一点进行详细说明。
3、Python的参数传递(重点)
Python的参数传递也就是Python变量的引用。
在Python中,所有的变量都是指向某一个地址,变量本身不保存数据,而数据是保存在内存中的一个地址上。
通俗的来说,在 Python 中,变量的名字类似于把便签纸贴在数据上。
我看网上很多文章说:
- Python基本的参数传递机制有两种:值传递和引用传递。
- 不可变参数是值传递,可变参数是引用传递。
这样理解也是可以的,但我认为都是引用的传递,下面我用示例说明一下。
先来说明一个知识点,在Python中,不可变数据类型和可变数据类型是如何分配内存地址的。
如下所示:
inta = 10086
intb = 10086
lista = [10,20,30]
listb = [10,20,30]
print('inta的内存地址',id(inta))
print('intb的内存地址',id(intb))
print('lista的内存地址',id(lista))
print('listb的内存地址',id(listb))
"""
运行结果如下:
inta的内存地址 37930032
intb的内存地址 37930032
lista的内存地址 30233096
listb的内存地址 30233608
"""
我们可以看到:
- 对于不可变数据类型变量,相同的值是共用内存地址的。(这一点很重要)
- 对于可变数据类型变量,如上面的
lista和listb,即使内容一样,Python也会给它们分配不同的内存地址。
(1)示例
下面来看一下示例:
提示:对不可变数据类型,
+和+=都会创建新对象,对可变数据类型来说,+=不会创建新对象。
1)可变数据类型变量示例
我们通过示例来看看可变数据类型变量的引用是如何传递的。
def ChangeParam(paramList):
paramList.append([1, 2, 3, 4])
print("函数内paramList状态1,取值: ", paramList)
print('函数内paramList状态1的内存地址:', id(paramList))
paramList += [888]
print("函数内paramList状态2,取值: ", paramList)
print('函数内paramList状态2的内存地址:', id(paramList))
paramList = paramList + [888]
print("函数内paramList状态3,取值: ", paramList)
print('函数内paramList状态3的内存地址:', id(paramList))
return
mylist = [10, 20, 30]
print('mylist函数外的内存地址(前):', id(mylist))
ChangeParam(mylist)
print("函数外取值: ", mylist)
print('mylist函数外的内存地址(后):', id(mylist))
"""
mylist函数外的内存地址(前): 32264712
函数内paramList状态1,取值: [10, 20, 30, [1, 2, 3, 4]]
函数内paramList状态1的内存地址: 32264712
函数内paramList状态2,取值: [10, 20, 30, [1, 2, 3, 4], 888]
函数内paramList状态2的内存地址: 32264712
函数内paramList状态3,取值: [10, 20, 30, [1, 2, 3, 4], 888, 888]
函数内paramList状态3的内存地址: 42905160
函数外取值: [10, 20, 30, [1, 2, 3, 4], 888]
mylist函数外的内存地址(后): 32264712
"""
2)不可变数据类型变量示例
我们通过示例来看看不可变数据类型变量的引用是否是值传递。
示例如下:
def NoChangeParam(prarmInt):
print('函数中变量prarmInt的初始状态,prarmInt变量的值', prarmInt)
print('函数中变量prarmInt的初始状态,prarmInt的内存地址', id(prarmInt))
prarmInt += prarmInt
print('函数中状态1,此时prarmInt的值:', prarmInt)
print('函数中状态1,prarmInt的内存地址:', id(prarmInt))
# 1.定义变量a
a = 1000
print('执行函数前,变量a的内存地址:', id(a))
# 2.调用函数
NoChangeParam(a)
# 3.打印执行函数后,a变量的值和指向内存地址
print('执行函数后,a变量的值。a =', a)
print('执行函数后,a的内存地址:', id(a))
"""
执行函数前,变量a的内存地址: 32817968
函数中变量prarmInt的初始状态,prarmInt变量的值 1000
函数中变量prarmInt的初始状态,prarmInt的内存地址 32817968
函数中状态1,此时prarmInt的值: 2000
函数中状态1,prarmInt的内存地址: 32818224
执行函数后,a变量的值。a = 1000
执行函数后,a的内存地址: 32817968
"""
(2)结论
通过上面示例我们可以看到:
- 在函数执行前后,不可变数据类型变量和可变数据类型变量的所指向的地址都没有发生改变。只不过不可变数据类型变量的值没有改变,而可变数据类型变量的值发生了改变。
- 不可变数据类型变量和可变数据类型变量,在传入函数的最开始的状态,都和原变量一致,说明函数的参数传递是地址传递。
- 不可变数据类型变量和可变数据类型变量,在函数中只要产生了新对象,内存引用地址都会发生改变。
也就是说:- 对于不可变数据类型变量来说,只有改变了变量的值,就会产生一个新对象,内存地址的引用就会发生改变。(因为前边的结论,对于不可变数据类型变量,相同的值是共用内存地址的)
- 对于可变数据类型变量来说,因为是可变的,所以改变变量的值,内存地址的引用不会发生改变。只有产生了新对象,如
mylist = mylist + [888],内存地址的引用才会发生改变。
(3)总结
通过上面的内容,我们可以知道:
- 对于不可变类型变量而言:因为不可变类型变量特性,修改变量需要新创建一个对象,形参的标签转而指向新对象,而实参没有变。
- 对于可变类型变量而言,因为可变类型变量特性,直接在原对象上修改,因为此时形参和实参都是指向同一个对象,所以实参指向的对象自然就被修改了。而如果可变类型变量在函数内的操作创建了新的对象,内存地址的引用也会发生改变,但仅限于在函数内。
(4)补充(重点)
感觉以上的话很啰嗦,在最后整理一下。
看下面例子:
# 交换函数
def swap(a, b):
# 下面代码实现a、b变量的值交换
a, b = b, a
print("swap函数里,a的值是", a, ";b的值是", b)
a = 777
b = 999
print("swap函数里第二次打印,a的值是", a, ";b的值是", b)
a = 666
b = 888
swap(a, b)
print("函数交换结束后,变量a的值是", a, ";变量b的值是", b)
"""
swap函数里,a的值是 888 ;b的值是 666
swap函数里第二次打印,a的值是 777 ;b的值是 999
函数交换结束后,变量a的值是 666 ;变量b的值是 888
"""
1)第一步,执行swap(a, b)函数
- 变量a把自己指向的内存地址传递给了函数的形参a,变量b把自己指向的内存地址传递给了函数的形参b。
- 这样变量a和
swap函数的形参a,都指向了同一个内存地址。变量b和形参b同理。 - 这也说明了上面(1)示例中,变量进入函数的初始索引地址没有变化的原因。
如下图所示:

2)第二步,swap(a, b)函数内进行了形参a和形参b的值交换。
也就时执行了a, b = b, a命令。
- 形参a和形参b的值进行了交换,因为内存中就有这两个值,所以只是内存地址的引用交互了一下。
- 之后就执行了打印命令,显示"swap函数里,a的值是 888 ;b的值是 666"
如下图所示:

提示:形参a和b就时给函数内的变量起一个名,用于区分。这里说明一下,因为我这样的描述不是很准确。
3)第三步,继续给形参a和b赋予新的值。
- 也就是模拟产生新的对象,并指引到新对象的内存地址上。
- 执行了
a = 777和b = 999,打印结果为“swap函数里第二次打印,a的值是 777 ;b的值是 999”。
如下图所示:

4)第四步,swap(a, b)函数执行完毕。
swap(a, b)函数执行完毕,形参a和b的生命周期也就结束了。- 所以变量a和b在函数结束后的打印结果还是初始的状态,“函数交换结束后,变量a的值是 666 ;变量b的值是 888”。
如下图所示:

5)总结:
所以对于不可变数据类型变量的参数传递,执行外表上看,好像只传递了数值,其实通过上面的例子弹道,也进行了引用地址的传递。
上面使用了不可变数据类型变量进行了示例,可变数据类型变量是一样的,只不过修改变量的内容,地址是不发生改变的。但产生了新的对象,内存地址的引用会到新的对象上,和不可变数据类型变量是一样的。
最后我觉得到现在再来讨论Python中参数的传递是值传递还是引用传递,就会发现在Python里讨论这个确实是没有意义。
『无为则无心』Python函数 — 29、Python变量和参数传递的更多相关文章
- 『无为则无心』Python函数 — 25、Python中的函数
目录 1.函数的使用 (1)定义函数 (2)调用函数 (3)使用函数的注意事项 2.函数的参数 3.实参的类型 Python函数的说明: Python中函数的应用非常广泛,前面章节中我们已经接触过多个 ...
- 『无为则无心』Python函数 — 26、Python函数参数的传递方式
目录 1.位置参数 2.关键字参数 3.缺省参数(默认参数) 4.不定长参数(可变参数) (1)包裹位置传递 (2)包裹关键字传递 5.位置参数.默认参数.可变参数的混合使用 6.拓展:参数解包 提示 ...
- 『无为则无心』Python函数 — 28、Python函数的简单应用
目录 1.函数嵌套调用 2.Python函数的简单应用 (1)打印线条 (2)函数计算 (3)打印图形 3.函数的说明文档 (1)函数的说明文档的作用 (2)函数说明文档的语法 (3)查看函数的说明文 ...
- 『无为则无心』Python函数 — 30、Python变量的作用域
目录 1.作用于的概念 2.局部变量 3.全局变量 4.变量的查找 5.作用域中可变数据类型变量 6.多函数程序执行流程 1.作用于的概念 变量作用域指的是变量生效的范围,在Python中一共有两种作 ...
- 『无为则无心』Python函数 — 31、命名空间(namespace)
目录 1.什么是命名空间 2.三种命名空间 3.命名空间查找顺序 4.命名空间的生命周期 5.如何获取当前的命名空间 1.什么是命名空间 命名空间指的是变量存储的位置,每一个变量都需要存储到指定的命名 ...
- 『无为则无心』Python函数 — 33、高阶函数
目录 1.高阶函数的定义 2.体验高阶函数 3.内置高阶函数 (1)map()函数 (2)reduce()函数 (3)filter()函数 1.高阶函数的定义 把函数作为参数传入(把一个函数作为另外一 ...
- 『无为则无心』Python基础 — 4、Python代码常用调试工具
目录 1.Python的交互模式 2.IDLE工具使用说明 3.Sublime3工具的安装与配置 (1)Sublime3的安装 (2)Sublime3的配置 4.使用Sublime编写并调试Pytho ...
- 『无为则无心』Python基础 — 6、Python的注释
目录 1.注释的作用 2.注释的分类 单行注释 多行注释 3.注释的注意事项 4.什么时候需要使用注释 5.总结 提示:完成了前面的准备工作,之后的文章开始介绍Python的基本语法了. Python ...
- 『无为则无心』Python基础 — 9、Python字符串的编码与转义
目录 1.查看变量类型 2.转义字符 (1)转义字符说明 (2)示例 (3)常用转义字符对照表 3.字符编码 (1)字符编码介绍 (2)Python中的字符编码 (3)编码格式应用于不同场景 提示:上 ...
随机推荐
- Linux基础命令---mailq显示邮件队列
mailq mailq指令可以显示出待发送的邮件队列. 此命令的适用范围:RedHat.RHEL.Ubuntu.CentOS.Fedora. 1.语法 mailq 2.选项参数列表 ...
- Linux服务器---drupal
Drupal Drupal为用户提供各种工具来管理网站,它可以帮助用户入门,建立自己的网站 1.下载drupal软件(https://www.drupal.org/project/drupal/rel ...
- Shell脚本定期清空大于1G的日志文件
一个关于如何在指定文件大于1GB后,自动删除的问题. 批处理代码如下: #!/bin/bash # 当/var/log/syslog大于1GB时 # 自动将其备份,并清空 # 注意这里awk的使用 i ...
- 『学了就忘』Linux启动引导与修复 — 74、Linux系统的修复模式(光盘修复模式)
目录 1.光盘修复模式概念 2.光盘修复模式修复系统问题 (1)准备系统光盘 (2)进入BIOS (3)修改BIOS的启动顺序 (4)进入光盘修复模式 (5)修复系统 (6)修复系统实操 (7)总结 ...
- 5、Redis五大基本数据类型——String类型
一.Redis支持数据类型简介 Redis支持五种数据类型:string(字符串),hash(哈希),list(列表),set(集合)及zset(sorted set:有序集合). 二.String类 ...
- 虚拟机+OS系统安装+Xshell
安装虚拟机 1.先下载好VMware Workstation.exe的安装包(最好直接下载破解版(非最新版) 在此私人网址不公布 可直接百度 资源很多) 2.直接下一步 直到安装完成(注意最好不要装在 ...
- Django-利用LogEntry生成操作历史
在开发测试平台的时候,虽然对某些关键功能做了权限设置,但毕竟是公司多人使用,有些数据的配置可能不小心被他人修改但未告知其他使用者,造成了诸多不便.所以决定开发一个操作历史表,可以方便查看数据地改动. ...
- SpringBoot(SpringMVC)使用addViewControllers设置统一请求URL重定向配置
只需要在配置中重写 addViewControllers方法 import org.springframework.context.annotation.Configuration; import o ...
- tomcat启动报错There is insufficient memory for the Java Runtime Environment to continue
tomcat启动报错后显示以下错误 ## There is insufficient memory for the Java Runtime Environment to continue.# Nat ...
- 总结Vue第三天:模块化和webpack模块化打包:
总结Vue第三天:模块化和webpack模块化打包: 一.❀ 模块化 [导入import-----导出export] 1.为什么需要模块化? JavaScript 发展初期,代码简单地堆积在一起,只要 ...