Python初体验

目录

最近在看《div into python》,正文部分快看完了,然后今天找个机会试试身手。

需求其实说简单也简单,说复杂用java写起来的话也挺麻烦的:遍历1w多张图片,确定图片的宽高,然后计算出最接近图片的背景色。为什么会有这么个奇怪的需求呢?因为受pinterest客户端启发。试用pinterest的android客户端,发现它的图片在加载的时候,应该会首先通过api获取图片的宽度和高度以及背景色,然后用这个色值作为背景,加载一个同样宽度和高度的色块,再去load图片。这样当图片出来的时候首先不会上下拉伸,其次不会显得突兀。

用java的话,我已经预感到一丝蛋蛋忧桑了,直接用python试试吧。网上搜了下,使用PIL和scipy这两个库应该就可以搞定了,顺便又感受了下python库的安装。不同于java只需要把jar文件复制到classpath,python的库安装起来稍微费点周折,其实估计如果有适合平台的py文件,直接复制到lib目录应该也是可以的,不过我是从source code安装的PIL,使用yum安装的scipy,以后再慢慢研究安装过程吧。从源码安装其实也不复杂,像这两个库都有setup.py脚本,直接运行python setup.py install就能安装,貌似这是python的标准流程?还得学习了解下。

首先是分析图片的宽度和高度:

import urllib2
import urlparse
from PIL import Image
from cStringIO import StringIO

f = open("imageLists")
urls = f.readlines()
for url in urls:
   data = StringIO(urllib2.urlopen(url).read())
   im = Image.open(data)
   x,y = im.size
   print "%s,%s,%s" %(url,x,y)

然后是分析图片的背景色,code来源于stackoverflow上面的这个问题

#!/usr/bin/python
import struct
import Image
import scipy
import scipy.misc
import scipy.cluster

NUM_CLUSTERS = 5

print 'reading image'
im = Image.open('image.jpg')
#im = im.resize((150, 150))      # optional, to reduce time
ar = scipy.misc.fromimage(im)
shape = ar.shape
ar = ar.reshape(scipy.product(shape[:2]), shape[2])

print 'finding clusters'
codes, dist = scipy.cluster.vq.kmeans(ar, NUM_CLUSTERS)
print 'cluster centres:\n', codes

vecs, dist = scipy.cluster.vq.vq(ar, codes)         # assign codes
counts, bins = scipy.histogram(vecs, len(codes))    # count occurrences

index_max = scipy.argmax(counts)                    # find most frequent
peak = codes[index_max]
colour = ''.join(chr(c) for c in peak).encode('hex')
print 'most frequent is %s (#%s)' % (peak, colour)

# bonus: save image using only the N most common colours
c = ar.copy()
for i, code in enumerate(codes):
    c[scipy.r_[scipy.where(vecs==i)],:] = code
    scipy.misc.imsave('clusters.png', c.reshape(*shape))
    print 'saved clustered image'

几点感悟:
1. 代码量。python的代码写出来代码量明显少很多,每一句都能直接看出code的意图,没有些乱七八糟琐碎的东西。想想java的构造器、getter&setter。当然这不是说java不好,作为一种工业级的语言,java的好处在于其可以写出极其严密和安全的code,前提是遵守code的规范。而python更适合作为hacker的语言,自由、高效。
2. python库的设计风格。python库的设计的也是非常的简洁,不会有特别复杂的接口,不过这也得益于python的语法,用起来很爽,code起来也很高效。但是同时也得看到,上面在使用httpclient类的库的时候并没有并发、多线程和链接复用的代码,具体不知道urllib里头有没有优化。
3. 可以当脚本语言使用的python太灵活了,相较于java如果想写个脚本跑某个任务那就费劲了。

##2013-12-05 update 尝试用python写了一个抓取文件的code,顺手又了解了下python的协程库gevent,废话不多说,先看code。
普通版本:

#!/usr/bin/python
import urllib2
import time

start = time.time()
f=open("data.out")
urls=f.readlines()
for data in urls:
    url = data.split('\t')
    req = urllib2.Request(url[0])
    req.get_method = lambda: 'HEAD'
    try:
        urllib2.urlopen(req)
    except urllib2.URLError, e:
#        print e.reason
        if e.code == 404:
            print url[1]
f.close()
elapsed = (time.time()-start)
print("time elapsed:",elapsed)

使用gevent之后的版本:

#!/usr/bin/python
import gevent
from gevent import monkey
import urllib2
import time
monkey.patch_all()

def checkImage(data):
    url = data.split('\t')
    req = urllib2.Request(url[0])
    req.get_method = lambda: 'HEAD'
    try:
        urllib2.urlopen(req)
    except urllib2.URLError, e:
        if e.code == 404:
            print url[1]
    
start = time.time()
f=open("data.out")
urls=f.readlines()
f.close()

jobs = [gevent.spawn(checkImage, data) for data in urls]
gevent.joinall(jobs)

elapsed = (time.time()-start)
print("start at %s,time elapsed:%s",start,elapsed)

代码的逻辑很简单,就是从data.out读取一个图片url的列表,判断图片地址是否已经失效,数据量大概在1w+图片左右。使用传统的urllib的方式去请求,大概需要N久……而使用python的gevent库提供的协程功能,可以看到性能有了质的提升。而且gevent的monkey可以直接hook原来的urllib代码,这样我们几乎不需要对原有代码做大的修改,就可以享受到协程带来的性能提升,实在是太赞了!corountine改变了我对多线程处理方法的认识,下次要攒一篇文章好好总结下,还有coroutine怎么充分利用多核cpu。