Python解惑之:整數比較

2021-02-19 Python之禪

題圖:unsplash.com

在 Python 中一切都是對象,毫無例外整數也是對象,對象之間比較是否相等可以用 ==,也可以用 is。 ==和 is操作的區別是:

清楚 is和 ==的區別之後,對此也許你有可能會遇到下面的這些困惑,於是就有了這樣一篇文章,試圖把Python中一些隱晦的東西趴出來,希望對你有一定的幫助。我們先來看兩段代碼:

片段一:

>>> a = 256

>>> b = 256

>>> a == b

True

>>>

片段二:

>>> a = 256

>>> b = 256

>>> a is b

True

>>>

在交互式命令行執行上面兩段代碼,代碼片段一中的 a==b返回 True很好理解,因為兩個對象的值都是256,對於片段二, a is b也返回True,這說明a和b是指向同一個對象的,可以檢查一下他們的id值是否相等:

>>> id(a)

8213296

>>> id(b)

8213296

>>>

結果證明他倆的確是同一個對象,指向的是同一個內存地址。那是不是所有的整數對象只要兩個對象的值(內容)相等,它們就是同一個實例對象呢?換句話說,對於整數對象只要 ==返回 True, is操作也會返回 True嗎?帶著這個問題來看下面這兩段代碼:

片段一:

>>> a = 257

>>> b = 257

>>> a == b

True

>>>

片段二:

>>> a = 257

>>> b = 257

>>> a is b

False

>>>

對於257, a is b返回的竟然是False,結果可能在你的意料之中,也有可能出乎你的意料,但不管怎麼,我們還是要刨根問底,找出問題的真相。

解惑一

出於對性能的考慮,Python內部做了很多的優化工作,對於整數對象,Python把一些頻繁使用的整數對象緩存起來,保存到一個叫 small_ints的鍊表中,在Python的整個生命周期內,任何需要引用這些整數對象的地方,都不再重新創建新的對象,而是直接引用緩存中的對象。Python把這些可能頻繁使用的整數對象規定在範圍[-5, 256]之間的小對象放在 small_ints中,但凡是需要用些小整數時,就從這裡面取,不再去臨時創建新的對象。因為257不再小整數範圍內,因此儘管a和b的值是一樣,但是他們在Python內部卻是以兩個獨立的對象存在的,各自為政,互不幹涉。

弄明白第一個問題後,我們繼續在Python交互式命令行中寫一個函數,再來看下面這段代碼:

片段一:

>>> c = 257

>>> def foo():

...     a = 257

...     b = 257

...     print a is b

...     print a is c

...

>>> foo()

True

False

呃,什麼情況,是的,你沒看錯,片段一中的這段代碼 a、b 值都是257的情況下,出現了 a is b返回 True,而 a is c 返回的 False,a、b、c的值都為257,為什麼會出現不同的結果呢?這對於剛剛好不容易建立起來的認知就被徹底否決了嗎,那這段代碼中究竟發生了什麼?難道解惑一中的結論是錯誤的嗎?

解惑二

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. structure-of-a-program

為了弄清楚這個問題,我們有必要先理解程序代碼塊的概念。Python程序由代碼塊構成,代碼塊作為程序的一個最小基本單位來執行。一個模塊文件、一個函數體、一個類、交互式命令中的單行代碼都叫做一個代碼塊。在上面這段代碼中,由兩個代碼塊構成, c = 257作為一個代碼塊,函數 foo作為另外一個代碼塊。Python內部為了將性能進一步的提高,凡是在一個代碼塊中創建的整數對象,如果存在一個值與其相同的對象於該代碼塊中了,那麼就直接引用,否則創建一個新的對象出來。Python出於對性能的考慮,但凡是不可變對象,在同一個代碼塊中的對象,只有是值相同的對象,就不會重複創建,而是直接引用已經存在的對象。因此,不僅是整數對象,還有字符串對象也遵循同樣的原則。所以 a is b就理所當然的返回 True了,而 c和 a不在同一個代碼塊中,因此在Python內部創建了兩個值都是257的對象。為了驗證剛剛的結論,我們可以借用 dis模塊從字節碼的角度來看看這段代碼。

>>> import dis

>>> dis.dis(foo)

 2           0 LOAD_CONST               1 (257)

             3 STORE_FAST               0 (a)

 3           6 LOAD_CONST               1 (257)

             9 STORE_FAST               1 (b)

 4          12 LOAD_FAST                0 (a)

            15 LOAD_FAST                1 (b)

            18 COMPARE_OP               8 (is)

            21 PRINT_ITEM          

            22 PRINT_NEWLINE      

 5          23 LOAD_FAST                0 (a)

            26 LOAD_GLOBAL              0 (c)

            29 COMPARE_OP               8 (is)

            32 PRINT_ITEM          

            33 PRINT_NEWLINE      

            34 LOAD_CONST               0 (None)

            37 RETURN_VALUE

可以看出兩個257都是從常量池的同一個位置 co_consts[1]獲取的。

總結

一番長篇大論之後,得出兩點結論:1、小整數對象[-5,256]是全局解釋器範圍內被重複使用,永遠不會被GC回收。2、同一個代碼塊中的不可變對象,只要值是相等的就不會重複創建新的對象。似乎這些知識點對日常的工作一點忙也幫不上,因為你根本不會用 is來比較兩個整數對象的值是否相等。那為什麼還要拿出來討論呢?嗯,程式設計師學知識,不應該淺嘗輒止,要充分發揮死磕到底的精神。


