使用cx_Freeze的distutils脚本打包Python程序

cx_Freeze打包Python程序的命令基本格式如下:

1
cxfreeze main.py --target-dir appdir

它表示把脚本main.py或以main.py为程序入口的程序打包并导出到当前路径中名为appdir的目录中。

对于Windows下的GUI应用程序,以上面的命令导出后,运行时会弹出cmd命令行的黑窗口,须加上如下命令中的参数:

1
cxfreeze main.py --target-dir appdir --base-name=win32gui

对于比较复杂的程序,cx_Freeze支持distutils格式的打包脚本,当然,彼此之间在引入的模块和支持的参数上还是有差别的。

cx_Freeze的文档中有其支持的全部命令参数及说明,写到setup.py脚本中时,所有参数中的-符号应换成下划线。

我的setup.py内容大致如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#!/usr/bin/python
# -*- coding: utf-8 -*-
from cx_Freeze import setup,Executable

includefiles = [('settings.ini.jctest','settings.ini')
,'README.mkd']
includes = []
excludes = ['Tkinter']
packages = ['sqlalchemy.engine', 'sqlalchemy.orm', 'sqlalchemy.dialects.mssql']

setup(
name = 'pyutil',
version = '0.1',
description = 'A general enhancement utility for XXX',
author = 'Lenin Lee',
author_email = 'lenin.lee@xxx.com',
options = {'build_exe':{'excludes':excludes,'packages':packages,'include_files':includefiles}},
executables = [Executable('jcitk.py')
, Executable('jcvfd.py')
, Executable('jcvdupcr.py')
, Executable('jcddupcr.py')
, Executable('jcclostfd.py')
, Executable('jcvcard.py')
, Executable('jcvcardii.py')
, Executable('jcclostsoid.py')]
)

在摸索如何写setup.py的过程中,遇到一些问题。

首先是如何将配置文件settings.ini自动复制到打包文件夹中。这个问题的解决办法是使用build_exe命令的参数include_files。此参数的值是一个列表,列表的每一项可以是一个表示要复制的文件的路径的字符串,或者是一个tuple。若是tuple,第一个元素是表示要复制的文件的路径,第二个元素是表示复制后要修改成的文件名。需要说明的是,文件夹可以和文件一样使用这样的方法复制到打包文件夹中。

其次,在打包引入了SQLAlchemy的程序后,若运行该程序时报某模块导入失败的错,应将报错信息中提示的模块所在的包填写到packages参数中。虽然也可以在程序中import这些包,但是在setup.py中使用packages参数的做法更合理。而且如果在程序中导入了没有被显式调用的模块或包的话,对于使用pyflakes检查语法错误的环境,会显示模块或包未被调用的警告,至少看起来不舒服。

再次,Windows下打包时应使用python 2.5,因为2.6版本需要Microsoft Visual C++ 2008 Redistributable,一般非开发环境的系统中都没安装这个,运行程序时就会报错。

开始玩儿茶道

前年买了个倒把小西施开始喝茶,渐渐地就觉得喝茶的趣味一半在茶,一半在泡。这周买了茶盘、茶道组等一应物件儿,直到周五晚上才有时间过把瘾。怕睡不着觉,没敢泡生茶,新开了一饼易武大树熟茶。

SQL Server的自定义函数:统计两日期之间工作日的数量

前段时间工作中遇到的一个需求,要求计算两个日期之间工作日的数量,即排除期间所有的周六和周日之后的天数。

在网上找到一个自定义函数,原函数有些小问题,例如如果传入的截止日期如果有时间且足够大,则计算结果可能出错,修正后代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
--函数:类似datediff,不统计期间所有的周六和周日
if exists (select * from dbo.sysobjects where id=object_id(N'[dbo].[f_WorkDay]') and xtype in (N'FN',N'IF',N'TF'))
drop function [dbo].[f_WorkDay]
GO

CREATE FUNCTION f_WorkDay(
@dt_begin datetime,
@dt_end datetime
)RETURNS int
AS
BEGIN
DECLARE @workday int,@i int,@bz bit,@dt datetime

set @dt_begin = convert(datetime, convert(nvarchar(32), @dt_begin, 23))
set @dt_end = convert(datetime, convert(nvarchar(32), @dt_end, 23))

