手把手教你學python第十七講(模組匯入的相關知識和爬蟲的準備內含深淺拷貝)

NO IMAGE

圖片刷不出來請到https://www.bilibili.com/read/cv334079/和https://www.bilibili.com/read/cv334092?from=articleDetail

str和repr

參考了https://blog.csdn.net/sxingming/article/details/52065242

介紹正式內容之前,先來補充一個repr和str的不同點。以前我們用過__str__和__repr__我直接給一個例子複習一下

現在你們應該是可以看懂錯誤原因了,我不再多解釋了。下面是正確的做法

那麼我們來先看一下str和repr在python裡的幫助怎麼說的,str是一個類物件,repr是python的內建函式。

實際用一下

這裡我直接複製了,敬請體諒,因為其實之前我已經寫過一遍這篇文章了,但是沒有儲存,然後。。。所以我已經不想打字了。其實下面就是python官方文件的中文翻譯。

什麼直譯器呢?我們官網下的其實自帶cpython的直譯器,因為是用c語言編寫的,還有其它的比如用java編寫的jpython,所謂直譯器,其實就是執行.py檔案的這個一個東西。關於截圖裡說的一點浮點數,我嘗試了下,發現似乎並沒有什麼不同的地方。並且元組集合的結果也是一樣的。

這裡就再順帶提一下前面出現過的evalhttps://www.cnblogs.com/liu-shuai/p/6098246.html。你們就到這個網站直接去看看好吧。我是真的有點火氣。

str和repr對於字串結果的差異也體現出來了

簡單來說就像是把最外面的引號去掉的功能。下面簡單演示一下eval實現了str和list之間的轉化,其它的容器型別類似。直接用list是不行的,用eval是可以的。

模組匯入相關的知識

先來補充一個變數的搜尋順序,名稱空間和locals(),globals()幾個知識點。參考了https://blog.csdn.net/scelong/article/details/6977867和

https://www.cnblogs.com/wanxsb/archive/2013/05/07/3064783.html和

https://www.cnblogs.com/shanys/p/5887023.html

下面說的名字空間就是名稱空間

看幾個嘗試

說明locals()只有在函式裡面用有意義,在函式外面直接在模組裡面用locals()相當於globals(),globals()在一個模組裡的的位置不影響結果。而且globals()會遮蔽掉人為的改動,而globals()則不會,因為其實函式的locals()只是一個拷貝而已,並不會影響函式裡變數的值。

閉包也不是例外

如果哪個變數是global,那麼它將不會出現在函式的locals()裡

這個搜尋順序其實很好理解,我們先來看1,2的先後是怎麼體現的。

2和3的順序怎麼體現的呢?

我覺得如果你跟著我一直學到這裡,通過對比程式碼,你是可以理解的。

內建名字空間其實是可以看到的

這還並沒有截全。

什麼是模組,前面其實也說過。

我們去看個模組,還記得以前有個os模組嗎?我們來看一下

這只是擷取了一段程式碼。在安裝目錄的lib裡有很多模組的程式碼

匯入模組有三種方法,這三種前面都已經講過,這裡就簡單地演示一下

第一種方法我們適合用模組名比較短的時候,因為每次呼叫模組的屬性或者函式的時候都要打一邊模組名字。

我們看一下這種方式對於locals()的影響

看到這裡你也應該理解為什麼前面必須加上模組名了。這種方法不能也沒有必要匯入單個函式或者變數

至於報錯說的os不是一個pakage是什麼意思我們下面會說。還有一點,不知道你注意到沒有,前面是有寫到變數的。

第二種方法還可以用萬用字元*來匯入所有的函式和變數,我們來看一下對globals()的影響

匯入某個具體的函式或者變數,這個函式名或者變數會直接出現在globals()裡面,而用萬用字元*匯入會把模組裡所有的函式和變數加到globals()裡面

不推薦使用,因為很可能兩個模組的屬性或者函式名有重疊的,就會發生取代現象。比如假如我們有下面兩個模組a,b都有t函式

用方法二匯入後面的就會取代前面的,這就是globals()裡的變數名字重合了,而且第二種方法是不可以用第一種模組的呼叫格式的。

如果有一些隱私變數,你不想被from a import*匯入,你可以在變數或者函式前面加一個_,舉個例子

可以看出來這種隱藏方法只能應付from my import*這種格式,其它的方式都還是可以呼叫到的。看一下globals()就能理解為什麼了

第二種方法可能會導致迴圈匯入問題,例子

先後執行a.py,b.py

這是為什麼呢?

我們先來看一下from a import x到底是什麼一個過程

