Python为什么文件运行和在命令行运行同样语句但结果却不同?

时间:2022-05-07
本文章向大家介绍Python为什么文件运行和在命令行运行同样语句但结果却不同?,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

这篇是之前知乎上的一个提问,感觉非常有趣而且内容丰富,所以把我自己的回答搬运到公众号来。

另外关于昨天的推送,是因为我之前把文章投到了Python中文社区的公众号,他们那边开通了微信的原创保护,我后发的文章被判定了转载。已经和那边的负责人取得联系,加入转载白名单后会把之前的几篇文章再发出来。后续也会坚持更新其他文章的。

微信文章没办法加入超链接。所以文中下划线的参考资料外链请到知乎页面打开:https://www.zhihu.com/question/53536750/answer/135411534。

◆提问◆

如图,都是同样的代码,但是输出结果却不同,请大神指点。

礼貌贴上代码。

a = 10.0
b = 10.0
print(a is b)

◆回答

答案放在最前面: 对于Python而言,存储好的脚本文件(Script file)和在Console中的交互式(interactive)命令,执行方式不同。对于脚本文件,解释器将其当作整个代码块执行,而对于交互性命令行中的每一条命令,解释器将其当作单独的代码块执行。而Python在执行同一个代码块的初始化对象的命令时,会检查是否其值是否已经存在,如果存在,会将其重用(这句话不够严谨,后面会详谈)。所以在你给出的例子中,文件执行时(同一个代码块)会把a、b两个变量指向同一个对象;而在命令行执行时,a、b赋值语句分别被当作两个代码块执行,所以会得到两个不同的对象,因而is判断返回False。 # 如果你能理解上面一段,就不用看下面的废话了。 下面是详细的回答: 这个问题远超我想象中的复杂。我本来以为我能用两分钟搞定这种每日一水的问题,结果我花了一个小时搜来搜去,读来读去,还跑去群里跟人讨论了一阵,都没能找到答案。 大概两个小时以后,我找到了相对正确的答案,把自己已经弄懂的部分强答一番,并邀请一些大神,希望能看到更为准确的回答。 这个问题的博大精深在于,能从中扯出许多小问题来,虽然这些东西很细枝末节,很trick,在日常编程中不怎么用的到,更不怎么需要额外关注,但是理解这些问题,对于我们理解Python的对象机制乃至内存处理机制有很大的帮助。 我从头开始说,大概会分以下几个部分来谈,每个部分其实都能展开很广,这次就把与问题相关的知识简单一提: (虽然我觉得按照我寻找答案的过程讲,可能对认知更有帮助,但是理清头绪的话可能更好理解,之后会找时间为这个问题写篇文章好好记录一下)

  1. Python中的数据类型——可变与不可变
  2. Python中is比较与==比较的区别
  3. Python中对小整数的缓存机制
  4. Python程序的结构——代码块
  5. Python的内存管理——新建对象时的操作

声明:以下所讲机制,与Python不同版本的具体实现有关(implement specific)可能不同。 Python中的数据类型 Python中的数据类型,这可能是大家入门Python的第一节课。很简单嘛,大家最常用的,int(包括long)、float、string、list、tuple、dict,加上bool和NoneType。 但是这里要重点说的,其实是可变类型和不可变类型。 不可变(immutable):Number(包括int、float),String,Tuple 可变(mutable):Dict,List,User-defined class 首先我们要记住一句话,一切皆对象。Python中把任何一种Type都当作对象来处理。其中有一些类型是不可变的,比如:

这个还是好理解的,在初始化赋值一个字符串后,我们没有办法直接修改它的值。但是数字呢?数字这种变来变去的又怎么理解。

可以看出,a的值虽然从10变成了11,但是a这个变量指向内存中的位置发生了变化,也就是说我们并没有对a指向的内存进行操作,而是对a进行了重新赋值。 再简单举一个可变的例子。

体会了可变与不可变的外在表现后,简单理解一下为什么不可变。 Python官方文档这样解释字符串不可变:

There are several advantages. One is performance: knowing that a string is immutable means we can allocate space for it at creation time, and the storage requirements are fixed and unchanging. This is also one of the reasons for the distinction between tuples and lists. Another advantage is that strings in Python are considered as “elemental” as numbers. No amount of activity will change the value 8 to anything else, and in Python, no amount of activity will change the string “eight” to anything else.

个人感觉,有性能上的考虑(比如对一些固定不变的元素给予固定的存储位置,整数这样操作比较方便,字符串的话涉及一些比较也会减少后续操作的时间),也有一些安全上的考虑(比如列表中的值会改变,元组不会)。这个我也不太精通,就不展开谈了。 Python中is比较与==比较的区别 前面已经提过一次,Python中一切皆对象。对象包含三个要素,id、type、value。 而Python中用于比较“相等”这一概念的操作符,is和==。 当两个变量指向了同一个对象时,is会返回True(即is比较的是两个变量的id); 当两个变量的值相同时,==会返回True(即==比较的是两个变量的value)。 示例(命令行交互模式下):

