工作三周的碎语

工作满三周,我的第一个任务也交付测试中,如无意外很快就能上线了。三周来,学了很多知识,也逐渐适应了和工程师们一起工作的气氛。刚开始的时候,我一个人憋着不好意思问同事,结果浪费了好几天时间,后来才知道其实工程师之间每天问问题是常态。后来逐渐和大家熟络起来,也就轻松很多了。我项目做得速度挺慢,但是好在同事们都很乐意帮我答疑解惑,倒也不会遇到死胡同。
同事里,有几个南方科技大学的金融、金融数学出身的实习生,水平很不错,读英文文献,写量化策略,甚至还写的一手好 Java/Python,让人刮目相看。南科大这所新学校的教学质量真让人佩服,准毕业生们的素质比当年的我强多了。
三周工作中,每天早晚通勤各一小时,没时间打游戏,也没时间写博客,在任务交付前期压力巨大,总觉得自己拖了团队的后腿。我估计这种感觉随着技术的进步会逐渐消退,但是紧迫感恐怕是所有工程师永远无法摆脱的心头巨石。
所以这周末我不加班了(前两个周末都在加班),好好的打开 steam 玩几盘游戏,看两部电影,陪老婆出去散散步。此时此刻,经常动辄一整年不工作的我才觉得,在打仗般的工程开发以外,温柔日常生活真的是最让人放松和热爱的。
Python 是个好东西,愈发觉得无论毕业于什么专业、目前做什么工作,都要好好学一学编程,编程会给自己提供极大的选择余地,有太多工作可以用代码来完成。今天代码在日程生活中的应用依然太少,如果做到每个人都能拿起编程这个武器来武装自己,那世界或许能美好很多。

Tagged : / /

我的求职经历

2015年下半年,我在一家外贸软件企业任运营总监,某天无意间看了一眼当时公司产品的前端代码,竟在已发布的产品中发现各种注释,而且注释都是51cto之类网站的教学文章链接。那时候想,如果让我来做的话,应该比他们做的更专业一些。在思考了几个月后,2015年11月,我申请离职,开始脱产学习 Python。
『他们不行,我上!』这个理由,其实是我学很多东西的动力。例如两年多前学爵士鼓,就是看侨城堂教会的鼓手实在太水、于是买了 Roland TP4K 开始练习的。后来没有坚持练下去,水平也就一般般,但临时顶场什么的已经无压力了。
断断续续学了大半年 Python,在闭门造车的做了几个小项目后,我从2016年9月开始投简历。最开始在拉勾上被拒了大概二十几遍,只拿到一个面试,是给一家网站做分布式爬虫,面试结果不好,而我对这种纯应用型的部门也不太感冒(虽然自己技术不好,但一直有个目标,就是要去技术型的公司)。
后来觉得这种海投策略不行,郭老师给我提意见,说写个 cover letter 吧。我乖乖的听话,而当时自己完全没想到这个举动会带来后面的巨大收获。

某日在 v2ex 上闲逛,发现 Ricequant 在招聘 Python 工程师,于是研究了一下这家创业公司,发现居然还开源了一个量化策略研究框架,创始人技术也很好,就写了一封长长的 cover letter 过去,居然也混到了一个面试(收到 Ricequant 面试通知电话的时候,我正在上文提到的那家公司面试中)。9月27日来到 Ricequant ,CTO 正和另一个求职者谈话,于是安排了一位工程师来面我,很快工程师觉得我基础太差无法胜任。CTO 此时也闲下来了,简单聊了一下后,留了学习排序算法的作业。后来我整个十一假期都在做这个作业。
十一结束后发邮件交付了作业,以为可以轻松一下了,但1个小时后就收到回信,要求用代码实现所有排序并提了一堆需求。二话不说,接下来一个星期就继续埋头苦干,把之前没搞懂的面向对象、递归、单元测试等等搞定。第2次作业交付后,长舒一口气,但是还是在2天后收到催命邮件,要求我继续优化代码。这一次把算法部分和界面部分分开,同时做多线程优化等等。就这样又忙了5天,交付了第三次作业,同时也第二次去 Ricequant 面试,并现场拿到 offer。我问了 CTO 为什么会这样反复测验和面试我这样的初学者而不是直接筛掉,他说你 cover letter 太有激情了。此处为郭老师鼓掌三分钟。
(在二面 Ricequant 之前,我还收到了腾讯云的电话面试,依旧因技术太差而被直接告知不行,但建议我转投运营开发岗位。面试我的工程师人也很好,但我没有再投腾讯。)
从开始学习 Python,到找到工作,刚好一年。一年间,我的学习速度很慢,又是零基础,对自己的要求也不严格,经常连续半个月没写几行代码。好在朱老师一直鼓励我,说小步快跑是坠吼的,不用强求速度。事实也证明我的确更适合这种轻松的学习状态,而不是苦大仇深的埋头苦学。
我在刚开始入门时报过开智的 Python 入门班,相信我,很垃圾,不要浪费钱。Python 的入门资料很丰富,看书、在 Stackoverflow 和 Google 上查资料、在 Github 上给牛人提 issue,就已经完爆你能找到的所有国内培训课程了,而这一切都是免费的。
学写代码是我这个习惯性半途而废者第一个坚持下来并让我进入职业圈子的事。我依旧是小白一个,要学的东西太多了,对未来很期待也很紧张。但一年的学习让我找到了一种自信,就是无论在哪里都要坚持下去,为了家人,为了自己,如果需要做律师,我就去读法学院,如果需要牙医,我就去读医学院,如果需要木匠,我就拜师学木艺。1年不够就2年,2年不够就10年,总会有实现目标的一天。
今天是2016年的1024程序员节,在耕耘一年后终于有了收获,我很骄傲。

