- 語言特性
- 1. 談談對 Python 和其他語言的區別
- 2. 簡述解釋型和編譯型程式語言
- 3. Python 的解釋器種類以及相關特點?
- 4. Python3 和 Python2 的區別?
- 5. Python3 和 Python2 中 int 和 long 區別?
- 6. xrange 和 range 的區別?
- 7. 什麼是 PEP8?
- 8. 了解 Python 之禪么?
- 9. 了解 DocStrings 么?
- 10. 了解類型註解么?
- 11. 例舉你知道 Python 對象的命名規範,例如方法或者類等
- 12. Python 中的注釋有幾種?
- 13. 如何優雅的給一個函數加註釋?
- 14. 如何給變數加註釋?
- 15. Python 程式碼縮進中是否支援 Tab 鍵和空格混用。
- 16. 是否可以在一句 import 中導入多個庫?
- 17. 在給 Py 文件命名的時候需要注意什麼?
- 18. 例舉幾個規範 Python 程式碼風格的工具
- 19. 列舉 Python 中的基本數據類型?
- 20. 如何區別可變數據類型和不可變數據類型
- 21. 將”hello world”轉換為首字母大寫”Hello World”
- 22. 如何檢測字元串中只含有數字?
- 23. 將字元串”ilovechina”進行反轉
- 24. Python 中的字元串格式化方式你知道哪些?
- 25. 有一個字元串開頭和末尾都有空格,比如「 adabdw 」,要求寫一個函數把這個字元串的前後空格都去掉。
- 26. 獲取字元串」123456「最後的兩個字元。
- 27. 一個編碼為 GBK 的字元串 S,要將其轉成 UTF-8 編碼的字元串,應如何操作?
- 28. (1)s=”info:xiaoZhang 33 shandong”,用正則切分字元串輸出[‘info’, ‘xiaoZhang’, ’33’, ‘shandong’]。(2)a = “你好 中國 “,去除多餘空格只留一個空格。
- 29. (1) 怎樣將字元串轉換為小寫。 (2) 單引號、雙引號、三引號的區別?
- 30. 已知 AList = [1,2,3,1,2],對 AList 列表元素去重,寫出具體過程。
- 31. 如何實現 “1,2,3” 變成 [“1″,”2″,”3”]
- 32. 給定兩個 list,A 和 B,找出相同元素和不同元素
- 33. [[1,2],[3,4],[5,6]] 一行程式碼展開該列表,得出 [1,2,3,4,5,6]
- 34. 合併列表 [1,5,7,9] 和 [2,2,6,8]
- 35. 如何打亂一個列表的元素?
- 36. 字典操作中 del 和 pop 有什麼區別
- 37. 按照字典的內的年齡排序
- 38. 請合併下面兩個字典 a = {“A”:1,”B”:2},b = {“C”:3,”D”:4}
- 39. 如何使用生成式的方式生成一個字典,寫一段功能程式碼。
- 40. 如何把元組 (“a”,”b”) 和元組 (1,2),變為字典 {“a”:1,”b”:2}
- 41. 下列字典對象鍵類型不正確的是?
- 42. 如何交換字典 {“A”:1,”B”:2}的鍵和值
- 43. Python 裡面如何實現 tuple 和 list 的轉換?
- 44. 我們知道對於列表可以使用切片操作進行部分元素的選擇,那麼如何對生成器類型的對象實現相同的功能呢?
- 45. 請將 [i for i in range(3)] 改成生成器
- 46. a=”hello” 和 b=”你好” 編碼成 bytes 類型
- 47. 下面的程式碼輸出結果是什麼?
- 48. 下面的程式碼輸出的結果是什麼?
- 49. Python 交換兩個變數的值
- 50. 在讀文件操作的時候會使用 read、readline 或者 readlines,簡述它們各自的作用
- 51. json 序列化時,可以處理的數據類型有哪些?如何訂製支援 datetime 類型?
- 52. json 序列化時,默認遇到中文會轉換成 unicode,如果想要保留中文怎麼辦?
- 53. 有兩個磁碟文件 A 和 B,各存放一行字母,要求把這兩個文件中的資訊合併(按字母順序排列),輸出到一個新文件 C 中。
- 54. 如果當前的日期為 20190530,要求寫一個函數輸出 N 天后的日期,(比如 N 為 2,則輸出 20190601)。
- 55. 寫一個函數,接收整數參數 n,返回一個函數,函數的功能是把函數的參數和 n 相乘並把結果返回。
- 56. 下面程式碼會存在什麼問題,如何改進?
- 57. 一行程式碼輸出 1-100 之間的所有偶數。
- 58. with 語句的作用,寫一段程式碼?
- 59. Python 字典和 json 字元串相互轉化方法
- 60. 請寫一個 Python 邏輯,計算一個文件中的大寫字母數量
- 61. 請寫一段 Python連接Mongo資料庫,然後的查詢程式碼。
- 62.說一說Redis的基本類型
- 63. 請寫一段 Python連接Redis資料庫的程式碼。
- 64. 請寫一段 Python連接Mysql資料庫的程式碼。
- 65.了解Redis的事務么
- 66.了解資料庫的三範式么?
- 67.了解分散式鎖么
- 68.用 Python 實現一個 Reids 的分散式鎖的功能
- 69.寫一段 Python 使用 mongo 資料庫創建索引的程式碼:
- 70. 函數裝飾器有什麼作用?請列舉說明?
- 71. Python 垃圾回收機制?
- 72. 魔法函數 _call_怎麼使用?
- 73. 如何判斷一個對象是函數還是方法?
- 74. @classmethod 和 @staticmethod 用法和區別
- 75. Python 中的介面如何實現?
- 76. Python 中的反射了解么?
- 77. metaclass 作用?以及應用場景?
- 78. hasattr()、getattr()、setattr() 的用法
- 79. 請列舉你知道的 Python 的魔法方法及用途。
- 80. 如何知道一個 Python 對象的類型?
- 81. Python 的傳參是傳值還是傳址?
- 82. Python 中的元類 (metaclass) 使用舉例
- 83. 簡述 any() 和 all() 方法
- 84. filter 方法求出列表所有奇數並構造新列表,a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
- 85. 什麼是猴子修補程式?
- 86. 在 Python 中是如何管理記憶體的?
- 87. 當退出 Python 時是否釋放所有記憶體分配?
- 88. (1)使用正則表達式匹配出<h1\>www.baidu.com中的地址(2)a=”張明 98 分”,用 re.sub,將 98 替換為 100
- 89. 正則表達式匹配中(.*)和(.*?)匹配區別?
- 90. 寫一段匹配郵箱的正則表達式
- 91. 解釋一下 Python 中 pass 語句的作用?
- 92. 簡述你對 input()函數的理解
- 93. Python 中的 is 和==
- 94. Python 中的作用域
- 95. 三元運算寫法和應用場景?
- 96. 了解 enumerate 么?
- 97. 列舉 5 個 Python 中的標準模組
- 98. 如何在函數中設置一個全局變數
- 99. pathlib 的用法舉例
- 100. Python 中的異常處理,寫一個簡單的應用場景
- 101. Python 中遞歸的最大次數,那如何突破呢?
- 102. 什麼是面向對象的 mro
- 103. isinstance 作用以及應用場景?
- 104. 什麼是斷言?應用場景?
- 105. lambda 表達式格式以及應用場景?
- 106. 新式類和舊式類的區別
- 107. dir()是幹什麼用的?
- 108. 一個包里有三個模組,demo1.py、demo2.py、demo3.py,但使用 from tools import *導入模組時,如何保證只有 demo1、demo3 被導入了。
- 109. 列舉 5 個 Python 中的異常類型以及其含義
- 110. copy 和 deepcopy 的區別是什麼?
- 111. 程式碼中經常遇到的*args, **kwargs 含義及用法。
- 112. Python 中會有函數或成員變數包含單下劃線前綴和結尾,和雙下劃線前綴結尾,區別是什麼?
- 113. w、a+、wb 文件寫入模式的區別
- 114. 舉例 sort 和 sorted 的區別
- 115. 什麼是負索引?
- 116. pprint 模組是幹什麼的?
- 117. 解釋一下 Python 中的賦值運算符
- 118. 解釋一下 Python 中的邏輯運算符
- 119. 講講 Python 中的位運算符
- 120. 在 Python 中如何使用多進位數字?
- 121. 怎樣聲明多個變數並賦值?
- 122. 已知:
- 123. 用 Python 實現一個二分查找的函數
- 124. Python 單例模式的實現方法
- 125. 使用 Python 實現一個斐波那契數列
- 126. 找出列表中的重複數字
- 127. 找出列表中的單個數字
- 128. 寫一個冒泡排序
- 129. 寫一個快速排序
- 130. 寫一個拓撲排序
- 131. Python 實現一個二進位計算
- 132. 有一組「+」和「-」符號,要求將「+」排到左邊,「-」排到右邊,寫出具體的實現方法。
- 133. 單鏈表反轉
- 134. 交叉鏈表求交點
- 135. 用隊列實現棧
- 136. 找出數據流的中位數
- 137. 二叉搜索樹中第 K 小的元素
- 138. 在 requests 模組中,requests.content 和 requests.text 什麼區別
- 139. 簡要寫一下 lxml 模組的使用方法框架
- 140. 說一說 scrapy 的工作流程
- 141. scrapy 的去重原理
- 142. scrapy 中間件有幾種類,你用過哪些中間件
- 143. 你寫爬蟲的時候都遇到過什麼?反爬蟲措施,你是怎麼解決的?
- 144. 為什麼會用到代理?
- 145. 代理失效了怎麼處理?
- 146. 列出你知道 header 的內容以及資訊
- 147. 說一說打開瀏覽器訪問 www.baidu.com 獲取到結果,整個流程。
- 148. 爬取速度過快出現了驗證碼怎麼處理
- 149. scrapy 和 scrapy-redis 有什麼區別?為什麼選擇 redis 資料庫?
- 150. 分散式爬蟲主要解決什麼問題
- 151. 寫爬蟲是用多進程好?還是多執行緒好? 為什麼?
- 152. 解析網頁的解析器使用最多的是哪幾個
- 153. 需要登錄的網頁,如何解決同時限制 ip,cookie,session(其中有一些是動態生成的)在不使用動態爬取的情況下?
- 154. 驗證碼的解決?
- 155. 使用最多的資料庫(mysql,mongodb,redis 等),對他的理解?
語言特性
1. 談談對 Python 和其他語言的區別
答:Python 是一門語法簡潔優美,功能強大無比,應用領域非常廣泛,具有強大完備的第三方庫,他是一門強類型的可移植、可擴展,可嵌入的解釋型程式語言,屬於動態語言。
拿 C 語言和 Python 比: Python 的第三方類庫比較齊全並且使用簡潔,很少程式碼就能實現一些功能,如果用 C 去實現相同的功能可能就比較複雜。但是對於速度來說 Python 的運行速度相較於 C 就比較慢了。所以有利的同時也有弊端,畢竟我們的學習成本降低了。
2. 簡述解釋型和編譯型程式語言
答:解釋型語言是在運行程式的時候才翻譯,每執行一次,要翻譯一次,效率較低。 編譯型就是直接編譯成機型可以執行的,只翻譯一次,所以效率相對來說較高。
3. Python 的解釋器種類以及相關特點?
答:
- CPython c 語言開發的,使用最廣的解釋器
- IPython 基於 cPython 之上的一個互動式計時器,交互方式增強功能和 cPython 一樣
- PyPy 目標是執行效率,採用 JIT 技術。對 Python 程式碼進行動態編譯,提高執行效率
- JPython 運行在 Java 上的解釋器,直接把 Python 程式碼編譯成 Java 位元組碼執行
- IronPython 運行在微軟 .NET 平台上的解釋器,把 Python 編譯成 . NET 的位元組碼。
4. Python3 和 Python2 的區別?
答: 這裡例舉 5 條
- print 在 Python3 中是函數必須加括弧,Python2 中 print 為 class。
- Python2 中使用 xrange,Python3 使用 range。
- Python2 中默認的字元串類型默認是 ASCII,Python3 中默認的字元串類型是 Unicode。
- Python2 中/的結果是整型,Python3 中是浮點類型。
- Python2 中聲明元類:_metaclass_ = MetaClass,Python3 中聲明元類:class newclass(metaclass=MetaClass):pass。
5. Python3 和 Python2 中 int 和 long 區別?
答:Python2 有 int 和 long 類型。int 類型最大值不能超過 sys.maxint,而且這個最大值是平台相關的。可以通過在數字的末尾附上一個L來定義長整型,顯然,它比 int 類型表示的數字範圍更大。在 Python3 里,只有一種整數類型 int,大多數情況下,和 Python2中的長整型類似。
6. xrange 和 range 的區別?
答:xrange 是在 Python2 中的用法,Python3 中只有 range xrange 用法與 range 完全相同,所不同的是生成的不是一個 list 對象,而是一個生成器。
編碼規範
7. 什麼是 PEP8?
答:PEP8 通常會聽別人提到,但是具體的指什麼內容呢,簡單介紹下。 《Python Enhancement Proposal #8》(8 號 Python 增強提案)又叫 PEP8,他針對的 Python 程式碼格式而編訂的風格指南。
8. 了解 Python 之禪么?
答:通過 import this 語句可以獲取其具體的內容。它告訴大家如何寫出高效整潔的程式碼。
9. 了解 DocStrings 么?
答:DocStrings 文檔字元串是一個重要工具,用於解釋文檔程式,幫助你的程式文檔更加簡單易懂。主要是解釋程式碼作用的。
10. 了解類型註解么?
答:PEP 484 引入了類型提示,這使得可以對 Python 程式碼進行靜態類型檢查。 在使用 Ide 的時候可以獲取到參數的類型,更方便傳入參數。使用格式如下
def foo(num: int) -> None:
print(f"接收到的數字是:{num}")
介紹下這個簡單例子,我們可以在函數的參數部分使用參數名+:+類型,來指定參數可以接受的類型,這裡的話就是 num 參數為 int 類型,然後後面->接的是返回值的類型。這裡返回值為 None,然後通過 fstring 格式化字元串輸出傳入的數字。
11. 例舉你知道 Python 對象的命名規範,例如方法或者類等
答:
類:總是使用首字母大寫單詞串,如 MyClass。內部類可以使用額外的前導下劃線。 變數:小寫,由下劃線連接各個單詞。方法名類似 常量:常量名所有字母大寫 等
12. Python 中的注釋有幾種?
答:總體來說分為兩種,單行注釋和多行注釋。
- 單行注釋在行首是
#
。 - 多行注釋可以使用三個單引號或三個雙引號,包括要注釋的內容。
13. 如何優雅的給一個函數加註釋?
答:可以使用 docstring 配合類型註解
14. 如何給變數加註釋?
答:可以通過變數名:類型的方式如下
a: str = "this is string type"
15. Python 程式碼縮進中是否支援 Tab 鍵和空格混用。
答:不允許 tab 鍵和空格鍵混用,這種現象在使用 sublime 的時候尤為明顯。
一般推薦使用 4 個空格替代 tab 鍵。
16. 是否可以在一句 import 中導入多個庫?
答:可以是可以,但是不推薦。因為一次導入多個模組可讀性不是很好,所以一行導入一個模組會比較好。同樣的盡量少用 from modulename import *,因為判斷某個函數或者屬性的來源有些困難,不方便調試,可讀性也降低了。
17. 在給 Py 文件命名的時候需要注意什麼?
答:給文件命名的時候不要和標準庫庫的一些模組重複,比如 abc。 另外要名字要有意義,不建議數字開頭或者中文命名。
18. 例舉幾個規範 Python 程式碼風格的工具
答:pylint 和 flake8
數據類型-字元串
19. 列舉 Python 中的基本數據類型?
答: Python3 中有六個標準的數據類型:字元串(String)、數字(Digit)、列表(List)、元組(Tuple)、集合(Sets)、字典(Dictionary)。
20. 如何區別可變數據類型和不可變數據類型
答: 從對象記憶體地址方向來說
- 可變數據類型:在記憶體地址不變的情況下,值可改變(列表和字典是可變類型,但是字典中的 key 值必須是不可變類型)
- 不可變數據類型:記憶體改變,值也跟著改變。(數字,字元串,布爾類型,都是不可變類型)可以通過 id() 方法進行記憶體地址的檢測。
21. 將”hello world”轉換為首字母大寫”Hello World”
答: 這個得看清題目是要求兩個單詞首字母都要大寫,如果只是第一個單詞首字母大小的話,只使用 capitalize 即可,但是這裡是兩個單詞,所以用下面的方法。
arr = "hello world".split(" ")
new_str = f"{arr[0].capitalize()} {arr[1].capitalize()}"
print(new_str)
後來評論中有朋友提到了下面的方法,這裡感謝這位朋友提醒。方案如下
"hello world".title()
非常簡單一句話搞定。
22. 如何檢測字元串中只含有數字?
答:可以通過 isdigit 方法,例子如下
s1 = "12223".isdigit()
print(s1)
s2 = "12223a".isdigit()
print(s2)
#結果如下:
#True
#False
23. 將字元串”ilovechina”進行反轉
答:
s1 = "ilovechina"[::-1]
print(s1)
24. Python 中的字元串格式化方式你知道哪些?
答:%s,format,fstring(Python3.6 開始才支援,現在推薦的寫法)
25. 有一個字元串開頭和末尾都有空格,比如「 adabdw 」,要求寫一個函數把這個字元串的前後空格都去掉。
答:因為題目要是寫一個函數所以我們不能直接使用 strip,不過我們可以把它封裝到函數啊
def strip_function(s1):
return s1.strip()
s1 = " adabdw "
print(strip_function(s1))
26. 獲取字元串」123456「最後的兩個字元。
答:切片使用的考察,最後兩個即開始索引是 -2,程式碼如下
a = "123456"
print(a[-2::])
27. 一個編碼為 GBK 的字元串 S,要將其轉成 UTF-8 編碼的字元串,應如何操作?
答:
a= "S".encode("gbk").decode("utf-8",'ignore')
print(a)
28. (1)s=”info:xiaoZhang 33 shandong”,用正則切分字元串輸出[‘info’, ‘xiaoZhang’, ’33’, ‘shandong’]。(2)a = “你好 中國 “,去除多餘空格只留一個空格。
答:
(1)我們需要根據冒號或者空格切分
import re
s = "info:xiaoZhang 33 shandong"
res = re.split(r":| ", s)
print(res)
(2)
s = "你好 中國 "
print(" ".join(s.split()))
29. (1) 怎樣將字元串轉換為小寫。 (2) 單引號、雙引號、三引號的區別?
答: (1) 使用字元串的 lower() 方法。
(2)單獨使用單引號和雙引號沒什麼區別,但是如果引號裡面還需要使用引號的時候,就需要這兩個配合使用了,然後說三引號,同樣的三引號也分為三單引號和三雙引號,兩個都可以聲名長的字元串時候使用,如果使用 docstring 就需要使用三雙引號。
數據類型 – 列表
30. 已知 AList = [1,2,3,1,2],對 AList 列表元素去重,寫出具體過程。
答:
list(set(AList))
31. 如何實現 “1,2,3” 變成 [“1″,”2″,”3”]
答:
s = "1,2,3"
print(s.split(","))
32. 給定兩個 list,A 和 B,找出相同元素和不同元素
答:
A、B 中相同元素:print(set(A)&set(B))
A、B 中不同元素:print(set(A)^set(B))
33. [[1,2],[3,4],[5,6]] 一行程式碼展開該列表,得出 [1,2,3,4,5,6]
答:
l = [[1,2],[3,4],[5,6]]
x=[j for i in l for j in i]
print(x)
34. 合併列表 [1,5,7,9] 和 [2,2,6,8]
答:使用 extend 和 + 都可以。
a = [1,5,7,9]
b = [2,2,6,8]
a.extend(b)
print(a)
35. 如何打亂一個列表的元素?
答:
import random
a = [1, 2, 3, 4, 5]
random.shuffle(a)
print(a)
數據類型 – 字典
36. 字典操作中 del 和 pop 有什麼區別
答:del 可以根據索引(元素所在位置)來刪除的,沒有返回值。 pop 可以根據索引彈出一個值,然後可以接收它的返回值。
37. 按照字典的內的年齡排序
d1 = [
{'name':'alice', 'age':38},
{'name':'bob', 'age':18},
{'name':'Carl', 'age':28},
]
答:
d1 = [
{'name': 'alice', 'age': 38},
{'name': 'bob', 'age': 18},
{'name': 'Carl', 'age': 28},
]
print(sorted(d1, key=lambda x:x["age"]))
38. 請合併下面兩個字典 a = {“A”:1,”B”:2},b = {“C”:3,”D”:4}
答: 合併字典方法很多,可以使用 a.update(b) 或者下面字典解包的方式
a = {"A":1,"B":2}
b = {"C":3,"D":4}
print({**a,**b})
39. 如何使用生成式的方式生成一個字典,寫一段功能程式碼。
答:
# 需求 3: 把字典的 key 和 value 值調換;
d = {'a':'1', 'b':'2'}
print({v:k for k,v in d.items()})
40. 如何把元組 (“a”,”b”) 和元組 (1,2),變為字典 {“a”:1,”b”:2}
答 zip 的使用,但是最後記得把 zip 對象再轉換為字典。
a = ("a", "b")
b = (1, 2)
print(dict(zip(a, b)))
數據類型 – 綜合
41. 下列字典對象鍵類型不正確的是?
A:{1:0,2:0,3:0}
B:{"a":0, "b":0, "c":0}
C: {(1,2):0, (2,3):0}
D: {[1,2]:0, [2,3]:0}
答:D 因為只有可 hash 的對象才能做字典的鍵,列表是可變類型不是可 hash 對象,所以不能用列表做為字典的鍵。
42. 如何交換字典 {“A”:1,”B”:2}的鍵和值
答:
s = {"A":1,"B":2}
#方法一:
dict_new = {value:key for key,value in s.items()}
# 方法二:
new_s= dict(zip(s.values(),s.keys()))
43. Python 裡面如何實現 tuple 和 list 的轉換?
答: Python 中的類型轉換,一般通過類型強轉即可完成 tuple 轉 list 是 list() 方法 list 轉 tuple 使用 tuple() 方法
44. 我們知道對於列表可以使用切片操作進行部分元素的選擇,那麼如何對生成器類型的對象實現相同的功能呢?
答: 這個題目考察了 Python 標準庫的 itertools 模快的掌握情況,該模組提供了操作生成器的一些方法。 對於生成器類型我們使用 islice 方法來實現切片的功能。例子如下
from itertools import islice
gen = iter(range(10)) #iter()函數用來生成迭代器
#第一個參數是迭代器,第二個參數起始索引,第三個參數結束索引,不支援負數索引
for i in islice(gen,0,4):
print(i)
45. 請將 [i for i in range(3)] 改成生成器
答:通過把列表生產式的中括弧,改為小括弧我們就實現了生產器的功能即,
(i for i in range(3))
46. a=”hello” 和 b=”你好” 編碼成 bytes 類型
答: 這個題目一共三種方式,第一種是在字元串的前面加一個 b,第二種可以使用 bytes 方法,第三種使用字元串 encode 方法。具體程式碼如下,abc 代表三種情況
a = b"hello"
b = bytes("你好", "utf-8")
c = "你好".encode("utf-8")
print(a, b, c)
47. 下面的程式碼輸出結果是什麼?
a = (1,2,3,[4,5,6,7],8)
a[2] = 2
答: 我們知道元組裡的元素是不能改變的所以這個題目的答案是出現異常。
48. 下面的程式碼輸出的結果是什麼?
a = (1,2,3,[4,5,6,7],8)
a[3][0] = 2
答:前面我說了元組的里元素是不能改變的,這句話嚴格來說是不準確的,如果元組裡面元素本身就是可變類型,比如列表,那麼在操作這個元素里的對象時,其記憶體地址也是不變的。a[3] 對應的元素是列表,然後對列表第一個元素賦值,所以最後的結果是: (1,2,3,[2,5,6,7],8)
操作類題目
49. Python 交換兩個變數的值
答:在 Python 中交換兩個對象的值通過下面的方式即可
a , b = b ,a
但是需要強調的是這並不是元組解包,通過 dis 模組可以發現,這是交換操作的位元組碼是 ROT_TWO,意思是在棧的頂端做兩個值的互換操作。
50. 在讀文件操作的時候會使用 read、readline 或者 readlines,簡述它們各自的作用
答:.read() 每次讀取整個文件,它通常用於將文件內容放到一個字元串變數中。如果希望一行一行的輸出那麼就可以使用 readline(),該方法會把文件的內容載入到記憶體,所以對於對於大文件的讀取操作來說非常的消耗記憶體資源,此時就可以通過 readlines 方法,將文件的句柄生成一個生產器,然後去讀就可以了。
51. json 序列化時,可以處理的數據類型有哪些?如何訂製支援 datetime 類型?
答: 可以處理的數據類型是 str、int、list、tuple、dict、bool、None, 因為 datetime 類不支援 json 序列化,所以我們對它進行拓展。
# 自定義時間序列化
import json
from datetime import datetime, date
# JSONEncoder 不知道怎麼去把這個數據轉換成 json 字元串的時候
# ,它就會去調 default()函數,所以都是重寫這個函數來處理它本身不支援的數據類型,
# default()函數默#認是直接拋異常的。
class DateToJson(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, datetime):
return obj.strftime('%Y-%m-%d %H:%M:%S')
elif isinstance(obj, date):
return obj.strftime('%Y-%m-%d')
else:
return json.JSONEncoder.default(self, obj)
d = {'name': 'cxa', 'data': datetime.now()}
print(json.dumps(d, cls=DateToJson))
52. json 序列化時,默認遇到中文會轉換成 unicode,如果想要保留中文怎麼辦?
答:可以通過 json.dumps 的 ensure_ascii 參數解決,程式碼示例如下:
import json
a=json.dumps({"name":"張三"},ensure_ascii=False)
print(a)
53. 有兩個磁碟文件 A 和 B,各存放一行字母,要求把這兩個文件中的資訊合併(按字母順序排列),輸出到一個新文件 C 中。
答:
#文件 A.txt 內容為 ASDCF
#文件 B.txt 內容為 EFGGTG
with open("A.txt") as f1:
f1_txt = f1.readline()
with open("B.txt") as f2:
f2_txt = f2.readline()
f3_txt = f1_txt + f2_txt
f3_list = sorted(f3_txt)
with open("C.txt", "a+") as f:
f.write("".join(f3_list))
輸出的文件 C 的內容為 ACDEFFGGGST
54. 如果當前的日期為 20190530,要求寫一個函數輸出 N 天后的日期,(比如 N 為 2,則輸出 20190601)。
答:這個題目考察的是 datetime 里的 timedelta 方法的使用,參數可選、默認值都為 0:datetime.timedelta(days=0, seconds=0, microseconds=0, milliseconds=0, minutes=0, hours=0, weeks=0) 通過這個參數可以指定不同的日期類型進行加減操作,這裡我們需要改的是 days,程式碼如下
import datetime
def datetime_operate(n: int):
now = datetime.datetime.now() # 獲取當前時間
_new_date = now + datetime.timedelta(days=n) # 獲取指定天數後的新日期
new_date = _new_date.strftime("%Y%m%d") # 轉換為指定的輸出格式
return new_date
if __name__ == '__main__':
print(datetime_operate(4))
55. 寫一個函數,接收整數參數 n,返回一個函數,函數的功能是把函數的參數和 n 相乘並把結果返回。
答:這個題目考查了閉包的使用程式碼示例如下,返回函數之類型是函數對象。
def mul_operate(num):
def g(val):
return num * val
return g
m = mul_operate(8)
print(m(5))
56. 下面程式碼會存在什麼問題,如何改進?
def strappend(num):
str='first'
for i in range(num):
str+=str(i)
return str
答: 首先不應該使用 Python 的內置類似 str 作為變數名這裡我把它改為了 s,另外在Python,str 是個不可變對象,每次迭代都會生成新的存儲空間,num 越大,創建的 str 對象就會越多,記憶體消耗越大。使用 yield 改成生成器即可, 還有一點就是命名規範的位置,函數名改為_分割比較好,完整的程式碼如下:
def str_append(num):
s = 'first'
for i in range(num):
s += str(i)
yield s
if __name__ == '__main__':
for i in str_append(3):
print(i)
57. 一行程式碼輸出 1-100 之間的所有偶數。
答:可以通過列表生成式,然後使用與操作如果如 1 與之後結果為 0 則表明為偶數,等於 1 則為奇數。
# 方法1
print([i for i in range(1, 101) if i & 0x1 == 0])
# 方法2:測試發現方法二效率更高
print(list(range(2, 101, 2)))
58. with 語句的作用,寫一段程式碼?
with 語句適用於對資源進行訪問的場合,確保不管使用過程中是否發生異常都會執行必要的「清理」操作,釋放資源,比如文件使用後自動關閉、執行緒中鎖的自動獲取和釋放等。
其他的內容看下面我之前寫的程式碼。
#一般訪問文件資源時我們會這樣處理:
f = open(
'c:\test.txt', 'r')
data = f.read()
f.close()
# 這樣寫沒有錯,但是容易犯兩個毛病:
# 1. 如果在讀寫時出現異常而忘了異常處理。
# 2. 忘了關閉文件句柄
#以下的加強版本的寫法:
f = open('c:\test.txt', 'r')
try:
data = f.read()
finally:
f.close()
#以上的寫法就可以避免因讀取文件時異常的發生而沒有關閉問題的處理了。程式碼長了一些。
#但使用 with 有更優雅的寫法:
with open(r'c:\test.txt', 'r') as f:
data = f.read()
#with 的實現
class Test:
def __enter__(self):
print('__enter__() is call!')
return self
def dosomething(self):
print('dosomethong!')
def __exit__(self, exc_type, exc_value, traceback):
print('__exit__() is call!')
print(f'type:{exc_type}')
print(f'value:{exc_value}')
print(f'trace:{traceback}')
print('__exit()__ is call!')
with Test() as sample:
pass
#當對象被實例化時,就會主動調用__enter__()方法,任務執行完成後就會調用__exit__()方法,
#另外,注意到,__exit__()方法是帶有三個參數的(exc_type, exc_value, traceback),
#依據上面的官方說明:如果上下文運行時沒有異常發生,那麼三個參數都將置為 None,
#這裡三個參數由於沒有發生異常,的確是置為了 None, 與預期一致.
# 修改後不出異常了
class Test:
def __enter__(self):
print('__enter__() is call!')
return self
def dosomething(self):
x = 1/0
print('dosomethong!')
def __exit__(self, exc_type, exc_value, traceback):
print('__exit__() is call!')
print(f'type:{exc_type}')
print(f'value:{exc_value}')
print(f'trace:{traceback}')
print('__exit()__ is call!')
return True
with Test() as sample:
59. Python 字典和 json 字元串相互轉化方法
答:
在 Python 中使用 dumps 方法 將 dict 對象轉為 Json 對象,使用 loads 方法可以將 Json 對象轉為 dict 對象。
dic = {'a': 123, 'b': "456", 'c': "liming"}
json_str = json.dumps(dic)
dic2 = json.loads(json_str)
print(dic2)
列印:
'{"a": 123, "b": "456", "c": "liming"}'
{'a': 123, 'b': '456', 'c': 'liming'}
我們再來看一個特殊的例子
import json
dic = {'a': 123, 'b': "456", 'c': "liming"}
dic_str = json.loads(str(dic).replace("'", "\""))
print(dic_str)
下面我解釋下上面程式碼是測試什麼:
首先 json.loads(jsonstr) 這裡面的參數只能是 jsonstr 格式的字元串.
當我們使用 str 將字典 dic 轉化為字元串以後,得到的結果為:"{'a': 123, 'b': '456', 'c': 'liming'}"。
如果直接使用 json.loads(str(dic)) 你會發現出現錯誤,原因就是,單引號的字元串不符合Json的標準格式所以再次使用了 replace("'", "\"")。得到字典
其實這個例子主要目的是告訴大家 Json 的標準格式是不支援單引號型字元串的,否則會出現以下錯誤。
json.decoder.JSONDecodeError: Expecting property name enclosed in double quotes: line 1 column 2 (char 1)
60. 請寫一個 Python 邏輯,計算一個文件中的大寫字母數量
答:
with open('A.txt') as fs:
count = 0
for i in fs.read():
if i.isupper():
count += 1
print(count)
61. 請寫一段 Python連接Mongo資料庫,然後的查詢程式碼。
答:
# -*- coding: utf-8 -*-
# @Author : 陳祥安
import pymongo
db_configs = {
'type': 'mongo',
'host': '地址',
'port': '埠',
'user': 'spider_data',
'passwd': '密碼',
'db_name': 'spider_data'
}
class Mongo():
def __init__(self, db=db_configs["db_name"], username=db_configs["user"],
password=db_configs["passwd"]):
self.client = pymongo.MongoClient(f'mongodb://{db_configs["host"]}:db_configs["port"]')
self.username = username
self.password = password
if self.username and self.password:
self.db1 = self.client[db].authenticate(self.username, self.password)
self.db1 = self.client[db]
def find_data(self):
# 獲取狀態為0的數據
data = self.db1.test.find({"status": 0})
gen = (item for item in data)
return gen
if __name__ == '__main__':
m = Mongo()
print(m.find_data())
62.說一說Redis的基本類型
答: Redis 支援五種數據類型: string(字元串) 、 hash(哈希)、list(列表) 、 set(集合) 及 zset(sorted set: 有序集合)。
63. 請寫一段 Python連接Redis資料庫的程式碼。
答:
from redis import StrictRedis, ConnectionPool
redis_url="redis://:[email protected]:6379/15"
pool = ConnectionPool.from_url(redis_url, decode_responses=True)
r= StrictRedis(connection_pool=pool)
64. 請寫一段 Python連接Mysql資料庫的程式碼。
答:
conn = pymysql.connect(host='localhost',
port=3306, user='root',
passwd='1234', db='user', charset='utf8mb4')#聲明mysql連接對象
cursor=conn.cursor(cursor=pymysql.cursors.DictCursor)#查詢結果以字典的形式
cursor.execute(sql語句字元串)#執行sql語句
conn.close()#關閉鏈接
65.了解Redis的事務么
答: 簡單理解,可以認為 redis 事務是一些列 redis 命令的集合,並且有如下兩個特點: 1.事務是一個單獨的隔離操作:事務中的所有命令都會序列化、按順序地執行。事務在執行的過程中,不會被其他客戶端發送來的命令請求所打斷。 2.事務是一個原子操作:事務中的命令要麼全部被執行,要麼全部都不執行。 一般來說,事務有四個性質稱為ACID,分別是原子性,一致性,隔離性和持久性。 一個事務從開始到執行會經歷以下三個階段:
- 開始事務
- 命令入隊
- 執行事務 程式碼示例:
import redis
import sys
def run():
try:
conn=redis.StrictRedis('192.168.80.41')
# Python中redis事務是通過pipeline的封裝實現的
pipe=conn.pipeline()
pipe.sadd('s001','a')
sys.exit()
#在事務還沒有提交前退出,所以事務不會被執行。
pipe.sadd('s001','b')
pipe.execute()
pass
except Exception as err:
print(err)
pass
if __name__=="__main__":
run()
66.了解資料庫的三範式么?
答: 經過研究和對使用中問題的總結,對於設計資料庫提出了一些規範,這些規範被稱為範式 一般需要遵守下面3範式即可: 第一範式(1NF):強調的是列的原子性,即列不能夠再分成其他幾列。 第二範式(2NF):首先是 1NF,另外包含兩部分內容,一是表必須有一個主鍵;二是沒有包含在主鍵中的列必須完全依賴於主鍵,而不能只依賴於主鍵的一部分。 第三範式(3NF):首先是 2NF,另外非主鍵列必須直接依賴於主鍵,不能存在傳遞依賴。即不能存在:非主鍵列 A 依賴於非主鍵列 B,非主鍵列 B 依賴於主鍵的情況。
67.了解分散式鎖么
答: 分散式鎖是控制分散式系統之間的同步訪問共享資源的一種方式。 對於分散式鎖的目標,我們必須首先明確三點:
- 任何一個時間點必須只能夠有一個客戶端擁有鎖。
- 不能夠有死鎖,也就是最終客戶端都能夠獲得鎖,儘管可能會經歷失敗。
- 錯誤容忍性要好,只要有大部分的Redis實例存活,客戶端就應該能夠獲得鎖。 分散式鎖的條件 互斥性:分散式鎖需要保證在不同節點的不同執行緒的互斥 可重入性:同一個節點上的同一個執行緒如果獲取了鎖之後,能夠再次獲取這個鎖。 鎖超時:支援超時釋放鎖,防止死鎖 高效,高可用:加鎖和解鎖需要高效,同時也需要保證高可用防止分散式鎖失效,可以增加降級。 支援阻塞和非阻塞:可以實現超時獲取失敗,tryLock(long timeOut) 支援公平鎖和非公平鎖
分散式鎖的實現方案 1、資料庫實現(樂觀鎖) 2、基於zookeeper的實現 3、基於Redis的實現(推薦)
68.用 Python 實現一個 Reids 的分散式鎖的功能
答:REDIS分散式鎖實現的方式:SETNX + GETSET,NX是Not eXists的縮寫,如SETNX命令就應該理解為:SET if Not eXists。 多個進程執行以下Redis命令:
SETNX lock.foo <current Unix time + lock timeout + 1>
如果 SETNX 返回1,說明該進程獲得鎖,SETNX將鍵 lock.foo 的值設置為鎖的超時時間(當前時間 + 鎖的有效時間)。 如果 SETNX 返回0,說明其他進程已經獲得了鎖,進程不能進入臨界區。進程可以在一個循環中不斷地嘗試 SETNX 操作,以獲得鎖。
import time
import redis
from conf.config import REDIS_HOST, REDIS_PORT, REDIS_PASSWORD
class RedisLock:
def __init__(self):
self.conn = redis.Redis(host=REDIS_HOST, port=REDIS_PORT, password=REDIS_PASSWORD, db=1)
self._lock = 0
self.lock_key = ""
@staticmethod
def my_float(timestamp):
"""
Args:
timestamp:
Returns:
float或者0
如果取出的是None,說明原本鎖並沒人用,getset已經寫入,返回0,可以繼續操作。
"""
if timestamp:
return float(timestamp)
else:
#防止取出的值為None,轉換float報錯
return 0
@staticmethod
def get_lock(cls, key, timeout=10):
cls.lock_key = f"{key}_dynamic_lock"
while cls._lock != 1:
timestamp = time.time() + timeout + 1
cls._lock = cls.conn.setnx(cls.lock_key, timestamp)
# if 條件中,可能在運行到or之後被釋放,也可能在and之後被釋放
# 將導致 get到一個None,float失敗。
if cls._lock == 1 or (
time.time() > cls.my_float(cls.conn.get(cls.lock_key)) and
time.time() > cls.my_float(cls.conn.getset(cls.lock_key, timestamp))):
break
else:
time.sleep(0.3)
@staticmethod
def release(cls):
if cls.conn.get(cls.lock_key) and time.time() < cls.conn.get(cls.lock_key):
cls.conn.delete(cls.lock_key)
def redis_lock_deco(cls):
def _deco(func):
def __deco(*args, **kwargs):
cls.get_lock(cls, args[1])
try:
return func(*args, **kwargs)
finally:
cls.release(cls)
return __deco
return _deco
@redis_lock_deco(RedisLock())
def my_func():
print("myfunc() called.")
time.sleep(20)
if __name__ == "__main__":
my_func()
69.寫一段 Python 使用 mongo 資料庫創建索引的程式碼:
答:
# -*- coding: utf-8 -*-
# @Time : 2018/12/28 10:01 AM
# @Author : cxa
import pymongo
db_configs = {
'type': 'mongo',
'host': '地址',
'port': '埠',
'user': 'spider_data',
'passwd': '密碼',
'db_name': 'spider_data'
}
class Mongo():
def __init__(self, db=db_configs["db_name"], username=db_configs["user"],
password=db_configs["passwd"]):
self.client = pymongo.MongoClient(f'mongodb://{db_configs["host"]}:{db_configs["port"]}')
self.username = username
self.password = password
if self.username and self.password:
self.db1 = self.client[db].authenticate(self.username, self.password)
self.db1 = self.client[db]
def add_index(self):
"""
通過create_index添加索引
"""
self.db1.test.create_index([('name', pymongo.ASCENDING)], unique=True)
def get_index(self,):
"""
查看索引列表
"""
indexlist=self.db1.test.list_indexes()
for index in indexlist:
print(index)
if __name__ == '__main__':
m = Mongo()
m.add_index()
print(m.get_index())
高級特性
70. 函數裝飾器有什麼作用?請列舉說明?
答: 裝飾器就是一個函數,它可以在不需要做任何程式碼變動的前提下給一個函數增加額外功能,啟動裝飾的效果。 它經常用於有切面需求的場景,比如:插入日誌、性能測試、事務處理、快取、許可權校驗等場景。 下面是一個日誌功能的裝飾器
from functools import wraps
def log(label):
def decorate(func):
@wraps(func)
def _wrap(*args,**kwargs):
try:
func(*args,**kwargs)
print("name",func.__name__)
except Exception as e:
print(e.args)
return _wrap
return decorate
@log("info")
def foo(a,b,c):
print(a+b+c)
print("in foo")
#decorate=decorate(foo)
if __name__ == '__main__':
foo(1,2,3)
#decorate()
71. Python 垃圾回收機制?
答:Python 不像 C++,Java 等語言一樣,他們可以不用事先聲明變數類型而直接對變數進行賦值。對 Python 語言來講,對象的類型和記憶體都是在運行時確定的。這也是為什麼我們稱 Python 語言為動態類型的原因。
主要體現在下面三個方法:
1.引用計數機制 2.標記-清除 3.分代回收
72. 魔法函數 _call_怎麼使用?
答: _call_ 可以把類實例當做函數調用。 使用示例如下
class Bar:
def __call__(self, *args, **kwargs):
print('in call')
if __name__ == '__main__':
b = Bar()
b()
73. 如何判斷一個對象是函數還是方法?
答:看程式碼已經結果就懂了
from types import MethodType, FunctionType
class Bar:
def foo(self):
pass
def foo2():
pass
def run():
print("foo 是函數", isinstance(Bar().foo, FunctionType))
print("foo 是方法", isinstance(Bar().foo, MethodType))
print("foo2 是函數", isinstance(foo2, FunctionType))
print("foo2 是方法", isinstance(foo2, MethodType))
if __name__ == '__main__':
run()
輸出
foo 是函數 False
foo 是方法 True
foo2 是函數 True
foo2 是方法 False
74. @classmethod 和 @staticmethod 用法和區別
答: 相同之處:@staticmethod 和@classmethod 都可以直接類名.方法名()來調用,不用在示例化一個類。 @classmethod 我們要寫一個只在類中運行而不在實例中運行的方法。如果我們想讓方法不在實例中運行,可以這麼做:
def iget_no_of_instance(ins_obj):
return ins_obj.__class__.no_inst
class Kls(object):
no_inst = 0
def __init__(self):
Kls.no_inst = Kls.no_inst + 1
ik1 = Kls()
ik2 = Kls()
print(iget_no_of_instance(ik1))
@staticmethod 經常有一些跟類有關係的功能但在運行時又不需要實例和類參與的情況下需要用到靜態方法
IND = 'ON'
class Kls(object):
def __init__(self, data):
self.data = data
@staticmethod
def check_ind():
return (IND == 'ON')
def do_reset(self):
if self.check_ind():
print('Reset done for:', self.data)
def set_db(self):
if self.check_ind():
self.db = 'New db connection'
print('DB connection made for: ', self.data)
ik1 = Kls(12)
ik1.do_reset()
ik1.set_db()
75. Python 中的介面如何實現?
答: 介面提取了一群類共同的函數,可以把介面當做一個函數的集合,然後讓子類去實現介面中的函數。但是在 Python 中根本就沒有一個叫做 interface 的關鍵字,如果非要去模仿介面的概念,可以使用抽象類來實現。抽象類是一個特殊的類,它的特殊之處在於只能被繼承,不能被實例化。使用 abc 模組來實現抽象類。
76. Python 中的反射了解么?
答:Python 的反射機制設定較為簡單,一共有四個關鍵函數分別是 getattr、hasattr、setattr、delattr。
77. metaclass 作用?以及應用場景?
答: metaclass 即元類,metaclass 是類似創建類的模板,所有的類都是通過他來 create 的(調用new),這使得你可以自由的控制創建類的那個過程,實現你所需要的功能。 我們可以使用元類創建單例模式和實現 ORM 模式。
78. hasattr()、getattr()、setattr() 的用法
答:這三個方法屬於 Python 的反射機制裡面的,hasattr 可以判斷一個對象是否含有某個屬性,getattr 可以充當 get 獲取對象屬性的作用。而 setattr 可以充當 person.name = “liming”的賦值操作。程式碼示例如下:
class Person():
def __init__(self):
self.name = "liming"
self.age = 12
def show(self):
print(self.name)
print(self.age)
def set_name(self):
setattr(Person, "sex", "男")
def get_name(self):
print(getattr(self, "name"))
print(getattr(self, "age"))
print(getattr(self, "sex"))
def run():
if hasattr(Person, "show"):
print("判斷 Person 類是否含有 show 方法")
Person().set_name()
Person().get_name()
if __name__ == '__main__':
run()
79. 請列舉你知道的 Python 的魔法方法及用途。
答:
1 __init__:
類的初始化方法。它獲取任何傳給構造器的參數(比如我們調用 x = SomeClass(10, 『foo』) , __init__就會接到參數 10 和 『foo』 。 __init__在 Python 的類定義中用的最多。
2 __new__:
__new__是對象實例化時第一個調用的方法,它只取下 cls 參數,並把其他參數傳給 __init__ 。 __new__很少使用,但是也有它適合的場景,尤其是當類繼承自一個像元組或者字元串這樣不經常改變的類型的時候.
3 __del__:
__new__和 __init__是對象的構造器, __del__是對象的銷毀器。它並非實現了語句 del x (因此該語句不等同於 x.__del__())。而是定義了當對象被垃圾回收時的行為。 當對象需要在銷毀時做一些處理的時候這個方法很有用,比如 socket 對象、文件對象。但是需要注意的是,當 Python 解釋器退出但對象仍然存活的時候,__del__並不會 執行。 所以養成一個手工清理的好習慣是很重要的,比如及時關閉連接。
80. 如何知道一個 Python 對象的類型?
答:可以通過 type 方法
81. Python 的傳參是傳值還是傳址?
答:Python 中的傳參即不是傳值也不是傳地址,傳的是對象的引用。
82. Python 中的元類 (metaclass) 使用舉例
答:可以使用元類實現一個單例模式,程式碼如下
class Singleton(type):
def __init__(self, *args, **kwargs):
print("in __init__")
self.__instance = None
super(Singleton, self).__init__(*args, **kwargs)
def __call__(self, *args, **kwargs):
print("in __call__")
if self.__instance is None:
self.__instance = super(Singleton, self).__call__(*args, **kwargs)
return self.__instance
class Foo(metaclass=Singleton):
pass # 在程式碼執行到這裡的時候,元類中的__new__方法和__init__方法其實已經被執行了,而不是在 Foo 實例化的時候執行。且僅會執行一次。
foo1 = Foo()
foo2 = Foo()
print(foo1 is foo2)
83. 簡述 any() 和 all() 方法
答: any(x):判斷 x 對象是否為空對象,如果都為空、0、false,則返回 false,如果不都為空、0、false,則返回 true。 all(x):如果 all(x) 參數 x 對象的所有元素不為 0、”、False 或者 x 為空對象,則返回 True,否則返回 False。
84. filter 方法求出列表所有奇數並構造新列表,a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
答
a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
print(list(filter(lambda x: x % 2 == 1, a)))
其實現在不推薦使用 filter,map 等方法了,一般列表生成式就可以搞定了。
85. 什麼是猴子修補程式?
答: 猴子修補程式(monkey patching):在運行時動態修改模組、類或函數,通常是添加功能或修正缺陷。猴子修補程式在程式碼運行時記憶體中)發揮作用,不會修改源碼,因此只對當前運行的程式實例有效。因為猴子修補程式破壞了封裝,而且容易導致程式與修補程式程式碼的實現細節緊密耦合,所以被視為臨時的變通方案,不是集成程式碼的推薦方式。大概是下面這樣的一個效果
def post():
print("this is post")
print("想不到吧")
class Http():
@classmethod
def get(self):
print("this is get")
def main():
Http.get=post #動態的修改了 get 原因的功能,
if __name__ == '__main__':
main()
Http.get()
86. 在 Python 中是如何管理記憶體的?
答: 垃圾回收:Python 不像 C++,Java 等語言一樣,他們可以不用事先聲明變數類型而直接對變數進行賦值。對 Python 語言來講,對象的類型和記憶體都是在運行時確定的。這也是為什麼我們稱 Python 語言為動態類型的原因(這裡我們把動態類型可以簡單的歸結為對變數記憶體地址的分配是在運行時自動判斷變數類型並對變數進行賦值)。
引用計數:Python 採用了類似 Windows 內核對象一樣的方式來對記憶體進行管理。每一個對象,都維護這一個對指向該對對象的引用的計數。當變數被綁定在一個對象上的時候,該變數的引用計數就是 1,(還有另外一些情況也會導致變數引用計數的增加),系統會自動維護這些標籤,並定時掃描,當某標籤的引用計數變為 0 的時候,該對就會被回收。
-
記憶體池機制 Python 的記憶體機制以金字塔行,1、2 層主要有作業系統進行操作
-
第 0 層是 C 中的 malloc,free 等記憶體分配和釋放函數進行操作
-
第 1 層和第 2 層是記憶體池,有 Python 的介面函數 PyMem_Malloc 函數實現,當對象小於 256K 時有該層直接分配記憶體
-
第 3 層是最上層,也就是我們對 Python 對象的直接操作
-
在 C 中如果頻繁的調用 malloc 與 free 時,是會產生性能問題的.再加上頻繁的分配與釋放小塊的記憶體會產生記憶體碎片。Python 在這裡主要乾的工作有:
-
如果請求分配的記憶體在 1~256 位元組之間就使用自己的記憶體管理系統,否則直接使用 malloc。
-
這裡還是會調用 malloc 分配記憶體,但每次會分配一塊大小為 256k 的大塊記憶體。
-
-
經由記憶體池登記的記憶體到最後還是會回收到記憶體池,並不會調用 C 的 free 釋放掉以便下次使用。對於簡單的 Python 對象,例如數值、字元串,元組(tuple 不允許被更改)採用的是複製的方式(深拷貝?),也就是說當將另一個變數 B 賦值給變數 A 時,雖然 A 和 B 的記憶體空間仍然相同,但當 A 的值發生變化時,會重新給 A 分配空間,A 和 B 的地址變得不再相同。
87. 當退出 Python 時是否釋放所有記憶體分配?
答:不是的,循環引用其他對象或引用自全局命名空間的對象的模組,在 Python 退出時並非完全釋放。
另外,也不會釋放 c 庫保留的記憶體部分
正則表達式
88. (1)使用正則表達式匹配出<html><h1\>www.baidu.com</h1></html>
中的地址(2)a=”張明 98 分”,用 re.sub,將 98 替換為 100
答: 第一問答案
import re
source = "<html><h1>www.baidu.com</h1></html>"
pat = re.compile("<html><h1>(.*?)</h1></html>")
print(pat.findall(source)[0])
第二問答案
import re
s = "張明 98 分"
print(re.sub(r"\d+","100",s))
89. 正則表達式匹配中(.*)和(.*?)匹配區別?
答:(.*) 為貪婪模式極可能多的匹配內容 ,(.*?) 為非貪婪模式又叫懶惰模式,一般匹配到結果就好,匹配字元的少為主,示例程式碼如下
import re
s = "<html><div>文本 1</div><div>文本 2</div></html>"
pat1 = re.compile(r"\<div>(.*?)\</div>")
print(pat1.findall(s))
pat2 = re.compile(r"\<div>(.*)\</div>")
print(pat2.findall(s))
輸出
['文本 1', '文本 2']
['文本 1</div><div>文本 2']
90. 寫一段匹配郵箱的正則表達式
答:關於郵箱的匹配這個還真的是一個永恆的話題。
電子郵件地址有統一的標準格式:用戶名@伺服器域名。用戶名表示郵件信箱、註冊名或信件接收者的用戶標識,@符號後是你使用的郵件伺服器的域名。@可以讀成「at」,也就是「在」的意思。整個電子郵件地址可理解為網路中某台伺服器上的某個用戶的地址。
- 用戶名,可以自己選擇。由字母 a~z(不區分大小寫)、數字 0~9、點、減號或下劃線組成;只能以數字或字母開頭和結尾。
- 與你使用的網站有關,代表郵箱服務商。例如網易的有@163.com 新浪有@vip.sina.com 等。
網上看到了各種各樣的版本,都不確定用哪個,於是自己簡單的總結了一個。大家有更好的歡迎留言。
r"^[a-zA-Z0-9]+[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$"
下面解釋上面的表達式
- 首先強調一點關於\w 的含義,\w 匹配英文字母和俄語字母或數字或下劃線或漢字。
- 注意^[]和[^]的區別,[]表示字符集合,^[]表示已[]內的任意字符集開始,[^]表示。
- ^[a-zA-Z0-9]+:這裡注意^[]和[^]的,第一個^表示已什麼開頭,第二個[]的^表示不等於[]內。所以這段表示以英文字母和數字開頭,後面緊跟的+,限定其個數>=1 個。
- [a-zA-Z0-9.+-]+:表示匹配英文字母和數字開頭以及.+-, 的任意一個字元,並限定其個數>=1 個。為了考慮@前面可能出現.+-(但是不在開頭出現)。
- @就是郵箱必備符號了
- @[a-zA-Z0-9-]+.:前面的不用說了,後面的.表示.轉義了,也是必備符號。
- [ a-zA-Z0-9-.]+:$符表示以什麼結束,這裡表示以英文字和數字或 -. 1 個或多個結尾。
來個例子驗證一波:
import re
plt=re.compile(r"^[a-zA-Z0-9]+[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$")
b=plt.findall('[email protected]')
print(b)
網上找了個驗證郵件地址的通用正則表達式(符合 RFC 5322 標準)
(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])
其他內容
91. 解釋一下 Python 中 pass 語句的作用?
答:pass 實際上就是一個佔位符,在寫一個函數但是不確定裡面寫啥的時候,這個時候可以使用 pass。示例如下
def foo():
pass
92. 簡述你對 input()函數的理解
答:在 Python3 中 input 函數可以接收用戶輸入的字元串。
然後根據程式的需要轉換成所需格式即可。
93. Python 中的 is 和==
答:先說==它的作用是判斷兩個對象的值是否相同,然後說 is。is 表示的誰是誰,這也就意味著對象完全相等。我們知道一個對象有各自的記憶體地址和對應的值,當記憶體地址和值都相同的時候使用 is 可以得到結果 True。另外需要注意的下面兩點特殊的情況。
這些變數很可能在許多程式中使用。 通過池化這些對象,Python 可以防止對一致使用的對象進行記憶體分配調用。
1.介於數字-5 和 256 之間的整數
2.字元串僅包含字母、數字或下劃線
94. Python 中的作用域
答:
Python 中,一個變數的作用域總是由在程式碼中被賦值的地方所決定
當 Python 遇到一個變數的話它會按照這的順序進行搜索
本地作用域(Local)--->當前作用域被嵌入的本地作用域(Enclosing locals)--->全局/模組作用域(Global)--->內置作用域(Built-in)
95. 三元運算寫法和應用場景?
答:Python 中的三元運算又稱三目運算,是對簡單的條件語句的簡寫。 是一種比較 Pythonic 的學法,形式為:val = 1 if 條件成立 else 2 程式碼示例如下:
a = 2
b = 5
# 普通寫法
if a > b:
val = True
else:
val = False
# 改為三元運算符後
val = a if a > b else b
print(val) # 5
96. 了解 enumerate 么?
答:enumerate 可以在迭代一個對象的時候,同時獲取當前對象的索引和值。 程式碼示例如下
from string import ascii_lowercase
s = ascii_lowercase
for index, value in enumerate(s):
print(index, value)
97. 列舉 5 個 Python 中的標準模組
答: pathlib:路徑操作模組,比 os 模組拼接方便。 urllib:網路請求模組,包括對 url 的結構解析。 asyncio: Python 的非同步庫,基於事件循環的協程模組。 re:正則表達式模組。 itertools:提供了操作生成器的一些模組。
98. 如何在函數中設置一個全局變數
答:
# 通過使用 global 對全局變數進行修改。
n = 0
def foo():
global n
n = 100
foo()
print(n)
x = 0
之前我在影片教程中對這塊做了個講解,具體點擊下方鏈接 https://www.bilibili.com/video/av50865713
99. pathlib 的用法舉例
答:pathlib 可以對文件以及文件的其他屬性進行操作。比較喜歡的一點是路徑拼接符”/”的使用,之前在公眾號中寫過 pathlib 一些其他的用法這裡就不一一例舉了。
100. Python 中的異常處理,寫一個簡單的應用場景
答: 比如在計算除法中出現為 0 的情況出現異常
try:
1 / 0
except ZeroDivisionError as e:
print(e.args)
101. Python 中遞歸的最大次數,那如何突破呢?
答:Python 有遞歸次數限制,默認最大次數為 1000。通過下面的程式碼可以突破這個限制
import sys
sys.setrecursionlimit(1500) # set the maximum depth as 1500
另外需要注意的是 sys.setrecursionlimit() 只是修改解釋器在解釋時允許的最大遞歸次數,此外,限制最大遞歸次數的還和作業系統有關。
102. 什麼是面向對象的 mro
答:Python 是支援面向對象編程的,同時也是支援多重繼承的。一般我們通過調用類對象的 mro()方法獲取其繼承關係。
103. isinstance 作用以及應用場景?
答:isinstance 是判斷一個對象是否為另一個對象的子類的,例如我們知道在 Python3 中 bool 類型其實是 int 的子類,所以我們可以對其檢測。
print(isinstance(True,int))
104. 什麼是斷言?應用場景?
答:在 Python 中是斷言語句 assert 實現此功能,一般在表達式為 True 的情況下,程式才能通過。
#author:陳祥安
#公眾號:Python 學習開發
#assert()方法,斷言成功,則程式繼續執行,斷言失敗,則程式報錯
# 斷言能夠幫助別人或未來的你理解程式碼,
# 找出程式中邏輯不對的地方。一方面,
# 斷言會提醒你某個對象應該處於何種狀態,
# 另一方面,如果某個時候斷言為假,
# 會拋出 AssertionError 異常,很有可能終止程式。
def foo(a):
assert a==2,Exception("不等於 2")
print("ok",a)
if __name__ == '__main__':
foo(1)
105. lambda 表達式格式以及應用場景?
答:lambda 表達式其實就是一個匿名函數,在函數編程中經常作為參數使用。 例子如下
a = [('a',1),('b',2),('c',3),('d',4)]
a_1 = list(map(lambda x:x[0],a))
106. 新式類和舊式類的區別
答:Python 2.x 中默認都是經典類,只有顯式繼承了 object 才是新式類,Python 3.x 中默認都是新式類,經典類被移除,不必顯式的繼承 object。 新式類都從 object 繼承,經典類不需要。 新式類的 MRO(method resolution order 基類搜索順序)演算法採用 C3 演算法廣度優先搜索,而舊式類的 MRO 演算法是採用深度優先搜索。 新式類相同父類只執行一次構造函數,經典類重複執行多次。
107. dir()是幹什麼用的?
答:當在使用某一個對象不知道有哪些屬性或者方法可以使用時,此時可以通過 dir() 方法進行查看。
108. 一個包里有三個模組,demo1.py、demo2.py、demo3.py,但使用 from tools import *導入模組時,如何保證只有 demo1、demo3 被導入了。
答: 增加_init_.py 文件,並在文件中增加:
__all__ = ['demo1','demo3']
109. 列舉 5 個 Python 中的異常類型以及其含義
答:
AttributeError 對象沒有這個屬性
NotImplementedError 尚未實現的方法
StopIteration 迭代器沒有更多的值
TypeError 對類型無效的操作
IndentationError 縮進錯誤
110. copy 和 deepcopy 的區別是什麼?
答: copy.copy()淺拷貝,只拷貝父對象,不會拷貝對象的內部的子對象。 copy.deepcopy()深拷貝,拷貝對象及其子對象。
111. 程式碼中經常遇到的*args, **kwargs 含義及用法。
答: 在函數定義中使用 *args 和**kwargs 傳遞可變長參數。 *args 用來將參數打包成 tuple 給函數體調用。 **kwargs 打包關鍵字參數成 dict 給函數體調用。
112. Python 中會有函數或成員變數包含單下劃線前綴和結尾,和雙下劃線前綴結尾,區別是什麼?
答: “單下劃線” 開始的成員變數叫做保護變數,意思是只有類對象和子類對象自己能訪問到這些變數; “雙下劃線” 開始的是私有成員,意思是只有類對象自己能訪問,連子類對象也不能訪問到這個數據。
以單下劃線開頭(_foo)的代表不能直接訪問的類屬性,需通過類提供的介面進行訪問,不能用「from xxx import *」而導入;以雙下劃線開頭的(__foo)代表類的私有成員;
以雙下劃線開頭和結尾的(_foo)代表 Python 里特殊方法專用的標識,如 _init()代表類的構造函數。
程式碼示例
class Person:
"""docstring for ClassName"""
def __init__(self):
self.__age = 12
self._sex = 12
def _sex(self):
return "男"
def set_age(self,age):
self.__age = age
def get_age(self):
return self.__age
if __name__ == '__main__':
p=Person()
print(p._sex)
#print(p.__age)
#Python 自動將__age 解釋成 _Person__age,於是我們用 _Person__age 訪問,這次成功。
print(p._Person__age)
113. w、a+、wb 文件寫入模式的區別
答: w 表示寫模式支援寫入字元串,如果文件存在則覆蓋。 a+ 和 w 的功能類型不過如果文件存在的話內容不會覆蓋而是追加。 wb 是寫入二進位位元組類型的數據。
114. 舉例 sort 和 sorted 的區別
答: 相同之處 sort 和 sorted 都可以對列表元素排序,sort() 與 sorted() 的不同在於,sort 是在原位重新排列列表,而 sorted() 是產生一個新的列表。 sort 是應用在 list 上的方法,sorted 可以對所有可迭代的對象進行排序操作。
list 的 sort 方法返回的是對已經存在的列表進行操作,而內建函數 sorted 方法返回的是一個新的 list,而不是在原來的基礎上進行的操作。
115. 什麼是負索引?
答:負索引一般表示的是從後面取元素。
116. pprint 模組是幹什麼的?
答:pprint 是 print 函數的美化版,可以通過 import pprint 導入。示例如下
import pprint
pprint.pprint("this is pprint")
117. 解釋一下 Python 中的賦值運算符
答:通過下面的程式碼列舉出所有的賦值運算符
a=7
a+=1
print(a)
a-=1
print(a)
a*=2
print(a)
a/=2
print(a)
a**=2
print(a)
a//=3
print(a)
a%=4
print(a)
118. 解釋一下 Python 中的邏輯運算符
答:Python 中有三個邏輯運算符:and、or、not
print(False and True) #False
print(7<7 or True) #True
print(not 2==2) #False
119. 講講 Python 中的位運算符
答:按位運算符是把數字看作二進位來進行計算的。Python 中的按位運演算法則如下:
下表中變數 a 為 60,b 為 13,二進位格式如下:
a = 0011 1100
b = 0000 1101
-----------------
a&b = 0000 1100
a|b = 0011 1101
a^b = 0011 0001
~a = 1100 0011
120. 在 Python 中如何使用多進位數字?
答: 我們在 Python 中,除十進位外還可以使用二進位、八進位和十六進位
- 二進位數字由 0 和 1 組成,我們使用 0b 或 0B 前綴表示二進位數
print(int(0b1010))#10
- 使用 bin()函數將一個數字轉換為它的二進位形式
print(bin(0xf))#0b1111
- 八進位數由數字 0-7 組成,用前綴 0o 或 0O 表示 8 進位數
print(oct(8))#0o10
- 十六進數由數字 0-15 組成,用前綴 0x 或者 0X 表示 16 進位數
print(hex(16))#0x10
print(hex(15))#0xf
121. 怎樣聲明多個變數並賦值?
答:Python 是支援多個變數賦值的,程式碼示例如下
#對變數 a,b,c 聲明並賦值
a,b,c = 1,2,3
演算法和數據結構
122. 已知:
AList = [1,2,3]
BSet = {1,2,3}
(1) 從 AList 和 BSet 中 查找 4,最壞時間複雜度哪個大? (2) 從 AList 和 BSet 中 插入 4,最壞時間複雜度哪個大?
答: (1) 對於查找,列表和集合的最壞時間複雜度都是 O(n),所以一樣的。 (2) 列表操作插入的最壞時間複雜度為 o(n),集合為 o(1),所以 Alist 大。 set 是哈希表所以操作的複雜度基本上都是 o(1)。
123. 用 Python 實現一個二分查找的函數
答:
def binary_search(arr, target):
n = len(arr)
left = 0
right = n-1
while left <= right :
mid = (left + right)//2
if arr[mid] < target:
left = mid + 1
elif arr[mid] > target:
right = mid - 1
else:
print(f"index:{mid},value:{arr[mid]}")
return True
return False
if __name__ == '__main__':
l = [1,3,4,5,6,7,8]
binary_search(l,8)
124. Python 單例模式的實現方法
答:實現單例模式的方法有多種,之前再說元類的時候用 call 方法實現了一個單例模式,另外 Python 的模組就是一個天然的單例模式,這裡我們使用 new 關鍵字來實現一個單例模式。
"""
通過 new 函數實現簡單的單例模式。
"""
class Book:
def __new__(cls, title):
if not hasattr(cls, "_ins"):
cls._ins = super().__new__(cls)
print('in __new__')
return cls._ins
def __init__(self, title):
print('in __init__')
super().__init__()
self.title = title
if __name__ == '__main__':
b = Book('The Spider Book')
b2 = Book('The Flask Book')
print(id(b))
print(id(b2))
print(b.title)
print(b2.title)
125. 使用 Python 實現一個斐波那契數列
答: 斐波那契數列:數列從第 3 項開始,每一項都等於前兩項之和。
def fibonacci(num):
"""
獲取指定位數的列表
:param num:
:return:
"""
a, b = 0, 1
l = []
for i in range(num):
a, b = b, a + b
l.append(b)
return l
if __name__ == '__main__':
print(fibonacci(10))
126. 找出列表中的重複數字
答:
"""
從頭掃到尾,只要當前元素值與下標不同,就做一次判斷,numbers[i]與 numbers[numbers[i]],
相等就認為找到了重複元素,返回 true,否則就交換兩者,繼續循環。直到最後還沒找到認為沒找到重複元素。
"""
# -*- coding:utf-8 -*-
class Solution:
def duplicate(self, numbers):
"""
:param numbers:
:return:
"""
if numbers is None or len(numbers) <= 1:
return False
use_set = set()
duplication = {}
for index, value in enumerate(numbers):
if value not in use_set:
use_set.add(value)
else:
duplication[index] = value
return duplication
if __name__ == '__main__':
s = Solution()
d = s.duplicate([1, 2, -3, 4, 4, 95, 95, 5, 2, 2, -3, 7, 7, 5])
print(d)
127. 找出列表中的單個數字
答:
def find_single(l :list):
result = 0
for v in l:
result ^= v
if result == 0:
print("沒有落單元素")
else:
print("落單元素" ,result)
if __name__ == '__main__':
l = [1,2,3,4,5,6,2,3,4,5,6]
find_single(l)
128. 寫一個冒泡排序
答:
"""
冒泡排序
"""
def bubble_sort(arr):
n = len(arr)
for i in range(n - 1):
for j in range(n - i - 1):
if arr[j] > arr[j + 1]:
arr[j], arr[j + 1] = arr[j + 1], arr[j]
if __name__ == '__main__':
l = [1, 2, 3, 4, 5, 55, 6, 3, 4, 5, 6]
bubble_sort(l)
print(l)
129. 寫一個快速排序
答:
"""
快速排序
"""
def quick_sort(arr, first, last):
if first >= last:
return
mid_value = arr[first]
low = first
high = last
while low < high:
while low < high and arr[high] >= mid_value:
high -= 1 # 游標左移
arr[low] = arr[high]
while low < high and arr[low] < mid_value:
low += 1
arr[high] = arr[low]
arr[low] = mid_value
quick_sort(arr, first, low - 1)
quick_sort(arr, low + 1, last)
if __name__ == '__main__':
l = [1, 2, 3, 4, 5, 55, 6, 3, 4, 5, 6]
quick_sort(l, 0, len(l) - 1)
print(l)
130. 寫一個拓撲排序
答:
"""
拓撲排序
對應於該圖的拓撲排序。每一個有向無環圖都至少存在一種拓撲排序。
"""
import pysnooper
from typing import Mapping
@pysnooper.snoop()
def topological_sort(graph: Mapping):
# in_degrees = {'a': 0, 'b': 0, 'c': 0, 'd': 0, 'e': 0, 'f': 0}
in_degrees = dict((u, 0) for u in graph)
for u in graph:
for v in graph[u]: # 根據鍵找出值也就是下級節點
in_degrees[v] += 1 # 對獲取到的下級節點的入度加 1
# 循環結束之後的結果: {'a': 0, 'b': 1, 'c': 1, 'd': 2, 'e': 1, 'f': 4}
Q = [u for u in graph if in_degrees[u] == 0] # 入度為 0 的節點
in_degrees_zero = []
while Q:
u = Q.pop() # 默認從最後一個移除
in_degrees_zero.append(u) # 存儲入度為 0 的節點
for v in graph[u]:
in_degrees[v] -= 1 # 刪除入度為 0 的節點,以及移除其指向
if in_degrees[v] == 0:
Q.append(v)
return in_degrees_zero
if __name__ == '__main__':
# 用字典的鍵值表示圖的節點之間的關係,鍵當前節點。值是後續節點。
graph_dict = {
'a': 'bf', # 表示 a 指向 b 和 f
'b': 'cdf',
'c': 'd',
'd': 'ef',
'e': 'f',
'f': ''
}
t = topological_sort(graph_dict)
print(t)
131. Python 實現一個二進位計算
答:
"""
二進位加法
"""
def binary_add(a: str, b: str):
return bin(int(a, 2) + int(b, 2))[2:]
if __name__ == '__main__':
num1 = input("輸入第一個數,二進位格式:\n")
num2 = input("輸入第二個數,二進位格式:\n")
print(binary_add(num1, num2))
132. 有一組「+」和「-」符號,要求將「+」排到左邊,「-」排到右邊,寫出具體的實現方法。
答:
"""
有一組「+」和「-」符號,要求將「+」排到左邊,「-」排到右邊,寫出具體的實現方法。
如果讓+等於 0,-等於 1 不就是排序了么。
"""
from collections import deque
from timeit import Timer
s = "++++++----+++----"
# 方法一
def func1():
new_s = s.replace("+", "0").replace("-", "1")
result = "".join(sorted(new_s)).replace("0", "+").replace("1", "-")
return result
# 方法二
def func2():
q = deque()
left = q.appendleft
right = q.append
for i in s:
if i == "+":
left("+")
elif i == "-":
right("-")
def func3():
data = list(s)
start_index = 0
end_index = 0
count = len(s)
while start_index + end_index < count:
if data[start_index] == '-':
data[start_index], data[count - end_index - 1] = data[count - end_index - 1], data[start_index]
end_index += 1
else:
start_index += 1
return "".join(data)
if __name__ == '__main__':
timer1 = Timer("func1()", "from __main__ import func1")
print("func1", timer1.timeit(1000000))
timer2 = Timer("func2()", "from __main__ import func2")
print("func2", timer2.timeit(1000000))
timer3 = Timer("func3()", "from __main__ import func3")
print("func3", timer3.timeit(1000000))
# 1000000 測試結果
# func1 1.39003764
# func2 1.593012875
# func3 3.3487415590000005
# func1 的方式最優,其次是 func2
133. 單鏈表反轉
答:
"""
單鏈表反轉
"""
class Node:
def __init__(self, val=None):
self.val = val
self.next = None
class SingleLinkList:
def __init__(self, head=None):
"""鏈表的頭部"""
self._head = head
def add(self, val: int):
"""
給鏈表添加元素
:param val: 傳過來的數字
:return:
"""
# 創建一個節點
node = Node(val)
if self._head is None:
self._head = node
else:
cur = self._head
while cur.next is not None:
cur = cur.next # 移動游標
cur.next = node # 如果 next 後面沒了證明以及到最後一個節點了
def traversal(self):
if self._head is None:
return
else:
cur = self._head
while cur is not None:
print(cur.val)
cur = cur.next
def size(self):
"""
獲取鏈表的大小
:return:
"""
count = 0
if self._head is None:
return count
else:
cur = self._head
while cur is not None:
count += 1
cur = cur.next
return count
def reverse(self):
"""
單鏈表反轉
思路:
讓 cur.next 先斷開即指向 none,指向設定 pre 游標指向斷開的元素,然後
cur.next 指向斷開的元素,再把開始 self._head 再最後一個元素的時候.
:return:
"""
if self._head is None or self.size() == 1:
return
else:
pre = None
cur = self._head
while cur is not None:
post = cur.next
cur.next = pre
pre = cur
cur = post
self._head = pre # 逆向後的頭節點
if __name__ == '__main__':
single_link = SingleLinkList()
single_link.add(3)
single_link.add(5)
single_link.add(6)
single_link.add(7)
single_link.add(8)
print("對鏈表進行遍歷")
single_link.traversal()
print(f"size:{single_link.size()}")
print("對鏈表進行逆向操作之後")
single_link.reverse()
single_link.traversal()
134. 交叉鏈表求交點
答:
# Definition for singly-linked list.
class ListNode:
def __init__(self, x):
self.val = x
self.next = None
class Solution:
def getIntersectionNode(self, headA, headB):
"""
:tye head1, head1: ListNode
:rtye: ListNode
"""
if headA is not None and headB is not None:
cur1, cur2 = headA, headB
while cur1 != cur2:
cur1 = cur1.next if cur1 is not None else headA
cur2 = cur2.next if cur2 is not None else headB
return cur1
cur1、cur2,2 個指針的初始位置是鏈表 headA、headB 頭結點,cur1、cur2 兩個指針一直往後遍歷。 直到 cur1 指針走到鏈表的末尾,然後 cur1 指向 headB; 直到 cur2 指針走到鏈表的末尾,然後 cur2 指向 headA; 然後再繼續遍歷; 每次 cur1、cur2 指向 None,則將 cur1、cur2 分別指向 headB、headA。 循環的次數越多,cur1、cur2 的距離越接近,直到 cur1 等於 cur2。則是兩個鏈表的相交點。
135. 用隊列實現棧
答: 下面程式碼分別使用 1 個隊列和 2 個隊列實現了棧。
from queue import Queue
#使用 2 個隊列實現
class MyStack:
def __init__(self):
"""
Initialize your data structure here.
"""
# q1 作為進棧出棧,q2 作為中轉站
self.q1 = Queue()
self.q2 = Queue()
def push(self, x):
"""
Push element x onto stack.
:type x: int
:rtype: void
"""
self.q1.put(x)
def pop(self):
"""
Removes the element on top of the stack and returns that element.
:rtype: int
"""
while self.q1.qsize() > 1:
self.q2.put(self.q1.get()) # 將 q1 中除尾元素外的所有元素轉到 q2 中
if self.q1.qsize() == 1:
res = self.q1.get() # 彈出 q1 的最後一個元素
self.q1, self.q2 = self.q2, self.q1 # 交換 q1,q2
return res
def top(self):
"""
Get the top element.
:rtype: int
"""
while self.q1.qsize() > 1:
self.q2.put(self.q1.get()) # 將 q1 中除尾元素外的所有元素轉到 q2 中
if self.q1.qsize() == 1:
res = self.q1.get() # 彈出 q1 的最後一個元素
self.q2.put(res) # 與 pop 唯一不同的是需要將 q1 最後一個元素保存到 q2 中
self.q1, self.q2 = self.q2, self.q1 # 交換 q1,q2
return res
def empty(self):
"""
Returns whether the stack is empty.
:rtype: bool
"""
return not bool(self.q1.qsize() + self.q2.qsize()) # 為空返回 True,不為空返回 False
#使用 1 個隊列實現
class MyStack2(object):
def __init__(self):
"""
Initialize your data structure here.
"""
self.sq1 = Queue()
def push(self, x):
"""
Push element x onto stack.
:type x: int
:rtype: void
"""
self.sq1.put(x)
def pop(self):
"""
Removes the element on top of the stack and returns that element.
:rtype: int
"""
count = self.sq1.qsize()
if count == 0:
return False
while count > 1:
x = self.sq1.get()
self.sq1.put(x)
count -= 1
return self.sq1.get()
def top(self):
"""
Get the top element.
:rtype: int
"""
count = self.sq1.qsize()
if count == 0:
return False
while count:
x = self.sq1.get()
self.sq1.put(x)
count -= 1
return x
def empty(self):
"""
Returns whether the stack is empty.
:rtype: bool
"""
return self.sq1.empty()
if __name__ == '__main__':
obj = MyStack2()
obj.push(1)
obj.push(3)
obj.push(4)
print(obj.pop())
print(obj.pop())
print(obj.pop())
print(obj.empty())
136. 找出數據流的中位數
答:對於一個升序排序的數組,中位數為左半部分的最大值,右半部分的最小值,而左右兩部分可以是無需的,只要保證左半部分的數均小於右半部分即可。因此,左右兩半部分分別可用最大堆、最小堆實現。
如果有奇數個數,則中位數放在左半部分;如果有偶數個數,則取左半部分的最大值、右邊部分的最小值之平均值。
分兩種情況討論: 當目前有偶數個數字時,數字先插入最小堆,然後選擇最小堆的最小值插入最大堆(第一個數字插入左半部分的最小堆)。
當目前有奇數個數字時,數字先插入最大堆,然後選擇最大堆的最大值插入最小堆。 最大堆:根結點的鍵值是所有堆結點鍵值中最大者,且每個結點的值都比其孩子的值大。 最小堆:根結點的鍵值是所有堆結點鍵值中最小者,且每個結點的值都比其孩子的值小。
# -*- coding:utf-8 -*-
from heapq import *
class Solution:
def __init__(self):
self.maxheap = []
self.minheap = []
def Insert(self, num):
if (len(self.maxheap) + len(self.minheap)) & 0x1: # 總數為奇數插入最大堆
if len(self.minheap) > 0:
if num > self.minheap[0]: # 大於最小堆里的元素
heappush(self.minheap, num) # 新數據插入最小堆
heappush(self.maxheap, -self.minheap[0]) # 最小堆中的最小插入最大堆
heappop(self.minheap)
else:
heappush(self.maxheap, -num)
else:
heappush(self.maxheap, -num)
else: # 總數為偶數 插入最小堆
if len(self.maxheap) > 0: # 小於最大堆里的元素
if num < -self.maxheap[0]:
heappush(self.maxheap, -num) # 新數據插入最大堆
heappush(self.minheap, -self.maxheap[0]) # 最大堆中的最大元素插入最小堆
heappop(self.maxheap)
else:
heappush(self.minheap, num)
else:
heappush(self.minheap, num)
def GetMedian(self, n=None):
if (len(self.maxheap) + len(self.minheap)) & 0x1:
mid = self.minheap[0]
else:
mid = (self.minheap[0] - self.maxheap[0]) / 2.0
return mid
if __name__ == '__main__':
s = Solution()
s.Insert(1)
s.Insert(2)
s.Insert(3)
s.Insert(4)
print(s.GetMedian())
137. 二叉搜索樹中第 K 小的元素
答: 二叉搜索樹(Binary Search Tree),又名二叉排序樹(Binary Sort Tree)。 二叉搜索樹是具有有以下性質的二叉樹:
- 若左子樹不為空,則左子樹上所有節點的值均小於或等於它的根節點的值。
- 若右子樹不為空,則右子樹上所有節點的值均大於或等於它的根節點的值。
- 左、右子樹也分別為二叉搜索樹。
二叉搜索樹按照中序遍歷的順序列印出來正好就是排序好的順序。所以對其遍歷一個節點就進行計數,計數達到 k 的時候就結束。
class TreeNode:
def __init__(self, x):
self.val = x
self.left = None
self.right = None
class Solution:
count = 0
nodeVal = 0
def kthSmallest(self, root, k):
"""
:type root: TreeNode
:type k: int
:rtype: int
"""
self.dfs(root, k)
return self.nodeVal
def dfs(self, node, k):
if node != None:
self.dfs(node.left, k)
self.count = self.count + 1
if self.count == k:
self.nodeVal = node.val
# 將該節點的左右子樹置為 None,來結束遞歸,減少時間複雜度
node.left = None
node.right = None
self.dfs(node.right, k)
爬蟲相關
138. 在 requests 模組中,requests.content 和 requests.text 什麼區別
答: requests.content 獲取的是位元組,requests.text 獲取的是文本內容。
139. 簡要寫一下 lxml 模組的使用方法框架
答:
from lxml import html
source='''
<div class="nam"><span>中國</span></div>
root=html.fromstring(source)
_content=root.xpath("string(//div[@class='nam'])")
if _content and isinstance(_content,list):
content=_content[0]
elif isinstance(_content,str):
content=_content
print(content)
140. 說一說 scrapy 的工作流程
答:
首先還是先看張圖
已 www.baidu.com 為例: 首先需要知道的事各個模組之間調用都是通過引擎進行的。
- spider 把百度需要下載的第一個 url:www.baidu.com 交給引擎。
- 引擎把 url 交給調度器排序入隊處理。
- 調度器把處理好的 request 返回給引擎。
- 通過引擎調動下載器,按照下載中間件的設置下載這個 request。
- 下載器下載完畢結果返回給引擎(如果失敗:不好意思,這個 request 下載失敗,然後引擎告訴調度器,這個 request 下載失敗了,你記錄一下,我們待會兒再下載。)
- 引擎調度 spider,把按照 Spider 中間件處理過了的請求,交給 spider 處理。
- spider 把處理好的 url 和 item 傳給引擎。
- 引擎根據不同的類型調度不同的模組,調度 Item Pipeline 處理 item。
- 把 url 交給調度器。 然後從第 4 步開始循環,直到獲取到你需要的資訊,
注意!只有當調度器中不存在任何 request 了,整個程式才會停止。
141. scrapy 的去重原理
答:scrapy 本身自帶一個去重中間件,scrapy 源碼中可以找到一個 dupefilters.py 去重器。裡面有個方法叫做 request_seen,它在 scheduler(發起請求的第一時間)的時候被調用。它程式碼裡面調用了 request_fingerprint 方法(就是給 request 生成一個指紋)。
就是給每一個傳遞過來的 url 生成一個固定長度的唯一的哈希值。但是這種量級千萬到億的級別記憶體是可以應付的。
142. scrapy 中間件有幾種類,你用過哪些中間件
答: scrapy 的中間件理論上有三種(Schduler Middleware,Spider Middleware,Downloader Middleware)。在應用上一般有以下兩種
- 爬蟲中間件 Spider Middleware:主要功能是在爬蟲運行過程中進行一些處理。
- 下載器中間件 Downloader Middleware:這個中間件可以實現修改 User-Agent 等 headers 資訊,處理重定向,設置代理,失敗重試,設置 cookies 等功能。
143. 你寫爬蟲的時候都遇到過什麼?反爬蟲措施,你是怎麼解決的?
答:
-
Headers: 從用戶的 headers 進行反爬是最常見的反爬蟲策略。Headers 是一種區分瀏覽器行為和機器行為中最簡單的方法,還有一些網站會對 Referer (上級鏈接)進行檢測(機器行為不太可能通過鏈接跳轉實現)從而實現爬蟲。 相應的解決措施:通過審查元素或者開發者工具獲取相應的 headers 然後把相應的 headers 傳輸給 Python 的 requests,這樣就能很好地繞過。
-
IP 限制 一些網站會根據你的 IP 地址訪問的頻率,次數進行反爬。也就是說如果你用單一的 IP 地址訪問頻率過高,那麼伺服器會在短時間內禁止這個 IP 訪問。
解決措施:構造自己的 IP 代理池,然後每次訪問時隨機選擇代理(但一些 IP 地址不是非常穩定,需要經常檢查更新)。
- UA 限制 UA 是用戶訪問網站時候的瀏覽器標識,其反爬機制與 ip 限制類似。
解決措施:使用隨機 UA
- 驗證碼反爬蟲或者模擬登陸 驗證碼:這個辦法也是相當古老並且相當的有效果,如果一個爬蟲要解釋一個驗證碼中的內容,這在以前通過簡單的影像識別是可以完成的,但是就現在來講,驗證碼的干擾線,噪點都很多,甚至還出現了人類都難以認識的驗證碼。
相應的解決措施:驗證碼識別的基本方法:截圖,二值化、中值濾波去噪、分割、緊縮重排(讓高矮統一)、字型檔特徵匹配識別。(Python 的 PIL 庫或者其他),複雜的情況需求接入打碼平台。
- Ajax 動態載入 網頁的不希望被爬蟲拿到的數據使用 Ajax 動態載入,這樣就為爬蟲造成了絕大的麻煩,如果一個爬蟲不具備 js 引擎,或者具備 js 引擎,但是沒有處理 js 返回的方案,或者是具備了 js 引擎,但是沒辦法讓站點顯示啟用腳本設置。基於這些情況,ajax 動態載入反制爬蟲還是相當有效的。
Ajax 動態載入的工作原理是:從網頁的 url 載入網頁的源程式碼之後,會在瀏覽器里執行 JavaScript 程式。這些程式會載入出更多的內容,並把這些內容傳輸到網頁中。這就是為什麼有些網頁直接爬它的 URL 時卻沒有數據的原因。
處理方法:找對應的 ajax 介面,一般數據返回類型為 json。
- cookie 限制 一次打開網頁會生成一個隨機 cookie,如果再次打開網頁這個 cookie 不存在,那麼再次設置,第三次打開仍然不存在,這就非常有可能是爬蟲在工作了。
解決措施:在 headers 掛上相應的 cookie 或者根據其方法進行構造(例如從中選取幾個字母進行構造)。如果過於複雜,可以考慮使用 selenium 模組(可以完全模擬瀏覽器行為)。
144. 為什麼會用到代理?
答:如果使用同一個 ip 去不斷的訪問的網站的話,會很容易被封 ip,嚴重的永久封禁,導致當前的訪問不了該網站。不只是通過程式,通過瀏覽器也無法訪問。
145. 代理失效了怎麼處理?
答:一般通過大家代理池來實現代理切換等操作,來實現時時使用新的代理 ip,來避免代理失效的問題。
146. 列出你知道 header 的內容以及資訊
答: User-Agent:User-Agent 的內容包含發出請求的用戶資訊。 Accept:指定客戶端能夠接收的內容類型。 Accept-Encoding:指定瀏覽器可以支援的 web 伺服器返回內容壓縮編碼類型。 Accept-Language:瀏覽器可接受的語言。 Connection:表示是否需要持久連接。(HTTP 1.1 默認進行持久連接)。 Content-Length:請求的內容長度。 If-Modified-Since:如果請求的部分在指定時間之後被修改則請求成功,未被修改則返回 304 程式碼。 Referer:先前網頁的地址,當前請求網頁緊隨其後,即來路。
147. 說一說打開瀏覽器訪問 www.baidu.com 獲取到結果,整個流程。
答: 瀏覽器向 DNS 伺服器發送 baidu.com 域名解析請求。 DNS 伺服器返回解析後的 ip 給客戶端瀏覽器,瀏覽器想該 ip 發送頁面請求。 DNS 伺服器接收到請求後,查詢該頁面,並將頁面發送給客戶端瀏覽器。 客戶端瀏覽器接收到頁面後,解析頁面中的引用,並再次向伺服器發送引用資源請求。 伺服器接收到資源請求後,查找並返回資源給客戶端。 客戶端瀏覽器接收到資源後,渲染,輸出頁面展現給用戶。
148. 爬取速度過快出現了驗證碼怎麼處理
答:一般在爬取過程中出現了驗證碼根據不同的情況,處理不一樣。 如果在一開始訪問就有驗證碼,那麼就想辦法繞開驗證碼,比如通過 wap 端或者 app 去發現其他介面等,如果不行就得破解驗證碼了,複雜驗證碼就需要接入第三方打碼平台了。 如果開始的時候沒有驗證碼,爬了一段時間才出現驗證碼,這個情況就要考慮更換代理 ip 了。 可能因為同一個訪問頻率高導致的。
149. scrapy 和 scrapy-redis 有什麼區別?為什麼選擇 redis 資料庫?
答: scrapy 是一個 Python 爬蟲框架,爬取效率極高,具有高度訂製性,但是不支援分散式。而 scrapy-redis 一套基於 redis 資料庫、運行在 scrapy 框架之上的組件,可以讓 scrapy 支援分散式策略,Slaver 端共享 Master 端 redis 資料庫里的 item 隊列、請求隊列和請求指紋集合。
為什麼選擇 redis 資料庫,因為 redis 支援主從同步,而且數據都是快取在記憶體中的,所以基於 redis 的分散式爬蟲,對請求和數據的高頻讀取效率非常高。
150. 分散式爬蟲主要解決什麼問題
答:使用分散式主要目的就是為了給爬蟲加速。解決了單個 ip 的限制,寬頻的影響,以及 CPU 的使用情況和 io 等一系列操作
151. 寫爬蟲是用多進程好?還是多執行緒好? 為什麼?
答: 多執行緒,因為爬蟲是對網路操作屬於 io 密集型操作適合使用多執行緒或者協程。
152. 解析網頁的解析器使用最多的是哪幾個
答:lxml,pyquery
153. 需要登錄的網頁,如何解決同時限制 ip,cookie,session(其中有一些是動態生成的)在不使用動態爬取的情況下?
答: 解決限制 IP 可以搭建代理 IP 地址池、adsl 撥號使用等。
不適用動態爬取的情況下可以使用反編譯 JS 文件獲取相應的文件,或者換用其他平台(比如手機端)看看是否可以獲取相應的 json 文件,一般要學會習慣性的先找需要爬取網站的 h5 端頁面,看看有沒有提供介面,進而簡化操作。
154. 驗證碼的解決?
答: 圖形驗證碼:干擾、雜色不是特別多的圖片可以使用開源庫 Tesseract 進行識別,太過複雜的需要藉助第三方打碼平台。 點擊和拖動滑塊驗證碼可以藉助 selenium、無圖形介面瀏覽器(chromedirver 或者 phantomjs)和 pillow 包來模擬人的點擊和滑動操作,pillow 可以根據色差識別需要滑動的位置。
155. 使用最多的資料庫(mysql,mongodb,redis 等),對他的理解?
答: MySQL 資料庫:開源免費的關係型資料庫,需要實現創建資料庫、數據表和表的欄位,表與表之間可以進行關聯(一對多、多對多),是持久化存儲。
mongodb 資料庫:是非關係型資料庫,資料庫的三元素是,資料庫、集合、文檔,可以進行持久化存儲,也可作為記憶體資料庫,存儲數據不需要事先設定格式,數據以鍵值對的形式存儲。
redis 資料庫:非關係型資料庫,使用前可以不用設置格式,以鍵值對的方式保存,文件格式相對自由,主要用與快取資料庫,也可以進行持久化存儲。
網路編程
156. TCP 和 UDP 的區別?
答: UDP 是面向無連接的通訊協議,UDP 數據包括目的埠號和源埠號資訊。
優點:UDP 速度快、操作簡單、要求系統資源較少,由於通訊不需要連接,可以實現廣播發送。
缺點:UDP 傳送數據前並不與對方建立連接,對接收到的數據也不發送確認訊號,發送端不知道數據是否會正確接收,也不重複發送,不可靠。
TCP 是面向連接的通訊協議,通過三次握手建立連接,通訊完成時四次揮手。
優點:TCP 在數據傳遞時,有確認、窗口、重傳、阻塞等控制機制,能保證數據正確性,較為可靠。
缺點:TCP 相對於 UDP 速度慢一點,要求系統資源較多。
157. 簡要介紹三次握手和四次揮手
答: 三次握手 第一次握手:主機 A 發送同步報文段(SYN)請求建立連接。 第二次握手:主機 B 聽到連接請求,就將該連接放入內核等待隊列當中,並向主機 A 發送針對 SYN 的確認 ACK,同時主機 B 也發送自己的請求建立連接(SYN)。 第三次握手:主機 A 針對主機 BSYN 的確認應答 ACK。
四次揮手 第一次揮手:當主機 A 發送數據完畢後,發送 FIN 結束報文段。 第二次揮手:主機 B 收到 FIN 報文段後,向主機 A 發送一個確認序號 ACK(為了防止在這段時間內,對方重傳 FIN 報文段)。 第三次揮手:主機 B 準備關閉連接,向主機 A 發送一個 FIN 結束報文段。 第四次揮手:主機 A 收到 FIN 結束報文段後,進入 TIME_WAIT 狀態。並向主機 B 發送一個 ACK 表示連接徹底釋放。
除此之外經常看的問題還有,為什麼 2、3 次揮手不能合在一次揮手中? 那是因為此時 A 雖然不再發送數據了,但是還可以接收數據,B 可能還有數據要發送給 A,所以兩次揮手不能合併為一次。
158. 什麼是粘包? socket 中造成粘包的原因是什麼? 哪些情況會發生粘包現象?
答:TCP 是流式協議,只有位元組流,流是沒有邊界的,根部就不存在粘包一說,一般粘包都是業務上沒處理好造成的。
但是在描述這個現象的時候,可能還得說粘包。TCP 粘包通俗來講,就是發送方發送的多個數據包,到接收方後粘連在一起,導致數據包不能完整的體現發送的數據。
導致 TCP 粘包的原因,可能是發送方的原因,也有可能是接受方的原因。
發送方 由於 TCP 需要儘可能高效和可靠,所以 TCP 協議默認採用 Nagle 演算法,以合併相連的小數據包,再一次性發送,以達到提升網路傳輸效率的目的。但是接收方並不知曉發送方合併數據包,而且數據包的合併在 TCP 協議中是沒有分界線的,所以這就會導致接收方不能還原其本來的數據包。
接收方 TCP 是基於「流」的。網路傳輸數據的速度可能會快過接收方處理數據的速度,這時候就會導致,接收方在讀取緩衝區時,緩衝區存在多個數據包。在 TCP 協議中接收方是一次讀取緩衝區中的所有內容,所以不能反映原本的數據資訊。
一般的解決方案大概下面幾種:
- 發送定長包。如果每個消息的大小都是一樣的,那麼在接收對等方只要累計接收數據,直到數據等於一個定長的數值就將它作為一個消息。
- 包尾加上\r\n 標記。FTP 協議正是這麼做的。但問題在於如果數據正文中也含有\r\n,則會誤判為消息的邊界。
- 包頭加上包體長度。包頭是定長的 4 個位元組,說明了包體的長度。接收對等方先接收包體長度,依據包體長度來接收包體。
並發
159. 舉例說明 concurrent.future 的中執行緒池的用法
答:
from concurrent.futures import ThreadPoolExecutor
import requests
URLS = ['http://www.163.com', 'https://www.baidu.com/', 'https://github.com/']
def load_url(url):
req= requests.get(url, timeout=60)
print(f'{url} page is {len(req.content))} bytes')
with ThreadPoolExecutor(max_workers=3) as pool:
pool.map(load_url,URLS)
print('主執行緒結束')
160. 說一說多執行緒,多進程和協程的區別。
答: 概念:
進程:
進程是具有一定獨立功能的程式關於某個數據集合上的一次運行活動,
進程是系統進行資源分配和調度的一個獨立單位。每個進程都有自己的獨立記憶體空間,
不同進程通過進程間通訊來通訊。由於進程比較重量,佔據獨立的記憶體,
所以上下文進程間的切換開銷(棧、暫存器、虛擬記憶體、文件句柄等)比較大,但相對比較穩定安全。
執行緒:
執行緒是進程的一個實體,是 CPU 調度和分派的基本單位,
它是比進程更小的能獨立運行的基本單位.
執行緒自己基本上不擁有系統資源,只擁有一點在運行中必不可少的資源(如程式計數器,一組暫存器和棧),
但是它可與同屬一個進程的其他的執行緒共享進程所擁有的全部資源。
執行緒間通訊主要通過共享記憶體,上下文切換很快,資源開銷較少,但相比進程不夠穩定容易丟失數據。
協程:
協程是一種用戶態的輕量級執行緒,協程的調度完全由用戶控制。
協程擁有自己的暫存器上下文和棧。
協程調度切換時,將暫存器上下文和棧保存到其他地方,在切回來的時候,恢復先前保存的暫存器上下文和棧,
直接操作棧則基本沒有內核切換的開銷,可以不加鎖的訪問全局變數,所以上下文的切換非常快。
區別: 進程與執行緒比較: 執行緒是指進程內的一個執行單元,也是進程內的可調度實體。執行緒與進程的區別:
1) 地址空間:執行緒是進程內的一個執行單元,進程內至少有一個執行緒,它們共享進程的地址空間,
而進程有自己獨立的地址空間
2) 資源擁有:進程是資源分配和擁有的單位,同一個進程內的執行緒共享進程的資源
3) 執行緒是處理器調度的基本單位,但進程不是
4) 二者均可並發執行
5) 每個獨立的執行緒有一個程式運行的入口、順序執行序列和程式的出口,
但是執行緒不能夠獨立執行,必須依存在應用程式中,由應用程式提供多個執行緒執行控制
協程與執行緒進行比較:
1) 一個執行緒可以多個協程,一個進程也可以單獨擁有多個協程,這樣 Python 中則能使用多核 CPU。
2) 執行緒進程都是同步機制,而協程則是非同步
3) 協程能保留上一次調用時的狀態,每次過程重入時,就相當於進入上一次調用的狀態
161. 簡述 GIL
答: GIL:全局解釋器鎖。每個執行緒在執行的過程都需要先獲取 GIL,保證同一時刻只有一個執行緒可以執行程式碼。
執行緒釋放 GIL 鎖的情況:在 IO 操作等可能會引起阻塞的 systemcall 之前,可以暫時釋放 GIL,但在執行完畢後, 必須重新獲取 GIL,Python3.x 使用計時器(執行時間達到閾值後,當前執行緒釋放 GIL)或 Python2.x,tickets 計數達到 100 。
Python 使用多進程是可以利用多核的 CPU 資源的。
多執行緒爬取比單執行緒性能有提升,因為遇到 IO 阻塞會自動釋放 GIL 鎖。
162. 進程之間如何通訊
答: 可以通過隊列的形式,示例如下
from multiprocessing import Queue, Process
import time, random
# 要寫入的數據
list1 = ["java", "Python", "JavaScript"]
def write(queue):
"""
向隊列中添加數據
:param queue:
:return:
"""
for value in list1:
print(f"正在向隊列中添加數據-->{value}")
# put_nowait 不會等待隊列有空閑位置再放入數據,如果數據放入不成功就直接崩潰,比如數據滿了。put 的話就會一直等待
queue.put_nowait(value)
time.sleep(random.random())
def read(queue):
while True:
# 判斷隊列是否為空
if not queue.empty():
# get_nowait 隊列為空,取值的時候不等待,但是取不到值那麼直接崩潰了
value = queue.get_nowait()
print(f'從隊列中取到的數據為-->{value}')
time.sleep(random.random())
else:
break
if __name__ == '__main__':
# 父進程創建出隊列,通過參數的形式傳遞給子進程
#queue = Queue(2)
queue = Queue()
# 創建兩個進程 一個寫數據 一個讀數據
write_data = Process(target=write, args=(queue,))
read_data = Process(target=read, args=(queue,))
# 啟動進程 寫入數據
write_data.start()
# 使用 join 等待寫數據結束
write_data.join()
# 啟動進程 讀取數據
print('*' * 20)
read_data.start()
# 使用 join 等待讀數據結束
read_data.join()
print('所有的數據都寫入並讀取完成。。。')
163. IO 多路復用的作用?
答: 阻塞 I/O 只能阻塞一個 I/O 操作,而 I/O 復用模型能夠阻塞多個 I/O 操作,所以才叫做多路復用。
I/O 多路復用是用於提升效率,單個進程可以同時監聽多個網路連接 IO。 在 IO 密集型的系統中, 相對於執行緒切換的開銷問題,IO 多路復用可以極大的提升系統效率。
164. select、poll、epoll 模型的區別?
答: select,poll,epoll 都是 IO 多路復用的機制。I/O 多路復用就通過一種機制,可以監視多個描述符,一旦某個描述符就緒(一般是讀就緒或者寫就緒),能夠通知程式進行相應的讀寫操作。
select 模型: select 目前幾乎在所有的平台上支援,其良好跨平台支援也是它的一個優點。select 的一 個缺點在於單個進程能夠監視的文件描述符的數量存在最大限制,在 Linux 上一般為 1024,可以通過修改宏定義甚至重新編譯內核的方式提升這一限制,但 是這樣也會造成效率的降低。
poll 模型: poll 和 select 的實現非常類似,本質上的區別就是存放 fd 集合的數據結構不一樣。select 在一個進程內可以維持最多 1024 個連接,poll 在此基礎上做了加強,可以維持任意數量的連接。
但 select 和 poll 方式有一個很大的問題就是,我們不難看出來 select 是通過輪訓的方式來查找是否可讀或者可寫,打個比方,如果同時有 100 萬個連接都沒有斷開,而只有一個客戶端發送了數據,所以這裡它還是需要循環這麼多次,造成資源浪費。所以後來出現了 epoll 系統調用。
epoll 模型: epoll 是 select 和 poll 的增強版,epoll 同 poll 一樣,文件描述符數量無限制。但是也並不是所有情況下 epoll 都比 select/poll 好,比如在如下場景:在大多數客戶端都很活躍的情況下,系統會把所有的回調函數都喚醒,所以會導致負載較高。既然要處理這麼多的連接,那倒不如 select 遍歷簡單有效。
165. 什麼是並發和並行?
答:「並行是指同一時刻同時做多件事情,而並發是指同一時間間隔內做多件事情」。
並發與並行是兩個既相似而又不相同的概念:並發性,又稱共行性,是指能處理多個同時性活動的能力;並行是指同時發生的兩個並發事件,具有並發的含義,而並發則不一定並行,也亦是說並發事件之間不一定要同一時刻發生。
並發的實質是一個物理 CPU(也可以多個物理 CPU) 在若干道程式之間多路復用,並發性是對有限物理資源強制行使多用戶共享以提高效率。 並行性指兩個或兩個以上事件或活動在同一時刻發生。在多道程式環境下,並行性使多個程式同一時刻可在不同 CPU 上同時執行。
並行,是每個 CPU 運行一個程式。
166. 一個執行緒 1 讓執行緒 2 去調用一個函數怎麼實現
答:
import threading
def func1(t2):
print('正在執行函數func1')
t2.start()
def func2():
print('正在執行函數func2')
if __name__ == '__main__':
t2 = threading.Thread(target=func2)
t1 = threading.Thread(target=func1, args=(t2,))
t1.start()
167. 解釋什麼是非同步非阻塞?
答: 非同步 非同步與同步相對,當一個非同步過程調用發出後,調用者在沒有得到結果之前,就可以繼續執行後續操作。當這個調用完成後,一般通過狀態、通知和回調來通知調用者。對於非同步調用,調用的返回並不受調用者控制。
非阻塞 非阻塞是這樣定義的,當執行緒遇到 I/O 操作時,不會以阻塞的方式等待 I/O 操作的完成或數據的返回,而只是將 I/O 請求發送給作業系統,繼續執行下一條語句。當作業系統完成 I/O 操作時,以事件的形式通知執行 I/O 操作的執行緒,執行緒會在特定時候處理這個事件。簡答理解就是如果程式不會卡住,可以繼續執行,就是說非阻塞的。
168. threading.local 的作用?
答: threading.local()這個方法是用來保存一個全局變數,但是這個全局變數只有在當前執行緒才能訪問,如果你在開發多執行緒應用的時候,需要每個執行緒保存一個單獨的數據供當前執行緒操作,可以考慮使用這個方法,簡單有效。程式碼示例
import threading
import time
a = threading.local()#全局對象
def worker():
a.x = 0
for i in range(200):
time.sleep(0.01)
a.x += 1
print(threading.current_thread(),a.x)
for i in range(20):
threading.Thread(target=worker).start()
Git
169. 說說你知道的 git 命令
答: git init:該命令將創建一個名為 .git 的子目錄,這個子目錄含有你初始化的 Git 倉庫中所有的必須文件,這些文件是 Git 倉庫的骨幹 git clone url:將伺服器程式碼下載到本地 git pull:將伺服器的程式碼拉到本地進行同步,如果本地有修改會產生衝突。 git push:提交本地修改的程式碼到伺服器 git checkout -b branch:創建並切換分支 git status:查看修改狀態 git add 文件名:提交到暫存區 git commit -m “提交內容”:輸入提交的注釋內容 git log:查看提交的日誌情況
170. git 如何查看某次提交修改的內容
答:我們首先可以 git log 顯示歷史的提交列表 之後我們用 git show 便可以顯示某次提交的修改內容 同樣 git show filename 可以顯示某次提交的某個內容的修改資訊。