SQLAlchemy 的 Unicode 问题

9 投票
3 回答
23520 浏览
提问于 2025-04-15 12:06

我知道我在处理Unicode转换时遇到了问题,但不太清楚具体是哪里出了错。

我正在从一堆HTML文件中提取关于最近一次欧洲旅行的数据。有些地点名称里包含非ASCII字符(比如é、ô、ü)。我通过正则表达式从文件的字符串表示中获取这些数据。

如果我在找到地点时直接打印出来,它们会正常显示这些字符,所以编码应该没问题:

Le Pré-Saint-Gervais, France
Hôtel-de-Ville, France

我使用SQLAlchemy把数据存储在SQLite表中:

Base = declarative_base()
class Point(Base):
    __tablename__ = 'points'

    id = Column(Integer, primary_key=True)
    pdate = Column(Date)
    ptime = Column(Time)
    location = Column(Unicode(32))
    weather = Column(String(16))
    high = Column(Float)
    low = Column(Float)
    lat = Column(String(16))
    lon = Column(String(16))
    image = Column(String(64))
    caption = Column(String(64))

    def __init__(self, filename, pdate, ptime, location, weather, high, low, lat, lon, image, caption):
        self.filename = filename
        self.pdate = pdate
        self.ptime = ptime
        self.location = location
        self.weather = weather
        self.high = high
        self.low = low
        self.lat = lat
        self.lon = lon
        self.image = image
        self.caption = caption

    def __repr__(self):
        return "<Point('%s','%s','%s')>" % (self.filename, self.pdate, self.ptime)

engine = create_engine('sqlite:///:memory:', echo=False)
Base.metadata.create_all(engine)
Session = sessionmaker(bind = engine)
session = Session()

我循环遍历这些文件,把每个文件中的数据插入到数据库里:

for filename in filelist:

    # open the file and extract the information using regex such as:
    location_re = re.compile("<h2>(.*)</h2>",re.M)
    # extract other data

    newpoint = Point(filename, pdate, ptime, location, weather, high, low, lat, lon, image, caption)
    session.add(newpoint)
    session.commit()

每次插入时,我都会看到以下警告:

/usr/lib/python2.5/site-packages/SQLAlchemy-0.5.4p2-py2.5.egg/sqlalchemy/engine/default.py:230: SAWarning: Unicode type received non-unicode bind param value 'Spitalfields, United Kingdom'
  param.append(processors[key](compiled_params[key]))

而当我尝试对这个表做任何操作,比如:

session.query(Point).all()

我得到的结果是:

Traceback (most recent call last):
  File "./extract_trips.py", line 131, in <module>
    session.query(Point).all()
  File "/usr/lib/python2.5/site-packages/SQLAlchemy-0.5.4p2-py2.5.egg/sqlalchemy/orm/query.py", line 1193, in all
    return list(self)
  File "/usr/lib/python2.5/site-packages/SQLAlchemy-0.5.4p2-py2.5.egg/sqlalchemy/orm/query.py", line 1341, in instances
    fetch = cursor.fetchall()
  File "/usr/lib/python2.5/site-packages/SQLAlchemy-0.5.4p2-py2.5.egg/sqlalchemy/engine/base.py", line 1642, in fetchall
    self.connection._handle_dbapi_exception(e, None, None, self.cursor, self.context)
  File "/usr/lib/python2.5/site-packages/SQLAlchemy-0.5.4p2-py2.5.egg/sqlalchemy/engine/base.py", line 931, in _handle_dbapi_exception
    raise exc.DBAPIError.instance(statement, parameters, e, connection_invalidated=is_disconnect)
sqlalchemy.exc.OperationalError: (OperationalError) Could not decode to UTF-8 column 'points_location' with text 'Le Pré-Saint-Gervais, France' None None

我希望能够正确存储地点名称,并保持原始字符不变。任何帮助都将非常感谢。

3 个回答

7

来自 sqlalchemy.org

请查看第0.4.2节

在String和create_engine()中新增了一个标志,叫做 _unicode,值可以是 (True|False|'warn'|None)。默认情况下,create_engine() 和 String 的值是 FalseNone,而Unicode类型的默认值是 'warn'。当设置为 True 时,如果传入一个非Unicode的字节串作为绑定参数,就会抛出异常。设置为 'warn' 时会发出警告。强烈建议所有支持Unicode的应用程序正确使用Python的Unicode对象(也就是用u'hello'而不是'hello'),这样数据在传输时才能准确。

我觉得你可能在尝试输入一个非Unicode的字节串。也许这能帮你找到正确的方向?需要进行某种形式的转换,比较一下 'hello' 和 u'hello'。

祝好

7

试着把unicode列的类型设为Unicode,而不是String:

Base = declarative_base()
class Point(Base):
    __tablename__ = 'points'

    id = Column(Integer, primary_key=True)
    pdate = Column(Date)
    ptime = Column(Time)
    location = Column(Unicode(32))
    weather = Column(String(16))
    high = Column(Float)
    low = Column(Float)
    lat = Column(String(16))
    lon = Column(String(16))
    image = Column(String(64))
    caption = Column(String(64))

编辑:对评论的回复:

如果你收到关于unicode编码的警告,可以尝试以下两种方法:

  1. 把你的地点转换成unicode。这意味着你需要这样创建你的Point:

    newpoint = Point(filename, pdate, ptime, unicode(location), weather, high, low, lat, lon, image, caption)

    这个unicode转换会在你传入字符串或unicode字符串时,生成一个unicode字符串,所以你不需要担心传入的是什么。

  2. 如果这样做还不能解决编码问题,可以尝试对你的unicode对象调用encode。也就是说,你可以使用这样的代码:

    newpoint = Point(filename, pdate, ptime, unicode(location).encode('utf-8'), weather, high, low, lat, lon, image, caption)

    这一步可能不一定需要,但它的作用是把unicode对象从unicode代码点转换成特定的字节表示(在这里是utf-8)。我预计SQLAlchemy在你传入unicode对象时会为你处理这个,但它可能不会。

11

我找到了一篇文章,帮我解释了一些困扰我的问题:

http://www.amk.ca/python/howto/unicode#reading-and-writing-unicode-data

我通过使用'codecs'模块,调整了我的程序,得到了想要的结果:

打开文件时:

infile = codecs.open(filename, 'r', encoding='iso-8859-1')

打印位置时:

print location.encode('ISO-8859-1')

现在我可以查询和处理表格中的数据,而不会再出现之前的错误。我只需要在输出文本时指定编码方式。

(我还是不太明白这个是怎么工作的,看来是时候多学习一下Python的unicode处理了…)

撰写回答