Tagged : / /

Python 中 list 的传值问题

我在做一个小程序,需要生成一个随机数列表,然后将之赋值给2个 list,并需要这两个列表完全独立(即列表值指向不同的内存地址)。最开始是这样的:

>>> a = [5,4,3,2,1]
>>> b = a
>>> print(a,'-',id(a)) # id是 python 查看对象地址的方法
[5, 4, 3, 2, 1] - 4516275720
>>> print(b,'-',id(b))
[5, 4, 3, 2, 1] - 4516275720 # 显然,此时a和b指向同一个地址
>>> b.sort() # sort()是 python 的内置排序函数
>>> print(b,'-',id(b))
[1, 2, 3, 4, 5] - 4516275720
>>> print(a,'-',id(a))
[1, 2, 3, 4, 5] - 4516275720 # 我去,怎么回事???a 怎么也变了?

Python 的变量赋值机制并不是真的给变量赋值(实际上从头到尾都不存在 list 的容器),只是把变量名当做一个标签,贴在内存地址上,当值发生变化的时候,并不是改变值本身,而是将内存地址对应的标签『唰』的一下撕下来,然后贴到另一个地址上——此所谓『铁打的内存,流水的变量名』。
对于复制 list,按照官方文档的说法,应该这样操作:

b = a[:]
# https://docs.python.org/3/faq/programming.html#how-do-i-copy-an-object-in-python

此时是这样的:

>>> a = [5,4,3,2,1]
>>> b = a[:]
>>> print(a,'-',id(a))
[5, 4, 3, 2, 1] - 4516276488
>>> print(b,'-',id(b))
[5, 4, 3, 2, 1] - 4516276232

可见在 b = a[:] 这一过程中,Python 在内存中创建了一个新值,并将 b 的标签贴到了上面,迥异于我们最开始的过程。
其实上述过程并非发生在所有数据类型中。

>>> A = 5
>>> B = A
>>> id(A)
4540535120
>>> id(B)
4540535120
>>> B = 3
>>> A
5
>>> id(A)
4540535120
>>> id(B)
4540535056

关于这个问题,我还没有研究明白,还没搞懂到底哪些变量是这样传值的,算是一个小小的坑吧。
参考资料:

  1. http://stackoverflow.com/questions/8744113/python-list-by-value-not-by-reference
  2. https://docs.python.org/3/faq/programming.html#how-do-i-copy-an-object-in-python
Tagged : /

几种排序算法的比较

整个十一假期就在折腾这几个算法,这篇总结性文章就是简要的介绍了几个基础算法的特性,并附带了 Python 的实现。
不同算法适合不同情况的数组,但在不知道输入规律的时候,使用时间复杂度低算法的比较保险。
Big O:上界
Big Ω:下界
Big Θ:确界

Big O 比较

big_o_complexity
O(n²):冒泡排序(稳定)/选择排序(不稳定)/插入排序(稳定)
O(nlgn)~O(n²):希尔排序(不稳定)
O(nlgn):堆排序(不稳定)/归并排序(稳定)/快速排序(不稳定)

Bubble Sort 冒泡排序

