Python中令人迷惑的4个引用
第一个:执行时机的差异
1.
array = [1, 8, 15]
g = (x for x in array if array.count(x) > 0)
array = [2, 8, 22]
Output:
>>> print(list(g))
[8]
2.
array_1 = [1,2,3,4]
g1 = (x for x in array_1)
array_1 = [1,2,3,4,5] array_2 = [1,2,3,4]
g2 = (x for x in array_2)
array_2[:] = [1,2,3,4,5]
Output:
>>> print(list(g1))
[1,2,3,4] >>> print(list(g2))
[1,2,3,4,5]
说明
在生成器表达式中,
in子句在声明时执行, 而条件子句则是在运行时执行.所以在运行前,
array已经被重新赋值为[2, 8, 22], 因此对于之前的1,8和15, 只有count(8)的结果是大于0的, 所以生成器只会生成8.第二部分中
g1和g2的输出差异则是由于变量array_1和array_2被重新赋值的方式导致的.在第一种情况下,
array_1被绑定到新对象[1,2,3,4,5], 因为in子句是在声明时被执行的, 所以它仍然引用旧对象[1,2,3,4](并没有被销毁).在第二种情况下, 对
array_2的切片赋值将相同的旧对象[1,2,3,4]原地更新为[1,2,3,4,5]. 因此g2和array_2仍然引用同一个对象(这个对象现在已经更新为[1,2,3,4,5]).
第二个:出人意料的is
下面是一个在互联网上非常有名的例子.
>>> a = 256
>>> b = 256
>>> a is b
True >>> a = 257
>>> b = 257
>>> a is b
False >>> a = 257; b = 257
>>> a is b
True
说明:
is 和 == 的区别
is运算符检查两个运算对象是否引用自同一对象 (即, 它检查两个运算对象是否相同).==运算符比较两个运算对象的值是否相等.因此
is代表引用相同,==代表值相等. 下面的例子可以很好的说明这点,
>>> [] == []
True
>>> [] is [] # 这两个空列表位于不同的内存地址.
False
256 是一个已经存在的对象, 而 257 不是
当你启动Python 的时候, 数值为 -5 到 256 的对象就已经被分配好了. 这些数字因为经常被使用, 所以会被提前准备好.
Python 通过这种创建小整数池的方式来避免小整数频繁的申请和销毁内存空间.
当前的实现为-5到256之间的所有整数保留一个整数对象数组, 当你创建了一个该范围内的整数时, 你只需要返回现有对象的引用. 所以改变1的值是有可能的. 我怀疑这种行为在Python中是未定义行为. :-)
>>> id(256)
10922528
>>> a = 256
>>> b = 256
>>> id(a)
10922528
>>> id(b)
10922528
>>> id(257)
140084850247312
>>> x = 257
>>> y = 257
>>> id(x)
140084850247440
>>> id(y)
140084850247344
这里解释器并没有智能到能在执行 y = 257 时意识到我们已经创建了一个整数 257, 所以它在内存中又新建了另一个对象.
当 a 和 b 在同一行中使用相同的值初始化时,会指向同一个对象.
>>> a, b = 257, 257
>>> id(a)
140640774013296
>>> id(b)
140640774013296
>>> a = 257
>>> b = 257
>>> id(a)
140640774013392
>>> id(b)
140640774013488
当 a 和 b 在同一行中被设置为
257时, Python 解释器会创建一个新对象, 然后同时引用第二个变量. 如果你在不同的行上进行, 它就不会 "知道" 已经存在一个257对象了.这是一种特别为交互式环境做的编译器优化. 当你在实时解释器中输入两行的时候, 他们会单独编译, 因此也会单独进行优化. 如果你在
.py文件中尝试这个例子, 则不会看到相同的行为, 因为文件是一次性编译的.
第三个:影子数组
# 我们先初始化一个变量row
row = [""]*3 #row i['', '', '']
# 并创建一个变量board
board = [row]*3
Output:
>>> board
[['', '', ''], ['', '', ''], ['', '', '']]
>>> board[0]
['', '', '']
>>> board[0][0]
''
>>> board[0][0] = "X"
>>> board
[['X', '', ''], ['X', '', ''], ['X', '', '']]
我们有没有赋值过3个 "X" 呢?
说明:
当我们初始化 row 变量时, 下面这张图展示了内存中的情况。
而当通过对 row 做乘法来初始化 board 时, 内存中的情况则如下图所示 (每个元素 board[0], board[1] 和 board[2] 都和 row 一样引用了同一列表.)
我们可以通过不使用变量 row 生成 board 来避免这种情况. (这个issue提出了这个需求.)
>>> board = [['']*3 for _ in range(3)]
>>> board[0][0] = "X"
>>> board
[['X', '', ''], ['', '', ''], ['', '', '']]
第四个:混乱的输出
#python学习群592539176
funcs = []
results = []
for x in range(7):
def some_func():
return x
funcs.append(some_func)
results.append(some_func()) # 注意这里函数被执行了 funcs_results = [func() for func in funcs]
Output:
>>> results
[0, 1, 2, 3, 4, 5, 6]
>>> funcs_results
[6, 6, 6, 6, 6, 6, 6]
即使每次在迭代中将 some_func 加入 funcs 前的 x 值都不相同, 所有的函数还是都返回6.
再换个例子
>>> powers_of_x = [lambda x: x**i for i in range(10)]
>>> [f(2) for f in powers_of_x]
[512, 512, 512, 512, 512, 512, 512, 512, 512, 512]
说明:
当在循环内部定义一个函数时, 如果该函数在其主体中使用了循环变量, 则闭包函数将与循环变量绑定, 而不是它的值. 因此, 所有的函数都是使用最后分配给变量的值来进行计算的.
可以通过将循环变量作为命名变量传递给函数来获得预期的结果. 为什么这样可行? 因为这会在函数内再次定义一个局部变量.
#python学习群592539176
funcs = []
for x in range(7):
def some_func(x=x):
return x
funcs.append(some_func)
Output:
>>> funcs_results = [func() for func in funcs]
>>> funcs_results
[0, 1, 2, 3, 4, 5, 6]
Python中令人迷惑的4个引用的更多相关文章
- [转]Python中函数的值传递和引用传递
首先还是应该科普下函数参数传递机制,传值和传引用是什么意思? 函数参数传递机制问题在本质上是调用函数(过程)和被调用函数(过程)在调用发生时进行通信的方法问题.基本的参数传递机制有两种:值传递和引用传 ...
- Python 中的赋值、拷贝、引用
在 python 中赋值语句总是建立对象的引用值,而不是复制对象.因此,python 变量更像是指针,而不是数据存储区域. 如图所示,当改变一个变量的值,另一个的值也会跟着改变.也就是浅拷贝. 若要实 ...
- python中令人惊艳的小众数据科学库
Python是门很神奇的语言,历经时间和实践检验,受到开发者和数据科学家一致好评,目前已经是全世界发展最好的编程语言之一.简单易用,完整而庞大的第三方库生态圈,使得Python成为编程小白和高级工程师 ...
- python中的引用传递,可变对象,不可变对象,list注意点
python中的引用传递 首先必须理解的是,python中一切的传递都是引用(地址),无论是赋值还是函数调用,不存在值传递. 可变对象和不可变对象 python变量保存的是对象的引用,这个引用指向堆内 ...
- 【Python】Python中的引用和赋值
本文转自:http://my.oschina.net/leejun2005/blog/145911 在 python 中赋值语句总是建立对象的引用值,而不是复制对象.因此,python 变量更像是指针 ...
- python中给函数传参是传值还是传引用
首先还是应该科普下函数参数传递机制,传值和传引用是什么意思? 函数参数传递机制问题在本质上是调用函数(过程)和被调用函数(过程)在调用发生时进行通信的方法问题.基本的参数传递机制有两种:值传递和引用传 ...
- 『Python』为什么调用函数会令引用计数+2
一.问题描述 Python中的垃圾回收是以引用计数为主,分代收集为辅,引用计数的缺陷是循环引用的问题.在Python中,如果一个对象的引用数为0,Python虚拟机就会回收这个对象的内存. sys.g ...
- Python中命名空间与作用域使用总结
1 引言 命名空间与作用域是程序设计中的基础概念,深入理解有助于理解变量的生命周期,减少代码中的莫名其妙bug.Python的命名空间与作用域与Java.C++等语言有很大差异,若不注意,就可能出现莫 ...
- python中垃圾回收机制
Python垃圾回收机制详解 一.垃圾回收机制 Python中的垃圾回收是以引用计数为主,分代收集为辅.引用计数的缺陷是循环引用的问题.在Python中,如果一个对象的引用数为0,Python虚拟 ...
随机推荐
- [HTML] websocket的模拟日志监控界面
模拟命令行的界面效果,使用swoole作为websocket的服务,重新做了下html的界面效果 <html> <head> <title>SwLog Montio ...
- python基础数据类型 数据类型
数据类型 计算机顾名思义就是可以做数学计算的机器,因此,计算机程序理所当然地可以处理各种数值.但是,计算机能处理的远不止数值,还可以处理文本.图形.音频.视频.网页等各种各样的数据,不同的数据,需要定 ...
- [C#] 委托与匿名方法
using System; namespace 匿名函数 { class Program { delegate void TestDelegate(string s); static void M(s ...
- ansible笔记(8):初识ansible playbook
回顾总结:我们来想象一个工作场景,看看怎样把之前的知识点应用到这个工作场景中.假设,我们想要在192.168.10.2主机上安装nginx并启动,我们可以在ansible控制主机中执行如下3条命令. ...
- winform学习(6)控件的对齐、比例、定位操作
窗体控件的常用操作 选中一个控件,按住Ctrl再点选另一个控件(此时编辑的是第二个控件,当使用框选选中多个控件时,编辑的是黑色锚点的控件) 代码中的居顶和居底: 控件标识符.BringToFront( ...
- codeforces E. The Contest(最长上升子序列)
题目链接:https://codeforces.com/contest/1257/problem/E 题意:给三个序列k1,k2,k3,每个序列有一堆数,k1是前缀,k3是后缀,k2是中间,现可以从任 ...
- Linux06——安装JDK、Tomcat、Eclipse
一.安装JDK(具体解压命令在Linux02中) ①将JDK解压到opt目录下(opt就是文件夹) ②配置环境变量 vim /etc/profile JAVA_HOME=/opt/jdk1.8.0 ...
- Oracle 11G统计信息自动收集及调整
查询统计信息的收集所对应的task,以及当前状态 col CLIENT_NAME for a50col TASK_NAME for a20SELECT client_name, task_name, ...
- 最小树形图模板 UVA11183
题意:给定n个节点m条边的有向带权图,求以0为根节点的最小树形图权值大小 用这个代码的时候要注意,这里的数据是从0开始的,边也是从0开始算, 所以在打主代码的时候,如果是从1开始,那么算法里面的从0开 ...
- 09day vi命令详解
vi有三种模式(互相切换) 1. 命令模式 2. 插入模式(编辑模式) 3. 低行模式 三种模式的切换方法: 使用技巧 vi 文件信息 i --- 进入编辑模式 esc --- 退出编辑模式 :wq ...