pythin tkinter

老闆要我幫 HW team 的同事開發一個工具,讓他們能比對線路圖。雖說是線路圖,但並不是真的去對圖,而是會先將圖轉為文字檔,再針對這個文字檔來做比對。文字檔的內容會描述線路經過哪些點,這樣實際上是可以自己做,但是問題在於第一次匯出跟第二次匯出的檔案內容並沒有順序性,也因此沒有辦法使用WinMerge之類的文字比對軟體來做。研究過匯出的文字檔內容以後,發覺並不難,只要用 python 很快就能處理完。輸出的結果就以我比較擅長的 HTML + jquery 來做,就可以達到動態的效果,同時也可以跨平台。只是對於 HW team 的同事來說,讓他們老是打指令來產生結果的HTML,是很麻煩的,所以我就在想 GUI 部份要怎麼做。
Python的GUI大致上有以下選擇,其實就涵蓋了主流的幾個知名 Framework:

  • pygtk
  • pyQt
  • wxPython
  • IronPython+Windows form+.Net framework
  • tkinter(tcl/tk)

pygtk、pyqt、wxpython、IronPython都需要安裝額外的runtime library,實在太麻煩,所以後來決定用 Windows python 就有內建的 tkinter 來做。python tkinter 實際上是 tcl/tk 的 binding,我不知道為什麼會包進去,不過既然有,那就方便多了。
教學可以參考PythonInfo 上的 TkInter,如果有碰過 pygtk,那麼,應該是不難。介面也大致跟 Windows 內建的相近,這邊我的心得就是其實可以不用 TopLevel,這會導致多一個多餘的視窗。

window=Tk()
window.title( "my_app" )
window.mainloop() # 直接呼叫 mainloop() 也可以

文字欄位的存取是用 Entry,Entry 內容的存取要透過 StringVar,也就是你要先產生一個 StringVar 物件,Assign 這個物件給 Entry 之後,才能用這個 StringVar 物件來存取。

filename_var=StringVar()
entry=Entry( window, width=100, textvariable=filename_var )
entry.pack()  # 一定要 pack 一下才會出現,要不然你什麼都看不到。
filename_var.set( "Hello" )  # 賦值
print filename_var.get() # 取值

寫到這裡,UI 已經悄然成型,但是字都…好小,找了好久,最後不得已,先產生 global font 物件之後,再一個一個控制項去設定字型:

global_font=("Arial",12)
entry=Entry( window, font=global_font, width=100, textvariable=filename_var )

好了,大致的 UI 有了,那麼怎麼選取檔案,原本以為要自己作一個 Dialog,後來仔細看過官方 TkInter文件之後,發現有提到一個 tkFileDialog,但卻沒提及用法。再根據 python tkFileDialog 關鍵字去找,發現 ActiveState Code上的這篇有使用範例,有了這個再去挖 source code,就可以知道有哪些 method 可以用,我只用到 tkFileDialog.askopenfilename()跟tkFileDialog.asksaveasfilename()。這個 Dialog 的底層是用到 Windows 內建的 Dialog,所以至少介面跟 Windows 接近一致了。

filename=tkFileDialog.askopenfilename( title="Select a file", filetypes=[
( 'JPEG files', '*.jpg' )
] )
output_html=tkFileDialog.asksaveasfilename( title="Save output as...", filetypes=[
( 'HTML files', '*.html' )
] )

Python PIL 貼圖

如果我沒找到這篇PIL Tutorial: How to Create a Button Generator的話,我不知道還要走多少冤枉路~總之要貼出透明的效果,要在 paste() 時把要貼的那張圖當作第3個參數傳進去,這樣出來的效果就是對的!

# 從指定目錄拿三張圖片出來做小圖,第1張傾斜10度,第3章傾斜-10度,然後在貼到一起,做出類似撲克牌或紙疊在一起的效果。
import glob
import Image
def createThumbnail( filename ):
im = Image.open( filename )
im.thumbnail((96,96), Image.ANTIALIAS )
newImage = Image.new( "RGBA", (144,144) )
newImage.paste( im, (16,16) )
return newImage
for dir in sys.argv[1:]:
files = glob.glob( os.path.join( dir, "*.jpg" ) )
im0 = createThumbnail( files[0] ).rotate( 10 ).crop( ( 0, 0, 128, 128 ) )
im1 = createThumbnail( files[1] ).crop( ( 0, 0, 128, 128 ) )
im2 = createThumbnail( files[2] ).rotate( -10 ).crop( (0, 0, 128, 128 ) )
im0.paste( im1, ( 10, 0 ), im1 )  # 關鍵!!
im0.paste( im2, ( 20, 10 ), im2 )# 關鍵!!
im0.save( "out.png")

