2012年5月31日 星期四

[Python]讀寫csv檔教學

Python自從2.3版之後就有提供csv module讓使用者去讀寫csv格式的檔案。簡單的說,csv(comma-separated values)就是一種data之間用特殊符號隔開的資料表示格式,通常是用逗號(,)隔開。
比如說下列是從證交所網站抓下來的csv檔範例:
日期,成交股數,成交金額,成交筆數,發行量加權股價指數,漲跌點數
 101/01/02,"2,104,640,545","50,472,201,894","497,889","6,952.21",-119.87
 101/01/03,"2,640,781,030","64,138,886,194","623,192","7,053.38",101.17
 101/01/04,"2,983,756,821","75,845,629,353","728,417","7,082.97",29.59
 101/01/05,"3,062,757,248","76,850,752,698","737,780","7,130.86",47.89
 101/01/06,"3,314,147,435","88,101,921,548","823,810","7,120.51",-10.35
 101/01/09,"2,610,433,477","71,323,370,369","634,137","7,093.04",-27.47
 101/01/10,"3,595,611,551","97,168,542,017","869,327","7,178.87",85.83
 101/01/11,"3,322,390,467","88,786,671,892","802,539","7,188.21",9.34
 101/01/12,"2,969,248,375","78,947,910,460","731,328","7,186.58",-1.63
 101/01/13,"3,497,838,901","99,286,437,370","819,762","7,181.54",-5.04
 101/01/16,"3,184,795,667","76,842,611,338","719,725","7,103.62",-77.92
 101/01/17,"3,720,277,205","101,133,309,290","899,597","7,221.08",117.46
 101/01/18,"4,088,756,925","113,988,641,382","1,020,420","7,233.69",12.61
 101/01/30,"4,540,373,544","143,900,658,474","1,113,727","7,407.41",173.72
 101/01/31,"5,486,734,180","162,361,181,834","1,283,951","7,517.08",109.67
這種資料格式被大量用在試算表以及資料庫。例如大多數的資料庫都可以讓使用者把table用csv格式匯出。這些csv檔可以很輕易的用Python內建的csv module來做讀寫。
以下是一個範例程式示範如何讀寫上面範例中的大盤指數csv檔:
# -*- coding: utf-8 -*-
import csv
f = open('example.csv', 'r')
for row in csv.reader(f):
    print row
f.close()
[' 101/01/02', '2,104,640,545', '50,472,201,894', '497,889', '6,952.21', '-119.87']
[' 101/01/03', '2,640,781,030', '64,138,886,194', '623,192', '7,053.38', '101.17']
[' 101/01/04', '2,983,756,821', '75,845,629,353', '728,417', '7,082.97', '29.59']
[' 101/01/05', '3,062,757,248', '76,850,752,698', '737,780', '7,130.86', '47.89']
[' 101/01/06', '3,314,147,435', '88,101,921,548', '823,810', '7,120.51', '-10.35']
[' 101/01/09', '2,610,433,477', '71,323,370,369', '634,137', '7,093.04', '-27.47']
[' 101/01/10', '3,595,611,551', '97,168,542,017', '869,327', '7,178.87', '85.83']
[' 101/01/11', '3,322,390,467', '88,786,671,892', '802,539', '7,188.21', '9.34']
[' 101/01/12', '2,969,248,375', '78,947,910,460', '731,328', '7,186.58', '-1.63']
[' 101/01/13', '3,497,838,901', '99,286,437,370', '819,762', '7,181.54', '-5.04']
[' 101/01/16', '3,184,795,667', '76,842,611,338', '719,725', '7,103.62', '-77.92']
[' 101/01/17', '3,720,277,205', '101,133,309,290', '899,597', '7,221.08', '117.46']
[' 101/01/18', '4,088,756,925', '113,988,641,382', '1,020,420', '7,233.69', '12.61']
[' 101/01/30', '4,540,373,544', '143,900,658,474', '1,113,727', '7,407.41', '173.72']
[' 101/01/31', '5,486,734,180', '162,361,181,834', '1,283,951', '7,517.08', '109.67']
此程式會先把csv檔打開,之後透過csv.reader()把每一行的內容用逗號切開,回傳一個list。其實有點像是split,但是cvs.reader()會幫你把" "處理掉。這是單純用split沒辦法做到的事情。
csv module還有提供其他好用的功能。比如說可以幫你把資料parsing成dictionary的格式,使用第一列當作dictionary的key。
# -*- coding: utf-8 -*- 
import csv
f = open('example.csv', 'r')
for row in csv.DictReader(f):
    print row['成交金額']
