歡迎光臨
每天分享高質量文章

談談 Python 那些不為人知的冷知識

01

省略號也是物件


 

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

在 Python 中,它叫做 Ellipsis 。

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

>>> …
Ellipsis
>>> type(…)
<class ‘ellipsis‘>


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

>>> Ellipsis
Ellipsis
>>> type(Ellipsis)
‘ellipsis’>
>>>


它轉為布林值時為真

>>> bool(…)
True


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

>>> id(…)
4362672336
>>> id(…)
4362672336


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

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

try:
    1/0
except ZeroDivisionError:
    …


02

類的首字母不一定是大寫


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

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

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

03

增量賦值的效能更好


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

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

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

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


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

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

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

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

04

and 和or 的取值順序


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

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

>>>(2 or 3) * (5 and 7)
14  # 2*7


05

如何修改直譯器提示符


這個當做今天的一個小彩蛋吧。應該算是比較冷門的,估計知道的人很少了吧。

正常情況下,我們在 終端下 執行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


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

時有時無的切片異常


這是個簡單例子

my_list = [1, 2, 3, 4, 5]
print(my_list[5])


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

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


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

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

my_list = [1, 2, 3]
print(my_list[5:])


09

哪些情況下不需要續行符


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

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

>>> 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”’


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

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

  • 類,和函式的定義。

  • 串列推導式,字典推導式,集合推導式,生成器運算式。

10

Py2 也可以使用 print()


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

今天,小明要為 Py2 正名一次。

在Python 2.6之前,只支援

print “hello”


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

print “hello”
print(“hello”)
print (“hello”)


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

print(“hello”)
print (“hello”)


11

for 死迴圈


for 迴圈可以說是 基礎得不能再基礎的知識點了。但是如果讓你用 for 寫一個死迴圈,你會寫嗎?(問題來自群友 陳**)

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

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

for i in iter(int1):pass


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

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

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

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

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

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

由於int() 永遠傳回0,永遠傳回不了1,所以這個 for 迴圈會沒有終點。一直執行下去。

12

奇怪的字串


示例一

# 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


13

兩次return


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

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

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

>>def func():
…     try:
…         return ‘try’
…     finally:
…         return ‘finally’

>>> func()
‘finally’


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

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

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


16

 

互動式“_”運運算元


對於 _ ,我想很多人都非常熟悉。

給變數取名好艱難,用 _
懶得長長的變數名,用 _
無用的垃圾變數,用 _

以上,我們都很熟悉了,今天要介紹的是他在互動式中使用。

>>> 3 + 4
7
>>> _
7
>>> name=‘ming’
>>> name
‘ming’
>>> _
‘ming’


它可以傳回上一次的執行結果。

但是,如果是print函式打印出來的就不行了。

>>> 3 + 4
7
>>> _
7
>>> print(“ming”)
ming
>>> _
7


我自己寫了個例子,驗證了下,用__repr__輸出的內容可以被獲取到的。
首先,在我們的目錄下,寫一個檔案 ming.py。內容如下

# ming.py
class mytest():
    def __str__(self):
        return “hello”

    def __repr__(self):
        return “world”


然後在這個目錄下進入互動式環境。

>>> import ming
>>> mt=ming.mytest()
>>> mt
world
>>> print(mt)
hello
>>> _
world


知道這兩個魔法方法的人,一看就明白了。

 

17

 

優雅的反轉字串/串列


反轉序列並不難,但是如何做到最優雅呢?

先來看看,正常是如何反轉的。

最簡單的方法是使用串列自帶的reverse()方法。

>>> ml = [1,2,3,4,5]
>>> ml.reverse()
>>> ml
[54321]


但如果你要處理的是字串,reverse就無能為力了。你可以嘗試將其轉化成list,再reverse,然後再轉化成str。轉來轉去,也太麻煩了吧?需要這麼多行程式碼(後面三行是不能合併成一行的),一點都Pythonic。

