2010年2月28日 星期日

注意 Python exec 的 scope

在 iPython 裡無法用 exec? 查到 exec 的文件, 卻能查到 eval、execfile。我才發現原來 Python 2 的 exec 是 statement! 如同 print, 到 Python 3 才改為 function。

若要讓 exec 執行後的結果能被其它 scope 的程式存取, 要改用「exec CODE in globals()」或「exec CODE in locals(), globals()」, 見下面的例子:

def add_hello():
    exec "def hello(): print 'hello'"
    hello()

add_hello()  # "hello"
hello()  # NameError: name 'hello' is not defined

def add_hello():
    exec "def hello(): print 'hello'" in locals(), globals()
    hello()

add_hello()  # "hello"
hello()  # "hello"

2010年2月25日 星期四

Blog 切割, 非技術文移至新 Blog

抱著隨便亂撒種子的心態種了這個 blog, 結果長沒多久儼然變成技術導向的短文集散地。有時想寫些非技術文, 卻覺得怪怪的, 寫了不知給誰看。另一方面, 偶而有非資工的朋友說他們來看我的 blog, 卻不知在寫啥。乾脆就將這兩者切開吧。想想自己去看些工程師 blog 時, 有時也沒興趣看他們的日常生活。

本 blog 英文名稱仍舊是 http://fcamel-life.blogspot.com/, 只是中文名稱改為「fcamel 技術隨手記」。Coding is my life! 所以這英文名稱絕對沒錯啦。另外新開 http://fcamel-daily.blogspot.com/, 中文名稱為「fcamel 雜記」, 歡迎舊雨新知逢場。

2010年2月23日 星期二

用 strace 和 ltrace 找出用到的 system call 和 library call

前面提到 host 沒有 call gethostbyaddr, 面惡心善的 Scott 大概是查覺我下載了原始碼, 卻沒有找出確認它的方法。於是在另一篇留言裡說可以用 strace、ltrace 或 gdb 輕易做到這事 (幸好我還沒開始試 profiler 啊...)。

strace 和 ltrace 顧名思議, 它們會列出執行期間用到的 system / library call。若不確定有興趣的函式是那個, 可以用 man page 編號來判別。比方 man gethostbyaddr 顯示被分在  section 3 下, 所以 gethostbyaddr 是 library call [*1]。

分別拿 host 和對照組 getent 試的結果, 可以看到 getent 有打開 /etc/hosts (從 strace 那看到的), 並呼叫 gethostbyaddr;而 host 卻兩者皆無。解開疑惑實乃人生一大痛快之事, 感謝 Scott 的指點。

附上參考用指令:
$ ltrace getent hosts 127.0.0.1 2>&1 | grep gethostby
gethostbyaddr("\177", 4, 2)                      = 0xb7ed0aa0
$ strace getent hosts 127.0.0.1 2>&1 | grep "/etc/hosts"
open("/etc/hosts", O_RDONLY|O_CLOEXEC)  = 3

$ strace host 127.0.0.1 2>&1 | grep "/etc/hosts"
$ ltrace host 127.0.0.1 2>&1 | grep gethostby

另外我好奇之下試了傳說中人人都會寫的 C 版 「Hello, world! 」, 結果發現 compiler 很聰明地用 puts 而非 printf。

參考程式如下:

#include <stdio.h>

int main(void)
{
    printf("hello, world!\n");
    puts("hello, world!\n");
    printf("hello, world! %d\n", 3);
    return 0;
}