f.close()
50,472,201,894
64,138,886,194
75,845,629,353
76,850,752,698
88,101,921,548
71,323,370,369
97,168,542,017
88,786,671,892
78,947,910,460
99,286,437,370
76,842,611,338
101,133,309,290
113,988,641,382
143,900,658,474
162,361,181,834
也可以自己指定key的名稱:
# -*- coding: utf-8 -*-
import csv
f = open('example.csv', 'r')
for row in csv.DictReader(f, ["日期", "成交股數", "成交金額", "成交筆數", "指數", "漲跌點數"]):
    print row['指數']
發行量加權股價指數
6,952.21
7,053.38
7,082.97
7,130.86
7,120.51
7,093.04
7,178.87
7,188.21
7,186.58
7,181.54
7,103.62
7,221.08
7,233.69
7,407.41
7,517.08
發行量加權股價指數的資料被對應到"指數"這個我們自行命名的key,由此可知csv.DictReader會根據你所提供list的順序來自己對應到資料中。
要寫把資料存成csv格式可以使用csv.writer(),以下是個簡單的範例:
data = [
  [' 101/01/31', '5,486,734,180', '162,361,181,834', '1,283,951', '7,517.08', '109.67'],
  [' 101/01/13', '3,497,838,901', '99,286,437,370', '819,762', '7,181.54', '-5.04'],
]
f = open("stock.csv","w")
w = csv.writer(f)
w.writerows(data)
f.close()

2012年5月29日 星期二

[Python]用Python抓證交所網站過去的大盤指數資料

今天用Python內建的urllib寫了一個可以抓過去大盤指數資料的程式
此程式抓下來的資料是.csv檔,會自動存在C:底下
範例程式只有抓2012年1月到5月的資料,有興趣的人歡迎自行修改拿去使用
import urllib

original_url = "http://www.twse.com.tw/ch/trading/exchange/FMTQIK/FMTQIK2.php?STK_NO=&myear=2012&mmon=%.2d&type=csv"
urlList = [original_url % month for month in range(1, 6)]

for index, url in enumerate(urlList, 1):
    try:
        urllib.urlretrieve(url, 'C:\\' + str(index))
    except IOError:
        print 'IOError'
    except urllib.ContentTooShortError:
        print 'ContentTooShortError'

[Python]如何在Python排序(Python Sorting)

Python提供兩種內建排序的function分別是sort()和sorted()
這兩個function都可以用來排序一個list
差別在於sorted()會回傳一個排序好新的list
sort()會直接修改原始的list並排序完成
以下是一個範例:
>>> a = [2, 4, 3, 5, 1]
>>> sorted(a)
[1, 2, 3, 4, 5]
>>> a
[2, 4, 3, 5, 1]
>>> a.sort()
>>> print a
[1, 2, 3, 4, 5]
從以上範例可以知道,如果你確定原始的list不需要保留下來的話,可以使用sort()來排序
如果要保留原本的list,就用sorted()來產生一個排序好的新list

這兩個function還有另一個不一樣的地方
sort()只能用在list上排序,而sorted()可用來排序任何的iterable(string, dictionary, tuple...)
下列範例用sorted()來排序一個dictionary
>>> sorted({1: 'D', 3: 'B', 2: 'B', 6: 'E', 5: 'A'})
[1, 2, 3, 5, 6]

sorted()提供了一些參數可以讓我們決定要怎麼排序
當我們把reverse設成True的時候,會把排序的結果由預設的ascending改成descending
strs = ['aa', 'BB', 'zz', 'CC']
print sorted(strs)  # ['BB', 'CC', 'aa', 'zz'] 
print sorted(strs, reverse=True)   # ['zz', 'aa', 'CC', 'BB']
可以設定一個function給key這個參數,在排序之前會自動對每個element call一次key所指定的function
透過這個方法可以做一些比較複雜一點的排序
sorted("aa BB cc ZZ".split())                       #['BB', 'ZZ', 'aa', 'cc']   (case sensitive)
sorted("aa BB cc ZZ".split(), key=str.lower)        #['aa', 'BB', 'cc', 'ZZ']   (case-insensitive)
如果要根據某些欄位來做sort,也可以用key這個參數
要注意的是,key只接受function當作參數。下列範例使用lambda來傳入一個匿名函式
students = [
        ('john', 'A', 15),
        ('jane', 'B', 12),
        ('dave', 'B', 10),
]
sorted(students, key = lambda x : x[2])   # sort by age
#[('dave', 'B', 10), ('jane', 'B', 12), ('john', 'A', 15)]
為了方便使用者使用,Python提供operator module,不用自己撰寫key的function
以下是使用operator module中的itemgetter()的範例
from operator import itemgetter, attrgetter
sorted(student_tuples, key=itemgetter(2))
#[('dave', 'B', 10), ('jane', 'B', 12), ('john', 'A', 15)]
如果sort的物件是class object的話,也可以使用operator module中的attrgetter()來做sort
class Student:
        def __init__(self, name, grade, age):
                self.name = name
                self.grade = grade
                self.age = age
        def __repr__(self):
                return repr((self.name, self.grade, self.age))