def bubble_sort(arry):
    n = len(arry)                   #获得数组的长度
    for i in range(n):
        for j in range(1,n-i):
            if  arry[j-1] > arry[j] :       #如果前者比后者大
                arry[j-1],arry[j] = arry[j],arry[j-1]      #则交换两者
    return arry

时间复杂度、稳定性

平均情况:O(n²)
最坏情况:O(n²)
最好情况:O(n)
稳定
辅助空间O(1)

特点

最优情况为全部正序时经过 n-1 次比较即可完成排序,最差情况是倒序。因此冒泡算法对数组的有序性很敏感,适合对规模较小、且比较有序的数据进行排序。[1]

优化方案

  1. 如果某一次遍历没有发生数据交换,则代表已完成排序,可停止迭代。
  2. 记录遍历时最后一次数据交换的位置,后面的数据已经有序,因此可以缩小下次循环的范围。

Selection Sort 选择排序

def select_sort(ary):
    n = len(ary)
    for i in range(0,n):
        min = i                             #最小元素下标标记
        for j in range(i+1,n):
            if ary[j] < ary[min] :
                min = j                     #找到最小值的下标
        ary[min],ary[i] = ary[i],ary[min]   #交换两者
    return ary

时间复杂度、稳定性

平均情况:O(n²)
最坏情况:O(n²)
最好情况:O(n²)
不稳定
辅助空间O(1)

特点

运行时间与输入状态无关(随机排列与正序排列消耗相同的时间)。同时移动次数最少(与数组大小呈线性关系)。[1]

Insertion Sort 插入排序

def insert_sort(ary):
    n = len(ary)
    for i in range(1,n):
        if ary[i] < ary[i-1]:
            temp = ary[i]
            index = i           #待插入的下标
            for j in range(i-1,-1,-1):  #从i-1 循环到 0 (包括0)
                if ary[j] > temp :
                    ary[j+1] = ary[j]
                    index = j   #记录待插入下标
                else :
                    break
            ary[index] = temp
    return ary

时间复杂度、稳定性

平均情况:O(n²)
最坏情况:O(n²)
最好情况:O(n)
稳定
辅助空间:O(1)

特点

输入状态会影响运行效率,对有序数组排序要比对随机排列、逆序排列的数组快,因此适用于部分有序的非随机数组。当『数组中每个元素距离它的最终位置都不愿』『一个有序的大数组接一个小数组』『数组中只有几个元素的位置不正确』时,插入算法的速度很可能比其他算法都快。[2]

Shell Sort 希尔排序

def shell_sort(ary):
    n = len(ary)
    gap = round(n/2)       #初始步长 , 用round四舍五入取整
    while gap > 0 :
        for i in range(gap,n):        #每一列进行插入排序 , 从gap 到 n-1
            temp = ary[i]
            j = i
            while ( j >= gap and ary[j-gap] > temp ):    #插入排序
                ary[j] = ary[j-gap]
                j = j - gap
            ary[j] = temp
        gap = round(gap/2)                     #重新设置步长
    return ary

时间复杂度、稳定性

平均情况:O(nlgn)~O(n²)
最坏情况:O(n²)
最好情况:O(n1.3)
不稳定
辅助空间:O(1)

特点

希尔排序将数组分成较短的子数组、并使之部分有序,很适合插入排序。希尔排序对中等大小数组的排序时间可以接受,代码量小,且不需要额外内存空间。当没有系统排序函数可用时,可以考虑先用希尔排序,再考虑是否值得替换为更复杂的排序算法。[3]

Merge Sort 归并排序

def merge_sort(ary):
    if len(ary) <= 1 : return ary
    num = int(len(ary)/2)       #二分分解
    left = merge_sort(ary[:num])
    right = merge_sort(ary[num:])
    return merge(left,right)    #合并数组
def merge(left,right):
    '''合并操作,
    将两个有序数组left[]和right[]合并成一个大的有序数组'''
    l,r = 0,0           #left与right数组的下标指针
    result = []
    while l<len(left) and r<len(right) :
        if left[l] < right[r]:
            result.append(left[l])
            l += 1
        else:
            result.append(right[r])
            r += 1
    result += left[l:]
    result += right[r:]
    return result

时间复杂度、稳定性

平均情况:O(nlgn)
最坏情况:O(nlgn)
最好情况:O(nlgn)
稳定
辅助空间:O(n)

特点