IF @dt_begin>@dt_end
SELECT @bz=1,@dt=@dt_begin,@dt_begin=@dt_end,@dt_end=@dt
ELSE
SET @bz=0

SELECT @i=DATEDIFF(Day,@dt_begin,@dt_end),@workday=@i/7*5,@dt_begin=DATEADD(Day,@i/7*7,@dt_begin)

WHILE @dt_begin<@dt_end
BEGIN
SELECT @workday=CASE WHEN (@@DATEFIRST+DATEPART(Weekday,@dt_begin)-1)%7 BETWEEN 1 AND 5 THEN @workday+1 ELSE @workday END,@dt_begin=@dt_begin+1
END

RETURN(CASE WHEN @bz=1 THEN -@workday ELSE @workday END)
END
GO

此函数的原理是,使用datediff计算两日期之间的差值A,然后取A与7的商,即计算期间内有几个整周。然后使用这个商与5相乘,得到所有整周内的工作日天数B。接着,使用A除以7再乘以7得到C,这就约去了A中最后不到一个整周的天数。再在起始日期的基础上加上C,得到一个新的起始日期,然后从这个新的起始日期开始遍历至截止日期的每一天,每增加一天,判断若此日期是工作日,则在C的基础上累加一。判断一个日期(假设使用@dt_begin表示)是否是工作日的方法是:(@@datefirst+datepart(Weekday, @dt_begin)-1)%7的值在1和5之间。

此外还有一个需求是计算两个日期之间排除最后一个周六周日后的天数,仿照上面的函数实现了一个新函数,现在想来,有点儿把问题复杂化了,完全可以直接从后往前推的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
--函数:类似datediff,不统计截止日期@dt_end前最近一次的周六和周日,若dt_end是周日,则不统计其前面的那个周六。
if exists (select * from dbo.sysobjects where id=object_id(N'[dbo].[f_WorkDayOnce]') and xtype in (N'FN',N'IF',N'TF'))
drop function [dbo].[f_WorkDayOnce]
GO

CREATE FUNCTION f_WorkDayOnce(
@dt_begin datetime,
@dt_end datetime
)RETURNS int
AS
BEGIN
DECLARE @day_count int,@weekday int,@weekend_dropped int,@i int,@bz bit,@dt datetime

set @dt_begin = convert(datetime, convert(nvarchar(32), @dt_begin, 23))
set @dt_end = convert(datetime, convert(nvarchar(32), @dt_end, 23))

IF @dt_begin>@dt_end
SELECT @bz=1,@dt=@dt_begin,@dt_begin=@dt_end,@dt_end=@dt
ELSE
SET @bz=0

SELECT @i=DATEDIFF(Day,@dt_begin,@dt_end),@weekday=(@@datefirst+datepart(weekday,@dt_end)-1)%7,@day_count=@i,@weekend_dropped=0

if @i=0
set @day_count=0
else
begin
if @weekday=0
set @day_count=@i-1
else
begin
while @dt_begin<@dt_end and @weekend_dropped<2
begin
select @weekend_dropped=@weekend_dropped+(case when (@@datefirst+datepart(weekday,@dt_begin)-1)%7 between 1 and 5 then 0 else 1 end),@dt_begin=@dt_begin+1
end
select @day_count=@day_count-@weekend_dropped
end
end

RETURN(CASE WHEN @bz=1 THEN -@day_count ELSE @day_count END)
END
GO

Python控制外部进程的灵异事件

春节前的一段时间,重新拾起近一年没动过的ForeverFantasy

虽然一年内没什么更新,但是我却一直都在用,基本上只限于将用Vim写好的Markdown格式的文档转换成HTML。

重写了相当一部分代码,较大程度地改变了界面布局,突然发现经过一年的沉淀,对wxPython的理解增进了不少,开发起来比起去年这个时候清楚了很多。

这些天来一直坚持着每天或多或少的做一些,如果说有什么主要的进展的话,那就是界面的重构,以及昨天实现了调用Vim编辑文档并回收文档内容的功能。

ForeverFantasy和Vim协同的一个最大的问题就是如何判断Vim已经退出。Python调用外部程序的方法有很多,比如传统的commands模块、os.system()等,subprocess是致力于替代这些旧有的方式的一个模块,它的一个特点是可以在启动一个外部程序作为子进程后还能监控这个进程的运行状态。这为ForeverFantasy在Vim退出后回收文档内容提供了更简捷的途径。