student_objects = [
        Student('john', 'A', 15),
        Student('jane', 'B', 12),
        Student('dave', 'B', 10),
]

sorted(student_objects, key=attrgetter('age'))
#[('dave', 'B', 10), ('jane', 'B', 12), ('john', 'A', 15)]
如果不使用operator module的話,也可以自己用lambda來寫一個key function
sorted(student_objects, key=lambda student: student.age)
#[('dave', 'B', 10), ('jane', 'B', 12), ('john', 'A', 15)]
如果要先比成績再比年齡的話,用operator module很簡單就可以做到
sorted(student_objects, key=attrgetter('grade', 'age'))
#[('john', 'A', 15), ('dave', 'B', 10), ('jane', 'B', 12)]
在Python 2.x時候sorted還有提供cmp這個參數
不過在Python 3.0之後被拿掉了,使用Python 3.0的時候要注意不要用到舊的語法

2012年5月28日 星期一

[Python]for loop, list comprehension, map效能比較

在Python中要走訪一個list有很多種方法,常見的有for loop, list comprehension, map
但是究竟哪一種方法效能比較好呢?以下是一個範例程式可以做個比較:
import time, sys
reps = 10000
repsList = range(reps)

def tester(func, *args):
    startTime = time.time()
    for i in range(reps):
        func(*args)
        elapsed = time.time() - startTime
        return elapsed

def forLoop():
    res = []
    for x in repsList:
        res.append(abs(x))

def listComprehension():
    res = [abs(x) for x in repsList]

def mapFunction():
    res = list(map(abs, repsList))

print (sys.version)
tests = (forStatement, listComprehension, mapFunction)
for testfunc in tests:
    print (testfunc.__name__.ljust(20), '=>', tester(testfunc))
這段範例程式會用三種方法分別建立一個內含10000個element的list,並且對每個element執行內建函式abs()
以下是程式執行結果,執行環境為Python 3.2.2
3.2.2 (default, Dec 23 2011, 11:21:15)
[GCC 4.6.1]
forLoop              => 0.0016720294952392578
listComprehension    => 0.0008997917175292969
mapFunction          => 0.0007519721984863281
由以上執行結果可知map最快、list comprehension次之、for loop大概要多花前面兩種方法一倍的時間才能執行完

為什麽map和list comprehension會跑這麼快呢?
其實是因為這兩種方法都有經過Python直譯器的最佳化,在執行時很接近C程式碼跑得速度
而for loop沒有經過最佳化直接在Python直譯器中執行,自然就跑得比較慢

以上範例是針對Python內建函式(abs)所做的測試,如果改用user自己定義的函式結果會不會不一樣呢?
下列範例會把list中的element都加上1
import time, sys
reps = 10000
repsList = range(reps)

def tester(func, *args):
    startTime = time.time()
    for i in range(reps):
        func(*args)
        elapsed = time.time() - startTime
        return elapsed

def forLoop():
    res = []
    for x in repsList:
        res.append(x + 1)

def listComprehension():
    res = [x + 1 for x in repsList]

def mapFunction():
    res = list(map((lambda x : x + 1), repsList))

print (sys.version)
tests = (forLoop, listComprehension, mapFunction)
for testfunc in tests:
    print (testfunc.__name__.ljust(20), '=>', tester(testfunc))
3.2.2 (default, Dec 23 2011, 11:21:15)
[GCC 4.6.1]
forLoop              => 0.0016200542449951172
listComprehension    => 0.0009100437164306641
mapFunction          => 0.0018360614776611328
執行結果出乎意料,map竟然變得比fop loop還慢。而list comprehension還是一樣快了快一倍
由此結果可知,Python直譯器當map裡頭放的不是內建函式時,並不會做最佳化的動作
當使用user自己定義的函式時,用list comprehension才能得到最佳的效能

這篇文章結論如下:
1. 當用到內建函式時,使用map可以得到最佳的效能
2. 除了1的case以外,盡量使用list comprehension才可以得到最佳的效能
3. for loop不管在什麽情況下都是跑最慢的
4. 最重要的是除非有特殊效能的考量,否則以上這三種方法盡量優先考慮Python程式的可讀性

Reference: Learning Python, 4e

2012年5月17日 星期四

[Python]Python和C之間的差異 - Python的型別和物件