归并排序在最坏的情况下复杂度为O(nlgn),和其他基于比较的排序算法所需的最小比较次数相同。

Quick Sort 快速排序

用递归在 Python 中实现快速排序会遇到 RuntimeError: maximum recursion depth exceeded 的错误提示,原因是 Python 的递归深度默认为1000(可以用 sys.getrecursionlimit() 来查看),有两种方式解决为题。一种是用循环重写算法,另一种比较简单粗暴,直接用 sys.setrecursionlimit(99999) 把递归深度设置为 99999 这种大数字,更详细可参见这里

def quick_sort(ary):
    return qsort(ary,0,len(ary)-1)
def qsort(ary,left,right):
    #快排函数,ary为待排序数组,left为待排序的左边界,right为右边界
    if left >= right : return ary
    key = ary[left]     #取最左边的为基准数
    lp = left           #左指针
    rp = right          #右指针
    while lp < rp :
        while ary[rp] >= key and lp < rp :
            rp -= 1
        while ary[lp] <= key and lp < rp :
            lp += 1
        ary[lp],ary[rp] = ary[rp],ary[lp]
    ary[left],ary[lp] = ary[lp],ary[left]
    qsort(ary,left,lp-1)
    qsort(ary,rp+1,right)
    return ary

时间复杂度、稳定性

平均情况:O(nlgn)
最坏情况:O(n²)
最好情况:O(nlgn)
不稳定
辅助空间:O(nlgn)~O(n)

特点

在实际应用中,一般比其他算法快很多,内循环很小,原地排序(仅需要很小的辅助栈),且将长度为 N 的数组排序的时间与 NlgN 成正比。但很脆弱,实际性能会因某些错误变成平方级。[4]
快速排序和归并排序使用分治法和递归进行排序,但快排在合并子数组后是自然有序的大数组;归并在合并阶段则繁琐一些,还要再次进行比较。
在对数组进行切分不平衡时,会导致性能低效(例如第一次从最小元素切分、第二次从第二小的元素切分……)。解决该问题,可以对数组进行随机排序,避免性能下降到极低。

优化

  1. 快排在小数组中比插入排序慢,因此在排序小数组时使用插入排序。
  2. 三取样切分。
  3. 熵最优排序[5]

以上三种优化是《Algorithms 4th》中对于快排性能的优化,都是对算法本身做了一些改进。而最著名的优化则是《算法导论》中提到的『随机化快速排序』,与上述三种性能优化有很大不同。
随机化快速排序,一般来说都是将取主元的过程随机化。随机化快速排序的『优化』,并没有提高快排的性能,而是避免了某些序列使快排性能大幅降低到O(n²)的可能性、防止他人设计一个序列对服务器发起DoS攻击,使排序更加稳定和安全(我发现还有很多人有类似的误解)。事实上随机化快速排序和普通快速排序在时间复杂度上同为O(nlgn),实际实验中也没有显著差异。

知乎上一个用户对快排和随机快排的性能做了测试,但我猜他可能也理解错了,他所做的只是在优化随机快排的代码,但并没有显著提高算法性能或降低复杂度(知乎链接)。Thomas Cormen 在 Quora 中也回答了关于随机化快排和普通快排性能差异的问题(Quora链接),但是他本人在《算法导论》(中文版100页,英文版179页)中并没有把这个问题讲的很透彻,也许算是一个微小的失误。

Heap Sort 堆排序

def heap_sort(ary) :
    n = len(ary)
    first = int(n/2-1)       #最后一个非叶子节点
    for start in range(first,-1,-1) :     #构造大根堆
        max_heapify(ary,start,n-1)
    for end in range(n-1,0,-1):           #堆排,将大根堆转换成有序数组
        ary[end],ary[0] = ary[0],ary[end]
        max_heapify(ary,0,end-1)
    return ary
#最大堆调整:将堆的末端子节点作调整,使得子节点永远小于父节点
#start为当前需要调整最大堆的位置,end为调整边界
def max_heapify(ary,start,end):
    root = start
    while True :
        child = root*2 +1               #调整节点的子节点
        if child > end : break
        if child+1 <= end and ary[child] < ary[child+1] :
            child = child+1             #取较大的子节点
        if ary[root] < ary[child] :     #较大的子节点成为父节点
            ary[root],ary[child] = ary[child],ary[root]     #交换
            root = child
        else :
            break

时间复杂度、稳定性

