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]赋值给ab,表达式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
"""

我们可以看到:

  • 对于不可变数据类型变量,相同的值是共用内存地址的。(这一点很重要)
  • 对于可变数据类型变量,如上面的listalistb,即使内容一样,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 = 777b = 999,打印结果为“swap函数里第二次打印,a的值是 777 ;b的值是 999”。

如下图所示:

4)第四步,swap(a, b)函数执行完毕。

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

如下图所示:

5)总结:

所以对于不可变数据类型变量的参数传递,执行外表上看,好像只传递了数值,其实通过上面的例子弹道,也进行了引用地址的传递。

上面使用了不可变数据类型变量进行了示例,可变数据类型变量是一样的,只不过修改变量的内容,地址是不发生改变的。但产生了新的对象,内存地址的引用会到新的对象上,和不可变数据类型变量是一样的。

最后我觉得到现在再来讨论Python中参数的传递是值传递还是引用传递,就会发现在Python里讨论这个确实是没有意义。

参考:http://c.biancheng.net/view/2258.html

『无为则无心』Python函数 — 29、Python变量和参数传递的更多相关文章

  1. 『无为则无心』Python函数 — 25、Python中的函数

    目录 1.函数的使用 (1)定义函数 (2)调用函数 (3)使用函数的注意事项 2.函数的参数 3.实参的类型 Python函数的说明: Python中函数的应用非常广泛,前面章节中我们已经接触过多个 ...

  2. 『无为则无心』Python函数 — 26、Python函数参数的传递方式

    目录 1.位置参数 2.关键字参数 3.缺省参数(默认参数) 4.不定长参数(可变参数) (1)包裹位置传递 (2)包裹关键字传递 5.位置参数.默认参数.可变参数的混合使用 6.拓展:参数解包 提示 ...

  3. 『无为则无心』Python函数 — 28、Python函数的简单应用

    目录 1.函数嵌套调用 2.Python函数的简单应用 (1)打印线条 (2)函数计算 (3)打印图形 3.函数的说明文档 (1)函数的说明文档的作用 (2)函数说明文档的语法 (3)查看函数的说明文 ...

  4. 『无为则无心』Python函数 — 30、Python变量的作用域

    目录 1.作用于的概念 2.局部变量 3.全局变量 4.变量的查找 5.作用域中可变数据类型变量 6.多函数程序执行流程 1.作用于的概念 变量作用域指的是变量生效的范围,在Python中一共有两种作 ...

  5. 『无为则无心』Python函数 — 31、命名空间(namespace)

    目录 1.什么是命名空间 2.三种命名空间 3.命名空间查找顺序 4.命名空间的生命周期 5.如何获取当前的命名空间 1.什么是命名空间 命名空间指的是变量存储的位置,每一个变量都需要存储到指定的命名 ...

  6. 『无为则无心』Python函数 — 33、高阶函数

    目录 1.高阶函数的定义 2.体验高阶函数 3.内置高阶函数 (1)map()函数 (2)reduce()函数 (3)filter()函数 1.高阶函数的定义 把函数作为参数传入(把一个函数作为另外一 ...

  7. 『无为则无心』Python基础 — 4、Python代码常用调试工具

    目录 1.Python的交互模式 2.IDLE工具使用说明 3.Sublime3工具的安装与配置 (1)Sublime3的安装 (2)Sublime3的配置 4.使用Sublime编写并调试Pytho ...

  8. 『无为则无心』Python基础 — 6、Python的注释

    目录 1.注释的作用 2.注释的分类 单行注释 多行注释 3.注释的注意事项 4.什么时候需要使用注释 5.总结 提示:完成了前面的准备工作,之后的文章开始介绍Python的基本语法了. Python ...

  9. 『无为则无心』Python基础 — 9、Python字符串的编码与转义

    目录 1.查看变量类型 2.转义字符 (1)转义字符说明 (2)示例 (3)常用转义字符对照表 3.字符编码 (1)字符编码介绍 (2)Python中的字符编码 (3)编码格式应用于不同场景 提示:上 ...

随机推荐

  1. my40_MySQL锁概述之意向锁

    本文在锁概述的基础上,通常实验举例,详细地介绍了意向锁的原理. 锁范围  全局锁(global lock)表锁(table lock)行锁 (row lock) ROW LOCK的粒度LOCK_REC ...

  2. 远程连接mysql库问题

    如果你想连接你的mysql的时候发生这个错误: ERROR 1130: Host '192.168.1.3' is not allowed to connect to this MySQL serve ...

  3. Spring Boot with H2 Database

    Learn to configure H2 database with Spring boot to create and use an in-memory database in runtime, ...

  4. JAVA日志发展史

    JAVA日志发展史 第一阶段 2001年以前,Java是没有日志库的,打印日志全凭System.out和System.err 缺点: 产生大量的IO操作同时在生产环境中无法合理的控制是否需要输出 输出 ...

  5. Redis cluster 集群部署和配置

    目录 一.集群简介 cluster介绍 cluster原理 cluster特点 应用场景 二.集群部署 环境介绍 节点部署 启动集群 三.集群测试 一.集群简介 cluster介绍 redis clu ...

  6. SQLserver 2014使用Convert()函数获取时间

    select convert(char(100),GetDate(),120) as Date 第3个参数就是用来设置日期类型数据的显示样式的,下面介绍几种样式的参数 SELECT CONVERT(v ...

  7. Node.js 中文乱码解决

    Node.js 中文乱码解决 Node.js 支持中文不太好(实际上是Javascript支持),见<Node.js开发指南>. 要想Node.js正常显示中文,需要两点: 1.js文件保 ...

  8. SROP例题

    具体攻击原理可以参考安全客这篇文章:入口 刚学了一点,也是懵懵懂懂的,拿几道题来练练手. ciscn_2019_es_7 64位程序,只开启了NX保护. 相当于执行了read(0,buf,0x400) ...

  9. C语言程序设计:二分查找(折半查找)

    目录 C语言程序设计:二分查找(折半查找) 1.什么是二分查找 2.二分查找的优点 3.二分查找的缺点 4.二分查找原理 5.源代码实现 6.后话 C语言程序设计:二分查找(折半查找) 1.什么是二分 ...

  10. java 编程基础:注解的功能和作用,自定义注解

    1,什么是注解: 从JDK5开始,Java增加了对元数据 (MetaData)的支持,也就是Annotation注解,这种注解与注释不一样,注解其实是代码里的特殊标记,这些标记可以在编译.类加载 运行 ...