執行結果:
$ ltrace ./f > /dev/null
__libc_start_main(0x80483f4, 1, 0xbf812ab4, 0x8048450, 0x8048440 
puts("hello, world!")                            = 14
puts("hello, world!\n")                          = 15
printf("hello, world! %d\n", 3)                  = 16
+++ exited (status 0) +++

接著我用 ltrace 執行 python (ltrace python -c ''), 結果 ltrace 狂噴訊息卻不會停......, 今日閒暇時間用盡這待那天有緣再來研究吧。

2010-02-25 更新

Scott 在留言裡提到也可以用 LD_PRELOAD  抽換動態載入的函式來達到同樣目的 (確認是否有呼叫 gethostbyaddr)。範例程式如下 (稍微修正 Scott 的範例讓它在我的機器能正常 compile):
$ printf '#include <stdio.h>\n#include <assert.h>\nvoid gethostbyaddr(void) { assert(0); }\n' > t.c
$ gcc -fPIC -shared t.c -o t.so
$ LD_PRELOAD=./t.so getent hosts 127.0.0.1
getent: t.c:3: gethostbyaddr: Assertion `0' failed.
Aborted
$ LD_PRELOAD=./t.so host 127.0.0.1
1.0.0.127.in-addr.arpa domain name pointer localhost.

上面的範例透過 LD_PRELOAD 讓程式改用自訂的 gethostbyaddr。如此一來, 抽換掉懷疑的函式再執行指令, 就知道是否有用到了。LD_PRELOAD 的詳細說明可參考 jserv 翻譯的 《Modifying a Dynamic Library Without Changing the Source Code / 在不更動原始程式碼的前提下,修改動態程式庫》。

備註
  1. man man 可看到各 section 的含意, 摘錄如下:
    1 Executable programs or shell commands
    2 System calls (functions provided by the kernel)
    3 Library calls (functions within program libraries)
    4 Special files (usually found in /dev)
    5 File formats and conventions eg /etc/passwd
    6 Games
    7 Miscellaneous (including macro packages and conven-
    tions), e.g. man(7), groff(7)
    8 System administration commands (usually only for root)
    9 Kernel routines [Non standard]

    總共也才九節, 遊戲竟然自成一節......。

2012-01-30 更新

見 tag strace 下的新文章, 了解比較詳細的 strace 使用例子。

2010年2月22日 星期一

安裝 man pages (Linux system calls (2) and Library calls (3))

Ubuntu 裝好後, 預設沒有 system 和 library calls (如 man printf )。得另外安裝:
sudo aptitude install manpages-dev

MySQL 建立連線時會延遲一下

最近用 Python 的 MySQLdb 連線忽然會卡個五秒才完成, 一路試的結果, 發現五秒鐘全用在 new _mysql.connection。改用一般的 mysql client 試, 也會卡五秒, 才發現問題出在 MySQL server。這和之前登入 SSH 時會延遲一下是同樣狀況, 連本機沒問題, 連另一台就會出問題。

參照《How MySQL Uses DNS》的說法, 即使連 IP, MySQL server 也會查 DNS。若沒設好 IP 反查, 就會卡個一陣子繼續。有兩種簡單解法:
  1. 如官網所言, 重跑 mysqld, 加上 --skip-name-resolve
  2. 在 mysqld 執行的 server 上, 在 /etc/hosts 裡幫 client 所在的 IP 隨便加筆記錄。如 client IP 為1.2.3.4 就加上:

    1.2.3.4 myhost.org.tw myhost
    這樣就不會去問 DNS 等個五秒才放棄。
以下是較不相關的碎碎念。
man page 說設好 /etc/hosts 後應該會立即生效, 除非是程式有設 cache。我試的結果是, host、nslookup 都沒反應, 確認過 /etc/nsswitch.conf、/etc/host.conf 都說會先查 /etc/hosts 才查 DNS, 搞不清楚為啥 (也不知道到底是以 nsswitch.conf 還是以 host.conf 為準)。後來乾脆用 C network API (gethostbyaddr) 直接查, 藉此確認我有設對 /etc/hosts.conf。原以為可以秒殺, 結果寫寫一堆問題。用 C coding 真麻煩, 後來改用 Python 瞬間搞定:
import socket
print socket.gethostbyaddr('127.0.0.1')

2010-02-22 更新

經 Scott 說明才知道 host 和 nslookup 不會查 /etc/hosts, 將他的留言直接貼進來:
This is a common problem that newbies run into. Answer: host(1) and nslookup(1) does not call gethostbyname(3) or the IPv6 safe getnameinfo(3) but issue DNS requests directly.

What you want is getent(1) from glibc:

getent hosts 127.0.0.1
查 man host 才發現, 文中沒有提到 /etc/hosts 啊!! 而 man gethostbyname, gethostbyaddr 才有提到 /etc/host.conf 和 /etc/hosts。

2010年2月20日 星期六

Practical Common Lisp - ch3 Practical: A Simple Database

http://gigamonkeys.com/book/practical-a-simple-database.html

由於之前有學過一點 Scheme 和 scripting language, 初讀前半時覺得有些無趣, 沒有那種神兵利器的感受也沒有新鮮感 [*1]。讀到後半看到 macro 時, 開始覺得有趣, 讀完後覺得「嗯......, 還不錯啦」, 直到自己試著用 Python 想寫類似的功能時, 才驚覺「沒可能!! (請用日文發音) 這麼點簡單的小事, Python 竟然做不到, 而 CL 輕易地做到了!!」[*2]


摘要

  • CL 並不是 pure functional language, 可看到像 imperative language 的條列式寫法 (即像 C 那樣一行行寫下去, 程式也一行行地執行)。
  • 函式、變數名稱沒分大小寫。
  • Nil 表示偽值, 其餘值表示真值。CL 有預設參數, 沒傳參數的預設值為 Nil。若要區分沒傳入參數或傳入 Nil, 得在寫函式參數時指明額外的變數用來表示是否有參數傳入。像 Java、Python 同時有 False 和 null (None), 就沒這問題。很難說何者的作法較佳。
  • plist 有點像 associated list, 但聽別人說 CL 沒有 hash table, 應該還是不同東西吧。現階段也只會照範例程式用。
  • CL 的 format 相當於 C 的 printf, 只不過換用另一種外星語表示參數。
  • macro 很威, 見下文。

macro


CL 可以定義函式或 macro, 定義 macro 和定義函式語法差不多, 差別在於傳給 macro 的參數不會被執行 (evaluated), 而是當成資料交給 macro 處理。運算完的結果再當作程式來執行 [*3]。書上的範例如下:

(defmacro backwards (expr) (reverse expr))

執行範例如下:

CL-USER> (backwards ("hello, world" t format))
hello, world
NIL

若 backwards 是一般函式的話, 這個例子會 compile error, 因為 "hello, word" 不是函式。若想看 macro 展開的結果, 可以用 macroexpand-1 (最後那個是數字一):

CL-USER> (macroexpand-1 '(backwards ("hello, world" t format)))
(FORMAT T "hello, world")
T

書上最後的範例是用 macro 做出產生 SQL where 語法的函式。這個函式會產生比對欄位值的函式 (用 Lisp / Python / Ruby 的角度來看, 可當作 filter 的參數)。這和直接寫死的差別在於:
  1. 可以接受不定的參數。
  2. 只會比對給定的參數, 不需執行沒給定的參數。注意, 是「不需執行」, 不是「不用比對」。
舉例來說, 若資料表查詢欄位有 A、B、C 三者, 寫死的話不管查詢時是傳入 A、B、C 還是只傳 A, 程式至少都會做三次判斷 (是否有傳入欄位 X? 若有, 傳入的值是否符合? )。但用 macro 產生程式的話, 要幾個欄位就只會比對幾個欄位 (比方產生一個函式, 它只比對 B 的值, 不檢查有沒有傳入 A、C)。接受不定參數還有辦法用其它方式搞定, 但不執行多餘的程式, 卻是非得動態產生程式碼不可。而 macro 讓這件事變得很容易。

作者給了個很妙的評論: 用 macro 可以寫出更抽象 (也意味著更短) 的程式, 並且還能執行得更快。和 C 的 macro 差異在於, C 的 macro 是用前置處理器展開的, 它不懂語法, 容易產生很難除的錯 (應該不少人誤加分號, 結果出現一大片莫明奇妙的 compile error 訊息吧)。而 CL 的 macro 是由 compiler 展開的, 比較安全且有彈性 [*4]。讓程式處理資料, 比前置處理器展開文字有更多發揮空間。並且, 在編輯期間展開 macro 意味著不會拖慢執行速度。難怪用過的都說神!!


疑問 / 抱怨

  • 太多外星符號, 像是 「'」、「`」、「#'」、「`」、「,@」。也許習慣後就好。
  • 用 macro 會不會很難除錯? 不易維護?

備註

  1. 有寫過 C/C++/Java 的人, 初次寫 scripting language 應該會很驚呀吧。若沒感到驚呀, 表示連皮毛都沒學到, 寫得太 C/C++/Java 了。不然應該能寫得又短又好懂。
  2. 雖然 Python 可以用 exec 在執行期間執行新產生的程式碼, 仍受限於用編輯器寫程式的框架, 不易用程式處理, 挺多用個文字樣板代換關鍵的程式碼。而產生完的程式, 更難做第二次處理。而 Lisp 用 list 表示程式碼成為它的優勢, 程式碼和資料並無顯著區別, 也難怪各家程式語言學不起 macro 這招。Paul Graham 曾對此吐槽, 若其它語言想加入 macro, 大概得用相似於 Lisp 的表示方式, 只不過這樣就不是一個新的程式語言, 而是另一個 Lisp 方言 (dialect) 了。
  3. 原文寫得比較清楚:

    the Lisp compiler passes the arguments, unevaluated, to the macro code, which returns a new Lisp expression that is then evaluated in place of the original macro call. 
  4. 但 CL 是 dynamic typing, 所以.......即使 macro 展開時沒有 compile error, 不表示執行時不會有語法錯誤。

Classification (ML) 和學英文都需要大量的訓練資料

最近聽 TED 練英文發覺有趣的事。TED上的題材五花八門,講者來自世界各地,讓我提早體會到不同發音的問題。若只在單家補習班練英文,大概無法體會到世界各地的腔調。體會歸體會,這樣聽下去能否一次通殺是另一個問題。

在練習聽力的同時,我同時觀察自己怎麼理解這些發音的。這實在是很奇妙的過程,有些音明明和標準發音差了十萬八千里,但是一連串的字彙連起來,卻能立即明白整句在說什麼 (比方 "William Kamkwamba: How I harnessed the wind" );有些字彙即使講得再標準,沒有相關知識也聽不懂 (即使是母語也是如此)。看來重點是前後文和背景知識。

以往學英文時總強調要多背單字,我試過幾次都失敗,我實在不是背書的料,但我深信有需求自然會用。回顧過去失敗的經驗,我發覺用不到的字背再多次也記不起來。更何況,既然 (幾乎) 用不到,背它們做什麼?投資報酬率太低了。反過來說,常用的字一再出現,有時不用查單字也能依上下文推論出它的意思。而且還會模糊地學會它在不同情況的差異,但要仔細地描述它是什麼意思,卻又說不上來。

這樣的學習過程中,我發覺若要增加我認識的字彙,我得聽讀更多內容,才能交差地認得這些單字。像我昨天讀 "2 China Schools Said to Be Tied to Online Attacks" 學到一些單字,其中一個單字是 specialist,意思是專家。今天重聽 "Ken Robinson says schools kill creativity" 又聽到 specialist,我自然就記得了。但若這幾天或幾週內沒再碰到 specialist,我可能會忘記。我得每天花上一小時以上的時間訓練英文能力,才能持續增長我的字彙。

想到這裡,忽然發現這和 classification 認得特徵 (feature) 的方式相似。提供大量的訓練資料和它們對應的分類,learning model 自然會從訓練資料中找到重覆的特徵,分辨出那些特徵和某些分類有關或是反相關。在同樣數量的訓練資料中,若訓練資料的內容相似、特徵類型集中,model 可以更精確地掌握特徵,但也會因此認得較少特徵;反過來說,若想認得更多特徵,得讓內容發散,但很可能學錯特徵。若想同時學得又準又多,只好提供更多訓練資料。

對應回英文字彙也是如此。若我想習得準確且多量的英文字彙,只好持續投入大量時間接觸英文。雖說是理所當然的事,想通後才更甘心地花時間練英文。話說回來,不同的 learning model 用不同方式掌握分類和特徵的關係。強的 model 可以用較少的資料學得又準又多。人腦這麼強大,應當能更有效率地學英文。雖說持續投入時間接觸英文是必要的,也要不斷摸索如何學得更有效率。目前的想法是當我能用自己的話用上這些字彙時,就能記牢它們。所以要試著用這些字造句,或直接重述一遍原文。很土法煉鋼的作法,總比過去坐在那唉唉叫都不動來得好。

附帶一提,人類學習語文時會不斷用上背景知識和前後文,classification 卻缺少這兩項東西,使得這類解法侷限在特定領域,被歸類到 applied AI。我還無法想像如何將人類的背景知識 (或稱常識?) 編入電腦,但至少待電腦的運算能力和記憶體更大之後 (這是可預見的發展),電腦可以認得更廣的前後文 (現今的作法最多只看三個字彙左右),配合更大量的訓練資料,也許會有出乎意料的進展。就像電腦在解決下棋之類的益智遊戲時,並無法如人類用較少的模式掌握情況,但能用較簡單的方式配合大量運算達到更好的效果。

在 Fedora 下裝 id-utils

Fedora 似乎因為執行檔撞名,而沒有提供 id-utils 的套件 ,但這是使用 gj 的必要套件,只好自己編。從官網抓好 tarball ,解開來編譯 (./configure && make)就是了。 但編譯後會遇到錯誤: ./stdio.h:10...