平均情况:O(nlgn)
最坏情况:O(nlgn)
最好情况:O(nlgn)
不稳定
辅助空间:O(1)
2. 特点
目前唯一的能最优利用时间与空间的算法,最坏情况下也能保证 2NlgN 次比较和恒定的辅助空间。当空间紧张时(例如嵌入式),堆排序用几行代码就能实现实现较好性能。
缺点是无法利用缓存,缓存未命中的次数远高于大多数在相邻元素间比较的算法。
sort

通过倍率实验预测程序的增长数量级

可以使用倍率实验来预测任意程序的增长数量级:
每次实验时使输入翻倍,计算时间,并求两次运行时间的比值。反复运行直到比值趋近于2的b次方。[6]

算法的『稳定性』是什么

稳定性指,当排序的元素中有相同的值时,这些具有相同值的元素在排序后的前后位置是否发生变化的性质。如果变化,则不稳定;如果不变化,则稳定。在 Bubble Sort 中,相邻元素互相交换,如果两个相邻元素相等则不需交换;如果两个相等的元素彼此之间有间隔,那么即便它们和相邻元素交换后,其相对的前后位置也不会发生变化,所以是稳定的。而在 Selection Sort 中,第一个元素与最小的元素交换、第二个元素与第二小元素交换等等,间隔有可能很大从而在交换时跳过了相同值的元素,进而造成相同值元素前后位置改变,因此是不稳定的。
排序的键值有可能只是元素的一个属性,如果元素本身还具有其他属性,那么键值相同的元素排序后的相对顺序还与其他属性有关。例如用稳定的算法对员工按工资排序,假如原数组是按年龄排序的,那么月薪同为7000元的3个人在按工资排序后仍然可以保持年龄正序,即最初的相对位置不变。

参考资料:

[1]《Algorithms 4th》 P248
[2]《Algorithms 4th》 P252
[3]《Algorithms 4th》 P262
[4]《Algorithms 4th》 P288
[5]《Algorithms 4th》 P296
[6]《Algorithms 4th》 P121

Tagged : / / / /

万用骰子脚本(跑团专用)

几个月前,我写过一个专门用来玩卡坦岛的命令行骰子工具,里面的骰子函数都是写死的,只能选2d6/2d10/3d4这几个,够用,但是不方便,适用性太差。最近入了《Pathfinder基础包》,准备重新开始跑团,看几个跑团QQ群里都有骰子机器人(方便大家开网团的时候投骰子),他们输入『.r 3d6』『.r 4d10』甚至『.r 1d97』这种实际中并不存在的骰子都可以得到值,自由度非常高。
于是我计划用Python来实现这种高自由度的骰子。首先遇到的问题就是如何让程序识别『3d6』『1d4』『1d8』这种跑团黑话。先普及一下,d4/d6/d8/d10/d20等都指骰子的面数,例如d4指的是四面骰,d20则指二十面骰,1d4指扔1个四面骰,2d6则指扔两个六面骰。普通游戏一般用不到这么多种类的骰子,而在以大量数值检定为核心的TRPG(桌面角色扮演游戏)中,这些骰子就不可或缺了。
首先想到的方法是用正则表达式来解析命令。以最常用的『1d6』为例,『1』指骰子个数,『6』是骰子类型(面数),『d』则是分割二者的分隔符。用正则表达式来写的话,应该是这样:

roll = input('> ')
match = re.search(r'(\d+)([Dd])(\d+)', roll)

先让用户输入命令,然后开始解析命令。命令的结构是『数字』+『D或d』+『数字』,正则表达式如上图。最早的版本里,是 r'(\d)([Dd])(\d)’ ,两个数字位都没『+』,后来发现第二个数字位必须带『+』(因为骰子类型有可能是两位数甚至三位数,例如1d20,1d100),于是我干脆把两个数字位都变成可以无限位取值的。
到此,解析命令完成。下一个问题发生在定义函数时的全局变量上。早期版本如下:

result = 0
def d(n):
    result = randint(1, n)

函数外部出现了变量 result,函数内部又给 result 赋值,电脑就懵圈了。在这里,我一直没搞懂的问题是,定义函数时的返回值,并不是返回给某个变量,而是对应了这个函数本身。result = 0 这个变量的初始化也可以删掉。在朱老师的指导下,终于搞明白这个问题,于是代码顺利改成这样:

def d(n):
    return randint(1, n)

最后,做好一个 for 循环来实现反复扔骰子的动作即可:

for i in range(m):
    result = d(n)
    dice.append(result)
    print(result)