這篇文章主要是想幫助那些從C轉換跑道到Python來的程式設計師
首先先解釋一個差異很大的地方,Python的型別(types)和物件(objects)

在Python中所有的東西都是object

string是object,int是object,function是object,連code都是object
這點和C或是C++非常的不同
我們可以用一個內建的function type()來得知一個object的type
>>> s = "sway"           #"sway"是一個string的object
>>> type(s)        
<type 'str'>
>>> i = 2                #2是一個int的object
>>> type(i)
<type 'int'> 
>>> def hello(): pass    #hello()是一個function的object  
...
>>> type(hello)
<type 'function'>
>>> type(hello.__code__) #hello()的code是存在一個code的object 
<type 'code'>

Python是動態型別(Dynamic Typing)的程式語言,和C和C++這類的編譯語言不一樣
C和C++變數的型別都在編譯時候就決定好了
而Python則是在執行期(Run Time)才決定type
>>> s = "sway"    #這時候s的type是string
>>> type(s)

>>> s = 2         #這時候s的type變成int了
>>> type(s)








由上面範例可以帶出一個Python很重要的觀念
Python variable的type完全被它所對應到的object所決定的
這在Python中稱為reference
每一個variable其實就像是C裡頭的pointer
先建立object之後,才用variable指向新建立的object,相當於給object一個別名
因此Python的variable其實就是reference

當我們執行i = 2的時候,其實Python會先建立一個integer的object叫做2
之後再產生一個variable並且把它指向2這個integer的object
Python的variable其實就只是一個name而已,我們透過這個name去間接access到object
這點是Python和C、C++很不一樣的地方,這也是大多數C programmer剛轉過來不太適應的地方
由於Python的這種特性,很多神奇的事情就發生了
>>> a = [1, 2, 3]
>>> b = a
>>> b[0] = 4
>>> print a
[4, 2, 3]
很多人看到這邊可能會覺得很奇怪,明明是修改b的值怎麼連a的值也被改到了?













其實原因很簡單,因為b = a這行程式碼其實只是把b指到a所指向的object而已
因此當執行完這行程式碼後,a和b就都指向同一個object
不論是改a或是改b,最後都會對應到同一個object
這是Python初學者最容易詢問的問題之一,其實只要搞懂Python的object架構,一切就會變得很直覺

上面範例如果object是一個不可改變(immutable)的object,情況就有點不一樣了。不可改變(immutable)的object像是:string, integer......
因為object是不可改變的,當你嘗試去更改object的時候,會自動建立新的物件,而不會更動到所指向的object。
聽起來有點難懂,看以下範例就知道了
>>> a = 2
>>> b = a
>>> id(a)                #id(a)可以取得a指向物件的id
17500064
>>> id(b)
17500064                 #a 和 b指向同一個物件(integer 2)
>>> b = 4
>>> print a
2
>>> id(b)                #b指向一個新的物件(integer 4)
17500016
由以上範例可以知道,兩個reference指向同一個不可改變的object時。因為object不可改變的特性,透過其中一個reference試圖去修改object都會自動建立一個新的物件

相反地,兩個reference指向同一個可改變(mutable)的object時。因為我們可以直接修改object的關係,所以透過其中一個reference去修改object不會建立新的物件。會直接修改到指向的object,因此透過任何一個reference去取值看起來就好像兩邊都被改過一樣。

All "variables" are references. 這是Python和C很不一樣的地方。

2012年5月1日 星期二

大話處理器 : 了解DSP.CPU及MCU基礎架構


這本書是一本講解處理器內部運作原理的書,看得出來作者試圖用很多生活化的例子來解釋CPU內部運作的原理。大致上可以把這本書看成是一本簡單的計算機組織入門書,整本書的內容絕大多數都是從Computer Organization and Design以及Computer Architecture: A Quantitative Approach這兩本書而來。這本書的內容比較淺,作者跳過了很多細節。因此只適合完全不懂計算機組織的人來念,否則會覺得書中寫得內容太淺了。

書中前面幾章在講解電腦的歷史,以及介紹一些常見的CPU和DSP,這部份我覺得還蠻不錯的。這本書最大的缺點就在於翻譯品質,可能是因為兩岸用語不同的關係,許多地方寫得中文名詞都有點奇怪。這點在很多大陸翻譯書都會出現,國內出版社真的該好好改善這一部份。

總而言之,這本書可以帶你初淺的認識CPU的內部運作原理。不過詳細的細節還是建議去閱讀計算機組織的聖經 Computer Organization and Design以及Computer Architecture: A Quantitative Approach 這兩本書,這兩本書才能真正帶你瞭解CPU的世界是怎麼運作的。