雖然僅僅是匯入了x,但是還是執行了整個a.py然後我們來分析一下,執行b.py的時候,第一行是from a import x,然後就跳到a.py,又遇到第一行from b import y,又跳到b.py又遇到第一行from a import x,就是一個無限迴圈。但是python開發人員應該是有自己的一套處理這種問題的辦法,有內建的異常處理,從而這種無限迴圈停在和起始相同的地方。開始是from a import x,結束也是在from a import x。

為了這裡稍微理解深入一點,我做了以下改動來看看效果。

改動1

b.py沒有改變。想一想執行a.py和b.py分別有什麼結果呢?

執行a.py為什麼會這樣呢?前三行是載入了x的,從a.py模組的locals()也能看出來(這裡的locals()和globals()沒什麼區別),第四行from b import y,CPU跳到了b.py,而第二行就是from a import x,我們又跳到了a.py。這裡是不是會有小問題?前面不是已經載入了x嗎?但是要注意我們執行的指令碼是a.py,在a.py的globals()裡有,但是在要匯入的模組b.py的globals()是沒有這個東西的,所以這裡仍然會跳到a.py,然後就出現了重複性匯入,在黑圈裡的語句一樣的嘛。

我們稍微來驗證一下,修改a.py和b.py如下

這裡有一個小細節不知道你們注意到沒有,為什麼b.py前後是*號?這代表你做了修改但是沒有儲存,你儲存一下就可以了,Ctrl S或者F5執行一下,執行自動儲存嘛,就沒有了。

執行一下a,py,結果很長哈,我們用Edit裡的Find來找’x’,最後結果只找到下面一個紅箭頭指的地方。

只是為了讓你信服b.py的globals()裡確實沒有’x’這個東西的。下面為了節省空間都把print(globals())去掉了。

那麼b.py是個怎麼樣的過程呢?第二行from a  import x跳到a.py,前三行已經把x匯入到了b.py的globals()裡了,到第四行from b import y,跳到b.py這時候就不用在第二行跳了,因為已經有了x了,於是就列印了離開b,而我們只從a的第四行跳過來的,又回去列印離開a,a又是從b.py的第二行跳過來的,跳回去執行結束列印離開b。過程就下面這樣,紅色序號是列印的順序。

下面這個小改動看著上面這個圖應該好分析。

前面也說了這種不建議使用,下面的改動就看看結果,就不展開分析了

改動二

分別執行a.py和b.py

看上去a好像是沒有問題的,但是還是有問題,所以不建議使用第二種方式

那麼第一種方法表現如何?我認為這裡python也是有機制的,如果a.py裡出現了import  b,那麼後來再遇到import b應該就不會再跳轉了,我們先來試驗一下這個特性對不對。

果然如此啊,因為進入b和離開b都只列印了一次。

迴圈巢狀匯入的問題是解決了,但是,還是有問題的,

所以還是別隨便迴圈巢狀匯入,可能會出現各種問題。當然這些問題不是不能解決的,下面會有解決的辦法。

第三種就是用as 後面的新名字代替前面的模組名而已,模組名很長的時候可以考慮,但是新名字別亂取,可能會和別的模組重名的

除了要說三種匯入方法之外呢,還有以下三點內容

下面參考了https://www.zhihu.com/question/49136398。和http://blog.konghy.cn/2017/04/24/python-entry-program/。

下面這段話我們看一看就好

如果你學過c語言,應該會理解得更快,因為mian(){}就是一個程式入口。下面這段我們要理解一下

我們先來看一下__name__,我們新啟動一次IDLE之後,沒有執行任何指令碼也就是.py檔案時

我們執行下面這樣一個指令碼檔案的時候。

並且是不會顯示隱藏的變數_b和_m的。雖然呼叫是正常的,只是不顯示,如果不顯示別人也不知道有這些隱藏的東西,就比較隱私嘛

如果不是直接執行a.py檔案而是把它作為一個模組匯入的時候

列印的就是my了,也就是檔案的名字(不包含副檔名)。也就是說

我們來稍微看一下前面的迴圈巢狀匯入會是怎樣的一個情況。

執行a.py

用一張簡單的圖來說就是,紅色是前向的順序,黃色是回去的順序。可以發現在a.py裡執行到import b的時候,就跳到了b.py,然後又跳到a.py裡執行,雖然我們執行的是a.py,但是在這個時候,a.py是在b.py裡匯入的,所以print(__name__)就是a,然後回到b.py裡,列印的__name__是b,這很好理解,然後回到a.py裡,這是我們執行的那個a.py。所以列印的是__main__。

這有什麼用呢?至少我們上面出現的錯誤,我在a里加這麼一句。

就不會報錯了,如果你看懂了上面的軌跡,你應該知道為什麼不會報錯了而且也知道為什麼上面會報錯。在b.py里加上一個if __name__=’__main__’也有一樣的效果,原理一樣的嘛,就不展示了。__name__=’__mian__’通常的用處是拿來做測試,比如說

