你所不知道的 Python 冷知識!(建議收藏)

Python程式設計時光發表於2018-09-03

首發於微信公眾號:Python程式設計時光
'
每週三更新五個冷知識,歡迎前往訂閱!

01. 省略號也是物件


... 這是省略號,在Python中,一切皆物件。它也不例外。

在 Python 中,它叫做 Ellipsis 。

在 Python 3 中你可以直接寫…來得到這玩意。

>>> ...
Ellipsis
>>> type(...)
<class 'ellipsis'>
複製程式碼

而在 2 中沒有…這個語法,只能直接寫Ellipsis來獲取。

>>> Ellipsis
Ellipsis
>>> type(Ellipsis)
<type 'ellipsis'>
>>>
複製程式碼

它轉為布林值時為真

>>> bool(...)
True
複製程式碼

最後,這東西是一個單例。

>>> id(...)
4362672336
>>> id(...)
4362672336
複製程式碼

這東西有啥用呢?據說它是Numpy的語法糖,不玩 Numpy 的人,可以說是沒啥用的。

在網上只看到這個 用 ... 代替 pass ,稍微有點用,但又不是必須使用的。

try:
    1/0
except ZeroDivisionError:
    ...
複製程式碼

02. 如何修改直譯器提示符


正常情況下,我們在 終端下 執行Python 命令是這樣的。

>>> for i in range(2):
...     print (i)
...
0
1
複製程式碼

你是否想過 >>>... 這兩個提示符也是可以修改的呢?

>>> import sys                      
>>> sys.ps1                         
'>>> '                              
>>> sys.ps2                         
'... '                              
>>>                                 
>>> sys.ps2 = '---------------- '                 
>>> sys.ps1 = 'Python程式設計時光>>>'       
Python程式設計時光>>>for i in range(2):     
----------------    print (i)                    
----------------                                 
0                                   
1                                   
複製程式碼

03. 增量賦值的效能更好


諸如 +=*= 這些運算子,叫做 增量賦值運算子。

這裡使用用 += 舉例,以下兩種寫法,在效果上是等價的。

# 第一種
a = 1 ; a += 1

# 第二種
a = 1; a = a + 1
複製程式碼

+= 其背後使用的魔法方法是 __iadd__,如果沒有實現這個方法則會退而求其次,使用 __add__ 。

這兩種寫法有什麼區別呢?

用列表舉例 a += b,使用 _add_ 的話就像是使用了a.extend(b),如果使用 _add_ 的話,則是 a = a+b,前者是直接在原列表上進行擴充套件,而後者是先從原列表中取出值,在一個新的列表中進行擴充套件,然後再將新的列表物件返回給變數,顯然後者的消耗要大些。

所以在能使用增量賦值的時候儘量使用它。

04. 奇怪的字串


示例一

# Python2.7
>>> a = "Hello_Python"
>>> id(a)
32045616
>>> id("Hello" + "_" + "Python")
32045616

# Python3.7
>>> a = "Hello_Python"
>>> id(a)
38764272
>>> id("Hello" + "_" + "Python")
32045616
複製程式碼

示例二

>>> a = "MING"
>>> b = "MING"
>>> a is b
True

# Python2.7
>>> a, b = "MING!", "MING!"
>>> a is b
True

# Python3.7
>>> a, b = "MING!", "MING!"
>>> a is b
False
複製程式碼

示例三

# Python2.7
>>> 'a' * 20 is 'aaaaaaaaaaaaaaaaaaaa'
True
>>> 'a' * 21 is 'aaaaaaaaaaaaaaaaaaaaa'
False

# Python3.7
>>> 'a' * 20 is 'aaaaaaaaaaaaaaaaaaaa'
True
>>> 'a' * 21 is 'aaaaaaaaaaaaaaaaaaaaa'
True
複製程式碼

05. and 和 or 的取值順序


and 和 or 是我們再熟悉不過的兩個邏輯運算子。而我們通常只用它來做判斷,很少用它來取值。

如果一個or表示式中所有值都為真,Python會選擇第一個值,而and表示式則會選擇第二個。

>>>(2 or 3) * (5 and 7)
14  # 2*7
複製程式碼

06. 預設引數最好不為可變物件


函式的引數分三種 - 可變引數 - 預設引數 - 關鍵字引數

這三者的具體區別,和使用方法在 廖雪峰的教程 裡會詳細的解釋。這裡就不搬運了。

今天要說的是,傳遞預設引數時,新手很容易踩雷的一個坑。

先來看一個示例

def func(item, item_list=[]):
    item_list.append(item)
    print(item_list)

func('iphone')
func('xiaomi', item_list=['oppo','vivo'])
func('huawei')
複製程式碼

在這裡,你可以暫停一下,思考一下會輸出什麼?

思考過後,你的答案是否和下面的一致呢

['iphone']
['oppo', 'vivo', 'xiaomi']
['iphone', 'huawei']
複製程式碼

如果是,那你可以跳過這部分內容,如果不是,請接著往下看,這裡來分析一下。

Python 中的 def 語句在每次執行的時候都初始化一個函式物件,這個函式物件就是我們要呼叫的函式,可以把它當成一個一般的物件,只不過這個物件擁有一個可執行的方法和部分屬性。

對於引數中提供了初始值的引數,由於 Python 中的函式引數傳遞的是物件,也可以認為是傳地址,在第一次初始化 def 的時候,會先生成這個可變物件的記憶體地址,然後將這個預設引數 item_list 會與這個記憶體地址繫結。在後面的函式呼叫中,如果呼叫方指定了新的預設值,就會將原來的預設值覆蓋。如果呼叫方沒有指定新的預設值,那就會使用原來的預設值。

個人理解的記憶方法,不代表官方

07. 訪問類中的私有方法


大家都知道,類中可供直接呼叫的方法,只有公有方法(protected型別的方法也可以,但是不建議)。也就是說,類的私有方法是無法直接呼叫的。

這裡先看一下例子

class Kls():
    def public(self):
        print('Hello public world!')
        
    def __private(self):
        print('Hello private world!')
        
    def call_private(self):
        self.__private()

ins = Kls()

# 呼叫公有方法,沒問題
ins.public()

# 直接呼叫私有方法,不行
ins.__private()

# 但你可以通過內部公有方法,進行代理
ins.call_private()
複製程式碼

既然都是方法,那我們真的沒有方法可以直接呼叫嗎?

當然有啦,只是建議你千萬不要這樣弄,這裡只是普及,讓你瞭解一下。

# 呼叫私有方法,以下兩種等價
ins._Kls__private()
ins.call_private()
複製程式碼

08. 類首字母不一定是大寫


在正常情況下,我們所編寫的所見到的程式碼,好像都默許了類名首字母大寫,而例項用小寫的這一準則。但這並不是強制性的,即使你反過來的也沒有關係。

但有一些內建的類,首字母都是小寫,而例項都是大寫。

比如 bool 是類名,而 True,False 是其例項; 比如 ellipsis是類名,Ellipsis是例項; 還有 int,string,float,list,tuple,dict等一系列資料型別都是類名,它們都是小寫。

09. 時有時無的切片異常


這是個簡單例子

my_list = [1, 2, 3, 4, 5]
print(my_list[5])
複製程式碼

執行一下,和我們預期的一樣,會丟擲索引異常。

Traceback (most recent call last):
  File "F:/Python Script/test.py", line 2, in <module>
    print(my_list[5])
IndexError: list index out of range
複製程式碼

但是今天要說的肯定不是這個,而是一個你可能會不知道的冷知識。

來看看,如下這種寫法就不會報索引異常,執行my_list[5:],會返回一個新list:[]。

my_list = [1, 2, 3]
print(my_list[5:])
複製程式碼

10. 哪些情況下不需要續行符


在寫程式碼時,為了程式碼的可讀性,程式碼的排版是尤為重要的。

為了實現高可讀性的程式碼,我們常常使用到的就是續行符 \

>>> a = 'talk is cheap,'\
...     'show me the code.'
>>>
>>> print(a)
talk is cheap,show me the code.
複製程式碼

那有哪些情況下,是不需要寫續行符的呢?

經過總結,在這些符號中間的程式碼換行可以省略掉續行符:[], () , {}

>>> my_list=[1,2,3,
...          4,5,6]

>>> my_tuple=(1,2,3,
...           4,5,6)

>>> my_dict={"name": "MING",
...          "gender": "male"}
複製程式碼

另外還有,在多行文字註釋中 ''' ,續行符也是可以不寫的。

>>> text = '''talk is cheap,
    ...           show me the code'''
複製程式碼

上面只舉了一些簡單的例子。

但你要學會舉一反三。一樣的,在以下這些場景也同樣適用

  • 類,和函式的定義。
  • 列表推導式,字典推導式,集合推導式,生成器表示式

11. Py2 也可以使用 print()


我相信應該有不少人,思維定式,覺得只有 Py3 才可以使用 print(),而 Py2 只能使用print ’’。

其實並不是這樣的。

在Python 2.6之前,只支援

print "hello"
複製程式碼

在Python 2.6和2.7中,可以支援如下三種

print "hello"
print("hello")
print ("hello")
複製程式碼

在Python3.x中,可以支援如下兩種

print("hello")
print ("hello")
複製程式碼

12. 兩次 return


我們都知道,try…finally… 語句的用法,不管try裡面是正常執行還是報異常,最終都能保證finally能夠執行。

同時,我們又知道,一個函式裡只要遇到 return 函式就會立馬結束。

基於以上這兩點,我們來看看這個例子,到底執行過程是怎麼樣的?

>>> def func():
...     try:
...         return 'try'
...     finally:
...         return 'finally'
...
>>> func()
'finally'
複製程式碼

驚奇的發現,在try 裡的return居然不起作用。

原因是,在try…finally…語句中,try中的return會被直接忽視,因為要保證 finally 能夠執行。

13. for 死迴圈


for 迴圈可以說是 基礎得不能再基礎的知識點了。

但是如果讓你用 for 寫一個死迴圈,你會寫嗎?(問題來自群友 陳**)

這是個開放性的問題,在往下看之前,建議你先嚐試自己思考,你會如何解答。

好了,如果你還沒有思路,那就來看一下 一個海外 MIT 群友的回答:

for i in iter(int, 1):pass
複製程式碼

是不是懵逼了。iter 還有這種用法?這為啥是個死迴圈?

這真的是個冷知識,關於這個知識點,你如果看中文網站,可能找不到相關資料。

還好你可以通過 IDE 看py原始碼裡的註釋內容,介紹了很詳細的使用方法。

原來iter有兩種使用方法,通常我們的認知是第一種,將一個列表轉化為一個迭代器。

而第二種方法,他接收一個 callable物件,和一個sentinel 引數。第一個物件會一直執行,直到它返回 sentinel 值才結束。

int 呢,這又是一個知識點,int 是一個內建方法。通過看註釋,可以看出它是有預設值0的。你可以在終端上輸入int() 看看是不是返回0。

你所不知道的 Python 冷知識!(建議收藏)

由於int() 永遠返回0,永遠返回不了1

你所不知道的 Python 冷知識!(建議收藏)
所以這個 for 迴圈會沒有終點。一直執行下去。

這些問題和答案都源自於群友的智慧。如果你也想加入我們的討論中,請到公眾號後臺,新增我個人微信。

14. 小整數池


先看例子。

>>> a = -6
>>> b = -6
>>> a is b
False

>>> a = 256
>>> b = 256
>>> a is b
True

>>> a = 257
>>> b = 257
>>> a is b
False

>>> a = 257; b = 257
>>> a is b
True
複製程式碼

為避免整數頻繁申請和銷燬記憶體空間,Python 定義了一個小整數池 [-5, 256] 這些整數物件是提前建立好的,不會被垃圾回收。

以上程式碼請在 終端Python環境下測試,如果你是在IDE中測試,並不是這樣的效果。

那最後一個示例,為啥又是True?

因為當你在同一行裡,同時給兩個變數賦同一值時,直譯器知道這個物件已經生成,那麼它就會引用到同一個物件。如果分成兩成的話,直譯器並不知道這個物件已經存在了,就會重新申請記憶體存放這個物件。

15. intern機制


字串型別作為Python中最常用的資料型別之一,Python直譯器為了提高字串使用的效率和使用效能,做了很多優化.

例如:Python直譯器中使用了 intern(字串駐留)的技術來提高字串效率,什麼是intern機制?就是同樣的字串物件僅僅會儲存一份,放在一個字串儲蓄池中,是共用的,當然,肯定不能改變,這也決定了字串必須是不可變物件。

>>> s1="hello"
>>> s2="hello"
>>> s1 is s2
True

# 如果有空格,預設不啟用intern機制
>>> s1="hell o"
>>> s2="hell o"
>>> s1 is s2
False

# 如果一個字串長度超過20個字元,不啟動intern機制
>>> s1 = "a" * 20
>>> s2 = "a" * 20
>>> s1 is s2
True

>>> s1 = "a" * 21
>>> s2 = "a" * 21
>>> s1 is s2
False

>>> s1 = "ab" * 10
>>> s2 = "ab" * 10
>>> s1 is s2
True

>>> s1 = "ab" * 11
>>> s2 = "ab" * 11
>>> s1 is s2
False
複製程式碼

參考文件



你所不知道的 Python 冷知識!(建議收藏)

相關文章