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,奇迹就这么出现了:
Windows下GVim的全屏

下载gvim的一个扩展“gvimfullscreen_win32”,并解压缩。 将gvimfullscreen.dll复制到gvim安装目录下,与gvim.exe同目录。 修改gvim配置文件_vimrc,在其中添加如下内容:
1
2
3
if has('win32')
map <F11> <Esc>:call libcallnr("gvimfullscreen.dll", "ToggleFullScreen", 0)<CR>
endif
此后,即可使用F11键开关gvim的全屏状态。
欧拉工程第十一解

In the 20×20 grid below, four numbers along a diagonal line have been marked in red.

08 02 22 97 38 15 00 40 00 75 04 05 07 78 52 12 50 77 91 08 49 49 99 40 17 81 18 57 60 87 17 40 98 43 69 48 04 56 62 00 81 49 31 73 55 79 14 29 93 71 40 67 53 88 30 03 49 13 36 65 52 70 95 23 04 60 11 42 69 24 68 56 01 32 56 71 37 02 36 91 22 31 16 71 51 67 63 89 41 92 36 54 22 40 40 28 66 33 13 80 24 47 32 60 99 03 45 02 44 75 33 53 78 36 84 20 35 17 12 50 32 98 81 28 64 23 67 10 26 38 40 67 59 54 70 66 18 38 64 70 67 26 20 68 02 62 12 20 95 63 94 39 63 08 40 91 66 49 94 21 24 55 58 05 66 73 99 26 97 17 78 78 96 83 14 88 34 89 63 72 21 36 23 09 75 00 76 44 20 45 35 14 00 61 33 97 34 31 33 95 78 17 53 28 22 75 31 67 15 94 03 80 04 62 16 14 09 53 56 92 16 39 05 42 96 35 31 47 55 58 88 24 00 17 54 24 36 29 85 57 86 56 00 48 35 71 89 07 05 44 44 37 44 60 21 58 51 54 17 58 19 80 81 68 05 94 47 69 28 73 92 13 86 52 17 77 04 89 55 40 04 52 08 83 97 35 99 16 07 97 57 32 16 26 26 79 33 27 98 66 88 36 68 87 57 62 20 72 03 46 33 67 46 55 12 32 63 93 53 69 04 42 16 73 38 25 39 11 24 94 72 18 08 46 29 32 40 62 76 36 20 69 36 41 72 30 23 88 34 62 99 69 82 67 59 85 74 04 36 16 20 73 35 29 78 31 90 01 74 31 49 71 48 86 81 16 23 57 05 54 01 70 54 71 83 51 54 69 16 92 33 48 61 43 52 01 89 19 67 48

The product of these numbers is 26 × 63 × 78 × 14 = 1788696.

What is the greatest product of four adjacent numbers in any direction (up, down, left, right, or diagonally) in the 20×20 grid?

这道题最难的地方在于将上面400个数输入到程序中的矩阵里,嘿嘿,后来看别人的解,真有这么干的,饿滴神呀。

我直接把这个矩阵复制到一个文本文件里,然后在程序里解析:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#!/bin/python
# -*- coding: utf-8 -*-

matrix = []
products = []

metaFile = open('c:/matrix.txt', 'rb')
for line in metaFile.readlines():
matrix.append(line.split())
metaFile.close()

for row in range(20):
for col in range(20):
if col+3 < 20:
products.append( int(matrix[row][col]) * int(matrix[row][col+1]) * int(matrix[row][col+2]) * int(matrix[row][col+3]) )
if row+3 < 20:
products.append( int(matrix[row][col]) * int(matrix[row+1][col]) * int(matrix[row+2][col]) * int(matrix[row+3][col]) )
if col+3 < 20 and row+3 < 20:
products.append( int(matrix[row][col]) * int(matrix[row+1][col+1]) * int(matrix[row+2][col+2]) * int(matrix[row+3][col+3]) )
if row+3 < 20 and col-3 > 0:
products.append( int(matrix[row][col]) * int(matrix[row+1][col-1]) * int(matrix[row+2][col-2]) * int(matrix[row+3][col-3]) )
products.sort()