在執行my.py的時候我們希望這個測試成功列印出來。但是當它作為模組被匯入的時候不希望測試的結果列印出來。那麼我們就可以用__name__==’__main__’。

當然這個__name__其實是可以改的。可以改為已經有的指令碼名字,但是改這個沒有太大意義。

你可以這麼皮一下,貌似會有用處。

下一個問題搜尋路徑。什麼是搜尋路徑呢?就類似於上面說過的變數和函式的搜尋順序,如果在路徑裡,當然路徑是有順序的,都沒有找到的話,就會報錯。

比如我現在在桌面有一個my.py

我能成功匯入嗎?

出錯了。因為不在sys.path裡面,我們可以新增進去

然後我們來看一下搜尋順序是怎麼樣的。我新建立一個同名的檔案,位置如標題欄顯示的

注意這裡要先Restart一下,因為你新建了一個檔案,如果沒有執行過它,IDLE

還沒有新增它

可以看到列表搜尋的順序應該是從前往後搜尋,我就來皮一下

這個sys.path可以改變,你就可以自己改變搜尋順序了,你可以對sys,path為所欲為

。只是需要RESTART一下哦。但是注意一點,你對sys.path的任何改動都不會影響內建的開發人員編寫的內部模組,下面並不影響os的匯入,但是影響到了我們自己新增的模組my的匯入。

並且呢

下面就是包(package)的相關內容,包只是為了管理指令碼檔案方便一點,分級好一點

下面來演示一下,__init_.py是python規定,而且你的包必須在sys.path裡面,不然是沒有辦法找到的

匯入的格式是資料夾的名字.指令碼的名字。你可以直接import 資料夾,也可以from 資料夾 import 指令碼名,上面的格式對於包來說都是適用的。並且這個資料夾名字其實是指向資料夾裡的__init__.py的。也就是說我們可以這麼幹

但是不可以這麼幹 ,因為m.a這個.前面首先必須是一個包,後面必須是一個.py檔案

匯入包的變數和函式其實就是匯入__init__.py裡的變數和函式。匯入成功以後,就會自動生成一個緩衝的資料夾。

我們刪除掉__init__.py可想而知後果是什麼。因為這個包的名字就指向__init__.py,沒了它,當然是會報錯的。但是其實不是這樣的,你把__init__刪掉它不會報錯,但是m就只是個namesapce,並且沒辦法匯入資料夾裡面的模組,就和沒用處一樣。匯入不存在的資料夾是會直接報錯。如果你不想每次都RESTART都匯入一邊搜尋路徑,最好還是放在原來的python預設路徑裡面,比如上面的site-packages。

__init__.py檔案就是告訴python你這個東西是個包而不是普通的資料夾。

下面是一些瑣碎知識

學習模組的方法的一些瑣碎知識

我們有三種方法檢視python的幫助。

第一種

調出,右邊這幾個藍字是什麼意思,我不解釋了,程式設計師基本必須會英語,計算機軟體專業還要學日語呢

你可以在索引裡面搜

第二種是檢視__doc__也就是文件。用print()出來是因為好看一些

第三種就是help,我就不演示了。

下面這個是收集第三方模組的一個網站,你也可以自己寫一個模組發上去

你可能經常聽到

說白了就是python社群開發的一些規範。還有API會經常聽到

最後要說的是__all__和__file__,不一定所有模組都有__all__,有的話__all__裡顯示的就是開發者希望你呼叫的變數或者函式,雖然不在__all__裡也可以呼叫,但是其實開發者希望你呼叫的就是在__all__裡,如果你寫一個模組,提供呼叫的介面最好都寫在__all__裡,__file__比較簡單了,就是返回原始碼檔案的位置。

我們就去原始檔裡看看__all__和__file__究竟是什麼

並沒有找到__file__在哪裡,這估計涉及python自帶的機制了。

最後,還是一些練習,基礎很重要的,基礎不會,下一講的爬蟲還是很難的。

練習(爬蟲之前先簡單複習一下,還有就是提醒一些易錯的地方)

0.定義一個常量模組,給我們呼叫。什麼是常量呢?最直接的就是π啊,e啊,還有尤拉常數C啊,等這些不需要改變的數學常量。為了鋪墊,我們先來介紹一個sys.modules

後面還有很多,這裡面記錄的是很多內建的模組是自動匯入的。下面你們先自己試著寫寫,要求是你自己一旦傳入一個引數,這個類就不可以被改變了。大家發揮自己想象力,創造屬於自己的常量吧。

python無處不物件,模組也是一個物件,我們上面sys.modules[__name__]=constant()。

