plurklib是official Plurk API的python實作。
原始碼只有兩個檔案,readme.py就是每個function的使用方法,plurklib.py則是主要的實作。
我主要用到 plurkSearch,plurkSearch 傳回的是一個 dictionary,所以就可以用 keys() 去看有哪些 key,大致上有 has_more、plurks、last_offset。has_more 可以用來判斷還有沒有更多的搜尋結果,last_offset 則是可以在下次 plurkSearch 時帶入,表示要搜索更之前的紀錄。plurks 則是一個 list,裏面每個元素則又是 dictionary。完全不使用 class 的好處是,用 python 基礎型別就可以搞定,不需要再多的檔案,其實也算是作者偷懶吧,因為內部是用 json 來解析結果,而 json 解出來的結果就是 dictionary。
再來就是日期,日期的解析,本來已經打算要用 datetime.strptime 來硬轉了,但還好在 StackOverflow: Converting string into datetime 看到可以用 dateutil.parser 來轉換,這樣就輕鬆省事多了。
gtk.FontSelectionDialog
這個類別可以選字型,只是,選好再呼叫它提供的 get_font_name() 以後,只會得到一個字串,裏面有字型的名稱、尺寸以及樣式,而且,還沒什麼規則可言,這造成我的困擾。
上網找了一陣,發現一般解法有兩種,一種是存取在 gtk.FontSelectionDialog 裡的 FontSelection.get_font_face,另一種則是透過 pango.FontDescription 來轉,我後來是用 pango.FontDescription 來轉。
用 pango.FontDescription 來轉是沒問題的,只是取得 font size 的時候,又遇到問題,原來這裡取得的大小會乘上 pango.SCALE,所以呼叫 get_size() 以後,還要除以 pango.SCALE,才是正確的。
def get_font_tuple( font_name ): import pango fontdesc = pango.FontDescription( font_name ) font_name = fontdesc.get_family() font_size = fontdesc.get_size()/pango.SCALE return (font_name, font_size)
自動分類pdf與shutil
這script主要是分類pdf用的,從謎之網站下載的 pdf 檔有規則,所以就可以寫code來自動分類。寫script時發現有shutil這個很方便的模組,可以用來複製、搬移檔案、目錄等,相當的方便,原本還以為要自己硬幹咧~
[python]#!/usr/bin/env python
–– coding: utf-8 ––
def main():
import glob
import os
import shutil
files = glob.glob( “*.pdf” )
for file_name in files:
parts = file_name.split(‘.’)
index = -2
while not parts[index].isdigit():
index = index + 1
dir_name = ” “.join(parts[:index])
if not os.path.exists(dir_name):
os.mkdir(dir_name)
try:
print(“Move ‘%s’ to ‘%s’.” % (
file_name, os.path.join(dir_name, file_name)))
shutil.move( file_name, os.path.join(dir_name, file_name))
except shutil.Error, e:
print(e)
if name == “main“:
main()
[/python]
python 的 private method
純粹記錄一下,讓自己知道曾經犯過這個錯。類別裡的 method 名稱加上 __ 就是 private,所以,子類別並不會知道有這個 method,也不會發生 Method overriding。
class Rectangle:
def __init__(self):
self.parse()
self.__parse()
def parse(self):
print("parse(): Rectangle")
def __parse(self):
print("__parse(): Rectangle")
class BoxType(Rectangle):
def parse(self):
print("parse(): BoxType")
# 不會被執行到!!
def __parse(self):
print("__parse(): BoxType")
b = BoxType()
# Output:
# parse(): BoxType
# __parse(): Rectangle
就這樣…
gtk.RecentChooserMenu
本來預期可以在 glade 裡就可以拉出這個元件,然後程式就不用寫,結果是不行。網路上範例不多,所以紀錄一下。
在 glade 裡,你要先預放一個 menuitem,假設命名為 mi_recent_items,你在程式裡就得先取出這個 menuitem,取得 RecentManager 然後建立 RecentChooserMenu,再用 mi_recent_items.set_submenu() 來設定。
RecentManager 是全系統的最近存取文件,這也是為什麼後續會要 RecentFilter 的原因,因為只要顯示需要的文件。當選取的時候,會觸發 item-activated 這個 signal。
代碼:
self.recent = gtk.RecentManager()
menu_recent = gtk.RecentChooserMenu(self.recent)
menu_recent.set_limit(10) # 設定要出現幾個
# 只取需要的檔案
self.file_filter = gtk.RecentFilter()
self.file_filter.add_pattern("*.pdb")
self.file_filter.add_pattern("*.updb")
menu_recent.set_filter(self.file_filter)
# 選取以後要做的事情
menu_recent.connect("item-activated", self.select_recent_cb)
# 取得在 glade 裡建立的 menuitem,然後用 set_submenu 設定。
menuitem_recent = self.builder.get_object("mi_recent_items")
menuitem_recent.set_submenu(menu_recent)
python的class attribute
前兩天被這個東西給陰到了,只怪我沒搞清楚,這在 Learning python 2nd 裡的 21.1 裡寫的很清楚。不過我想,從Java/C++/C#跳槽過來的人應該都會搞糊塗吧~
以C++舉例,下面的code:
[cpp]class Object {
private:
int id;
};
[/cpp]
到了 python,應該都會很自然的寫成:
[python]# 例A
class Object:
id=0
[/python]
看起來沒錯,對吧~
不過,事實上,應該要寫成這樣才對:
[python]# 例 B
class Object:
def init(self):
self.id=0
[/python]
換句話說,例A翻譯成C++的話,其實是類似 static 的用法,也就是說,id 會變成一個共用的 static 變數:
[cpp]class Object {
private:
static int id;
};
[/cpp]
pyexiv2 裡的 Rational 與 GPSCoordinate
要存GPS的相關tag到圖片時,因為某些tag要求存Rational或GPSCoordinate,我不知道該怎麼轉換,所以花了不少功夫查。我基本上是參考 這個老先生的code,他主要是使用 GPS tracker,所以會要讀取 tracker 裡的紀錄,然後以時間跟照片做比對,再把 GPS 位置寫進圖片裡。
可是遇到兩個問題:
- 他的 code 是用 surd 處理 Rational,我找不到 surd 了。
- 算出來的度、分、秒不準確。
首先是解決Rational問題,python 2.6 已經有內置的 module – Fraction 可以解決了。
def R(f): """R(float) - get a Rational number for a float""" from fractions import Fraction from pyexiv2 import Rational r = Fraction.from_float( float(f) ) return Rational( r.numerator, r.denominator )
再來就是轉換的問題,從 Google gear 那邊拿到的經緯度,要轉換成度分秒,我後來是參考 上河文化網站-座標轉換程式與 Google Maps 地圖定位裡的 javascript 來改寫,這才得到正確的度、分、秒:
def d(angle): """d(any) - get degrees from a number :eg d(33.41) = 33""" return math.floor(angle) def m(angle): """m(any) - get minutes from a number :eg d(33.41) = 24""" return math.floor( (angle-d(angle))*60 ) def s(angle): """s(any) - get seconds from a number :eg s(33.41) = 36""" return round((angle-d(angle)-m(angle)/60)*3600) def degree2coordinate( lat, lon ): """Convert degree to coordinate string.""" latR = 'N' lonR = 'E' if lat < 0: lat = -lat latR= 'S' if lon < 0: lon = -lon lonR= 'W' slat = "%03d,%02d,%02d%s" % (d(lat),m(lat),s(lat),latR ) slon = "%03d,%02d,%02d%s" % (d(lon),m(lon),s(lon),lonR ) return (pyexiv2.GPSCoordinate.from_string( slat ), pyexiv2.GPSCoordinate.from_string( slon ) )
最後,記一下,Tag裡要存的資料型態:
- Xmp.exif.GPSLatitude、Xmp.exif.GPSLongitude 裡存的是 pyexiv2.GPSCoordinate
- Xmp.exif.GPSMapDatum、Exif.GPSInfo.GPSMapDatum裡存的是字串,我是用 WGS-84
- Exif.GPSInfo.GPSLatitude、Exif.GPSInfo.GPSLongitude 裡存的是度、分、秒的tuple,要注意,度、分、秒都要轉成 Rational
- Exif.GPSInfo.GPSLatitudeRef、Exif.GPSInfo.GPSLongitudeRef 裡存的是度、分、秒的表示字串,如:125.3,67.5,12E
XMP and Cell ID 之二
在之後打算要寫入 geotagging 到圖片的時候,才發現 python XMP toolkit 的不足,於是又踏上尋覓的路程。後來找到 pyexiv2,這個 library 更好用,不僅可以讀,也能寫,用法也不會太複雜。唯一的缺點是,ubuntu 10.04 包的是 1.x 版,好在,有人包了最新的版本在ppa裡。
安裝:
sudo apt-add-repository ppa:pyexiv2-developers/ppa sudo apt-get install python-pyexiv2
讀取跟寫入,基本上參考官方的tutorial與API 文件就行了。Cell ID 資訊在這幾個tag裡:
- Xmp.cell.mcc
- Xmp.cell.mnc
- Xmp.cell.lac
- Xmp.cell.cellid
XMP and Cell ID
去年就看到這篇文章:Cell ID Photo PC Tool 0.3,想試試看了。因為是c#寫的,照理來說,Linux 上的 mono 應該可以用才對,哪知道卻發現他用了 System.Windows.Media.Imaging 這個 mono 沒實作的類別庫。
沒辦法,於是就想說用 python 寫一個好了,當作練習。
首先遇到的問題就是 cell id 怎麼取得,原本以為是在 exif 裏面,用了 PIL 去撈,卻沒撈到。胡亂找了一陣,才知道這是放在 XMP metadata 裏面,得用 XML toolkit sdk 來撈才行,python 也是有 binding:Python XMP toolkit,只是得另外裝,ubuntu 沒有包進去。
是故,得先裝 libexempi3 (sudo apt-get install libexempi3),再下載 Python XMP toolkit,解開後,執行 sudo python setup.py install 來安裝。這樣就大功告成了。
程式很簡單,簡單到爆:
#!/usr/bin/env python import sys from libxmp import file_to_dict CELL_NS="http://developer.sonyericsson.com/cell/1.0/" if __name__ == '__main__': xmp = file_to_dict( sys.argv[1] ) for prop_name, prop_value, prop_options in xmp[CELL_NS]: print prop_name, prop_value
就這樣就可以把 Cell ID 相關的資訊都撈出來了。
pywin32應用:刪Outlook過期郵件
寫了個小程式,來幫我把 outlook 寄件備份跟已刪除郵件的資料夾裡超過30天的郵件刪除掉。
主要使用python跟pywin32,雖然程式很短,但花了不少時間摸索。
首先是對 Office Outlook 的物件模型不了解,我參考了不少網路上的範例(例如:MG: Python for Windows)以及微軟提供的參考才摸索出來。
另外就是對時間的處理,OLE的日期時間跟python的日期時間不一樣,我參考了Converting OLE datetime values into Python datetime objects來作。
最後就是意外發現了 pywin32 有提供 makepy.py 這個工具,可以預先根據 COM Type Library 產生 code 出來,這就很類似 .Net framework 提供的 tlbimp。
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import os, sys
import win32com.client
from datetime import datetime
from datetime import timedelta
import pywintypes
# Microsoft Outlook Constants
# http://msdn.microsoft.com/en-us/library/aa219371(office.11).aspx
olFolderDeletedItems=3
olFolderSentMail=5
# or you can use the following command to generate
# c:\python26\python.exe c:\Python26\lib\site-packages\win32com\client\makepy.py -d
# After generated, you can use win32com.client.constants.olFolderSentMail
# http://code.activestate.com/recipes/496683-converting-ole-datetime-values-into-python-datetim/
OLE_TIME_ZERO = datetime(1899, 12, 30, 0, 0, 0)
def ole2datetime(oledt):
return OLE_TIME_ZERO + timedelta(days=float(oledt))
if __name__ == '__main__':
app = win32com.client.Dispatch( "Outlook.Application" )
ns = app.GetNamespace( "MAPI" )
folders = [
ns.GetDefaultFolder(olFolderSentMail),
ns.GetDefaultFolder(olFolderDeletedItems)
]
for folder in folders:
print( "Processing %s" % folder.Name )
past30days=datetime.now()-timedelta(days=30)
mark2delete=[]
#If you use makepy.py, you have to use the following codes instead of "for item in folder.Items"
#for i in range(1,folder.Items.Count+1):
# item = folder.Items[i]
for item in folder.Items:
if ole2datetime(item.LastModificationTime)<past30days:
mark2delete.append( item )
if len(mark2delete)>0:
for item in mark2delete:
print( "Removing %s" % item.Subject )
item.Delete()
else:
print("No matched mails.")