第一个和第三个示例是好理解的。 但是第二个就不那么好理解了,尤其是配合下面这个(假定我们已经知道命令行中的语句执行是单独执行两次不会相互影响,后面会具体解释):

为什么a、b分别赋值1000时is比较返回False,可以分别赋值100就会返回True? Python中对小整数的缓存机制 Python官方文档中这么说:

The current implementation keeps an array of integer objects for all integers between -5 and 256, when you create an int in that range you actually just get back a reference to the existing object. So it should be possible to change the value of 1. I suspect the behaviour of Python in this case is undefined. :-)

简单来说就是,Python自动将-5~256的整数进行了缓存,当你将这些整数赋值给变量时,并不会重新创建对象,而是使用已经创建好的缓存对象。 Python程序的结构——代码块&Python的内存管理——新建对象时的操作 终于要来到题主问题的部分了。 先来看最让我们困惑的,也就是题主给出的示例吧(接下来用float演示,int是同样的情况): 交互命令行下:

同样的还有:

(说好的小整数才有缓存呢(摔)!这跟你讲的不一样啊教练!) 这就很尴尬了对吧。 其实从结果论出发,我们很容易猜到结论,就像题主自己也猜了个差不多——缓存机制不同。毕竟is比较的就是对象的id,也就是对象在内存中的位置,也就是是不是同一个对象。 既然脚本文件的执行结果是True,那么,他俩就是同一个对象;既然命令行执行的结果是False,那么他俩就不是同一个对象。(这他喵的不是废话吗!) 所以我开始了漫长的找原理的过程……然而网上这方面提及的实在太少。尤其是大家的大部分讨论都是int的小整数缓存机制;就算讨论到了float,也不实际解决我们的问题。(比如有小伙伴帮我找到的链接https://groups.google.com/forum/m/#!topic/comp.lang.python/EsLWI3Mogig) 其实我都快要放弃了,漫无目的地翻stackoverflow推荐的相关问题时终于找到了一个类似的情况,但是人家并不是比较的脚本文件和命令行执行,而是比较的函数体和赋值语句:

同样的代码,拆开就是False,放函数里就是True!是不是很像我们遇到的情况了。 根据提示我们从官方文档找到了这样的说法:

A Python program is constructed from code blocks. A block is a piece of Python program text that is executed as a unit. The following are blocks: a module, a function body, and a class definition. Each command typed interactively is a block. A script file (a file given as standard input to the interpreter or specified as a command line argument to the interpreter) is a code block. A script command (a command specified on the interpreter command line with the ‘-c‘ option) is a code block. The string argument passed to the built-in functions eval() and exec() is a code block. A code block is executed in an execution frame. A frame contains some administrative information (used for debugging) and determines where and how execution continues after the code block’s execution has completed.

没错!跟我们猜的一样!这就是原理的出处了! 代码块作为一个执行单元,一个模块、一个函数体、一个类定义、一个脚本文件,都是一个代码块。 在交互式命令行中,每行代码单独视作一个代码块。 至此问题解决……了吗?视作一个代码块,就意味着要把相同value的赋值指向相同的对象吗? 在此重复一下'is' operator behaves unexpectedly with non-cached integers中提到的实验,并简单翻译结论。 通过compile()函数和dis模块的code_info()函数来检测我们执行的命令的信息。 示例:

可以看出,分别赋值a,b得到的value相等,id是不一样的。

把10.0 10.0 10.1分别赋值给a,b,c,可以看出结果中其实只保存了一个10.0,也就是a,b共用了这个数值。 也就是说,当命令行执行时,是以single的模式来compile代码(Built-in Functions)。它会在u_consts字典中记录对象常量。

The mode argument specifies what kind of code must be compiled; it can be 'exec' if source consists of a sequence of statements, 'eval' if it consists of a single expression, or 'single' if it consists of a single interactive statement (in the latter case, expression statements that evaluate to something other than None will be printed).

而在同一代码块执行时,当增加新的常量,会先在字典中查询记录,所以相同赋值的变量会指向同一个对象而不是新建对象。 至此…问题大概是解决了。 (当然这是implement specific的事情,参见docs.python.org/3/reference/datamodel.html#objects-values-and-typeshttps://docs.python.org/3/reference/datamodel.html#objects-values-and-types中的CPython implementation detail) 要说这道题教会了我什么…… 那一定是:比较数值请一定一定用==不要用is! 以上。 参考资料:

Immutable vs Mutable types

Design and History FAQ

"is" operator behaves unexpectedly with integers

Integer Objects - Python 3.5.2 documentation

'is' operator behaves unexpectedly with non-cached integers

'is' operator behaves unexpectedly with floats

What is the difference between "a is b" and "id(a) == id(b)" in Python?

Same value for id(float)

Python Memory Management

也推荐大家看R大 @RednaxelaFX的一个回答:

https://www.zhihu.com/question/29089863/answer/53970119