print products

rxvt-unicode的真透明

nacre同学说,urxvt是可以实现真透明的。起初我以为是要用transset-df来实现,但这个东西一般需要手动操作,不具有太大的实用性,而且会把整个窗口透明化。不过后来发现真的不需要用它来画蛇添足,有xcompmgr足矣。

因为我用openbox,要实现窗口的阴影和动画效果,xcompmgr是必须的,我把它设成了开机自启动。比起xcompmgr默认的参数值,下面这条定制的命令实现了简洁的阴影和合理的渐隐渐显时间,因此提供了一个各方面都比较均衡、合理的桌面体验:

xcompmgr -Ss -n -Cc -fF -I-10 -O-10 -D1 -t-3 -l-4 -r4

然后在.Xresources中添加以下两行:

URxvt.depth:32 URxvt.background:rgba:0000/0000/0000/dddd

最后当然要执行一下:

xrdb ~/.Xresources

此后直接启动urxvt即可。

这里面最有意思的就是background项的配置,它有两种形式,一种是:

URxvt.background:[80]black

还有一种就是前面提到的形式。

第一种形式中,中括号里的数字表示半透明度对应的百分比,括号外是颜色名称;第二种形式提供了比第一种更多的色彩选择,四组数字都是十六进制数,前三组是RGB颜色值,最后一组是半透明度,数值越大,透明度越低。

在查阅“man urxvt”的时候,发现urxvt的man pages里的内容真是异常丰富,大部分功能都讲解得言简意赅。以往还抱怨这个东西在网上连个健全的文档都找不到,原来全在这儿呢,真不知道以往无数次地man的时候为什么没有注意到这些,难道man了rxvt了?

PS:这样实现urxvt的真半透明后,貌似xcompmgr实现的阴影在urxvt身上就消失了,求解中……

完美的终端模拟器:rxvt-unicode

urxvtrxvt的unicode版本,支持多国语言,官方网站)。

具有以下特性:

  1. 支持真半透明
  2. 支持Unicode编码,支持多国语言
  3. 支持英文和非英文字符使用不同的字体
  4. 支持CS模式,节省系统资源
  5. 轻量,速度快
  6. 支持Perl扩展功能

但是urxvt有一个很影响情绪的缺陷,就是字符的间距过大,看起来很不舒服。不过已有补丁用来解决这个问题,例如Archlinux用户可以安装rxvt-unicode-chinese

urxvt的配置选项可以参考其官方网站上的说明,另外这里有一份详细的配置文件。 更新日志: 2011-01-15
  1. 修改文章标题
  2. 重写文章内容
  3. 由于原来的rxvt-unicode-256color包已不存在,更换为rxvt-unicode-chinese
  4. 补充一份配置文件
欧拉工程第十解

The sum of the primes below 10 is 2 + 3 + 5 + 7 = 17.

Find the sum of all the primes below two million.

题目越来越变态,开始好玩儿了。

第七解里的算法在这里算是废了,一万个素数都算得那么费劲,两百万以下的素数有十几万个,不得不用筛选法了。

普通的筛选效率也不行,当初就是因为这个原因才没用它。不过优化过的筛选法就很奇妙了,下面是Lua的实现:

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
require('math')

local limit = 2000000

local primes = {}
for i=1,limit do
table.insert(primes,true)
end
primes[0] = false
primes[1] = false

for i=0,math.floor(math.sqrt(limit)) do
if primes[i] then
for j=math.pow(i,2),limit,i do
primes[j] = false
end
end
end

local sumVal = 0
for i,j in ipairs(primes) do
if j then
sumVal = sumVal + i
end
end

print(sumVal)

在我这里两秒半就出结果了,Python的表现也不错,四秒半出结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from math import sqrt

limit = 2000000
primes = [True for i in range(0,limit)]
primes[0] = False
primes[1] = False

for i in range(1,int(sqrt(limit))+1):
if primes[i]:
for j in range(i**2,limit,i):
primes[j] = False

sumVal = 0
for i in range(len(primes)):
if primes[i]:
sumVal += i

print sumVal