日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > python >内容正文

python

python3爬虫讲座ppt+demo

發(fā)布時間:2024/1/8 python 35 豆豆
生活随笔 收集整理的這篇文章主要介紹了 python3爬虫讲座ppt+demo 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

python3爬蟲講座ppt+demo(頂點網站自動下載小說、杭電學評教、杭電比賽信息爬取)

文件:590m.com/f/25127180-480831281-7c9e11
(訪問密碼:551685)

以下內容無關:

-------------------------------------------分割線---------------------------------------------

在開始本篇的內容前,我們先來思考幾個問題。

我們先來看一段簡單的代碼:
void func(int a) {
if (a > 100000000) return;

int arr[100] = {0}; func(a + 1);

}
你能看出這段代碼會有什么問題嗎?

我們在之前的文章《高性能高并發(fā)服務器是如何實現(xiàn)的》一中提到了一項關鍵技術——協(xié)程,你知道協(xié)程的本質是什么嗎?有的同學可能會說是用戶線程,那么什么是用戶態(tài)線程,這是怎么實現(xiàn)的?
函數運行起來后是什么樣子?
這個問題看似沒什么關聯(lián),但這背后有一樣東西你需要理解,這就是所謂的函數運行時棧,run time stack。

接下來我們就好好看看到底什么是函數運行時棧,為什么徹底理解函數運行時棧對程序員來說非常重要。

從進程、線程到函數調用
汽車在高速上行駛時有很多信息,像速度、位置等等,通過這些信息我們可以直觀的感受汽車的運行時狀態(tài)。

pexels-mike-945443
pexels-mike-945443
同樣的,程序在運行時也有很多信息,像有哪些程序正在運行、這些程序執(zhí)行到了哪里等等,通過這些信息我們可以直觀的感受系統(tǒng)中程序運行的狀態(tài)。

其中,我們創(chuàng)造了進程、線程這樣的概念來記錄有哪些程序正在運行,關于進程和線程的概念請參見《看完這篇還不懂進程和線程你來打我》。

進程和線程的運行體現(xiàn)在函數執(zhí)行上,函數的執(zhí)行除了函數內部執(zhí)行的順序執(zhí)行還有子函數調用的控制轉移以及子函數執(zhí)行完畢的返回。其中函數內部的順序執(zhí)行乏善可陳,重點是函數的調用。

因此接下來我們的視角將從宏觀的進程和線程拉近到微觀下的函數調用,重點來討論一下函數調用是怎樣實現(xiàn)的。

函數調用的活動軌跡:棧
玩過游戲的同學應該知道,有時你為了完成一項主線任務不得不去打一些支線的任務,支線任務中可能還有支線任務,當一個支線任務完成后退回到前一個支線任務,這是什么意思呢,舉個例子你就明白了。

假設主線任務西天取經A依賴支線任務收服孫悟空B和收服豬八戒C,也就是說收服孫悟空B和收服豬八戒C完成后才能繼續(xù)主線任務西天取經A;

支線任務收服孫悟空B依賴任務拿到緊箍咒D,只有當任務D完成后才能回到任務B;

整個任務的依賴關系如圖所示:

1603672619352
1603672619352
現(xiàn)在我們來模擬一下任務完成過程。

首先我們來到任務A,執(zhí)行主線任務:

1603672811135
1603672811135
執(zhí)行任務A的過程中我們發(fā)現(xiàn)任務A依賴任務B,這時我們暫停任務A去執(zhí)行任務B:

1603673078596
1603673078596
執(zhí)行任務B的時候,我們又發(fā)現(xiàn)依賴任務D:

1603673983874
1603673983874
執(zhí)行任務D的時候我們發(fā)現(xiàn)該任務不再依賴任何其它任務,因此C完成后我們可以會退到前一個任務,也就是B:

1603673078596
1603673078596
任務B除了依賴任務C外不再依賴其它任務,這樣任務B完成后就可以回到任務A:

1603672811135
1603672811135
現(xiàn)在我們回到了主線任務A,依賴的任務B執(zhí)行完成,接下來是任務C:

1603673950126
1603673950126
和任務D一樣,C不依賴任何其它其它任務,任務C完成后就可以再次回到任務A,再之后任務A執(zhí)行完畢,整個任務執(zhí)行完成。

讓我們來看一下整個任務的活動軌跡:

1603674440674
1603674440674
仔細觀察,實際上你會發(fā)現(xiàn)這是一個First In Last Out 的順序,天然適用于棧這種數據結構來處理。

再仔細看一下棧頂的軌跡,也就是A、B、D、B、A、C、A,實際上你會發(fā)現(xiàn)這里的軌跡就是任務依賴樹的遍歷過程,是不是很神奇,這也是為什么樹這種數據結構的遍歷除了可以用遞歸也可以用棧來實現(xiàn)的原因。

A box
函數調用也是同樣的道理,你把上面的ABCD換成函數ABCD,本質不變。

因此,現(xiàn)在我們知道了,使用棧這種結構就可以用來保存函數調用信息。

和游戲中的每個任務一樣,當函數在運行時每個函數也要有自己的一個“小盒子”,這個小盒子中保存了函數運行時的各種信息,這些小盒子通過棧這種結構組織起來,這個小盒子就被稱為棧幀,stack frames,也有的稱之為call stack,不管什么命名方式,總之,就是這里所說的小盒子,這個小盒子就是函數運行起來后占用的內存,這些小盒子構成了我們通常所說的棧區(qū)。關于棧區(qū)詳細的講解你可以參考《深入理解操作系統(tǒng):程序員應如何理解內存》一文。

那么函數調用時都有哪些信息呢?

函數調用與返回信息
我們知道當函數A調用函數B的時候,控制從A轉移到了B,所謂控制其實就是指CPU執(zhí)行屬于哪個函數的機器指令,CPU從開始執(zhí)行屬于函數A的指令切換到執(zhí)行屬于函數B的指令,我們就說控制從函數A轉移到了函數B。

控制從函數A轉移到函數B,那么我們需要有這樣兩個信息:

我從哪里來 (返回)

要到去哪里 (跳轉)

是不是很簡單,就好比你出去旅游,你需要知道去哪里,還需要記住回家的路。

函數調用也是同樣的道理。

當函數A調用函數B時,我們只要知道:

函數A對于的機器指令執(zhí)行到了哪里 (我從哪里來,返回)

函數B第一條機器指令所在的地址 (要到哪里去,跳轉)

有這兩條信息就足以讓CPU開始執(zhí)行函數B對應的機器指令,當函數B執(zhí)行完畢后跳轉回函數A。

那么這些信息是怎么獲取并保持的呢?

現(xiàn)在我們就可以打開這個小盒子,看看是怎么使用的了。

假設函數A調用函數B,如圖所示:

1603845345171
1603845345171
當前,CPU執(zhí)行函數A的機器指令,該指令的地址為0x400564,接下來CPU將執(zhí)行下一條機器指令也就是:

call 0x400540
這條機器指令是什么意思呢?

這條機器指令對應的就是我們在代碼中所寫的函數調用,注意call后有一條機器指令地址,注意觀察上圖你會看到,該地址就是函數B的第一條機器指令,從這條機器指令后CPU將跳轉到函數B。

現(xiàn)在我們已經解決了控制跳轉的“要到哪里去”問題,當函數B執(zhí)行完畢后怎么跳轉回來呢?

原來,call指令除了給出跳轉地址之外還有這樣一個作用,也就是把call指令的下一條指令的地址,也就是0x40056a push到函數A的棧幀中,如圖所示:

1603845893680
1603845893680
現(xiàn)在,函數A的小盒子變大了一些,因為裝入了返回地址:

1603846004468
1603846004468
現(xiàn)在CPU開始執(zhí)行函數B對應的機器指令,注意觀察,函數B也有一個屬于自己的小盒子(棧幀),可以往里面扔一些必要的信息。

1603846305325
1603846305325
如果函數B中又調用了其它函數呢?

道理和函數A調用函數B是一樣的。

讓我們來看一下函數B最后一條機器指令ret,這條機器指令的作用是告訴CPU跳轉到函數A保存在棧幀上的返回地址,這樣當函數B執(zhí)行完畢后就可以跳轉到函數A繼續(xù)執(zhí)行了。

至此,我們解決了控制轉移中“我從哪里來”的問題。

參數傳遞與返回值
函數調用與返回使得我們可以編寫函數,進行函數調用。但調用函數除了提供函數名稱之外還需要傳遞參數以及獲取返回值,那么這又是怎樣實現(xiàn)的呢?

在x86-64中,多數情況下參數的傳遞與獲取返回值是通過寄存器來實現(xiàn)的。

假設函數A調用了函數B,函數A將一些參數寫入相應的寄存器,當CPU執(zhí)行函數B時就可以從這些寄存器中獲取參數了。

同樣的,函數B也可以將返回值寫入寄存器,當函數B執(zhí)行結束后函數A從該寄存器中就可以讀取到返回值了。

我們知道寄存器的數量是有限的,當傳遞的參數個數多于寄存器的數量該怎么辦呢?

這時那個屬于函數的小盒子也就是棧幀又能發(fā)揮作用了。

原來,當參數個數多于寄存器數量時剩下的參數直接放到棧幀中,這樣被調函數就可以從前一個函數的棧幀中獲取到參數了。

現(xiàn)在棧幀的樣子又可以進一步豐富了,如圖所示:

1603948689593
1603948689593
從圖中我們可以看到,調用函數B時有部分參數放到了函數A的棧幀中,同時函數A棧幀的頂部依然保存的是返回地址。

局部變量
我們知道在函數內部定義的變量被稱為局部變量,這些變量在函數運行時被放在了哪里呢?

原來,這些變量同樣可以放在寄存器中,但是當局部變量的數量超過寄存器的時候這些變量就必須放到棧幀中了。

因此,我們的棧幀內容又一步豐富了。

1604018423586
1604018423586
細心的同學可能會有這樣的疑問,我們知道寄存器是共享資源可以被所有函數使用,既然可以將函數A的局部變量寫入寄存器,那么當函數A調用函數B時,函數B的局部變量也可以寫到寄存器,這樣的話當函數B執(zhí)行完畢回到函數A時寄存器的值已經被函數B修改過了,這樣會有問題吧。

這樣的確會有問題,因此我們在向寄存器中寫入局部變量之前,一定要先將寄存器中開始的值保存起來,當寄存器使用完畢后再恢復原值就可以了。

那么我們要將寄存器中的原始值保存在哪里呢?

有的同學可能已經猜到了,沒錯,依然是函數的棧幀中。

1604019378874
1604019378874
最終,我們的小盒子就變成了如圖所示的樣子,當寄存器使用完畢后根據棧幀中保存的初始值恢復其內容就可以了。

現(xiàn)在你應該知道函數在運行時到底是什么樣子了吧,以上就是問題3的答案。

Big Picture
需要再次強調的一點就是,上述討論的棧幀就位于我們常說的棧區(qū)。

棧區(qū),屬于進程地址空間的一部分,如圖所示,我們將棧區(qū)放大就是圖左邊的樣子。

1604020019183
1604020019183
關于棧區(qū)詳細的講解你可以參考《深入理解操作系統(tǒng):程序員應如何理解內存》這篇。

最后,讓我們回到文章開始的這段簡單代碼:

void func(int a) {
if (a > 100000000) return;

int arr[100] = {0}; func(a + 1);

}

void main(){
func(0);
}
想一想這段代碼會有什么問題?

總結
本章我們從幾個看似沒什么關聯(lián)的問題出發(fā),詳細講解了函數運行時棧是怎么一回事,為什么我們不能創(chuàng)建過多的局部變量。細心的同學會發(fā)現(xiàn)第2個問題我們沒有解答,這個問題講解放到下一篇,也就是協(xié)程中講解。

希望這篇文章能對大家理解函數運行時棧有所幫助。

總結

以上是生活随笔為你收集整理的python3爬虫讲座ppt+demo的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。