1.可迭代對象、迭代器、生成器
可迭代對象包括迭代器,序列,字典,支持in和not in訪問對象中的元素。迭代器可以記住操作的位置,是可迭代對象的子集,而生成器又是迭代器的子集。
迭代器與其他可迭代對象的區別在於迭代器支持__next()__方法,可以通過iter()方法將可迭代對象轉化為迭代器。
生成器是迭代器的子集。
yield可以與函數組合成一個生成器,其作用有2個:1.類似於return,返回一個值,函數執行到此處退出。
2.可以記住每次遍歷的位置,再次調用函數,就從上次執行的yield的下一句開始執行。
生成器與迭代器相比,在提高運行速度的同時,大大降低了占用的內存。
可以看個例子:
def foo():
print("starting...")
while True:
res = yield 4
print("res:",res)
g = foo()
print(next(g))
print("*"*20)
print(next(g))
輸出:
starting...
4
********************
res: None
4
2.深拷貝與淺拷貝
在python中,對象賦值實際上是對象的引用。當創建一個對象,然后把它賦給另一個變量的時候,python並沒有拷貝這個對象,而只是拷貝了這個對象的引用。
判斷的法則是:看拷貝前后變量的內存地址是否發生變化
(1)直接賦值,默認淺拷貝傳遞對象的引用而已,原始列表改變,被賦值的b也會做相同的改變
>>> b=alist
>>> print b
[1, 2, 3, ['a', 'b']]
>>> alist.append(5)
>>> print alist;print b
[1, 2, 3, ['a', 'b'], 5]
[1, 2, 3, ['a', 'b'], 5]
(2)copy淺拷貝,沒有拷貝子對象,所以原始數據改變,子對象會改變,但注意僅僅是被拷貝的部分
>>> import copy
>>> c=copy.copy(alist)
>>> print alist;print c
[1, 2, 3, ['a', 'b']]
[1, 2, 3, ['a', 'b']]
>>> alist.append(5)
>>> print alist;print c
[1, 2, 3, ['a', 'b'], 5]
[1, 2, 3, ['a', 'b']]
>>> alist[3]
['a', 'b']
>>> alist[3].append('cccc')
>>> print alist;print c
[1, 2, 3, ['a', 'b', 'cccc'], 5]
[1, 2, 3, ['a', 'b', 'cccc']] 里面的子對象被改變了
(3)深拷貝,包含對象里面的自對象的拷貝,所以原始對象的改變不會造成深拷貝里任何子元素的改變
>>> import copy
>>> d=copy.deepcopy(alist)
>>> print alist;print d
[1, 2, 3, ['a', 'b']]
[1, 2, 3, ['a', 'b']]始終沒有改變
>>> alist.append(5)
>>> print alist;print d
[1, 2, 3, ['a', 'b'], 5]
[1, 2, 3, ['a', 'b']]始終沒有改變
>>> alist[3]
['a', 'b']
>>> alist[3].append("ccccc")
>>> print alist;print d
[1, 2, 3, ['a', 'b', 'ccccc'], 5]
[1, 2, 3, ['a', 'b']] 始終沒有改變
3.python下划線
-
以單下划線開頭,表示這是一個保護成員,只有類對象和子類對象自己能訪問到這些變量。以單下划線開頭的變量和函數被默認當作是內部函數,使用from module improt *時不會被獲取,但是使用import module可以獲取
-
以單下划線結尾僅僅是為了區別該名稱與關鍵詞
-
雙下划線開頭,表示為私有成員,只允許類本身訪問,子類也不行。在文本上被替換為_class__method
-
雙下划線開頭,雙下划線結尾。一種約定,Python內部的名字,用來區別其他用戶自定義的命名,以防沖突。是一些 Python 的“魔術”對象,表示這是一個特殊成員,例如:定義類的時候,若是添加__init__方法,那么在創建類的實例的時候,實例會自動調用這個方法,一般用來對實例的屬性進行初使化,Python不建議將自己命名的方法寫為這種形式。
4.方法
Python其實有3個方法,即靜態方法(staticmethod),類方法(classmethod)和實例方法,如下:
class A(object):
'''
實例方法:對於實例方法,我們知道在類里每次定義方法的時候都需要綁定這個實例,就是`foo(self, x)`,為什么 要這么做呢?因為實例方法的調用離不開實例,我們需要把實例自己傳給函數,調用的時候是這樣的`a.foo(x)`(其實 是`foo(a, x)`).
'''
def foo(self,x):
print "executing foo(%s,%s)"%(self,x)
'''
類方法一樣,只不過它傳遞的是類而不是實例,`A.class_foo(x)`.注意這里的self和cls可以替換別的參數,但是 python的約定是這倆,還是不要改的好.
'''
@classmethod
def class_foo(cls,x):
print "executing class_foo(%s,%s)"%(cls,x)
'''
對於靜態方法其實和普通的方法一樣,不需要對誰進行綁定,唯一的區別是調用的時候需要使用`a.static_foo(x)` 或者`A.static_foo(x)`來調用.
'''
@staticmethod
def static_foo(x):
print "executing static_foo(%s)"%x
a=A()
5.為什么python不支持函數重載
函數重載主要是為了解決兩個問題。
1。可變參數類型。
2。可變參數個數。
對於情況 1 ,函數功能相同,但是參數類型不同,python 如何處理?答案是根本不需要處理,因為 python 可以接受任何類型的參數,如果函數的功能相同,那么不同的參數類型在 python 中很可能是相同的代碼,沒有必要做成兩個不同函數。
那么對於情況 2 ,函數功能相同,但參數個數不同,python 如何處理?大家知道,答案就是缺省參數。對那些缺少的參數設定為缺省參數即可解決問題。因為你假設函數功能相同,那么那些缺少的參數終歸是需要用的。
6.函數參數傳遞
對於不可變參數,例如數字,字符串,元組,類似於值傳值,函數內部產生一個副本,函數內對參數的操作,不會影響到函數外的參數。
對於可變參數,例如列表,集合,字典,類似於引用傳值,在函數內對參數的操作,在函數外部也會顯現出來,但是這種操作不包括重新創建一個對象的修改,這樣內存內置就會發生變化。
7.*args
and **kwargs
表示可變長度的參數,其中*args
表示位置參數,**kwargs
表示關鍵字參數。
args
因為 *
前綴 的存在, 會把位置參數收集到一個tuple
元組中。
kwargs
因為 **
前綴 的存在, 會把位置參數收集到一個dict
字典中
當然 args
和 kwargs
可以為空
8.閉包
閉包又被稱為閉合函數,其特征是:一個函數(外函數)嵌套了另外一個函數(內函數),外函數返回了內函數的引用,而內函數使用了外函數的臨時變量。
當外函數執行時,會將外函數的臨時變量存儲下來供內函數使用,需要注意是,多次調用閉包函數,其內函數使用的都是一套外函數的臨時變量。可以使用閉包的 closure 屬性,來查看該臨時變量的地址。
def nth_power(exponent):
def exponent_of(base):
return base ** exponent
return exponent_of
square = nth_power(2)
#查看 __closure__ 的值
print(square.__closure__)
************************************
(<cell at 0x0000014454DFA948: int object at 0x00000000513CC6D0>,)
為什么要使用閉包函數?
閉包函數的優勢主要在多次調用上,一般情況下,在確定具有某些功能的函數的時候,都需要先執行一些額外的工作,如果多次執行這個函數,那么這些額外的操作會執行多次,而使用閉包函數,就可以將這些額外操作放在外函數內,這樣閉包函數的多次調用,便會直接去提取外函數的這些變量。避免了不必要的開銷,提高了程序的運行效率。此外,表達也會更加簡潔。
9.裝飾器
裝飾器基於閉包,本質上是一個函數,其可以讓函數在不做任何代碼改動的情況下,增加其額外的功能。
def test(func):
def wrapper():
print("ok")
return func()
return wrapper
這是一個最簡單的裝飾器的例子, print("ok")是原本的功能,func()是拓展額外的功能。從這也可以看出,他的基本結構就是一個閉包。
@test
def func():
print("yes")
func()
***************************
ok
yes
@符號是裝飾器的語法糖,等同於func=test(func),其避免了額外的賦值操作。
從以上的例子不難看出,裝飾器提高了程序的可重復利用性,並增加了程序的可讀性。
-
帶參數的裝飾器
def nth_power(exponent): def exponent_of(base): return base ** exponent return exponent_of
-
類裝飾器
相比於函數裝飾器,類裝飾器靈活度更大,封裝性更強。使用類裝飾器可以依靠內部的__call__方法
class test(object): def __init__(self,func): self._func=func def __call__(self, *args, **kwargs): print("ok") self._func() print("yes") @test def foo(): print("good") foo()
-
functools.wrap
裝飾器有個問題就是func的函數的元信息會被wrapper取代,這些元信息包括__name__,docstring等
這個時候可以使用functools.wrap,wraps本身也是一個裝飾器,其可以使得func的函數的元信息不會被wrapper取代。
from functools import wraps def test(func): @wraps(func) def wrapper(): print("ok") return func() return wrapper @test def func(): print("yes") print(func.__name__) #加 @wraps返回func,不加 @wraps返回wrapper
10.read,readline和readlines
- read 讀取整個文件
- readline 讀取下一行,使用生成器方法
- readlines 讀取整個文件到一個迭代器以供我們遍歷
11.內存管理
python的內存管理機制主要類似於金字塔模型
- -1,-2層主要有操作系統進行操作;
- 第0層是C中的malloc,free等內存分配和釋放函數進行操作;
- 第1層和第2層是內存池,有Python的接口函數PyMem_Malloc函數實現,當對象小於256K時有該層直接分配內存;
- 第3層是最上層,也就是我們對Python對象的直接操作;
對第二層的python的內存池機制做一些說明:
由於頻繁創建大量消耗小內存的對象時,調用new/malloc會導致大量的內存碎片,致使效率降低,因此第1層和第2層的內存池,主要是為了處理小塊內存的申請和釋放,操作原理是先預先在內存中申請一定數量的,大小相等的內存塊留作備用,當有新的內存需求時,就先從內存池中分配內存給這個需求,不夠了之后再使用第0層的malloc申請256kb的內存,這樣做最顯著的優勢就是能夠減少內存碎片,提升效率。
釋放內存時,當一個對象的引用計數變為 0 時,python就會調用它的析構函數。調用析構函數並不意味着最終一定會調用free 釋放內存空間,如果真是這樣的話,那頻繁地申請、釋放內存空間會使 Python的執行效率大打折扣。因此在析構時也采用了內存池機制,從內存池申請到的內存會被歸還到內存池中,以避免頻繁地 釋放 動作。
12.異常處理
-
try…except...else
s = 'Hello girl!' try: print( s[100]) except: print('error') print ('continue') ************************ ''' try-except語句按照如下方式工作: 1)執行try子句(在關鍵字try和except之間的語句) 2)如果沒有異常發生,忽略except子句,try子句執行后結束 3)如果在執行try子句的過程中發生了異常,那么try子句余下的部分將被忽略;如果異常的類型和excet之后的名稱相符,那么對應的except子句將被執行;最后執行try語句之后的代碼 4)如果一個異常沒有與任何except匹配,那么這個異常將會傳遞給上層的try語句中 一個try語句可以包含多個except子句,分別用來處理特定的異常,但是最多只有一個except子句分支會被執行 一個except子句可以同時處理多個異常,這些異常將被放在圓括號里以元組形式出現 except關鍵字后面甚至可以不加名稱(當通配符使用) 另外,try-except語句還有一個可選的else子句,這個子句必須寫在except子句之后,當且僅當try子句沒有任何異常發生時執行(Phthonic的寫法) '''
-
try…except...finally
簡而言之,finally語句就是用來做異常處理的掃尾工作的
不管try子句中到底有沒有異常,finally子句都會執行
如果一個異常在try子句中(或者except和else子句中)被拋出,而沒有任何的except把它截住,那么這個異常就會在fianly子句執行后再次被拋出
13.多線程、多進程
-
多線程和多進程關系
''' ”進程是資源分配的最小單位,線程是CPU調度的最小單位“ 做個簡單的比喻:進程=火車,線程=車廂 1.線程在進程下行進(單純的車廂無法運行) 2.一個進程可以包含多個線程(一輛火車可以有多個車廂) 3.不同進程間數據很難共享(一輛火車上的乘客很難換到另外一輛火車,比如站點換乘) 4.同一進程下不同線程間數據很易共享(A車廂換到B車廂很容易) 5.進程要比線程消耗更多的計算機資源(采用多列火車相比多個車廂更耗資源) 6.進程間不會相互影響,一個線程掛掉將導致整個進程掛掉(一列火車不會影響到另外一列火車,但是如果一列火車上中間的一節車廂着火了,將影響到所有車廂) 7.進程可以拓展到多機,進程最多適合多核(不同火車可以開在多個軌道上,同一火車的車廂不能在行進的不同的軌道上) 8.進程使用的內存地址可以上鎖,即一個線程使用某些共享內存時,其他線程必須等它結束,才能使用這一塊內存。(比如火車上的洗手間)-"互斥鎖"進程使用的內存地址可以限定使用量(比如火車上的餐廳,最多只允許多少人進入,如果滿了需要在門口等,等有人出來了才能進去)-“信號量” '''
14.GIL
-
背景:
1.GIL是全局解釋器鎖,用來保證數據安全。
2.cpu在同一時間只能執行一個線程。(單核cpu的多線程其實是單線程的並發,並行是同一時刻干多個事情,並發是相同的時間間隔干多個事情)
-
GIL的執行過程
在python中,GIL的執行過程是:1.線程獲取GIL。2.執行代碼直至sleep或者python虛擬機將其掛起。3.釋放GIL
從這里看出,某個線程想要執行,必須先拿到GIL,我們可以把GIL看作是“通行證”,並且在一個python進程中,GIL只有一個。拿不到通行證的線程,就不允許進入CPU執行,因此python的多線程其實是單線程
-
python多線程的效率如何呢?
判斷效率前,首先需要明確GIL鎖的釋放方法:當前線程遇見IO操作或者ticks計數達到100時,自動釋放GIL。(在python3中改為了計時器,達到一定的時間來釋放)
- 對於計算密集型任務來說,ticks計數很容易達到100,這樣很容易出發GIL的釋放於再競爭,容易造成資源浪費。
- 對於IO密集型任務來說,IO操作會進行IO等待。此時,開啟多線程能在線程A等待時,自動切換到線程B,可以不浪費CPU的資源,從而能提升程序執行效率
- 需要強調的是,多核多線程比單核單線程更差,因為涉及了核與核之間線程的競爭,容易造成線程顛簸。
-
綜上所述,在多核任務下,每個進程都有一個GIL鎖,因此多進程類似於多線程,多進程的執行效率更高。
參考:https://zhuanlan.zhihu.com/p/20953544
15.內置數據類型、可變數據類型、不可變數據類型
-
內置數據類型
數字,字符串,列表,字典,元組,集合
-
可變數據類型
列表,集合,字典
-
不可變數據類型
數字,字符串,元組
16.python的特點
python是面向對象的解釋型的腳本語言,主要特點如下
- 簡單好學(近乎偽代碼)
- 開源
- 可擴展性(第三方庫很多)
- 可移植性
- 解釋型(編譯型語言:c++/c需要編譯器編譯,python之間利用解釋器,將源代碼轉換成機器語言)
- 高級語言(自動管理內存,封裝了很多實現細節)
- 功能強大
17.python解釋器的種類與特點
-
CPython
c語言開發的 使用最廣的解釋器
-
IPython
- 基於cpython之上的一個交互式計時器 交互方式增強 功能和cpython一樣
-
JPython
- 運行在Java上的解釋器 直接把python代碼編譯成Java字節碼執行