下面的代码可以说明如何使用subprocess运行外部程序并监控运行状态:

1
2
3
4
5
6
7
import subprocess
process = subprocess.Popen('gvim', shell=True)
status = process.poll()
if 0 == status:
print 'The external program exited.'
if status is None:
print 'The external program is still running.'

理论上,可以拿实例process的poll()方法监视进程的运行状态,而且这一点在Windows上也确实可以做到,但是到了Linux下,诡异的事出现了,即使刚刚打开gvim,poll()方法也会马上返回一个0,同时process.pid的值总是和实际在运行的那个gvim进程的pid的值差3,而且在虚拟终端中输入gvim命令也不会阻塞终端,就像别的命令加上“&”符号的效果一样。尝试用strace跟踪gvim的运行,试图找到问题原因,无果。我猜想可能/usr/bin/gvim是一个跳板,它启动后会启动一个新的gvim进程。总之,这个方法在Linux下是行不通的。

因此我觉得只能另寻出路了,既然不能监控gvim进程,那就监控gvim进程所编辑的文件,只要这个文件不被任何进程占用,就可以判定gvim已退出。这一点,在Linux下易如反掌,不用说,lsof当仁不让。

commands.getstatusoutput('lsof file.txt')测试发现,如果文件file.txt被某进程占用,则返回的状态值为0,反之,返回256。

最终,我在程序中使用了两种判断方式,在Windows平台使用subprocess跟踪gvim.exe进程,而在Linux及Unix平台使用lsof检查文档占用情况。

剩下的就是进行这个判断的时间问题了。

很显然,如果在子进程被启动后马上使用while循环不停的检查,一来必须使用多线程,二来系统资源占用也会很高。这时就需要利用wxPython的事件机制了,当ForeverFantasy启动Gvim时,主窗口失去焦点,而当Gvim退出时,ForeverFantasy又会获得焦点,只要能在ForeverFantasy窗口获得焦点时做一次检查即可。不过,在选择最合适的事件的问题上,又是一波三折。

在wxPython的API文档中没有找到事件列表,倒是在Wiki中找到了。顾名思义,觉得wx.EVT_SET_FOCUS比较靠谱,但试用失败,看API中关于FocusEvent的说明,这个事件适用于窗口控件;然后又试了wx.EVT_CHILD_FOCUS,只有在窗口包含的控件中有获得焦点的情况才会触发;最后才发现wx.EVT_ACTIVATE,这个事件会在窗口失去焦点和获得焦点时各触发一次,使用GetActive()方法可以判断是获得焦点还是失去焦点。

完成与Vim的协同使ForeverFantasy在我手上由原来单纯的格式转换工具进化为基本可用的文档编辑器,就算是Milestone 2吧。

此外,还有一些小的经验:

1. 调用非环境变量下的程序,即命令中必须带程序所在的路径时,应当将程序所在目录的完整路径以自然字符串的形式传递给subprocess.Popen类的构造方法的cwd参数,即如下所示:

1
process = subprocess.Popen('gvim.exe', cwd=r'C:\program files\vim\vim72', shell=True)

这样可以有效避免路径中的空格和特殊字符对命令执行的影响。

2. Vim编辑一个文档时,实际操作的是一个临时文件,而不是原文件,这个临时文件与原文件同路径,名称为在原文件名的基础上,前面加一个句点,后面加后缀“.swp”。应该用lsof监控这个临时文件,才可以判断出编辑该文档的Vim进程的运行状态。由于对于不存在的文件使用lsof命令的返回值也是256,故可以同时判断临时文件和原文件的占用情况,这样就为对其它编辑器的支持奠定了基础。

2010-03-10 Wednesday 22:52:13 更新

感谢KL童鞋和依云童鞋指教,果然加上-f参数就可以了。

Vim的终极自动补全插件:NeoComplCache

关于自动补全,最初用的是SuperTab,那个时候Vim的自动补全插件寥寥无几,也就SuperTab比较有名。不过实际使用过程中这个插件给我的体验不是很好,原因是补全的准确度不高。

后来出了一个新插件,AutoComplPop,功能和使用都很简单。但是很快我就又用回SuperTab,原因是AutoComplPop在输入的同时实时地查询匹配的关键词,导致输入极不流畅,效率很低。