print('和为: ', sum(dice))

至此,其实还没有写完,脚本还有很多地方需要完善,但是已经不再是当初那个被朱老师批评的『到处给全局变量赋值的超级烂代码』了。日拱一卒,余欣慰也。
Github地址
欢迎各位去围观我写的其他小脚本,帮我改改这些超级烂代码!

Tagged : / /

完成《Flask Web Development》纪念

DSC_0129-01_meitu_1
(上图为计划中的下一本书《Web Scraping with Python》)
这本 Flask 教材抱着啃了一个月,终于搞定了。标题里之所以说是『完成』而不是『学完』,是因为实际上我确实只是完成——所有的代码都手打了一遍。但是到底学没学会,对自己持悲观态度。这本书内容比较集中,但是细节很多,我又是第一次接触 web 框架甚至是第一次接触 web 开发,读完全书、打完全部代码,实际上也只是盲人摸象的对 web 开发有了大致印象,远远不能算『学会』。(Ps.本书中有若干小错,例如代码文件的路径等等,但是辨识后不影响学习。官方提供的代码有一些教材内的功能没有完成。)
不过我对这种感觉也并不陌生。在完成《笨方法学 Python》的时候,也有类似的体验,书中内容都学了,但合上书后并不知道自己能实现什么,深深的『啥都不会做』感,只是后面接触了更多的 Python 知识后,这种不安才慢慢消散。有了那一次的经验以后,我就不再担心这种『无力感』。相信这种感觉在短时间内可能还会重现多次,但并不代表我真的『啥都不会做』。
回顾一下从去年11月开始至今的学习,自己的速度并不快。学习过的书有:

除了笨方法以外,其余都是英文。所以读这些技术书,对自己的英文阅读也有一些帮助。从《Automate》一书开始,我习惯将原书 PDF 下载后通过淘宝打印。并且多亏了朱老师的全程指导,才能在转行程序员的道路上坚持走下去。
目前写过的一些小工具:

  • 桌游《阿瓦隆》——基本使用 python 语法把规则写了一遍,当时完全不知道数据库,也不懂部署。
  • 基金净值统计——用爬虫抓取自己的基金现价,然后计算目前自己的持仓市值
  • 给基友用 pandas 做的数据处理脚本——这个代码最少,但是我最喜欢,因为解决了原本很麻烦的实际问题。
  • 其他的都是一些跟着教程做出来的东西。

在 Python 的各个用途中,最让我感到神奇的是爬虫和数据处理,也是我曾经实现过的两个脚本的功能。爬虫可以自己阅读、解析、抓取、统计网络上的各种数据、甚至可以跨过 JS 像真实用户一样去操作页面;而 pandas/numpy 这些工具居然可以以远超过我想象的速度处理惊人的数据。我希望自己能够在这两方面加深一下学习。
后面我为自己规划的学习路径是这样的:
1、完成实验楼的 Flask 轻博客项目(已完成)
2、开发一个自己的网站(还没想好需求)
3、完成《Web Scraping with Python》
将这三个任务完成,目测应该已经起码到达八月中旬了。届时找一份 junior developer 的工作应该问题不大。如果在工作之余仍有时间学习,会继续在爬虫和数据处理方面钻研,然后看看自己更喜欢哪方面。

Tagged : / / /

一刷

听从朱老师的教诲,开始刷《Automate the boring stuff with Python》,目前已经进行到Chapter 9,全书过半。
很大一个感受就是,看书都能看懂,最后的Practice Project却做起来非常困难,没有头绪。这种状态和我初学《笨方法学Python》时候一模一样,当时落后进度很多,浪费了很多时间。
前几天反省了一下,自己每天花在代码上的时间表面上有五六个小时,其实也就最多2小时在学习。因此变换了一下学习方式,从事无巨细的按顺序完成教程的每一部分,变为第一遍先读完全书、扫过课后练习,完成一刷后在读第二遍——这一边要快速浏览课程内容,重点看课后习题。
目前正在尝试这样的方法,每天集中2小时进行学习,更轻松,而且根据经验,第二遍应该会很快(目前估计应该可以7-10天解决二刷)。总的来说,会节省很多时间。
过慢的学习基础知识,看来是一种很落后的学习方法。学习变成似乎也和背单词一样,重点不在于第一遍学习时记住、吸收了多少,而在于学习的次数。这是我自己的一点经验。

Tagged : /

Mac上修改Path,及用Pyenv管理多版本Python

