Python量化随笔丨那些年,我写Python踩过的坑(一)

    这是一篇关于Python量化的笔记~坑的时候有一种很奇怪的感觉,我对python似曾相识,又觉得陌生。打游戏发现新的隐藏关卡带来的是喜悦,而python的坑带来的是自我怀疑,甚至可能是实实在在的经济损失,所以,坑,我们踩过的,一定带你绕过去。

  下面让我们一起看看python隐藏的坑,排名不分先后。 >>>点击咨询量化投资行业前景

  1. Python量化之基础篇

  基础的坑,最重要的是举一反三,最怕的是在黑板上老师写了‘一’,回家在笔记本上写一个横线就不认识这种事情。

  忘记写冒号。在def,if,while,for等语句第一行某位输入”:”。

  缩进要一致。避免在缩进时混合使用制表符和空格。

  不要认为在原处修改对象的函数会返回结果。例如a = list.append(),a是不会被赋值的。

  不要在变量赋值前就使用变量。例如a = b写这行代码时,b在此之前一定要被赋值过的。

  函数调用一定要加括号。例如dataframe.dropna()。

  不要写死循环。例如while True: 之后的代码块中没有跳出循环的代码。

  判断两个变量是否相等用的是‘==’号。大多数初学者,觉得‘=’号就可以了。

  写字符串,写各种样式的括号时注意他们是成对出现的。常见['a', b']这样的错误,然后觉得自己代码没有问题的初学者。

  >>>点击咨询如何系统学习Python量化投资​

  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量化培训班哪家比较好?

  不过,坑总是有的,学习的时候你可以抱着填坑的心态,也可以怀着获取的目的,两者都取决于你的选择。而且,个人觉的,怀着获取的态度,容易满足,不断追求,也就一直快乐。

  最后,还是用毒翼神龙的一段话结个尾:“几盘卡带就可以陪我们走过整个童年的那个时代恐怕也一去不复返了吧。每天的生活被工作、学习、应酬充斥着,也让我们渐渐忘记了那个传说~虽然它是一段美丽的谣言,但想起那个信以为真的年纪,仍然有许多美好的回忆。我怀念那时单纯的快乐,怀念那个很容易就能满足的童心”
Python量化随笔丨那些年,我写Python踩过的坑(一)

  AQF考友群:760229148

  金融宽客交流群:801860357

  微信公众号:量化金融分析师

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章