sqlalchemy中文问题解决方案
sqlalchemy是python下一个很强大的ORM,最近刚刚开始使用。当然由于只是刚刚接触,只学习使用了他的DB API,对于Mapper还有些不得要领。
最近写的一个程序,用到MySql数据库,有些数据库读写的操作,对sqlalchemy已经有了些了解,自然要练练,更何况它能大幅度的提高程序开发的效率。于是就驱动sqlalchemy,用它开始了第一个数据库应用程序的编写。
代码说话:
#coding=utf-8
from sqlalchemy import *
connstr = 'mysql://uid:pwd@localhost/mydb'
db = create_engine(connstr, echo=True)
metadata = MetaData(db)
table = Table('mytable', metadata, autoload=True)
metadata = MetaData(db)
table = Table('mytable', metadata, autoload=True)
i = table.insert()
i.execute(c1='value')
i.execute(c1='value')
我通过上面的代码连接连接到数据库,打开表并执行插入数据的操作。执行程序,检查结果,ok,没有问题,心里一阵暗爽。
但当我把这段代码加入到程序中时,问题出来了,所有插入的中文全部是乱码,囧。按照常规思路,比较简单的做法是对中文进行编码转化.我的数据库表使用的是utf-8编码,于是写了这样的代码:'中文'.decode('gbk').encode('utf8'),结果依然是乱码。
在google上搜索发现,也有人遇到了类似的问题(大家都是中国人嘛:)),有两篇文章提出了解决方案,其中一个方法是在create_engine时增加两个参数,即:create_engine(connstr, encoding='utf8', convert_unicode=True),试了一下,无效。另一篇《MySql+SQLAlchemy+wxPython的Unicode解决方案》中提到修改MYSQLdb的connection.py文件的set_character_set方法,直接设为utf-8编码,我试了一下,的确可以了。但...这样似乎太暴力了一点,于是我决定找到原因。
根据上文中的思路发现,由于向set_character_set传递的charset参数为空,所以,该函数自动设置采用默认的编码连接mysql,也就是latin-1。因为sqlalchemy没有向该MYSQLdb传递charset,所以也就出现了上文作者郁闷的情况:总是latin-1。而前一篇文章里提到的方法和这个根本不沾边。
调试发现,create_engine调用了strategies.py的DefaultEngineStrategy类连接数据库的,该类的create方法里有个cparams变量,这个变量保存的是从连接url解析出来的数据库连接参数,比如host、username、pwssword这些。阅读MYSQLdb的代码知道,它是通过charset参数指定连接的编码的,于是修改了这个变量增加了charset参数(cparams.update({'charset':'utf8'})),居然也可以正常保存中文了。突破口就在这里,看来应该在这个url上做点文章了。
继续阅读代码,在database下的mysql这个模块中,发现了这么一段注释:
但当我把这段代码加入到程序中时,问题出来了,所有插入的中文全部是乱码,囧。按照常规思路,比较简单的做法是对中文进行编码转化.我的数据库表使用的是utf-8编码,于是写了这样的代码:'中文'.decode('gbk').encode('utf8'),结果依然是乱码。
在google上搜索发现,也有人遇到了类似的问题(大家都是中国人嘛:)),有两篇文章提出了解决方案,其中一个方法是在create_engine时增加两个参数,即:create_engine(connstr, encoding='utf8', convert_unicode=True),试了一下,无效。另一篇《MySql+SQLAlchemy+wxPython的Unicode解决方案》中提到修改MYSQLdb的connection.py文件的set_character_set方法,直接设为utf-8编码,我试了一下,的确可以了。但...这样似乎太暴力了一点,于是我决定找到原因。
根据上文中的思路发现,由于向set_character_set传递的charset参数为空,所以,该函数自动设置采用默认的编码连接mysql,也就是latin-1。因为sqlalchemy没有向该MYSQLdb传递charset,所以也就出现了上文作者郁闷的情况:总是latin-1。而前一篇文章里提到的方法和这个根本不沾边。
调试发现,create_engine调用了strategies.py的DefaultEngineStrategy类连接数据库的,该类的create方法里有个cparams变量,这个变量保存的是从连接url解析出来的数据库连接参数,比如host、username、pwssword这些。阅读MYSQLdb的代码知道,它是通过charset参数指定连接的编码的,于是修改了这个变量增加了charset参数(cparams.update({'charset':'utf8'})),居然也可以正常保存中文了。突破口就在这里,看来应该在这个url上做点文章了。
继续阅读代码,在database下的mysql这个模块中,发现了这么一段注释:
Many MySQL server installations default to a ``latin1`` encoding for client connections. All data sent through the connection will be converted into ``latin1``, even if you have ``utf8`` or another character set on your tables and columns. With versions 4.1 and higher, you can change the connection character set either through server configuration or by passing the ``charset`` parameter to ``create_engine``. The ``charset`` option is passed through to MySQL-Python and has the side-effect of also enabling ``use_unicode`` in the driver by default. For regular encoded strings, also pass ``use_unicode=0`` in the connection arguments.
这也证明了上面切入点是正确的。在create_engine的注释中找到了url的定义:
The URL is a string in the form ``dialect://user:password@host/dbname[?key=value..]``, where ``dialect`` is a name such as ``mysql``, ``oracle``, ``postgres``, etc. Alternatively, the URL can be an instance of ``sqlalchemy.engine.url.URL``.
很明白了,这个URL居然还支持"QueryString",于是把connstr修改为connstr = 'mysql://uid:pwd@localhost/mydb?charset=utf8',在看了这么多废话以后:),如你所愿,汉字正确的保存到里数据库里,O(∩_∩)O哈哈~。
真是郁闷,这么重要的用法,在文档里怎么没有描述呢?还是我没有找到?在google group里有很多人都遇到了utf8编码的问题,但似乎都没有正解。所以我把排除问题的全过程写下来,欢迎交流。