一直就这么凑合着用着SuperTab,随着这个插件版本的更新,旧功能不断地完善,新功能也接二连三地引入,SuperTab在匹配关键词的准确度上有了一些改善,但是新的问题又出现了。SuperTab后来加入一个新功能,对于程序源文件,可以在其引入的文件以及API文件中匹配关键词。比如假设我当前正在编辑a.php,在a.php中有include ‘b.php’;这样的语句,当我输入array并按下Tab键时,SuperTab不但会在当前文件中查询所有匹配项,还会到b.php中查询,如果配置过vim、指定一个包含了php的API的文件,则SuperTab还会自动从这个文件中查询匹配项。按理说这个功能的理念很好,但问题就在于SuperTab做的是实时查询,如果源文件中包含的文件较多,各个文件又较大,问题就显而易见了。我不得不在写程序时小心地使用Tab键,否则有时就会出现按一下Tab键然后等着Vim在那狂搜的情况。

前两天发现了NeoComplCache,光看名字就让我有点儿兴奋,一般使用缓存的速度都很快。这个插件会在Vim打开文件的时候对上下文作一个索引,并把索引结果保存到缓存中。同时,文件更改的内容会在保存的时候被索引。此外,NeoComplCache支持多种关键词索引模式,例如它会判断当前路径下的文件或目录的名字是否匹配补全条件,也可以从缓存的程序语言API中匹配补全条件。到此为止,它就解决了SuperTab和AutoComplPop共同的效率问题,并具备它们各自的长处。看了一遍文档,发现这个插件的功能比较细致,大概有以下一些特点:

1、使用缓存,自动补全时效率高;
2、生成的关键词列表准确;
3、支持下划线分割的关键词,如apple_boy_cat,就可以只输入a_b_c,然后补全;
4、支持驼峰格式匹配关键词,如AppleBoyCat,就可以只输入ABC,然后补全;
5、既可以像AutoComplPop那样在Vim中输入的同时自动弹出补全列表,又可以自定义快捷键手动触发;
6、支持从文件名和目录名中匹配补全条件;
7、对于程序源文件,支持从语言API中匹配补全条件;

NeoComplCache的缺点是文档不全,虽然从只言片语中发现它还支持Snippet,但从文档中没有找到足够的有用信息。加之一直用SnipMate感觉不错,所以目前还是用它来实现snippet功能。

这就有个搭配问题:虽然NeoComplCache不存在补全时的效率问题,但我仍然打算只在需要补全时才用快捷键触发此功能,最主要的原因是我既希望用Tab键触发SnipMate的代码块补全功能,又希望修SuperTab那样用Tab选择补全列表中的选项。也就是要达到只用Tab键就可以完成打开自动补全列表、补全列表选项选择和SnipMate代码块替换的效果。但是,如果将Tab映射到触发自动补全,则补全列表选择和SnipMate均无法使用Tab,反之亦然。

所以我想如果能让NeoComplCache、SuperTab、SnipMate和谐共存,那问题就解决了,几经摸索,终于找到了办法:

1、设置NeoComplCache不自动弹出补全列表,即在vimrc中加入:


let g:NeoComplCache_DisableAutoComplete = 1


2、由于NeoComplCache在手工模式下使用快捷键组合<C-X><C-U>打开补全列表,故设置SuperTab的默认补全操作为<C-X><C-U>,即在vimrc中加入:


let g:SuperTabDefaultCompletionType = ‘<C-X><C-U>


这样,NeoComplCache只负责补全关键词缓存的生成,SuperTab控制Tab键的行为并在需要触发补全操作时打开补全列表、进而在列表中的选项间移动焦点,而当光标前的关键词是snippet时,SnipMate会被优先调用并完成代码块的替换。

就在写这篇文章的时候,我突然觉得NeoComplCache自动弹出补全列表+SnipMate的方式也挺好,只是这样就不能用Tab键选择列表中的选项了。

相关阅读:

SQLAlchemy操作SQL Server的中文问题

最初将脚本的文件编码和coding行都设定为UTF-8,在windows下执行时,中文无法保存,报编码错误。将上述两个编码改为GBK后,保存正常,但查询时报错。