相關焦點

  • 一日一技:Python裡面的//並不是做了除法以後取整
    但是如果你把其中一個數字換成浮點數,就會發現事情並沒有這麼簡單:import math>>> math.floor(-10/3.0)-4>>> -10//3.0-4.0當有一個數為浮點數的時候, //的結果也是浮點數,但是 math.floor的結果是整數。
  • 有趣且鮮為人知的 Python 特性,火了!
    項目地址為:https://github.com/leisurelicht/wtfpython-cn來體會一些難以理解和反人類直覺的Python特性吧!>>> print('wtfpython''')wtfpython>>> print("wtfpython""")wtfpython>>> # 下面的語句會拋出 `SyntaxError` 異常>>> # print('''wtfpython')>>&
  • Python學習:mac電腦安裝python教程
    與python2.7 共存2 下載安裝包進入官方安裝包下載頁面,https://www.python.org/downloads/mac-osx/找到合適的安裝包,基本上mac電腦都是64位的系統,因此選擇64位的安裝包進行下載
  • Python基礎模塊:日期與時間模塊@time+datetime
    終於,回家之後準備好好學習一下,從python基礎模塊開始,今天為大家準備的是python的日期與時間處理模塊time和datetime。目錄:1. time模塊1.1.(1)(6)t1 = t2 * i or t1 = i * t2乘以一個整數。運算後假如 i != 0 則 t1 // i == t2 必為真值。t1 = t2 * f or t1 = f * t2乘以一個浮點數,結果會被捨入到 timedelta 最接近的整數倍。精度使用四舍五偶入奇不入規則。f = t2 / t3總時間 t2 除以間隔單位 t3 (3)。返回一個 float 對象。
  • python黑知識:python本體
    講述python的實現本體,版本,構建時間,構建工具和構建參數python的實現有很多種,如果想研究一下它語言本身一些機制的實現,可能需要看原始碼,那麼,就需要找到相應的實現,分支和版本。目前使用的python實現,根據python實現存在有這幾種CPython, Stackless Python, MicroPython, CLPython, Cython, IronPython, Jython, Pyjs, PyPy, Numba, Shed Skin Nuitka ,可以說是讓人眼花繚亂。
  • python基礎學習教程:Python基礎語法
    Python 語言與 Perl,C 和 Java 等語言有許多相似之處。但是,也存在一些差異。在本章中我們將來學習 Python 的基礎語法,讓你快速學會 Python 編程。第一個 Python 程序交互式編程交互式編程不需要創建腳本文件,是通過 Python 解釋器的交互模式進來編寫代碼。
  • Python 教程:從零到大師
    https://pythoncaff.com/topics/104/python-tutorials-from-zero-to-master-suitable-for-experienced-developers首先, 什麼是Python?
  • 學習Python的利器:內置函數dir()和help()
    在Python中所有的一切都是對象,除了整數、實數、複數、字符串、列表、元組、字典、集合等等,還有range對象、enumerate對象、zip對象、filter對象、map對象等等,函數也是對象,類也是對象,模塊也是對象。這樣的話,dir()的用武之地就大了。
  • 《流暢的Python》微信抽獎程序
    請微信公眾號Python之美做《流暢的Python》這本書的活動時,圖靈的公眾號也同時做了一波,活動見 《流暢的Python》好在哪裡?
  • Python生成一維碼,二維碼
    我們的生活已完全離不開一維碼和二維碼,本文會簡單的介紹如果通過python的方法來生成它們
  • Python詞雲:Windows安裝Wordcloud報錯解決辦法
    首先,先看清楚你的python版本,以及搞清楚你的python是基於32位系統還是64位系統。具體操作方法:1、 在左下角搜索windows搜索框裡輸入cmd,打開命令行窗口;2、 輸入python;3、 這裡可以看到python的版本是3.8.2,基於32位系統。
  • Python 進階必備:圖像庫 pillow
    3.1 打開圖像文件>>> im = Image.open(r'D:\CSDN\python_counselor.jpg')>>> im.show() # show()方法會自動調用系統默認的圖像查看工具顯示圖像>>> im = Image.open(r'D:\CSDN\python_counselor.jpg
  • Python的數據可視化:對比7種工具包
    Python部落(python.freelycode.com)組織翻譯,禁止轉載,歡迎轉發。
  • Python炫技操作:花式導包的八種方法
    /os.pyc'>>>> myos.getcwd()'/home/wangbm'從 python 3 開始,內建的 reload 函數被移到了 imp 模塊中。語法如下:execfile(filename[, globals[, locals]])參數有這麼幾個:>>> execfile("/usr/lib64/python2.7/os.py")>>> >>> getcwd()'/home/wangbm'
  • Python可視化:Seaborn庫熱力圖使用進階
    前言在日常工作中,經常可以見到各種各種精美的熱力圖,熱力圖的應用非常廣泛
  • Python爬蟲實戰:爬取天氣數據的實例詳解
    在本篇文章裡小編給大家整理的是一篇關於python爬取天氣數據的實例詳解內容,有興趣的朋友們學習下。
  • Python 炫技操作:五種 Python 轉義表示法
    舉個例子>>> msg = "hello\013world\013hello\013python">>> print(msg)hello     world          hello               python>>> 是不是有點神奇
  • Python 炫技操作:模塊重載的五種方法
    >>> from foo import barsuccessful to be imported>>> from foo import bar>>>重載模塊方法一 如果你使用的 python2(記得前面在 foo 文件夾下加一個 __init__.py),有一個 reload 的方法可以直接使用