這是一篇關於Python量化的筆記~坑的時候有一種很奇怪的感覺,我對python似曾相識,又覺得陌生。打遊戲發現新的隱藏關卡帶來的是喜悅,而python的坑帶來的是自我懷疑,甚至可能是實實在在的經濟損失,所以,坑,我們踩過的,一定帶你繞過去。
下面讓我們一起看看python隱藏的坑,排名不分先後。 >>>點擊諮詢量化投資行業前景
1. Python量化之基礎篇
基礎的坑,最重要的是舉一反三,最怕的是在黑板上老師寫了‘一’,回家在筆記本上寫一個橫線就不認識這種事情。
忘記寫冒號。在def,if,while,for等語句第一行某位輸入”:”。
縮進要一致。避免在縮進時混合使用製表符和空格。
不要認爲在原處修改對象的函數會返回結果。例如a = list.append(),a是不會被賦值的。
不要在變量賦值前就使用變量。例如a = b寫這行代碼時,b在此之前一定要被賦值過的。
函數調用一定要加括號。例如dataframe.dropna()。
不要寫死循環。例如while True: 之後的代碼塊中沒有跳出循環的代碼。
判斷兩個變量是否相等用的是‘==’號。大多數初學者,覺得‘=’號就可以了。
寫字符串,寫各種樣式的括號時注意他們是成對出現的。常見['a', b']這樣的錯誤,然後覺得自己代碼沒有問題的初學者。
2. Python量化之進階篇
有一句歌詞唱的挺好的,“跨過這道山,越過那道嶺,眼前又是一座峯”。基礎的坑排完了,還會有更多,更隱藏的坑等着你去踩。(比較熟悉這個歌的同學,我覺得你大概率上是北方人,更具體點的話,是東北人)
2.1 賦值僅僅生成引用
a = [1, 2, 3]
b = a
這裏你認爲自己構建了兩個相同的列表,準備用在不同的用途。然而當開開心心的進行接下來的其他操作時,比如,對a進行修改,就發生了其他的事情。
a[1] = 100000a
out: [1, 100000, 3]
b
out: [1, 100000, 3]
結果發現,本來希望只對a進行修改,結果發現b也受到了影響,其原因在於通過b = a的方式進行對b賦值,其實a、b指向同一個列表對象。我們可以通過查看a、b的內存地址或用is進行驗證
print(id(a), id(b))
out: 124006856 124006856
可以看到a、b指向的其實是同一塊內存
a is b
out: True
用is也檢測出a和b完全就是一個東西的不同名字而已。
上面的賦值方法還是比較容易看出,因爲有等號,那麼下面的賦值方法可能就稍微難一點看出來了。
c = [1, a, 3]
c
out: [1, [1, 100000, 3], 3]
當對a修改時,c同樣也會受到影響
a.append(100000)
a
out: [1, 100000, 3, 100000]
c
out: [1, [1, 100000, 3, 100000], 3]
所以,不要覺得寫完了,print出來的東西看着和自己想的一樣就萬事大吉,不實際踩一下肯定不知道接下來有坑。
那麼,如何解決呢?用 .copy(),這樣就會產生兩個不相干的對象,當然如果不嫌麻煩的話,可以把相同的東西再打一遍,然後,你有沒有看到同行鄙視的眼神?
我們看一下效果。
a = [1, 2, 3]
b = a.copy()
a is b
out: False
print(id(a), id(b))
out:125323528 87389000
可以看到a,b指向了不同的內存地址,並且用is檢測顯示是不同對象。
接下來修改a。
a[1] = 100000a
out: [1, 100000, 3]
b
out: [1, 2, 3]
可以看到修改a已經不會對b產生影響了,此坑已填。
2.2 乘法與嵌套列表
編程的某個時候,你希望生成這樣一個嵌套列表[[],[],[],..., [],[]],裏面的列表爲空或者默認值,那麼第一選擇肯定是利用列表乘法一次性生成多個列表,像這樣
a = [[]] * 5a
out: [[], [], [], [], []]
確實滿足了需求,然後當你開開心心的使用時,發覺事情有點不太對。比如對a列表中作爲元素的某一個列表進行修改
a[0].append(1000)
a[0]
out: [1000]
a
out: [[1000], [1000], [1000], [1000], [1000]]
然後發現,怎麼所有作爲元素的列表全都發生了變化。這次同樣可以用id或這is進行檢測。
print(id(a[0]), id(a[1]))
out: 125325256 125325256
a[0] is a[1]
out: True
可以看出,原來a列表中作爲元素的每一個列表,其實都是同一個東西,這也就解釋了爲什麼對其中一個作爲元素的列表進行原地修改時,其他所有作爲元素的列表也發生了變化。
那麼,解決方案如下
a = [[] for i in range(5)]
a
out: [[], [], [], [], []]
a[0] is a[1]
out: False
print(id(a[0]), id(a[1]))
out: 125323144 125321544
可以看到,a中作爲元素的列表已經不是同一個了,這樣對其中的列表進行修改時候就不會影響其他列表。
a[0].append(1000)
a
out: [[1000], [], [], [], []]
此坑已填
2.3 本地變量靜態檢測
剛剛瞭解作用域的同學應該對LEGB原則有一定了解,然後實踐中可能大膽的寫出了這樣的函數。
a = 1def print_a():
print(a)
a = 2
這個函數的目的也比較容易理解,打印a的值之後將a的值修改爲2,但是,實際運行時發生了這樣的事情。
print_a()
---------------------------------------------------------------------------
out: UnboundLocalError Traceback (most recent call last)
<ipython-input-24-4bb72463237f> in <module>()
----> 1 print_a()
<ipython-input-23-feb3b9a58246> in print_a()
1 a = 1
2 def print_a():
----> 3 print(a)
4 a = 2
UnboundLocalError: local variable 'a' referenced before assignment
發生這樣問題的原因是在python讀入並編譯這段代碼時,發現def裏面有一個對a賦值的語句,然後決定a在函數中屬於本地變量名。那麼當print_a執行時,就會對print(a)按照LEGB原則執行搜索,發現a屬於本地變量名但還未賦值。也就是我們在前面基礎坑裏面提到的在變量未賦值前進行使用。
解決方案需要使用global聲明a是一個全局變量。
a = 1def print_a():
global a
print(a)
a = 2
print_a()
out: 1
a
out: 2
可以看到,函數已經可以正常使用,並且全局變量a按照預期進行了修改。此坑已填。
2.4 可變對象作函數默認參數
默認參數在def語句運行時完成了保存,對於可變對象而言,當函數被調用並對該參數進行原地修改,默認參數將發生變化並進而影響接下來的函數調用。
先用可變對象作爲默認參數編寫一個函數。
def test(a=[]):
a.append(1)
print(a)
接下來多次調用test函數,你一定以爲每次打出來的都是一個包含1的列表。但是,事實並不是這樣。
test()
out: [1]
test()
out: [1, 1]
test()
out: [1, 1, 1]
是不是感覺三觀盡毀啊。
當然,有坑就會有填坑的方案。官方給出的標準解決方案是這樣的。
def test(a=None):
if a is None:
a = []
a.append(1)
print(a)
test()
out: [1]
test()
out: [1]
可以看到,test多次被調用已經不會出現之前的情況了。此坑已填。
2.5 嵌套在循環中的lambda
在知道python中一切皆對象之後,也許你就會嘗試把一些特別的東西放進一個list,雖然不清楚未來實際會不會用到這樣的寫法,反正先試試。然後你就想將一套簡單的函數放進一個列表,然後用lambda編寫。
func_list = []for i in range(5):
func_list.append(lambda x: x + i)
這樣生成了一個元素爲函數的列表,其中每個函數都可以傳入一個參數,然後返回的是傳入值和0,1,2,3,4的和。當然理想情況下是這樣,現實並不會如此。
func_list0
out: 5
func_list1
out: 5
func_list2
out: 5
可以發現,所有結果都是4+1的和。發生這種現象的原因在於變量i在函數調用時才進行查找,而此時循環已經結束,i=4,所以所有函數對象在被調用時參數i全部都爲5.
那麼解決方案就是按照本文2.4中寫的,默認參數在def語句運行時完成了保存,也就是使用默認參數的方式解決該問題。
func_list = []for i in range(5):
func_list.append(lambda x, i=i: x + i)
func_list0
out: 1
func_list1
out: 2
func_list2
out: 3
可以看到,問題已經得到解決。此坑已填。
2.6 列表和enumerate
熟悉列表,學習python算是入門;熟悉enumerate,學習python可以說不是純小白了。enumerate返回下標和元素的方式還是爲python使用者提供了不大不小的便利,不過同時也挖下了一個坑。
比如你寫了一個循環,想從列表中刪除偶數。
a = [1,2,3,4,5,6]for i, item in enumerate(a): if a[i] % 2==0: del a[i]
a
out: [1, 3, 5]
是不是想說沒什麼錯誤啊,然後在給你看看這個。
a = [1,2,6,3,4,4]for i, item in enumerate(a): if a[i] % 2==0: del a[i]
a
out: [1, 6, 3, 4]
enumerate(a)
out: <enumerate at 0x78e6d80>
產生這個問題的原因是在當i=1時,a[1]的值爲2,符合條件,那麼刪除2這個值之後,a整個列表發生了變化,6這個數值前進一個位置到了a[1]的位置,然後i在執行下一次循環時候值取了2,我們以爲應該對應着檢驗6,其實6被前移一個位置,也就被遺漏了。
下面我們將for 循環的部分手動執行,看現實是不是和我們理解的一樣。
a = [1,2,6,3,4,4]
e = enumerate(a)
e
out: <enumerate at 0x78edc18>
i, item = e.next()
(i, item)
out: (0, 1)
if a[i] % 2==0: del a[i]
a
out: [1, 2, 6, 3, 4, 4]
此時我們可以看到,執行完循環的第一步,a並沒有發生變化。接下來我們繼續第二步。
i, item = e.next()
(i, item)
out: (1, 2)
if a[i] % 2==0: del a[i]
a
out: [1, 6, 3, 4, 4]
可以發現,a已經發生了變化,那麼我們接下來只要看在enumerate提供新的下標好元素是不是還按照未調整的a進行提供的。當然,可以告訴你們答案,肯定不是了。
i, item = e.next()
(i, item)
out: (2, 3)
可以看到6已經不是第三個元素, 它通過向前移動一個位置的方式逃過了檢查。這個坑真的是非常難發現,因爲有時候碰巧你的運算方式和提供的列表剛剛好結果是你想要的,然後讓你覺得這樣用就是正確的。這種時候就非常可怕了。
這個坑的解決方案可以使用列表解析式添加篩選條件即可。
a = [1, 2, 6, 3, 4, 4]
a = [x for x in a if x%2 != 0]
a
[1, 3]
此坑已填。
是不是覺得覺得一不留神就掉進Python的坑裏了?哈哈哈,寫到這裏,python編程一些比較基礎的坑也已經描述的比較詳細了。我從來不產生失落感,我只是信心的粉碎機。當你覺得自己python已經學的差不多的時候,總會有那麼一個人、或者一篇文章告訴你,你懂得還不夠多。如果你想學習Python量化培訓相關的內容的話,歡迎閱讀這篇文章:求推薦!上海Python量化培訓班哪家比較好?
不過,坑總是有的,學習的時候你可以抱着填坑的心態,也可以懷着獲取的目的,兩者都取決於你的選擇。而且,個人覺的,懷着獲取的態度,容易滿足,不斷追求,也就一直快樂。
最後,還是用毒翼神龍的一段話結個尾:“幾盤卡帶就可以陪我們走過整個童年的那個時代恐怕也一去不復返了吧。每天的生活被工作、學習、應酬充斥着,也讓我們漸漸忘記了那個傳說~雖然它是一段美麗的謠言,但想起那個信以爲真的年紀,仍然有許多美好的回憶。我懷念那時單純的快樂,懷念那個很容易就能滿足的童心”
AQF考友羣:760229148
金融寬客交流羣:801860357
微信公衆號:量化金融分析師