Traceback内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Traceback (most recent call last):
File "test.py", line 36, in <code>&lt;module&gt;</code>
&nbsp;&nbsp;&nbsp;&nbsp;for obj in session.query(User):
File "C:\Python26\lib\site-packages\sqlalchemy-0.6beta1-py2.6.egg\sqlalchemy\orm\query.py", line 1411, in instances
&nbsp;&nbsp;&nbsp;&nbsp;rows = [process[0](row, None) for row in fetch]
File "C:\Python26\lib\site-packages\sqlalchemy-0.6beta1-py2.6.egg\sqlalchemy\orm\mapper.py", line 1788, in _instance
&nbsp;&nbsp;&nbsp;&nbsp;populate_state(state, dict_, row, isnew, only_load_props)
File "C:\Python26\lib\site-packages\sqlalchemy-0.6beta1-py2.6.egg\sqlalchemy\orm\mapper.py", line 1677, in populate_state
&nbsp;&nbsp;&nbsp;&nbsp;populator(state, dict_, row, isnew=isnew, **flags)
File "C:\Python26\lib\site-packages\sqlalchemy-0.6beta1-py2.6.egg\sqlalchemy\orm\strategies.py", line 118, in new_execute
&nbsp;&nbsp;&nbsp;&nbsp;dict_[key] = row[col]
File "C:\Python26\lib\site-packages\sqlalchemy-0.6beta1-py2.6.egg\sqlalchemy\engine\base.py", line 1634, in __getitem__
&nbsp;&nbsp;&nbsp;&nbsp;return self.__colfuncs[key][0](self.__row)
File "C:\Python26\lib\site-packages\sqlalchemy-0.6beta1-py2.6.egg\sqlalchemy\engine\base.py", line 1716, in getcol
&nbsp;&nbsp;&nbsp;&nbsp;return processor(row[index])
File "C:\Python26\lib\site-packages\sqlalchemy-0.6beta1-py2.6.egg\sqlalchemy\types.py", line 568, in process
&nbsp;&nbsp;&nbsp;&nbsp;return decoder(value)[0]
File "C:\Python26\lib\encodings\utf_8.py", line 16, in decode
&nbsp;&nbsp;&nbsp;&nbsp;return codecs.utf_8_decode(input, errors, True)
UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-1: ordinal not in range(128)</p>

环境为:

OS:Windows XP简体中文版 DB:SQL Server 2008 Express简体中文版 DB模块:pyodbc 脚本文件编码:GBK 脚本coding行:GBK

脚本内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#!/usr/bin/python
# -*- encoding: gbk -*-

from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
from sqlalchemy import Column, Integer, String, Text, ForeignKey, Numeric, Unicode

Base = declarative_base()

class User(Base):
"""User class"""

__tablename__ = 'users'

id = Column(Numeric(22,0), primary_key=True)
name = Column(Unicode(128), nullable=False, unique=True)

def __init__(self, id, name):
self.id = id
self.name = name

if __name__ == '__main__':
db_engine = create_engine('mssql://sa:password@localhost/mydatabase', echo=True)
Session = sessionmaker(bind=db_engine)
session = Session()

Base.metadata.drop_all(db_engine)
Base.metadata.create_all(db_engine)

jim = User(1, '中文')
session.add(jim)
session.commit()

'''
for obj in session.query(User):
print obj.name
'''

上面的脚本执行后,数据得以正常保存,在数据库中的查询结果也正常,没有乱码。但是,当把从drop_all()到commit()行注释掉,取消for循环前后的多行字符串起止符后,即运行查询时,抛出上面的Traceback。

Google了很长时间,没有找到有用的东西。CPyUG更没指望。

回溯Traceback,打开sqlalchemy的types.py,UnicodeEncodeError的抛出点在String类的result_processor()方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def result_processor(self, dialect, coltype):
wants_unicode = self.convert_unicode or dialect.convert_unicode
needs_convert = wants_unicode and \
(not dialect.returns_unicode_strings or
self.convert_unicode == 'force')

if needs_convert:
# note we *assume* that we do not have a unicode object
# here, instead of an expensive isinstance() check.
decoder = codecs.getdecoder(dialect.encoding)
def process(value):
if value is not None:
# decoder returns a tuple: (value, len)
return decoder(value)[0]
else:
return value
return process
else:
return None

