星期四, 五月 31, 2007

python how to write a daemon

学习 C 语言的系统调用已经是几年前的事情了,现在都有点忘了。最近用 Python 可能要写 daemon,找资料先看看。参照《UNIX 环境高级编程》第十三章:
(1) 首先做的是调用 fork,然后使父进程 exit。这样做实现了下面几点:
第一,如果该精灵进程是由一条简单 shell 命令起动的,那么使父进程终止使得 shell 认为这条命令已经执行完成。

第二,子进程继承了父进程的进程组 ID,但具有一个新的进程 ID,这就保证了子进程不是一个
进程组的首进程。这对于下面就要做的 setsid 调用是必要的前提条件。

(2) 调用 setsid 以创建一个新对话期。于是执行 9.5 节中列举的三个操作,使调用进程:
(a). 成为新对话期的首进程,(b) 成为一个新进程组的首进程,(c) 没有控制终端。

在 SVR 之下,有些人建议在此时再调用 fork,并使父进程终止。第二个子进程作为精灵进程继续运行。这样就保证了该精灵进程不是对话期首进程,于是按照 SVR4 规则(见9.6节)可以防止它取得控制终端。另一方面,为了避免取得控制终端,无论何时打开一个中断设备都要指定 O_NOCTTY。

(3) 将当前工作目录更改为根目录。从父进程继承过来的当前工作目录可能在一个装配的文件系统中。因为精灵进程通常在系统再引导之前是一直存在的,所以如果精灵进程的当前工作目录在一个装配文件系统中,那么该文件系统就不能被拆卸。另外,某些精灵进程可能会把当前工作目录更改到某个指定位置,在此位置做它们的工作。例如,行式打印机假脱机精灵进程常常将其工作目录更改到它们的 spool 目录上。

(4) 将文件方式创建屏蔽字设置为0。由继承得来的文件方式创建屏蔽字可能会拒绝设置某些许可权。例如,若精灵进程要创建一个组可读、写的文件,而继承的文件方式创建屏蔽字,屏蔽了这两种许可权,则所要求的组可读、写就不能起作用。

(5) 关闭不再需要的文件描述符。这样使精灵进程就不再持有从其父进程继承来的某些文件描述符(父进程可能是 shell 进程,或某个其他进程)。但是,究竟关闭哪些描述符则与具体的精灵进程有关,所以在下面的例子中不包含此步骤。可以使用程序2-3中的 open_max 函数来决定最高文件描述符值,并关闭直到该值的所有描述符。


一个 Python 的实例如下:
http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/66012
import sys, os 

def main():
""" A demo daemon main routine, write a datestamp to
/tmp/daemon-log every 10 seconds.
"""
import time

f = open("/tmp/daemon-log", "w")
while 1:
f.write('%s\n' % time.ctime(time.time()))
f.flush()
time.sleep(10)


if __name__ == "__main__":
# do the UNIX double-fork magic, see Stevens' "Advanced
# Programming in the UNIX Environment" for details (ISBN 0201563177)
try:
pid = os.fork()
if pid > 0:
# exit first parent
sys.exit(0)
except OSError, e:
print >>sys.stderr, "fork #1 failed: %d (%s)" % (e.errno, e.strerror)
sys.exit(1)

# decouple from parent environment
os.chdir("/")
os.setsid()
os.umask(0)

# do second fork
try:
pid = os.fork()
if pid > 0:
# exit from second parent, print eventual PID before
print "Daemon PID %d" % pid
sys.exit(0)
except OSError, e:
print >>sys.stderr, "fork #2 failed: %d (%s)" % (e.errno, e.strerror)
sys.exit(1)

# start the daemon main loop
main()
注意还有一个很重要的事情就是出错记录,即写入日志。比较好的方法可能是使用 syslog 设施。

星期三, 五月 30, 2007

bash number