目前二刷《笨方法》即将结束,但是在Round1时遭遇的问题一直没有解决。问题是这样的:

在ex46中,要求安装若干个第三方库(如nose用来测试)。我按步骤安装之后,执行nosetest(nose的测试命令),反馈command not found。其他几个库问题亦然。

之前研究了很久不明白怎么回事,现在终于搞清楚,并快速修复了这个问题。
其实问题的核心就是Mac上Python的Path与第三方库(及第三方Python)的目录不统一,换句话说,当我在终端执行Python后,进入的是Mac OSX自带的Python的交互模式,而非手动安装的版本。自带的版本往往是比较旧(然而绝对绝对不能删除,手残党也尽量别去尝试更新了)的稳定版。
官方文档清晰的描述了Mac自带Python与用户手动安装Python的异同(https://docs.python.org/2.7/using/mac.html):

  1. Mac自带的Python用以支撑苹果官方及第三方软件(就是给电脑用的,不是专门给你编程用的!虽然也可以编程!),目录位置是 /System/Library/Frameworks/Python.framework 和 /usr/bin/python;
  2. 用户自行安装的第三方Python则当然用于编程,目录位置/Library/Frameworks/Python.framework 。

在官方文档中对于Mac OSX自带Python又着重强调了一句:You should never modify or delete these, as they are Apple-controlled and are used by Apple- or third-party software.
故而,问题很清晰了,只要修改日常代码所用的Python的路径为第三方Python路径即可。
修改Path的命令,我是在stackoverflow上面找到的(http://stackoverflow.com/questions/3696124/changing-python-path-on-mac)。

PATH="/Library/Frameworks/Python.framework/Versions/2.7/bin:${PATH}"
export PATH

注意,上述路径中的2.7数字,根据你自己的安装版本进行调整。如果安装的是3.5,就改成3.5。
ps. 经测试发现,上述命令没有写入配置文件.bash_profile,因此只是临时更改了path,关闭终端(我用的iterm2)后再重启,依然会恢复老path。此时需要修改配置文件.bash_profile。执行以下命令:

open ~/.bash_profile

此时会用Mac自带的文本编辑器打开配置文件,在最后一行添加:

export PATH=/Library/Frameworks/Python.framework/Versions/2.7/bin:$PATH

具体路径参考自己的安装位置,基本应该差不多。然后保存文件并关闭。此时新Path已经被写入配置,关闭终端、重新打开,执行which python,可以查看目前的path。如果改过来了,即成功,否则请检查自己的步骤哪里有错。
注意,以上步骤仅供Mac用户参考,Mac的终端配置文件是.bash_profile,而没有很多中英文教程中提到的.bashrc,请注意。
附录:.bash_profile与.bashrc的区别:http://www.joshstaiger.org/archives/2005/07/bash_profile_vs.html


另外,我个人的下一本学习教程是朱老师推荐的《Automate the boring stuff》,教材中以Python3为标准,因此可能需要一段时间让Python2与3共存和切换,因此也记录一下知乎上推荐的Python版本切换工具,Pyenv(Github地址)。目前我还没有安装,大概这两天会试用。


报告各位,已经完成用Pyenv管理多版本的Python。中间绕过一些弯路,请各位严格按照下列顺序操作,否则可能导致前功尽弃、从头来过。
另外,强烈建议看一遍pyenv的Github说明,因为我下面的步骤仅针对Mac,如果你跟我情况不同,请按官方教程来。

首先,看看你有没有自己安装过Python,如果有,删掉!

在Python的官方文档中,并没有一章专门讲怎样卸载Python,因为放在安装章节了(这里)……懒得看英文的可以看下面。

  1. 删除Applications文件夹下面的Python文件夹。(打开Finder,点击应用程序,将Python27/Build Applet/IDLE都拖进垃圾桶)
  2. 删除/Library/Frameworks/Python.framework下面以Python版本号命名的对应文件夹。(例如你想删Python2.7,就把27这个文件夹删除。在本教程中,除了Current文件夹以外,所有版本都删掉!)
  3. 删掉上文中那句加在~/.bash_profile文件中的那行代码

然后,安装包管理工具Homebrew(已安装的跳至下一步)

  1. 进入Homebrew官网http://brew.sh/index_zh-cn.html
  2. 把首页那行代码复制到终端,然后执行
    ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
  3. 此时已经安装好Homebrew了

用Homebrew安装Pyenv

  1. 直接用brew命令安装
    $ brew update
    $ brew install pyenv
  2. 将以下代码添加到~/.bash_profile文件的第一行(让bash运行后首先加载pyenv自己的path)
    eval "$(pyenv init -)"

用Pyenv安装需要的Python版本

  1. 查看可安装的Python版本列表,其中形如 x.x.x 这样的只有版本号的为Python官方版本,其他的形如 xxxxx-x.x.x 这种既有名称又有版本后的属于“衍生版”或发行版。
    $ pyenv install --list
  2. 截至今天2016/1/15,Python最新版本是3.5.1,以安装这个版本为例
    $ pyenv install 3.5.1
  3. 如果有其他版本需求,用同样的命令安装其他的,例如2.7.11。
  4. 最后更新数据库
    $ pyenv rehash

Pyenv切换Python版本

  1. 查看Pyenv下面已存在的Python版本
    $ pyenv versions
  2. 切换版本
    $ pyenv global 3.5.1
  3. 再执行python,看一看Python版本有没有切换过来?

删除Pyenv

我还没有删除过Pyenv,所以在此只是翻译一下官方文档:

  1. 删除之前添加在~/.bash_profile文件第一行的那行代码
  2. 如果是用git clone命令安装的,请删除pyenv的整个根目录(一般是~/.pyenv)
    rm -rf `pyenv root`
  3. 如果你是按照我上面的步骤用Homebrew安装的,直接用brew来卸载即可
    brew uninstall pyenv

 

Tagged : /

Life is short, you need Python!

早在20年前,我在小学期间,就敲过代码。
那是在当时叫做『微机课』的课外兴趣班上,老师让每个人带一个本子,然后板书满满几黑板的代码,学生们在下面抄写到本子上,再手输到电脑中。写过几行代码的同学恐怕都知道,能完整的将几百行代码手敲至文本编辑器里,这是一个需要多么细心和耐心的工作,稍有不慎就会这里句号变逗号、那里括号少半个、或者某个变量的拼写大小写有误……因此从板书到手写、再从手写到键盘,中间的两次媒介转换,让bug的出现概率大大提升。如果我没记错,当时每次上课可能仅有一两名同学能够顺利的在『微机』屏幕上画出一个个炫目的几何图形。
我从来没成功过。并且由于中间多次的誊写,debug几乎也是不可能完成的任务。
但我的程序员之路,就此中断。初中以后,我学会用金山游侠修改游戏(仙剑这种游戏最好玩的就是改999个金蚕蛊),后来会用文本编辑器直接编写游戏目录下的config文件(把红警里的小兵改成超人,一个兵横扫六国)。高中以后迷恋音乐,写一些谱子,做一堆midi曲子。大学以后沉迷bbs,并写自己的独立博客,接触wordpress,走上了折腾css的无尽之路。毕业后又隔三差五的学一点软硬件知识,玩Arduino做图形化编程,学了HTML和PHP的皮毛。
到今天,才算是正经的学一门有用的语言,Python。以前那都算是玩的。
Bruce Eckel 说过一句著名的话:Life is short, you need Python。这也是我选择Python作为人生第一门深入学习的语言的原因。Python是一门拥有大量库的高级语言,库多意味大多数需求的开发都不需要反复制造轮子、用现成的即可,同时简洁易学。朱作君老师最早向我安利了Python,感谢朱老师的推荐,在学习了几周之后发现Python确实非常适合我这种业余选手。
可能我学Python最大的动力,是源于内心的一种『创造欲』。人和动物之间的巨大鸿沟,就是是否能够『创造』。而对现代人来说,需要『制造』的物品已经不多了,世界上的物质之丰富超过大多数人的想象。然后就像木匠永远差一把好椅子,猎人也永远有一只捉不到的野兽,人如果不能亲手制造些东西,似乎是放弃了本性中很重要的一部分——那就是创造。一辈子不创造、依赖于前人的工作生存下去,固然已经被证明了一万次可行,但掌握命运却应该从创造开始。
学习中遇到了很多困难,还要一次又一次的克制住懒惰的心。希望每一点坚持都能变成自己最终的心血。
在这里我与大家分享一下我的学习笔记。最近写的并不勤快,因为很多内容自己无法理解,但是如果有朋友愿意一起学习,可以发邮件给我(邮件地址在博客的『关于』页面中)。
Python学习笔记:https://zjuguxi.gitbooks.io/hard-way-to-python/content/

Tagged : /