ConfigParser/ConfigObj

前一陣子心血來潮,做了一個很簡單的 subversion 管理網站,用來設定 htpasswd 跟 svnmailer 的設定。原本想說 svnmailer 的設定正好可以用 python 內建的 ConfigParser 來做,想不到用來讀設定是可以,但遇到寫的時候,就出錯了。因為寫的時候,ConfigParser 會因為 Hash 的特性,而無法依照原來的順序寫回去。沒辦法,只能摸摸鼻子,找別的 library 來做,最後是找到 ConfigObj 來做,幸好方法也不難,依照官方文件依樣畫葫蘆就行了。

我的 mod_python 初體驗

上週因為覺得老是要用遠端桌面登入 server 去管理 subversion 這件事情很麻煩,所以就在想,該怎麼簡化這工作?
剛好 server 上的 Apache 有裝 mod_python,就想用 django/turbogear2 來寫 web 介面來管理,但仔細 survey 之後,發現 django/turbogear2 並不是那麼的方便,最後決定用 viewvc 的方法,也就是用 mod_python 提供的 library 來寫。
寫起來出乎想像的簡單,我最主要是參考官方提供的 example site 以及 Manual
mod_python 提供的 psp (python server page) 基本上就是類似 ASP 的機制,你可以在 template 放 python 的程式或是用 <%=變數%> 引用變數,然後就可以在程式裡呼叫函式,將 template 轉為字串,然後送出去,這裡拿 example site 的程式來改是最方便的了。
我並不是用最基本的 handler,而是用 publisher handler,所以不需要在 .htaccess 指定 handler 的函式名稱,而是交給 publisher handler 來處理,他會根據網址自動找到你程式裡的函式,例如:http://examplesite.org/index ,publisher 會呼叫你程式裡的 index() 函式。這邊可以參考 Manual 裡的 7.1.2.1 Traversal 一節。
帳號認證機制,也很簡便,就只要提供 __auth__ 跟 __access__ 即可,__auth__ 是作帳號認證,而 __access__ 則是作控管。這部份可以參考 Manual 的 7.1.2.3 Authentication 一節。
用這些,基本上就可以搭出類似 MVC 架構的網站了。有了這些,要解決的就是 svn server 帳號管理、svn 目錄管理以及存取 svnmailer 設定檔的問題了。

移除七天前檔案

無心幹正事,可是卻有心搞 script,這真是…

#!/usr/bin/env python
# Usage:
#   remove_files_older_than.py ./*.jpg
import sys
import glob
import stat
import os
import time
if len(sys.argv)expireDays :
print diff/(60.0*60*24)
print "Remove %s, mtime=%s..." % (filename, time.strftime(
"%Y/%m/%d %H:%M:%S", modifiedTime ) )
os.remove( filename )
deletedCount=deletedCount+1
if deletedCount > 0:
print "Remove %d files" % deletedCount
else:
print "No files expired."

以下則是摘錄自:Windows Shell 刪除七天前資料
PowerShell的用法

Get-ChildItem -Recurse -force C:\files | Where-Object {!($_.Mode -match "d") -and ((Get-Date).Subtract($_.LastWriteTime).TotalDays -gt 7) } | Remove-Item -Force

Bash 的用法

find -mtime +7 -exec rm -f {} \;

viewvc 出現 ImportError: DLL load failed

環境:

  • Apache 2.0.x
  • mod_python 3.3.1
  • python 2.5.4
  • Subversion 1.5.6
  • svn-python 1.5.6
  • viewvc 1.0.7

查了好久,原本以為是 PATH 問題,手動在 viewvc 的 mod_python.py 裡加上 sys.path.append( r”c:\program files\subversion\bin” ) 也沒有用。
後來才爬到這篇文:#6739 (trac svn-python mismatch with apache 2.2 under windows),說是要把 subversion 的 dll 複製到 Apache 的執行目錄下。
查了 Apache 的執行路徑,發現真的有重複的 dll:

  • libapr.dll
  • libapriconv.dll
  • libaprutil.dll
  • libeay32.dll
  • ssleay32.dll

,於是備份之後,再把 subversion 下的這些 dll 複製過來,重新啟動 Apache 就解決了。

python 的 site-packages 與 dist-packages

好鳥,如果你打算把你的 python script 包成 debian package,在 debian/rules 裡呼叫 setup.py 時,記得加上 –install-layout=deb。
如果不加,預設會把 package 裝到 site-packages 目錄下,但這個目錄是不在預設搜索packages路徑裡的。加上 –install-layout=deb 以後,才會裝到 dist-packages 下,這個路徑才會在預設搜索packages路徑裡。
想知道預設搜索packages路徑,可以先 import sys,再 print sys.path,就可以看到。
參考自:Python modules, distutils.core setup and install paths – Ubuntu Forums