sh$ i=0
sh$ i++
-bash: i++: command not found
sh$ (j=0)
sh$ echo $j
# 空
sh$ ((k=0))
sh$ echo $k
0
sh$ ((k = 0))
# 注意等号两边有空格
sh$ echo $k
0
sh$ (k++)
-bash: k++: command not found
sh$ ((k++))
sh$ echo $k
1

sh$ i=5
sh$ ((k = $i))
sh$ echo $k
5

bash array (1D)

sh$ names=(Roc Chowroc Zhou)
sh$ echo $names
Roc
sh$ echo ${names[@]}
Roc Chowroc Zhou
sh$ echo ${#names[@]}
3
sh$ echo ${names[1]}
Chowroc

sh$ names="Roc Chowroc Zhou"
sh$ echo $names
Roc Chowroc Zhou
sh$ echo ${names[@]}
Roc Chowroc Zhou Chowroc Zhou
# why?

sh$ echo ${#names[@]}
3
sh$ echo ${names[2]}
Zhou

sh$ i=2
sh$ echo ${names[$i]}
Zhou
sh$ echo ${#names[$i]}
4
sh$ echo ${#names[1]}
7

# 追加和插入数组元素
sh$ names=(Roc Chowroc Zhou)
sh$ names=(${names[@]} Test)
sh$ echo ${names[@]}
Roc Chowroc Zhou Test
sh$ names=( Roc Chowroc Zhou)
# 注意有空格
sh$ echo ${names[@]}
Roc Chowroc Zhou
sh$ x="TEST"
sh$ names=($x Roc Chowroc Zhou )
sh$ echo ${names[@]}
TEST Roc Chowroc Zhou

sh$ names=('Roc Zhou' 'roc' 'chowroc')
# 注意,这里希望引号里面的内容作为一个元素,即使有空格
sh$ echo ${#names[@]}
3
sh$ echo ${names[2]}
chowroc
sh$ echo ${names[0]}
Roc Zhou

# 更改元素内容:
sh$ names[2]='testing'
sh$ echo ${names[@]}
Roc Zhou roc testing
# 如果是数字:
sh$ a=(1 2 3)
sh$ ((a[1]++))
sh$ echo ${a[@]}
1 3 3
参考:

http://www.tech-recipes.com/bourne_shell_scripting_tips636.html

http://www.faqs.org/docs/bashman/bashref_71.html

星期二, 五月 29, 2007

rpm -qa --last

按照安装时间进行排序,这样可以看到最后安装的包,从而对配置的备份等作出调整。

星期一, 五月 28, 2007

删除以“-”打头的目录

每次都不记得。记下来:
sh$ rm ./-f

星期二, 五月 22, 2007

xargs output quota symbol

sh$  find . -type f | xargs grep 'src=http://google.171738.org/ad.js'
grep: ./syssite/shopadmin/images/loginbg: No such file or directory
grep: copy.jpg: No such file or directory
grep: ./phpmyadmin/lang/复件: No such file or directory
grep: english-utf-8.inc.php: No such file or directory
这是因为实际的文件是 './phpmyadmin/lan/复件 english-utf-8.inc.php',中间有空格,所以处理时出问题了。

那么为了 find 的输出结果加上引号以避免这样的问题,可以这样做:
sh$ find . -type f | grep '\.php' | xargs -i echo \'{}\'

sh$ find . -type f | grep '\.php' | xargs -i echo \'{}\' | xargs grep 'src=http://google.171738.org/ad.js'
即在 xargs 输出 ' 号时必须使用反斜杠转义!

星期五, 五月 18, 2007

apache logrotate

必须设置 Apache 的日志自动轮转,否则其日志增长可能会很快,最终撑爆硬盘。在 /etc/logroate.conf 或 /etc/logrotate.d/httpd 中加入:
/usr/local/apache2/logs/*log {
monthly
rotate 3
}
但这样还不够,因为日志轮转后,如果不重启 httpd 进程,那么日志就会写到 access_log.1 中。可以在 logroate 配置中通过如下方法来指定重启:
    postrotate
/bin/kill -HUP `cat /usr/local/apache2/logs/httpd.pid 2>/dev/null` 2> /dev/null || true
endscript
}

星期四, 五月 17, 2007

bash cp && mv perm denied file

$ ls file.txt -l
-rw------- 1 root root 3731 Mar 9 11:06 file.txt

$ cp ftpd.conf tmp
cp: cannot open `file.txt' for reading: Permission denied

$ mv file.txt tmp
# OK !!!!!!

bash or(||)

echo "testing1" || echo "testing2"
testing1

(echo "testing1"; exit 1) || echo "testing2"
testing1
testing2

zip -d

可以使用 zip -d 将 zip 文件中某些目录删除。在删除之前,可以先用 unzip -l 查看路径结构,找到需要删除的路径,然后:
sh$ zip -d singlev47.zip syssite/smtpmail/\*
deleting: syssite/smtpmail/
deleting: syssite/smtpmail/big5.txt
deleting: syssite/smtpmail/jp.txt
deleting: syssite/smtpmail/kr.txt
deleting: syssite/smtpmail/sm.php
deleting: syssite/smtpmail/test_mail.php
deleting: syssite/smtpmail/zh.txt

python class attribute as method args?

>>> class test1:
... def __init__(self):
... self.logfile = '/var/log/httpd/access_log'
... def func(self, logfile=self.logfile):
... print logfile
...
Traceback (most recent call last):
File "", line 1, in ?
File "", line 4, in test1
NameError: name 'self' is not defined

SNMP shell script for process monitor

一个临时解决办法,为了监测进程的健康状态,首先必须在被控机上安装 net-snmp,然后配置 /etc/snmp/snmpd.conf 如下:
syscontact  sysadm@sample.com
proc vsftpd 100 1
proc httpd 3000 1
proc mysqld 3000 1
disk /data 100G
com2sec mynet 192.168.0.0/24 process-mon
group mynet v1 mynet
group mynet v2c mynet
view system included .1.3.6.1.2.1
view system included .1.3.6.1.4.1.2021.2
access mynet "" any noauth exact system none none
接着编辑脚本:

#!/bin/sh

PATH=$PATH:/usr/bin
PROGRAM=`basename $0`

community='demo'
host='localhost'
mailto=''
items=''
ilist='tmp'

args=`getopt -l help,item:items-list: c:h:m:i:I: $*`
if [ $? -gt 0 ]; then
strerr="Invalid options"
echo "$strerr" >&2
logger -it "$strerr"
exit 1
fi

for i in $args; do
case $i in
-c) shift; community=$1; shift;;
-h) shift; host=$1; shift;;
-m) shift; mailto=$1; shift;;
-i|--item)
shift
if [ -n "$items" ]; then
items=`echo -e "$items\n$1"`
else
items="$1"
fi
shift
;;
-I|--items-list)
shift; ilist=$1; shift;;
--help)
shift
echo "useage: $PROGRAM [-c|-h|-m] [--item|--help]
-c community
-h host
-m mailto
-i|--item item_map, 'community host' map
--help, print this message"
exit 0
;;
esac
done

if [ -z "$items" ]; then
items="$community $host"
fi

STRERR=""
if [ "$ilist" == "tmp" ]; then echo "$items" >$ilist; fi
while read community host; do
# echo "$items" | while read community host; do
# for item in "$items"; do
# community=`echo $item | awk '{print $1}'`
# host=`echo $item | awk '{print $2}'`
if [ -z "$community" -o -z "$host" ]; then
strerr="Invalid 'community host' item"
echo strerr >&2
logger -it $PROGRAM "$strerr"
continue
fi

snmp_result=`snmpwalk -v2c -c $community $host UCD-SNMP-MIB::prNames`
if [ $? -gt 0 ];then
strerr="$host: SNMP ERROR, maybe system is down"
echo "$strerr" >&2
logger -it "$PROGRAM" $strerr
STRERR="$STRERR\n$strerr"
fi
num=`echo "$snmp_result" | wc -l` && \
for i in `seq 1 $num`; do
status=$(snmpget -v2c -c $community $host UCD-SNMP-MIB::prErrorFlag.$i | awk -F' = ' '{print $2}') && \
process=$(echo "$snmp_result" | sed -n "$i{s/^UCD-SNMP-MIB::prNames.$i = STRING: \(.*\)$/\1/p}") && \
if [ "$status" == "INTEGER: error(1)" -o "$status" == "INTEGER: 1" ]; then
message=$(snmpget -v2c -c $community $host UCD-SNMP-MIB::prErrMessage.$i | sed "s/UCD-SNMP-MIB::prErrMessage.$i = STRING: \(.*\)$/\1/")
strerr="$host: $message"
echo "$strerr" >&2
logger -it $PROGRAM "$strerr"
STRERR="$STRERR\n$strerr"
fi
done
done <$ilist
# rm -f tmp

if [ -n "$mailto" ] && [ -n "$STRERR" ]; then
STRERR="*** CRITICAL ERRORs ***\n$STRERR"
echo -e "$STRERR" | mail -s "*** CRITICAL: process monitor ERRORs reporting ***" $mailto
fi
这个脚本可以这样运行:
sh$ process-mon -c demo -h 192.168.0.98
sh$ process-mon -i 'demo 192.168.0.98' -i 'mon 192.168.0.99' -m chowroc@sample.com
检查两台主机并在有问题时发送邮件到(mailto)指定的的地址
sh$ process-mon -I process-mon.list -m roc@sample.com
sh$ cat process-mon
demo 192.168.0.98
mon 192.168.0.99
...

bash while read line and variable scope?

比较下面两个 bash 脚本:
script (1)
echo -e "aaa\nbbb\nccc\nddd" >tmpfile
value=0
while read line; do
value=`expr $value + 1`
echo $value;
done "<"tmpfile
echo "at last: $value"

result:
1
2
3
4
at last: 4

script (2):
value=0
echo -e "aaa\nbbb\nccc\nddd" | while read line; do
value=`expr $value + 1`
echo $value
done
echo "at last: $value"

result:
1
2
3
4
at last: 0
在第二个脚本中,全局变量的变更没有起作用。这是因为 bash 中管道是在子 shell 中执行的。所以这里只能使用重定向。

参考:comp.unix.shell FAQ
This is because in the Bourne shell redirected control structures
run in a subshell
In shells other than ksh (not pdksh) and
zsh elements of a pipeline are run in subshells.


可以用如下的脚本验证
#!/bin/sh

echo -e "aaa" >tmpfile
while read line; do
ps aux | grep "$0" | grep -v grep
done "<"tmpfile
rm -f tmpfile

echo "---"
echo -e "aaa" | while read line; do
ps aux | grep "$0" | grep -v grep
done

a MySQL charset problem

有一个 dump1.sql 文件,其中中文字符集使用的是 UTF-8,但 CREATE 语句的 DEFAULT CHARSET=GBK,所以导入时是会出问题的:
sh$ mysql -u root test "<"dump1.sql
ERROR 1062 (23000) at line 5653: Duplicate entry '????' for key 2
进入数据库可以看到中文字段都是?,所以中文字数相同就会重复。要解决这个问题,首先需要对文件转码,然后使用正确的参数导入。

当然数据库首先必须支持 GBK 字符集,可以用 SHOW CHARACTER SET 查看所有支持的字符集,如果不支持,可以在编译的 ./configure 增加 --with-extra-charsets=gbk 参数。

然后如下操作:
转码:
sh$ iconv -f UTF-8 -t GBK -o dump2.sql dump1.sql
导入:
sh$ mysql -u root -p --default-character-set=gbk test "<"dump2.sql

星期二, 五月 15, 2007

sed -n "$i{p}"

如果要在循环中,从一个文件或字符串结果中过滤第 $i 行,用 sed -n "$ip" 肯定不行,那么可以增加使用 {} 号,将变量和命令隔开,即 sed -n "$i{p}"。例如:
sh$ snmpwalk -v2c -c demo 192.168.0.98 UCD-SNMP-MIB:prNames | sed -n "$i{p}"
UCD-SNMP-MIB::prNames.1 = STRING: vsftpd

bash getopt

先看一下几个简单的例子,就可以了解 getopt 大致怎样使用了:
sh$ args=`getopt abc:d -a -b -c opt`
sh$ echo $args
-a -b -c opt --
sh$ args=`getopt -l long1,long2: abc:d -a -b -c opt1 --long1 opt2 arg1 arg2`
sh$ echo $args
-a -b -c 'opt1' --long1 -- 'opt2' 'arg1' 'arg2'
sh$ args=`getopt -l long1,long2: abc:d -a -b -c opt1 --long2 opt2 arg1 arg2`
sh$ echo $args
-a -b -c 'opt1' --long2 'opt2' -- 'arg1' 'arg2'
那么在脚本中,则通常可以这样:
args=`getopt abc:d $*`
echo $args
一个比较完整的例子如下:
args=`getopt -l help,item:items-list: c:h:m:i:I: $*`
if [ $? -gt 0 ]; then
strerr="Invalid options"
echo "$strerr" >&2
logger -it "$strerr"
exit 1
fi

for i in $args; do
case $i in
-c) shift; community=$1; shift;;
-h) shift; host=$1; shift;;
-m) shift; mailto=$1; shift;;
-i|--item)
shift
if [ -n "$items" ]; then
items=`echo -e "$items\n$1"`
else
items="$1"
fi
shift
;;
-I|--items-list)
shift; ilist=$1; shift;;
--help)
shift
echo "useage: $PROGRAM [-c|-h|-m] [--item|--help]
-c community
-h host
-m mailto
-i|--item item_map, 'community host' map
--help, print this message"
exit 0
;;
esac
done

python datetime object from time.localtime()

datetime 作为一个 class 对象,其操作较 time 模块更直观和直接,唯一不方便的是 datetime 必须使用给定的参数来生成,而 time 模块可以直接使用 time.localtime() 方法生成当前时间的对象。那么可以结合这两个模块的优点:
>>> d = datetime.datetime(*time.localtime()[0:6])
>>> print d
2007-05-15 09:35:06
>>>
注意在参数中使用的 * 号,这是一个之前没有认真注意过的有用的特性。

星期五, 五月 11, 2007

python merge regular expression from file

#!/usr/bin/env python

import re

# record = open('test.txt', 'r').read()
record = '60.176.247.144 - - [09/May/2007:16:40:54 +0800] "GET /bbs/thread-19160-1-7.html HTTP/1.1" 200 22481 "http://www.google.com/search?hl=en&q=%E6%94%AF%E4%BB%98%E5%AE%9D+HAS_NO_PRIVILEGE&btnG=Google+Search" "Mozilla/5.0 (Windows; U; Windows NT 5.2; zh-CN; rv:1.8.1.3) Gecko/20070309 (FoxPlus) Firefox/2.0.0.3"'

regexp1 = r'(?P[\d\.]+) - - (?P
如果改变 regexps.conf 的内容为:
google.com, r'search.*[?&]q=.*&.*'
即去除其中的分组设置,则运行为:
sh$ python test.py
google.com, r'search.*[?&]q=.*&.*'
('google.com', 'search.*[?&]q=.*&.*')
google.com search.*[?&]q=.*&.*
http://www.google.com/search?hl=en&q=%E6%94%AF%E4%BB%98%E5%AE%9D+HAS_NO_PRIVILEGE&btnG=Google+Search
http://[\w\.]*(?Pgoogle.com)/(?Psearch.*[?&]q=.*&.*)
('google.com', 'search?hl=en&q=%E6%94%AF%E4%BB%98%E5%AE%9D+HAS_NO_PRIVILEGE&btnG=Google+Search')
Traceback (most recent call last):
File "test.py", line 26, in ?
print mo.group('word')
IndexError: no such group
再把条件弄复杂一点,以保证总由 word 这个 group:
rmo = re.match(r'\s*([\w\.]+)\s*,\s*r\'(.*=\(\?\P\\.\*\).*)\'', r)

星期三, 五月 09, 2007

python list extend

>>> L = [1, 2, 3]
>>> L1 = ['a', 'b']
>>> i = 0
>>> for l in L:
if i == 0:
i += 1
L.extend(L1)
continue
print l
2
3
a
b
比较有用的特性,比如在 find_topfiles 时,循环中可以对 /usr/local/* 这样的路径使用 glob(),将新的 list 插入到该条目后面。

python module __variable

sh$ cat module.py
#!/usr/bin/python

_var = 0
__var1 = 1
var2 = 2

def __func():
print "__func"
func = __func

sh$ cat main.py
#!/usr/bin/python

import module

v = module.__var1
print v
v = module._var
print v
f = module.__func
f()

class test:
def __init__(self):
self.v = module._var
# self.v = module.__var1 # (1)
# self.f = module.__func # (2)
self.v = module.var2
self.f = module.func # (3)
def run(self):
print self.v
self.f()

ti = test()
ti.run()
(1) 的输出:
sh$ python main.py
1
0
__func
Traceback (most recent call last):
File "main.py", line 23, in ?
ti = test()
File "main.py", line 15, in __init__
self.v = module.__var1
AttributeError: 'module' object has no attribute '_test__var1'
(2)的输出:
sh$ python main.py
1
0
__func
Traceback (most recent call last):
File "main.py", line 23, in ?
ti = test()
File "main.py", line 16, in __init__
self.f = module.__func
AttributeError: 'module' object has no attribute '_test__func'
在 module 层面都可以,但在 class 中 _var 可以但 __var 就不行,应该是和 class pseudo private 冲突了。当然 __var 是以 _ 开头,所以不能用 from module import __var 语法。

vim split window

可以在同一个 vim 中同时打开几个窗口来编辑,这样比较直观:
sh$ vim -O main.py module.py
这样会使用垂直分割,水平风格使用 -o。如果使用 -O3 则会同时打开3个窗口。在一个打开的窗口中,可以使用 CTRL-WV 来复制一个垂直窗口,然后在新的窗口中就可以编辑新的文件,否则两边会同时变化。

窗口之间可以使用 CTRL-TAB 来进行切换(Windows gvim),Linux 下,使用 CTRL-W,> 也是可以的。

如果使用 :sp newfile 来获得水平分割,可以使用 CTRL-W,j 下移,CTRL-W,k 上移(和编辑移动命令一样)。CTRL-W,_(need SHIFT) 来最小化其它窗口,默认情况下最小化高度是 1(一行),可以使用 :set wmh=0 设置高度为 0,这样就只显示文件名。这样就可以在 ~/.vimrc 中定义几个 map:
sh$ vi ~/.vimrc
set wmh=0
map "<"C-J">" "<"C-W">"j"<"C-W">"_
map "<"C-K">" "<"C-W">"k"<"C-W">"_
,这样直接按 CTRL-J 就会切换到下一个窗口,并最小化其他窗口!

垂直分割则可以采用这样的方法:
sh$ vi ~/.vimrc
set wmw=0
nmap "<"C-H">" "<"C-W">"h"<"C-W">""<"bar">"
nmap "<"C-L">" "<"C-W">"l"<"C-W">""<"bar">"
然后使用 :vsp newfile (:vs 也可)来使用垂直分割窗口打开一个新文件。但如果要切换回去,比如 CTRL-L 之后,只能用 CTRL-W,H 来切换,所以设置 wmw=16 比较好一点,这样至少可以看到文件名。

Switch between splits very fast (for multi-file editing)
Vim documentation: vim_faq
Vim documentation: windows

这样 ~/.vimrc 为:
set number
set tabstop=4
au BufNewFile,BufRead *.t2t set ft=txt2tags
" colorscheme pablo
" colorscheme desert
set wmh=0
" winminheight
" set wh=999
" winheight
map "<"C-J">" "<"C-W">"j"<"C-W">"_
map "<"C-K">" "<"C-W">"k"<"C-W">"_
set wmw=16
" winwidth
nmap "<"C-H">" "<"C-W">"h"<"C-W">""<"bar">"
nmap "<"C-L">" "<"C-W">"l"<"C-W">""<"bar">"

星期二, 五月 08, 2007

mplayer with srt subtitle

mplayer 使用字幕的方法:
sh$ mplayer -sub 'The Terminal.2004.HDRip.CD1.CHS.srt' \
/mnt/fedora/opt/.aMule/Incoming/*Terminal.2004.HDRip.XviD-CNXP.CD1.avi \
-font ~roc/simsun.ttf \
-subcp cp936 \
-subfont-text-scale 4
使用 v 可以开启/关闭字幕。可以同时选择两个字幕,然后使用 b/j 来轮转:
sh$ mplayer -sub 'The Terminal.2004.HDRip.CD1.CHS.srt','The Terminal.2004.HDRip.CD1.ENG.srt' \ 
/mnt/fedora/opt/.aMule/Incoming/*Terminal.2004.HDRip.XviD-CNXP.CD1.avi \
-font ~roc/simsun.ttf \
-subcp cp936 \
-subfont-text-scale 4
-subfont-text-scale 默认为 5,-subcp cp936 为中文支持,目前只知道使用 ttf(True-Type Font)字体的办法,点阵字体如文泉驿等还不清楚,所以可以使用 simsun.ttf。但考虑到版权问题可以使用 Fedora 的 uming.ttf,实际上显示效果更好。

星期日, 五月 06, 2007

python __import__(module)

如果要 import 的模块是字符串(比如利用参数进行传递),可以事业 builtin 的 __import() 函数,例如:
def __init__(self, module='__main__', defaultTest=None,
argv=None, testRunner=None, testLoader=defaultTestLoader):
if type(module) == type(''):
self.module = __import__(module)
for part in module.split('.')[1:]:
self.module = getattr(self.module, part)
else:
self.module = module
又如:
mod = __import__('random')
当然也可以使用
exec "import %s as mod" % module
来解决。

可以看看 unittest 对它的使用,得到一个更清楚的认识:
class TestLoader:
...
def loadTestsFromModule(self, module):
"""Return a suite of all tests cases contained in the given module"""
tests = []
for name in dir(module):
obj = getattr(module, name)
if (isinstance(obj, (type, types.ClassType)) and
issubclass(obj, TestCase)):
tests.append(self.loadTestsFromTestCase(obj))
return self.suiteClass(tests)

......

class TestProgram:
def __init__(self, module='__main__', defaultTest=None,
argv=None, testRunner=None, testLoader=defaultTestLoader):
if type(module) == type(''):
self.module = __import__(module)
for part in module.split('.')[1:]:
self.module = getattr(self.module, part)
else:
self.module = module
...
def parseArgs(self, argv):
import getopt
try:
options, args = getopt.getopt(argv[1:], 'hHvq',
['help','verbose','quiet'])
for opt, value in options:
if opt in ('-h','-H','--help'):
self.usageExit()
if opt in ('-q','--quiet'):
self.verbosity = 0
if opt in ('-v','--verbose'):
self.verbosity = 2
if len(args) == 0 and self.defaultTest is None:
self.test = self.testLoader.loadTestsFromModule(self.module)
return
if len(args) > 0:
self.testNames = args
else:
self.testNames = (self.defaultTest,)
self.createTests()
except getopt.error, msg:
self.usageExit(msg)