下面的const.pi就相當於constant().pi了。

我們當然可以不這麼做。上面就是強行用一個sys.modules,233。

這裡的程式碼是有根據的哦,原來的時候很多人都建議把6.18作為圓周率,這樣圓的周長就是π*r了,尤拉也是有的時候π=6.28,有時候π=3.14。後來尤拉有一本書裡寫了π=3.14,然後某本課本上用了這個,後來就廣泛流傳了。

第0道應該沒什麼難度,

第一道涉及到以前沒有講過的一個複數的類。

python裡用了工程裡常用的虛數單位j或者J,而不是數學裡的i。

第二題帶我們複習了一下匿名函式

第三題很簡單。

第四題就是繼承的一個函式的覆蓋問題而已,也不難。

五實際上就是很久以前說過的一個關於標籤的問題。看了下面你應該能回憶起來

a,b是兩個標籤,第一行是把a這個標籤貼到了1上,第二行把b也貼在了1上,第三行把b從1上撕下來貼到3上,和a沒有什麼關係。

這裡就必須要和下面再區別一次了,因為太重要了

首先要明白列表是容器型別,然後b=a是把a,b指向了同一個地址空間。b[0]=0是改變地址空間的內容,而沒有改變b標籤指向的地址空間,這裡就可以說是將其中某一個電容放電了,於是1就變成了0,由於a,b指向同一地址空間,所以a也跟著變。並且是可以改變內容的,可以看到改變內容前後的id都是不變的,還是在!7768536這個地址空間。

這種b=a[:]或者用b=a.copy()本身a和b就指向不同的地址空間。所以b改變和a是沒有關係的。

這道題就是複習一下filter還有集合而已,本身並沒有很難的地方。

學到這裡我覺得你應該可以看懂下面呢英文了

就是把iterable作為引數傳進functio裡,只返回結果是True的嘛。其實這道題並沒有用到集合的唯一性。

就是複習一下

就是把後面作為引數,一個一個傳進去函式作用而已。這裡再提醒一點

lambda後面是不能有return的。

這道沒什麼說的

只要你前面都看了這裡不會錯。

並沒有什麼,只是注意{}是空字典不是空集合。

只是考了一個收集引數是元組而已。

也許你注意到了題目裡其實是python2裡print的寫法。

這道題我覺得蠻容易錯的。因為你看到dict2=dict1.copy()(其實這是一種淺拷貝,下面的題會說)裡對吧,但是卻忽視了集合的元素是列表這個容器型別啊。dict[‘1’][0]=5就是把list的第一個元素改為5,你要注意dict1和dict2裡key=’1’對應的value都是list1,那結果自然是10咯。

這道題涉及到一個copy模組的深拷貝。我以前也說過,但是應該是有錯誤的,以這裡的為準

,你不記得更好嘛,就看這裡就行了。

翻譯一下就是說淺拷貝和深拷貝的區別只有在作用在複合物件(包含其它物件的物件,比如列表和類的例項化物件)才有實質性差別。一個淺拷貝建立了一個新的複合物件(在可能的範圍被內)把原始物件的內容,比如說列表的元素,插入到這個新的複合物件裡。深拷貝建立了一個新的複合物件然後遞迴地把原來物件內容的拷貝插入到新的複合物件裡。

參考了https://blog.csdn.net/u014745194/article/details/70271868。話說的再明白一點,

淺拷貝就是把最外圍的物件複製了一個,換了一個id,裡面的內容我只是新建了一個地址指標指向它們而已。而深拷貝是不只是最外圍的物件新建一個id,裡面的內容我都新建了id,可見覆制的程度更深,所以叫深拷貝。

下面舉例子來區別=賦值號,分片,copy()和deepcopy()

賦值號

我覺得賦值既不是深拷貝也不是淺拷貝,因為我們看到最外層的物件的id都一樣,說明並沒有開闢記憶體空間,只是把c標籤貼在b上了,我稱之為比淺拷貝還淺。物件可不可變不影響結果的。

分片和內建方法copy()

分片才是真正的淺拷貝。下面我就不展示不可變物件了,我自己實驗過結果是一樣的。

內建的copy()也是一種貨真價實的淺拷貝。只是不是所有物件都有內建的copy()方法。

可變物件列表,字典,集合都是有這個內建方法的,不可變物件元組,字串,frozenset沒有。但是我們還有copy.copy()

copy.copy和copy.deepcopy()

對於深拷貝,物件可變不可變是有很大影響的。先看元組

再看列表

這裡我說一下id返回的值是一個通識符,不是地址,但是它在每次執行IDLE的時候是唯一的,和地址對應關係是唯一的,所以我們看id來看地址是否一樣是可以的。