發佈你的 Python 程式

因為覺得老是從專案目錄下執行程式也不是很方便,所以想,應該要把程式弄的標準一點,能依循比較正式的方法去發佈程式。一般來說,各大語言都會有建議怎麼去散佈、發佈你的程式。
所以今天早上胡亂地用 python deployment guide 找了一下,發現找不到這樣的 guide。後來才發現找錯關鍵字,python 裡是用 distribute/spreading,改變一下關鍵字,就能找到這幾份:

文件不難,照著試一試,的確一下子就寫好了。
比較麻煩的是程式如果要用到 module 裡的一些檔案時,會不知道路徑。舉例來說,我是把 glade 產生的 gtkbuilder ui 檔案放在 module 路徑下,那程式該怎麼讀取呢??總不能寫死吧。還好 Stack Overflow有解答:Retrieving python module path,就是用 os.path.dirname(module.__file__) 取得。

Bing translate in Python

根據 Microsoft Translator HTTP 介面,然後用Python寫的。程式不長,其實跟 google translate 的方法很相似,最下面有使用範例。

附帶一提的,Bing翻譯網站上完全沒提到要怎麼申請 appid,申請 appid 要去 Bing Developer Center 申請,所以你要自己去申請,取得 appid 之後,把程式裡的 your_app_id 換成你申請的 appid。

#!/usr/bin/env python
# -*= coding: utf-8 -*-
import sys
import os
from urllib import urlencode
import urllib2
from urllib2 import Request, urlopen, URLError, HTTPError
import json
from translate import *
class BingTranslate:
appid = 'your_app_id'
base_uri = "http://api.microsofttranslator.com/V1/Http.svc"
def __read_from_req( self, req ):
try:
response = urllib2.urlopen( req )
result = response.read()
except HTTPError, e:
print e.code
print e.read()
result=""
return result
def detect( self, text ):
uri="%s/Detect?appId=%s" % ( self.base_uri, self.appid )
req = urllib2.Request( uri, text, { 'Content-Type':'text/plain'} )
return self.__read_from_req( req )
def getLanguageNames( self, locale=None ):
"""
Thanks MSDN, I still don't know the value of parameter 'locale'
Don't pass any parameter to getLanguageNames, or you will get error.
"""
uri="%s/GetLanguageNames?appId=%s" % ( self.base_uri, self.appid )
req = urllib2.Request( uri, locale, { 'Content-Type':'text/plain'} )
return self.__read_from_req( req ).split( '\n' )
def getLanguages( self ):
uri="%s/GetLanguages?appId=%s" % ( self.base_uri, self.appid )
req = urllib2.Request( uri, None, { 'Content-Type':'text/plain'} )
return self.__read_from_req( req ).split("\r\n")
def translate( self, text, fr="en", to="zh-CHT" ):
uri="%s/Translate?appId=%s&from=%s&to=%s" % ( self.base_uri,
self.appid, fr, to )
req = urllib2.Request( uri, text, { 'Content-Type':'text/plain'} )
return self.__read_from_req( req )
if __name__ == "__main__":
t = BingTranslate()
print t.translate( "test" )
print t.translate( "鬼", "zh-CHT", "en" )
print t.detect( "中文測試" )
print t.detect( "中文测试" )
names = t.getLanguageNames()
langs = t.getLanguages()
print "Language Names (total %d):" % len(names)
for l in names:
print l
print "Languages (total %d):" % len(langs)
for l in langs:
print l

Python 練習 – Google translate

因為看了A Bit? No!!!的這篇:写了个小小的翻译工具而做的小小練習。
基本上是作中翻英,但只要稍稍更動 langpair,就可以調整翻譯語言。

#!/usr/bin/env python
# -*= coding: utf-8 -*-
# http://www.oreillynet.com/pub/h/476 Encode Text for URLs
# http://evanjones.ca/python-utf8.html How to Use UTF-8 with Python
# http://abitno.linpie.com/a-small-translate-tool.html 寫了一個小小的翻譯工具
import sys
import os
from urllib import urlencode
import urllib2
import json
def get_json( uri ):
response = urllib2.urlopen( uri )
return response
def translate( text ):
uri="http://www.ajax.googleapis.com/ajax/services/language/translate"
query=urlencode( { 'v': '1.0', 'langpair': 'zh|en', 'q':
text.encode('utf-8') } )
uri = uri+"?"+query
return json.load( get_json( uri ) )
text = unicode( sys.argv[1], "utf-8" )
print unicode( text ) + u" => " + translate( text )['responseData']['translatedText']