这个方法就是根据数据库方言dialect和字段类型coltype返回一个字符串的解码函数。若在if语句上面将needs_convert置为False,即不对该字段使用解码器,则再执行上面的脚本时,查询正常。

由于前面create_engine()函数的encoding参数缺省为UTF-8,故dialect.encoding的值为“UTF-8”,故if语句中decoder实际引用的是codecs.utf_8_decode()。也就是说,result_processor()方法在实际执行过程中返回的是一个封装了utf_8_decode()函数的函数。即,UnicodeEncodeError是在对从数据库中查询出来的中文字符串进行UTF-8解码时抛出的。

对传入process()函数的值作isinstance(value,unicode)判断,显示为True,表明从数据库中查询出来的中文本身就是unicode字节码,当对它再进行UTF-8解码时,就抛出了UnicodeEncodeError的错误。为验证以上判断,做如下实验:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
>>>t = '中文'
>>>u = u'中文'
>>>isinstance(t, str)
True
>>>isinstance(t, unicode)
False
>>>isinstance(u, str)
False
>>>isinstance(u, unicode)
True
>>>x = t.decode('utf-8')
>>>x
u'\u4e2d\u6587'
>>>isinstance(x, unicode)
True
>>>x == u
True
>>> import codecs
>>> dc = codecs.getdecoder('utf-8')
>>> dc(u)
Traceback (most recent call last):
File "<input>", line 1, in <module>
File "/usr/lib/python2.6/encodings/utf_8.py", line 16, in decode
return codecs.utf_8_decode(input, errors, True)
UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-1: ordinal not in range(128)

得证。

在Python中,字符串类型str和unicode类型是两种不同的数据类型,str类型的数据可以通过指定正确的编码来转换成unicode类型,对unicode类型的数据作重复的解码操作就会抛出类似上面的错误。

实事上,若将name字段声明为String类,则保存和查询操作均无问题。但由于我需要sqlalchemy建表时将相应字段的类型设为nvarchar,故必须使用Unicode类声明该列。

那有没有办法使result_processor()方法不返回一个对字段值作重复解码的函数呢?

返回result_processor()方法,self.convert_unicode对于Unicode类是True,dialect.convert_unicode由create_engine()函数的convert_unicode参数控制,缺省为False,故needs_convert变量为True,无法更改;dialect.returns_unicode_strings是由sqlalchemy.engine模块default.py中的DefaultDialect类的_check_unicode_returns()方法返回的,该方法内容为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def _check_unicode_returns(self, connection):
cursor = connection.connection.cursor()
cursor.execute(
str(
expression.select(
[expression.cast(
expression.literal_column("'test unicode returns'"),sqltypes.VARCHAR(60))
]).compile(dialect=self)
)
)

row = cursor.fetchone()
result = isinstance(row[0], unicode)
cursor.close()
return result

此方法的功能为生成一条SQL语句,在数据库中执行后,判断返回的值是否为unicode类型。由于SQL Server是ASCII编码,故此方法返回False。因此,dialect.returns_unicode_strings的值为False。最终,needs_convert只能为True。我觉得这是sqlalchemy的一个Bug。

在此条件下,目前尚未找到较好的解决办法,只能使用硬编码强制置result_processor()方法中的needs_convert变量为False。

2010-02-25 更新:

谢谢KL童鞋指出问题原因和解决办法,使问题得以完美解决。

1、由于Python在载入site模块时会删除setdefaultencoding()函数,故不能以在脚本开头调用此函数的方式指定默认编码;sitecustomize.py是一个python会自动导入的模块,故应当使用这个文件指定默认编码;

2、我这里需要使用utf-8作默认编码器,sitecustomize.py的内容如下:

1
2
3
4
#!/usr/bin/python
# -*- coding: gbk -*-
import sys
sys.setdefaultencoding('utf-8')

3、将sitecustomize.py保存到python安装目录下的Lib\site-packages目录中;

另外,在此处发现了跟本问题相关的资料,辅助治疗,效果更佳。

被迫弃UMS而用KMS

昨天更新了一下系统,今天开机就白屏了。于是先关掉Compiz,看了一下Archlinux的新闻,发现新的xf86-video-intel中已经移除了UMS,也就意味着只能使用KMS做3D加速了。