mstr1 = ‘abc’
ml1 = list(mstr1)
ml1.reverse()
mstr2 = str(ml1)


對於字串還有一種稍微複雜一點的,是自定義遞迴函式來實現。

def my_reverse(str):
    if str == “”:
        return str
    else:
        return my_reverse(str[1:]) + str[0]


在這裡,介紹一種最優雅的反轉方式,使用切片,不管你是字串,還是串列,簡直通殺。

>>> mstr = ‘abc’
>>> ml = [1,2,3]
>>> mstr[::-1]
‘cba’
>>> ml[::-1]
[321]


 

18

 

改變遞迴次限制


上面才提到遞迴,大家都知道使用遞迴是有風險的,遞迴深度過深容易導致堆疊的上限溢位。如果你這字串太長啦,使用遞迴方式反轉,就會出現問題。

那到底,預設遞迴次數限制是多少呢?

可以使用sys這個庫來檢視

>>> import sys
>>> sys.getrecursionlimit()
1000


可以查,當然也可以自定義修改次數,退出即失效。不過友情提醒,這玩意還是不要輕易去碰,萬一導致系統崩潰了小明可不背鍋。

>>> sys.setrecursionlimit(2000)
>>> sys.getrecursionlimit()
2000


 

19

 

一行程式碼實現FTP伺服器


搭建FTP,或者是搭建網路檔案系統,這些方法都能夠實現Linux的目錄共享。但是FTP和網路檔案系統的功能都過於強大,因此它們都有一些不夠方便的地方。比如你想快速共享Linux系統的某個目錄給整個專案團隊,還想在一分鐘內做到,怎麼辦?很簡單,使用Python中的SimpleHTTPServer。

SimpleHTTPServer是Python 2自帶的一個模組,是Python的Web伺服器。它在Python 3已經合併到http.server模組中。具體例子如下,如不指定埠,則預設是8000埠。

# python2
python -m SimpleHTTPServer 8888

# python3
python3 -m http.server 8888


直接在瀏覽器訪問即可。

SimpleHTTPServer有一個特性,如果待共享的目錄下有index.html,那麼index.html檔案會被視為預設主頁;如果不存在index.html檔案,那麼就會顯示整個目錄串列。

20

 

讓你暈頭轉向的 else 用法


if else 用法可以說最基礎的語法運算式之一,但是今天不是講這個的,一定要講點不一樣的。

if else 早已爛大街,但可能有很多人都不曾見過 for else 和 try else 的用法。為什麼說它曾讓我暈頭轉向,因為它不像 if else 那麼直白,非黑即白,腦子經常要想一下才能才反應過來程式碼怎麼走。反正我是這樣的。

先來說說,for else

def check_item(source_list, target):
    for item in source_list:
        if item == target:
            print(“Exists!”)
            break

    else:
        print(“Does not exist”)


在往下看之前,你可以思考一下,什麼情況下才會走 else。是迴圈被 break,還是沒有break?

給幾個例子,你體會一下。

check_item([“apple”“huawei”“oppo”], “oppo”)
# Exists!

check_item([“apple”“huawei”“oppo”], “vivo”)
# Does not exist


可以看出,沒有被 break 的程式才會正常走else流程。

再來看看,try else 用法。

def test_try_else(attr1 = None):
    try:
        if attr1:
            pass
        else:
            raise
    except:
        print(“Exception occurred…”)
    else:
        print(“No Exception occurred…”)


同樣來幾個例子。當不傳引數時,就丟擲異常。

test_try_else()
# Exception occurred…

test_try_else(“ming”)
# No Exception occurred…


可以看出,沒有 try 裡面的程式碼塊沒有丟擲異常的,會正常走else。

總結一下,for else 和 try else 相同,只要程式碼正常走下去,不被 break,不丟擲異常,就可以走else。

-END-

贊(0)

分享創造快樂