-
1 # 初九未成
-
2 # 千鋒大前端酷炫世界
函式
一.引言
a) 概念:函式是完成指定或者特定任務的一組程式碼。在面向物件程式設計的類中函式通常被稱為方法。不同的函式在程式中會扮演不同的角色完成不同的功能。
b) 作用:
i. 函式的使用可以提高程式碼的複用性,省去重複程式碼的編寫,提升程式程式碼重複利用率。
ii. 函式能封裝內部的實現,保護內部的資料。通常,我們可以將函式看做成一個“黑盒子”。往往函式的使用者並不是函式的編寫者,函式的使用者並不需要對函式內部實現和行為進行考慮,可以將精力更多投入到自身業務邏輯的設計中。只有函式的編寫者才需要考慮函式內部的實現細節,如何暴露對外的介面,返回什麼樣的資料值,也就是API的設計。
iii. 提高程式的可讀性。使得程式模組化。從一盤散散沙變成整齊佇列。
iv. 提高程式的可維護性。案例:列印一個不同字元組成的分隔符。
二.函式基礎
a) 可以將函式抽象成現實生活中的工具。工具需要先製作出來才可以使用。函式也是一樣的需要先進行函式的定義,然後方可進行函式呼叫。
b) 函式定義語法結構:
def 函式名(引數):
# 內部程式碼
return 表示式
c) Return:return不是必須要寫的,如果需要則寫,不需要可不寫。Return後面什麼都不跟表示return None。一旦函式執行過程中遇到了return語句,那麼函式中return後面的所有語句將不會執行,函式執行結束。
d) 函式呼叫:
i. 語法結構:函式名(引數值)
ii. 注意事項:
1. 引數之前使用逗號隔開
2. 由於python動態語言的特點,函式呼叫時填寫的引數,python不會對引數型別進行檢查,如果函式呼叫中的引數不符合函式內部執行機制的話,會報錯。
e) 引數:現實函式和呼叫者之間的互動
i. 函式-》工具:使用ATM取錢時需要傳入密碼資料。
ii. 實參和形參的概念。
iii. 函式呼叫時,實參會傳值給形參
1. 注意:
a) Python的函式引數傳遞實際上傳遞的是實參的地址
b) Python中的引數型別分為可變資料型別和不可變資料型別。
2. Test:使用可變資料型別和不可變資料型別的資料作為引數
a = 1
def func(a):
print("在函式內部修改之前,變數a的記憶體地址為: %s" % id(a))
a = 2
print("在函式內部修改之後,變數a的記憶體地址為: %s" % id(a))
print("函式內部的a為: %s" % a)
print("呼叫函式之前,變數a的記憶體地址為: %s" % id(a))
func(a)
print("函式外部的a為:%s" % a)
列印結果為:
呼叫函式之前,變數a的記憶體地址為: 1401140288
在函式內部修改之前,變數a的記憶體地址為: 1401140288
在函式內部修改之後,變數a的記憶體地址為: 1401140320
函式內部的a為: 2
函式外部的a為:1
解釋:作為引數,a被傳入函式時,將數字物件1的地址傳遞給了函式內部的a。執行第一句內部程式碼時,此時內部的a和外面的a其實是一個東西,因此打印出了同樣的記憶體地址。而當a=2被執行後,由於整數是不可變的資料型別,所以建立了一個新的內部變數a,並賦值2,將數字物件2的記憶體地址賦給變數a。我們知道,首先,賦值語句具有建立新變數的功能。
剛才說的是不可變型別引數,如果是可變型別的,比如列表呢?
a = [1, 2, 3]
def func(b):
print("在函式內部修改之前,變數b的記憶體地址為: %s" % id(b))
b.append(4)
print("在函式內部修改之後,變數b的記憶體地址為: %s" % id(b))
print("函式內部的b為: %s" % b)
print("呼叫函式之前,變數a的記憶體地址為: %s" % id(a))
func(a)
print("函式外部的a為:%s" % a)
執行結果是:
呼叫函式之前,變數a的記憶體地址為: 34875720
在函式內部修改之前,變數b的記憶體地址為: 34875720
在函式內部修改之後,變數b的記憶體地址為: 34875720
函式內部的b為: [1, 2, 3, 4]
函式外部的a為:[1, 2, 3, 4]
三.引數型別:python函式的引數定義靈活度很大,可以定義位置引數,預設引數,動態引數,關鍵字引數等。
a) 位置引數
i. 概念:也叫做必傳引數,在函式呼叫時必須明確提供的引數,切引數順序個數必須和形參保持一致。但是如果在函式呼叫的時候給實參指定引數名,那麼位置引數的順序可以不同。
b) 預設引數:如果給某個引數提供一個預設值,該引數就是預設引數。在函式呼叫時,可以給預設引數傳遞一個值,也可以使用預設值。
i. Test:
def power(x, n = 2):
return x**n
ret1 = power(10) # 使用預設的引數值n=2
ret2 = power(10, 4) # 將4傳給n,實際計算10**4的值
ii. 使用預設引數的注意事項:
1. 預設引數必須在順序引數後面
2. 預設引數儘量指向不變的物件
a) Test:
下面是國內某上市網際網路公司Python面試真題:
def func(a=[]):
a.append("A")
return a
print(func())
print(func())
print(func())
不要上機測試,僅憑程式碼,你能說出列印的結果嗎?
很多同學可能會說,這還不簡單,肯定是下面的結果啊:
["A"]
["A"]
["A"]
真的是這樣嗎?錯了!真正的結果是:
["A"]
["A", "A"]
["A", "A", "A"]
Why?為什麼會這樣?
因為Python函式體在被讀入記憶體的時候,預設引數a指向的空列表物件就會被建立,並放在記憶體裡了。因為預設引數a本身也是一個變數,儲存了指向物件[]的地址。每次呼叫該函式,往a指向的列表裡新增一個A。a沒有變,始終儲存的是指向列表的地址,變的是列表內的資料!我們可以測試一下:
def func(a=[]):
print("函式內部a的地址為:%s" % id(a))
a.append("A")
return a
b = func()
print("此時b的值為:%s" % b)
print("函式外部b的地址為:%s" % id(b))
print("-------------")
c = func()
print("此時c的值為:%s" % c)
print("函式外部c的地址為:%s" % id(c))
print("-------------")
d = func()
print("此時d的值為:%s" % d)
print("函式外部d的地址為:%s" % id(d))
列印結果是:
函式內部a的地址為:39287880
此時b的值為:["A"]
函式外部b的地址為:39287880
-------------
函式內部a的地址為:39287880
此時c的值為:["A", "A"]
函式外部c的地址為:39287880
-------------
函式內部a的地址為:39287880
此時d的值為:["A", "A", "A"]
函式外部d的地址為:39287880
那麼如何避免這個問題呢?
使用不可變的資料型別作為預設值!
def func(a=None):
# 注意下面的if語句
if a is None:
a = []
a.append("A")
return a
print(func())
print(func())
print(func())
將預設引數a設定為一個類似None,數字或字串之類的不可變物件。在函式內部,將它轉換為可變的型別,比如空列表。這樣一來,不管呼叫多少次,執行結果都是["A"]了。
iii. 動態引數
1. 概念:函式呼叫時傳入的引數可以是動態數量的,可以為任意個,甚至是0個。
2. 分類:重點是*的個數,*後的名字任意。動態引數必須放在所有位置引數和動態引數後面。
a) *args:可以接受任意個引數。呼叫時,會將實參打包成一個元組傳給形參。如果引數是一個列表,則會將整個列表當做一個引數傳入。
i. Test
def func(*args):
for arg in args:
print(arg)
func("a", "b", "c")
li = [1, 2, 3]
func(li)
ii. 上述案例中,我們本意是將li這個列表中的元素作為引數進行傳值,但是實際卻是將列表本身作為一個整體進行了傳遞。如果想要將一個序列型別的物件(元組,列表,字串,字典)的元素依次作為引數進行傳遞,則可以在序列物件前加一個*即可。如果引數是字典,則會將字典所有的key逐一傳遞進去。
1. Test:
def func(*args):
for arg in args:
print(arg)
li = [1, 2, 3]
func(*li)
b) **kwargs:表示接受鍵值對的動態引數,數量任意。呼叫時,會將引數打包成一個字典。
i. Test:
def func(**kwargs):
for kwg in kwargs:
print(kwg, kwargs[kwg])
print(type(kwg))
func(k1="v1", k2=[0, 1, 2])
執行結果是:
k1 v1
k2 [0, 1, 2]
ii. 而如果我們這樣傳遞一個字典dic呢?我們希望字典內的鍵值對能夠像上面一樣被逐一傳入。
1. Test:
def func(**kwargs):
for kwg in kwargs:
print(kwg, kwargs[kwg])
dic = {
"k1": "v1",
"k2": "v2"
}
func(**dic)
iv. 關鍵字引數:關鍵字引數前面需要一個特殊分隔符*和位置引數及預設引數分隔開來。*後面的引數被視為關鍵字引數。在函式呼叫時,關鍵字引數必須傳入引數名。
1. Test:
def student(name, age, *, sex):
pass
student(name="jack", age=18, sex="male")
Test2:如果函式定義中已經有了一個*args引數,後面跟著的命名關鍵字引數就不再需要一個特殊分隔符*了。
def student(name, age=10, *args, sex, classroom, **kwargs):
pass
student(name="jack", age=18, sex="male", classroom="202", k1="v1")
總結:引數定義順序:位置引數,動態引數,關鍵字引數,動態引數
回覆列表
python爬蟲,我用最多的是框架Scrapy,其次便是beautiful soup,以及selenium、Requests庫等,最基礎的就是urllib和正則re了。
當然,我不知道題主為什麼要問內建函式,所謂內建函式,一般都是因為使用比較頻繁或是元操作,然後提前定義好,直接呼叫。這樣的內建函式,python裡面有很多,大概可以分為基本的資料操作、邏輯操作、集合操作、基本IO操作、反射操作、字串操作等。它們隨著python直譯器的執行而建立,在Python的程式中,你可以隨時呼叫這些函式,不需要定義。
仔細想來,我也不好說哪些內建函式就一定是為爬蟲而生的,它們只是在程式碼的某個環節小用了一下而已,主要還是靠爬蟲相關的包來支撐。
但如果追根溯源,題主可以去參考python的原始碼,現在python原始碼已經遷移到GitHub上了,不過由於是用C寫的,故看起來會比較費神。原始碼地址:https://github.com/python/cpython
下面就講講爬蟲常用到的一些模組包好了。
re、urllib、time標準庫time可以用來延時,以免爬蟲程式碼被封,而正則表示式re和urllib模組則是學習python爬蟲最基礎的,也是最重要的。
urllib模組提供了豐富的上層介面,使我們可以處理跟url相關的大多數事務,包括設定Headers、Proxy 、錯誤解析、Cookie 等,從而像讀取本地檔案一樣讀取www和ftp上的資料,包括文字、圖片和影片,同時也涉及到許多HTTP 協議相關知識。
而正則表示式相信學過高階語言的朋友都不會陌生了,當用urllib提取到目標頁面的資訊後,我們需要用正則表示式來匹配查詢,獲得最終的內容,然後進行下一步的處理。
這裡需要提一下,urllib模組在python2和python3中變化很大,上面是用python3寫的,學習的時候注意一下。
requests當然,如果你把urllib和re用熟了,再來學用requests庫的話,你會感覺眼前豁然開朗,它基於 urllib開發,比urllib用起來更簡單順手,函式功能更強大,是一個很實用的Python HTTP客戶端庫,在編寫爬蟲和測試伺服器響應資料時經常會用到。同時,requests 的設計哲學是以 PEP 20 的習語為中心開發的,所以它比 urllib 更加 Pythoner(如果你不知道什麼是pythoner,可以輸入程式碼:import this)。
有趣的是,現在requests的官方文件出中文版了,十分詳盡,言語風趣幽默:http://cn.python-requests.org/zh_CN/latest/index.html
Beautiful Soup和 lxml、Selenium和PhantomJS、PyQuery等從這裡開始,就要步入爬蟲真正的門檻了,上面的這些工具都可以同時學,體驗一下。同時,考慮到我們爬取的網頁內容可能有靜態的、動態的,甚至還有將資料壓縮的網站,除此之外,還涉及到網頁需要登入,登入需要驗證碼,驗證碼的難易程度,還有付費與免費資源的區別等等!這些都是在這一階段必須要考慮和解決的問題了。
Beautiful Soup 是解析網頁的一款神器。它可以從 HTML 或者 XML 檔案中提取資料;Xpath 也是一款神器。它是一款高效的、表達清晰簡單的分析語言。掌握它以後介意棄用正則表示式了。一般是使用瀏覽器的開發者工具加 lxml 庫。
PhantomJS是一款沒有介面的瀏覽器,Selenium便是瀏覽器驅動,他們倆配合使用,可以爬取那些動態載入的網頁,當然,測試的時候還是可以使用Selenium+Chrome的。
若是提到驗證碼識別,那涉及的就多了,不過,在爬蟲領域,你先需要了解的,也不算多,可以瞭解一下PIL+Tesseract,一個是影象處理,一個便是訓練和識別驗證碼的庫,這裡有很多難點,需要多查資料學習。
如果你對js熟悉,又來做爬蟲,那麼PyQuery對你來說就是最友好的了, 它是仿照 jQuery ,語法與 jQuery 幾乎完全相同,所以不用再去費心去記一些奇怪的方法了,這樣解析起網頁來就更得心應手了。
多執行緒threading和多程序muiltprocessing有人說, Python 的多執行緒是雞肋,不是真正意義上的多執行緒?但以我的親身實踐來看,開的執行緒達到10以上,甚至50,那肯定對效率是有很大提升的。
所以,到了這一步,你就把他用起來吧!別管那麼多。
終極利器Scrapy框架、PySpider框架等Scrapy 是一個功能非常強大的分散式爬蟲框架,學會了它,就可以不用重複造輪子,但基礎還是要一步一步來。
當然,爬蟲框架越來越多,如果非要做一個比較,pyspider上手簡單,操作簡便,它增加了 WEB 介面,寫爬蟲迅速,且集成了phantomjs,可以用來抓取js渲染的頁面。
而Scrapy自定義程度相對較高,比 PySpider更底層一些,適合學習研究,需要學習的相關知識多,不過自己拿來研究分散式和多執行緒等等是非常合適的。
最後,給大家推薦一個學習爬蟲的部落格
python爬蟲系列課程推薦:https://cuiqingcai.com/1052.html
祝君進步!新年快樂!