UMS的全称是User Mode-Setting,是一种传统的图形界面初始化方案,即在X加载之后由它初始化图形界面。这种方案的弊端是虚拟终端不具备显示和处理图形的能力,同时虚拟终端和图形界面之间的切换显得缓慢并带有闪烁。

KMS的全称是Kernel Mode-Setting,是新一代图形界面初始化方案,它将图形界面的初始化由X加载之后由X负责进行改为在内核初始化时由内核进行。KMS的好处不仅仅是解决了上面UMS的问题,同时也使得Linux具备了在启动时显示漂亮的开机图示的能力。另外,在3D加速性能和低功耗方面,KMS也较UMS更胜一筹。

我的Thinkpad X200使用的是Intel GMA 945的芯片组,而Archlinux的Wiki上仍以915为例,所以尝试着做如下内容:

一、去除/boot/grub/menu.lst中Kernel启动参数中的vga参数

二、加入以下内容到/etc/modprobe.d/modprobe.conf:

options i945 modeset=1

三、在/etc/rc.conf中的MODULES行加入intel_agpi945

重启系统后,Compiz白屏问题解决,3D加速性能似乎有所上升。

PHP后台echo过多会导致进程崩溃

这是去年晚些时侯发现的一个问题。

公司产品的后台定时任务应该是用独立的PHP进程驱动的。在做一个项目的时候,为了便于手工调试,我在定时任务中使用了大量的echo输出信息并在浏览器中手工执行此任务的脚本。

手工执行通过,输出信息完全没有问题。但在实际运行的过程中,一个很诡异的事情发生了:即使我将实际处理数据的代码全部注释掉,只对原始数据做简单的查询,此定时任务仍然极其健壮地中途退出。最要命的是无论程序日志还是PHP的错误日志,都没有任何相关信息。直到后来我把所有echo输出都取消,问题才解决。

事后模糊地记得很久以前似乎在哪本书上看到过在无输出设备的情况下echo过多数据会导致PHP进程崩溃的内容,但原因已想不起来了。

VirtualBox的Host Interface网络接入方式的配置

VirtualBox安装后默认的网络接入方式是NAT,也是最简单的一种。但NAT网络中的虚拟机系统不具有和主机同一网段的IP地址,不能和主机直接通信。这对于开发者来说是不适用的。

Host Interface网络接入方式可以使虚拟机系统获得与主机同一网段的IP地址,因此也使得虚拟机系统像主机所在的局域网中的一台真实的计算机一样,可以和其它计算机通信。

对于Linux平台的VirtualBox,可以有两种方式实现Host Interface网络。一是使用系统网桥,二是使用VirtualBox内建的机制。

第一种方式复杂但通用,尤其是对于2.1.0以前的版本来说,这是唯一的途径。从2.1.0开始,VirtuaBox内建了支持Host Interface的机制,这极大地简化了此类型网络的配置。

首先,加载vboxnetflt模块:

sudo modprobe vboxnetflt

然后在VirtualBox中配置虚拟机的网络连接方式,选择“Bridged Adapter”。

最后启动虚拟机即可。

另外,若虚拟机系统是精简版的Windows,网卡驱动可能不会自动安装,这时需要另外下载网卡的驱动并安装。

为方便起见,可将vboxnetflt模块加入到开机自动启动的模块列表中,每种发行版设置自启动模块的位置和方法不一样,在Archlinux下,是在/etc/rc.conf文件中的modules行中设置。

参考文章:Archlinux Wiki: VirtualBox

立方体归来

上次鼓捣出立方体还是几年前在学校的时候,Ubuntu+Beryl。 我那个时候的台式机,Celeron Tualatin+256M SDRAM+GForce 440,跑XP要了亲命,忍无可忍而投奔Linux,结果一不小心鼓捣出了立方体,捎带着Firefox、OpenOffice开了一大堆,屁事儿没有,我就是这么死心塌地地上了贼船,直至今天。 后来一直用Archlinux,习惯了FluxboxOpenbox的简洁。买了X200后曾试图找寻一下旧日华丽的记忆,但却人是物非,新的X200、新的Archlinux、新的Compiz-fusion远不如当年的老牛破车那么搭调。虽说也搞出了立方体,但所有的窗口都没有边框,于是作罢。 上周六鬼使神差的进入GNOME并打开Fusion-icon,奇迹就这么出现了: