最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會員登陸 & 注冊

Python為什么文件運行和在命令行運行同樣語句但結(jié)果卻不同?

2018-08-10 17:49 作者:不二小段  | 我要投稿


這篇是之前知乎上的一個提問,感覺非常有趣而且內(nèi)容豐富,所以搬運過來。

#  bilibili專欄沒辦法加入超鏈接。所以文中下劃線的參考資料請復制粘貼地址訪問。

這篇文章解釋的問題蠻有趣的,而且牽扯一些Python的基礎(chǔ)設(shè)計和概念,

推薦有一定基礎(chǔ)的同學親自動手嘗試并吃透。


歡迎關(guān)注公眾號:不二小段

知乎:段小草


 ◆提問

如圖,都是同樣的代碼,但是輸出結(jié)果卻不同,請大神指點。

禮貌貼上代碼。

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



◆回答



答案放在最前面:
對于Python而言,存儲好的腳本文件(Script file)和在Console中的交互式(interactive)命令,執(zhí)行方式不同。對于腳本文件,解釋器將其當作整個代碼塊執(zhí)行,而對于交互性命令行中的每一條命令,解釋器將其當作單獨的代碼塊執(zhí)行。而Python在執(zhí)行同一個代碼塊的初始化對象的命令時,會檢查是否其值是否已經(jīng)存在,如果存在,會將其重用(這句話不夠嚴謹,后面會詳談)。所以在你給出的例子中,文件執(zhí)行時(同一個代碼塊)會把a、b兩個變量指向同一個對象;而在命令行執(zhí)行時,a、b賦值語句分別被當作兩個代碼塊執(zhí)行,所以會得到兩個不同的對象,因而is判斷返回False。

# 如果你能理解上面一段,就不用看下面的廢話了。

下面是詳細的回答:
這個問題遠超我想象中的復雜。我本來以為我能用兩分鐘搞定這種每日一水的問題,結(jié)果我花了一個小時搜來搜去,讀來讀去,還跑去群里跟人討論了一陣,都沒能找到答案。
大概兩個小時以后,我找到了相對正確的答案,把自己已經(jīng)弄懂的部分強答一番,并邀請一些大神,希望能看到更為準確的回答。

這個問題的博大精深在于,能從中扯出許多小問題來,雖然這些東西很細枝末節(jié),很trick,在日常編程中不怎么用的到,更不怎么需要額外關(guān)注,但是理解這些問題,對于我們理解Python的對象機制乃至內(nèi)存處理機制有很大的幫助。

我從頭開始說,大概會分以下幾個部分來談,每個部分其實都能展開很廣,這次就把與問題相關(guān)的知識簡單一提:
(雖然我覺得按照我尋找答案的過程講,可能對認知更有幫助,但是理清頭緒的話可能更好理解,之后會找時間為這個問題寫篇文章好好記錄一下)

  1. Python中的數(shù)據(jù)類型——可變與不可變

  2. Python中is比較與==比較的區(qū)別

  3. Python中對小整數(shù)的緩存機制

  4. Python程序的結(jié)構(gòu)——代碼塊

  5. Python的內(nèi)存管理——新建對象時的操作


聲明:以下所講機制,與Python不同版本的具體實現(xiàn)有關(guān)(implement specific)可能不同。

Python中的數(shù)據(jù)類型
Python中的數(shù)據(jù)類型,這可能是大家入門Python的第一節(jié)課。很簡單嘛,大家最常用的,int(包括long)、float、string、list、tuple、dict,加上bool和NoneType。
但是這里要重點說的,其實是可變類型和不可變類型。
不可變(immutable):Number(包括int、float),String,Tuple
可變(mutable):Dict,List,User-defined class
首先我們要記住一句話,一切皆對象。Python中把任何一種Type都當作對象來處理。其中有一些類型是不可變的,比如:


這個還是好理解的,在初始化賦值一個字符串后,我們沒有辦法直接修改它的值。但是數(shù)字呢?數(shù)字這種變來變?nèi)サ挠衷趺蠢斫狻?br/>

可以看出,a的值雖然從10變成了11,但是a這個變量指向內(nèi)存中的位置發(fā)生了變化,也就是說我們并沒有對a指向的內(nèi)存進行操作,而是對a進行了重新賦值。
再簡單舉一個可變的例子。


體會了可變與不可變的外在表現(xiàn)后,簡單理解一下為什么不可變。
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.

個人感覺,有性能上的考慮(比如對一些固定不變的元素給予固定的存儲位置,整數(shù)這樣操作比較方便,字符串的話涉及一些比較也會減少后續(xù)操作的時間),也有一些安全上的考慮(比如列表中的值會改變,元組不會)。這個我也不太精通,就不展開談了。

Python中is比較與==比較的區(qū)別
前面已經(jīng)提過一次,Python中一切皆對象。對象包含三個要素,id、type、value。
而Python中用于比較“相等”這一概念的操作符,is和==。
當兩個變量指向了同一個對象時,is會返回True(即is比較的是兩個變量的id);
當兩個變量的值相同時,==會返回True(即==比較的是兩個變量的value)。
示例(命令行交互模式下):

第一個和第三個示例是好理解的。
但是第二個就不那么好理解了,尤其是配合下面這個(假定我們已經(jīng)知道命令行中的語句執(zhí)行是單獨執(zhí)行兩次不會相互影響,后面會具體解釋):

為什么a、b分別賦值1000時is比較返回False,可以分別賦值100就會返回True?

Python中對小整數(shù)的緩存機制
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的整數(shù)進行了緩存,當你將這些整數(shù)賦值給變量時,并不會重新創(chuàng)建對象,而是使用已經(jīng)創(chuàng)建好的緩存對象。

Python程序的結(jié)構(gòu)——代碼塊&Python的內(nèi)存管理——新建對象時的操作
終于要來到題主問題的部分了。
先來看最讓我們困惑的,也就是題主給出的示例吧(接下來用float演示,int是同樣的情況):
交互命令行下:

同樣的還有:

(說好的小整數(shù)才有緩存呢(摔)!這跟你講的不一樣啊教練?。?br/>這就很尷尬了對吧。
其實從結(jié)果論出發(fā),我們很容易猜到結(jié)論,就像題主自己也猜了個差不多——緩存機制不同。畢竟is比較的就是對象的id,也就是對象在內(nèi)存中的位置,也就是是不是同一個對象。
既然腳本文件的執(zhí)行結(jié)果是True,那么,他倆就是同一個對象;既然命令行執(zhí)行的結(jié)果是False,那么他倆就不是同一個對象。(這他喵的不是廢話嗎!
所以我開始了漫長的找原理的過程……然而網(wǎng)上這方面提及的實在太少。尤其是大家的大部分討論都是int的小整數(shù)緩存機制;就算討論到了float,也不實際解決我們的問題。(比如有小伙伴幫我找到的鏈接https://groups.google.com/forum/m/#!topic/comp.lang.python/EsLWI3Mogig)

其實我都快要放棄了,漫無目的地翻stackoverflow推薦的相關(guān)問題時終于找到了一個類似的情況,但是人家并不是比較的腳本文件和命令行執(zhí)行,而是比較的函數(shù)體和賦值語句:

同樣的代碼,拆開就是False,放函數(shù)里就是True!是不是很像我們遇到的情況了。
根據(jù)提示我們從官方文檔找到了這樣的說法:

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.

沒錯!跟我們猜的一樣!這就是原理的出處了!
代碼塊作為一個執(zhí)行單元,一個模塊、一個函數(shù)體、一個類定義、一個腳本文件,都是一個代碼塊。
在交互式命令行中,每行代碼單獨視作一個代碼塊。

至此問題解決……了嗎?視作一個代碼塊,就意味著要把相同value的賦值指向相同的對象嗎?
在此重復一下https://stackoverflow.com/questions/34147515/is-operator-behaves-unexpectedly-with-non-cached-integers/39325641#39325641中提到的實驗,并簡單翻譯結(jié)論。
通過compile()函數(shù)和dis模塊的code_info()函數(shù)來檢測我們執(zhí)行的命令的信息。
示例:

可以看出,分別賦值a,b得到的value相等,id是不一樣的。

把10.0 10.0 10.1分別賦值給a,b,c,可以看出結(jié)果中其實只保存了一個10.0,也就是a,b共用了這個數(shù)值。

也就是說,當命令行執(zhí)行時,是以single的模式來compile代碼(https://docs.python.org/3/library/functions.html#compile)。它會在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).

而在同一代碼塊執(zhí)行時,當增加新的常量,會先在字典中查詢記錄,所以相同賦值的變量會指向同一個對象而不是新建對象。

至此…問題大概是解決了。
(當然這是implement specific的事情,參見docs.python.org/3/reference/datamodel.html#objects-values-and-types中的CPython implementation detail

要說這道題教會了我什么……
那一定是:比較數(shù)值請一定一定用==不要用is!

以上。

參考資料:

https://stackoverflow.com/questions/8056130/immutable-vs-mutable-types

https://docs.python.org/3/faq/design.html#why-are-python-strings-immutable

https://stackoverflow.com/questions/306313/is-operator-behaves-unexpectedly-with-integers

https://docs.python.org/3/c-api/long.html#c.PyLong_FromLong

https://stackoverflow.com/questions/34147515/is-operator-behaves-unexpectedly-with-non-cached-integers

https://stackoverflow.com/questions/38834770/is-operator-behaves-unexpectedly-with-floats

https://stackoverflow.com/questions/2906177/what-is-the-difference-between-a-is-b-and-ida-idb-in-python

https://stackoverflow.com/questions/17132047/same-value-for-idfloat

http://deeplearning.net/software/theano/tutorial/python-memory-management.html



也推薦大家看R大 @RednaxelaFX的一個回答:

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


Python為什么文件運行和在命令行運行同樣語句但結(jié)果卻不同?的評論 (共 條)

分享到微博請遵守國家法律
佛坪县| 沙湾县| 正定县| 凤城市| 安庆市| 崇阳县| 临安市| 辽宁省| 耒阳市| 霸州市| 兰州市| 九龙坡区| 湘潭市| 西吉县| 普定县| 益阳市| 星子县| 鄂伦春自治旗| 新密市| 安泽县| 裕民县| 土默特左旗| 苏州市| 岚皋县| 海阳市| 大兴区| 荥阳市| 茶陵县| 东乌珠穆沁旗| 湘潭县| 龙里县| 绥滨县| 宜宾市| 永胜县| 会泽县| 开远市| 江口县| 陆川县| 英超| 鹤壁市| 张家川|