Python 典型错误及关键知识点
1. 不要在 for 循環后面寫 else 塊
In [40]: for i in range(3):....: print i....: else:....: print "end"....:
0
1
2
end
記住一點:for 循環里面有 break 時 else 不會執行,否則執行完 for 循環會繼續執行 else 語句
2. 函數傳遞
Python函數參數傳遞注意點:
- 對于可變的對象的修改在函數外部和內部都可見,調用者和被調用者共享這個對象。
- 而對于不可變對象,由于并不能真正被修改,因為修改一般都是通過生成一個新的對象然后賦值來實現的。
3. 如何避開變量作用域的陷阱
全局變量簡單說就是這3點:
- 全局變量是位于模塊文件內部的頂層的變量名。
- 全局變量如果是在函數內被改變的話,一定要用global。
- 全局變量名在函數內部不經過聲明也可以被引用。
4. 函數內的變量解析原則
有的書上叫 LEGB 法則,其實講白了就是下面 4 個過程,當在函數中使用沒有聲明過的變量時,Python 的搜索順序是:
- 先是在函數內部的本地作用域(L)
- 然后是在上一層的函數的本地作用域(E)
- 然后是全局作用域(G)
- 最后是內置作用域(B)
5. 全局解釋器鎖(GIL)
是什么原因導致多線程不快反慢的呢?
原因就在于 GIL ,在 Cpython 解釋器(Python語言的主流解釋器)中,有一把全局解釋鎖(Global Interpreter Lock),在解釋器解釋執行 Python 代碼時,先要得到這把鎖,意味著,任何時候只可能有一個線程在執行代碼,其它線程要想獲得 CPU 執行代碼指令,就必須先獲得這把鎖,如果鎖被其它線程占用了,那么該線程就只能等待,直到占有該鎖的線程釋放鎖才有執行代碼指令的可能。
因此,這也就是為什么兩個線程一起執行反而更加慢的原因,因為同一時刻,只有一個線程在運行,其它線程只能等待,即使是多核CPU,也沒辦法讓多個線程「并行」地同時執行代碼,只能是交替執行,因為多線程涉及到上線文切換、鎖機制處理(獲取鎖,釋放鎖等),所以,多線程執行不快反慢。
但是多線程有個問題,怎么解決共享數據的同步、一致性問題,因為,對于多個線程訪問共享數據時,可能有兩個線程同時修改一個數據情況,如果沒有合適的機制保證數據的一致性,那么程序最終導致異常,所以,Python之父就搞了個全局的線程鎖,不管你數據有沒有同步問題,反正一刀切,上個全局鎖,保證數據安全。這也就是多線程雞肋的原因,因為它沒有細粒度的控制數據的安全,而是用一種簡單粗暴的方式來解決。
6. is 和 == 的區別
官方文檔中說 is 表示的是對象標示符(object identity),而 == 表示的是相等(equality)。is 的作用是用來檢查對象的標示符是否一致,也就是比較兩個對象在內存中的地址是否一樣,而 == 是用來檢查兩個對象是否相等。
我們在檢查 a is b 的時候,其實相當于檢查 id(a) == id(b)。而檢查 a == b 的時候,實際是調用了對象 a 的 --eq()–方法,a == b 相當于 a.–eq–(b)。
總結一下,is 是檢查兩個對象是否指向同一塊內存空間,而 == 是檢查他們的值是否相等。可以看出,is 是比 == 更嚴格的檢查,is 返回True表明這兩個對象指向同一塊內存,值也一定相同。
Python里和None比較時,為什么是 is None 而不是 == None 呢?
這是因為None在Python里是個單例對象,一個變量如果是None,它一定和None指向同一個內存地址。而 == None背后調用的是–eq–,而 eq 可以被重載,
7. 連接字符串用 join 還是 +
字符串是不可變對象,當用操作符+連接字符串的時候,每執行一次+都會申請一塊新的內存,然后復制上一個+操作的結果和本次操作的右操作符到這塊內存空間,因此用+連接字符串的時候會涉及好幾次內存申請和復制。而join在連接字符串的時候,會先計算需要多大的內存存放結果,然后一次性申請所需內存并將字符串復制過去,這是為什么join的性能優于+的原因。所以在連接字符串數組的時候,我們應考慮優先使用join。
8. 函數可以在容器中使用
函數可以容器中使用,比如列表,字典里面象參數一樣使用:
def show_apple(price):print "The apple's price is {0}".format(price)def show_orange(price):print "The orange's price is {}".format(price)func_dict = {'apple': show_apple, 'orange': show_orange}def main(fruit, price):func_dict.get(fruit)(price)main('apple', 10)
main('orange', 8)
9. __new__和__init__的區別
#實際上,__init__函數并不是真正意義上的構造函數,__init__方法做的事情是在對象創建好
#之后初始化變量。真正創建實例的是__new__方法。
class Person(object):def __init__(self, name, age):print("in __init__")self._name = nameself._age = agedef __new__(cls, *args, **kwargs):print("in __new__")instance = object.__new__(cls, *args, **kwargs)return instancep = Person("li", 20)## 代碼輸出
in __new__
in __init__
'''上面的代碼中實例化了一個Person對象,可以看到__new__和__init__都被調用了。
__new__方法用于創建對象并返回對象,當返回對象時會自動調用__init__方法進行初始化。
__new__方法是靜態方法,而__init__是實例方法。'''
-
__new__至少要有一個參數 cls,代表當前類,此參數在實例化時由 Python 解釋器自動識別。
-
__new__必須要有返回值,返回實例化出來的實例,這點在自己實現__new__時要特別注意,可以 return 父類(通過 super(當前類名, cls))__new__出來的實例,或者直接是 object 的__new__出來的實例。
-
__init__有一個參數 self,就是這個__new__返回的實例,__init__在__new__的基礎上可以完成一些其它初始化的動作,__init__不需要返回值。
-
如果__new__創建的是當前類的實例,會自動調用__init__函數,通過 return 語句里面調用的__new__函數的第一個參數是 cls 來保證是當前類實例,如果是其他類的類名;那么實際創建返回的就是其他類的實例,其實就不會調用當前類的__init__函數,也不會調用其他類的__init__函數。
10. with 與上下文管理器
with open('test.txt', 'w') as f:f.write('Hello world')'''上面的with代碼背后發生了些什么?我們來看下它的執行流程首先執行open('output', 'w'),返回一個文件對象
調用這個文件對象的__enter__方法,并將__enter__方法的返回值賦值給變量f
執行with語句體,即with語句包裹起來的代碼塊
不管執行過程中是否發生了異常,執行文件對象的__exit__方法,在__exit__方法中關閉文件。
這里的關鍵在于open返回的文件對象實現了__enter__和__exit__方法。
一個實現了__enter__和__exit__方法的對象就稱之為上下文管理器。__enter__方法在語句體執行之前進入運行時上下文,__exit__在語句體執行完后從運行時上下文退出。
在實際應用中,__enter__一般用于資源分配,如打開文件、連接數據庫、獲取線程鎖;
__exit__一般用于資源釋放,如關閉文件、關閉數據庫連接、釋放線程鎖。我們先來看下__enter__和__exit__方法的定義:__enter__() - 進入上下文管理器的運行時上下文,在語句體執行前調用。
如果有as子句,with語句將該方法的返回值賦值給 as 子句中的 target。__exit__(exception_type, exception_value, traceback) - 退出與上下文管理器相關的
運行時上下文,
返回一個布爾值表示是否對發生的異常進行處理。如果with語句體中沒有異常發生,
則__exit__的3個參數都為None,即調用 __exit__(None, None, None),
并且__exit__的返回值直接被忽略。如果有發生異常,則使用 sys.exc_info
得到的異常信息為參數調用__exit__(exception_type, exception_value, traceback)。
出現異常時,如果__exit__(exception_type, exception_value, traceback)返回 False,
則會重新拋出異常,讓with之外的語句邏輯來處理異常;如果返回 True,則忽略異常,
不再對異常進行處理。'''
我們來自己定義一個簡單的上下文管理器。這里不做實際的資源分配和釋放,而用打印語句來表明當前的操作。
class ContextManager(object):def __enter__(self):print("[in __enter__] acquiring resources")def __exit__(self, exception_type, exception_value, traceback):print("[in __exit__] releasing resources")if exception_type is None:print("[in __exit__] Exited without exception")else:print("[in __exit__] Exited with exception: %s" % exception_value)return Falsewith ContextManager():print("[in with-body] Testing")
運行上面代碼輸出以下內容:
[in __enter__] acquiring resources
[in with-body] Testing
[in __exit__] releasing resources
[in __exit__] Exited without exception
11. 淺拷貝與深拷貝
Python中對象的賦值實際上是簡單的對象引用,也就是說,當你創建一個對象,然后把它復制給另一個變量的時候,Python并沒有拷貝這個對象,而是拷貝了這個對象的引用。
In [1]: a = [1,2,3,4]In [2]: b = aIn [3]: id(a)
Out[3]: 140555756386496In [4]: id(b)
Out[4]: 140555756386496In [5]: id(a) == id(b)
Out[5]: True
淺拷貝
一般使用copy.copy(),可以進行對象的淺拷貝。它復制了對象但對于對象中的子對象,依然使用原始的引用。
In [7]: import copyIn [8]: a = [1, [1,2,3,4]]In [9]: b = copy.copy(a)In [10]: id(a)
Out[10]: 140555756497592In [11]: id(b)
Out[11]: 140555772667448In [12]: id(a) == id(b)
Out[12]: FalseIn [13]: id(a[1])
Out[13]: 140555756497376In [14]: id(a[1]) == id(b[1])
Out[14]: True# a[1] 和 b[1] 指向同一個對象
深拷貝
深度拷貝需要用copy.deepcopy()進行深拷貝。它會復制一個容器對象,以及它里面的所有元素(包含元素的子元素)
In [15]: a = [2, [3,4,5,6]]In [16]: b = copy.deepcopy(a)In [17]: id(a) == id(b)
Out[17]: FalseIn [18]: id(a[1]) == id(b[1])
Out[18]: False# a[1] 和 b[1] 指向不同的對象
12. 字典排序
In [23]: dd = {'cc':1, 'bb':2, 'aa':3}In [24]: dd
Out[24]: {'aa': 3, 'bb': 2, 'cc': 1}In [25]: sorted(dd.iteritems(), key=lambda x:x[0]) #表示按照key排序
Out[25]: [('aa', 3), ('bb', 2), ('cc', 1)]In [26]: sorted(dd.iteritems(), key=lambda x:x[1]) #表示按照value排序
Out[26]: [('cc', 1), ('bb', 2), ('aa', 3)]
13. 字典取值
建議:盡量用dict.get()來代替dict[key]
14. 字典中提取部分子集
In [27]: students_score={'jack':80,'james':91,'leo':100,'sam':60}In [28]: good_score={name:score for name,score in students_score.items() if score>90}In [29]: good_score
Out[29]: {'james': 91, 'leo': 100}
15. 字典的翻轉
In [30]: a = {'a':1, 'b':2, 'c':3}In [31]: invert_a = dict([(v, k) for k, v in a.iteritems()])In [32]: invert_a
Out[32]: {1: 'a', 2: 'b', 3: 'c'}
16. 可變與不可變類型
將一個整數變量傳遞給函數,函數對它進行操作,但原整數變量a不發生變化。對于基本數據類型的變量,變量傳遞給函數后,函數會在內存中復制一個新的變量,從而不影響原來的變量。(我們稱此為值傳遞)。
In [4]: a = 1In [5]: def change_integer(a):...: a = a + 1...: return a...: In [6]: change_integer(a)
Out[6]: 2In [7]: a
Out[7]: 1
對于表來說,表傳遞給函數的是一個指針,指針指向序列在內存中的位置,在函數中對表的操作將在原有內存中進行,從而影響原有變量。 (我們稱此為指針傳遞)
In [1]: b = [1,2,3]...: def change_list(b):...: b[0] = b[0] + 1...: return b...: In [2]: print change_list(b)
[2, 2, 3]In [3]: b
Out[3]: [2, 2, 3]
注意:元組是不可變的,但是元組里面子元素如果含有列表,則列表子元素又是可變的。
>>> t = (1,2,[3,4], 5)
>>> id(t)
48192144
>>> t[2].append(10)
>>> t
(1, 2, [3, 4, 10], 5)
>>> id(t)
48192144
總結
以上是生活随笔為你收集整理的Python 典型错误及关键知识点的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Python2 与 Python3 区别
- 下一篇: Python 将字符串转为字典