MySQL的latin1编码的Python字符串编解码器
项目描述
- 许可:
3条款BSD
- 网址:
概述
本项目提供MySQL的latin1编码的Python字符串编解码器,并附带类似iconv的命令行脚本,用于在shell管道中使用。
理由
MySQL默认使用latin1编码处理所有文本数据,但其latin1编码实际上是MySQL特定的变体。
由于应用程序编写不当或数据库配置错误,许多现有数据库仍然将数据保存在MySQL的latin1列中,即使这些数据实际上不是latin1数据。
MySQL不会对此提出异议,因此这种情况往往不被注意。只有在尝试从其他应用程序访问数据库或尝试使用mysqldump产生的数据库转储进行grep时,问题才会出现。
许多库无法正确支持此编码,使用真正的latin1编码会导致数据在处理过程中损坏,尤其是在数据库包含非西欧语言的文本时。
此字符串编解码器使能够重建MySQL中存储的确切字节。这对于修复此类数据库的文本编码问题是很有价值的工具。为了方便起见,还包括一个类似iconv的命令行界面。
版本历史
版本 2.0 (2014-01-31)
添加了显式的 register() 函数,而不是在模块导入时产生副作用。
版本 1.0(2013-12-04)
初始发布
安装
$ pip install mysql-latin1-codec
该软件包支持 Python 2 和 Python 3。
用法
您可以使用此项目两种方式:作为独立的命令行工具和作为 Python 模块。
命令行工具
命令行工具的行为类似于 iconv
$ python -m mysql_latin1_codec --help usage: mysql_latin1_codec.py [-h] [-f encoding] [-t encoding] [-o filename] [-c] [inputs [inputs ...]] iconv-like tool to encode or decode data using MySQL's "latin1" dialect, which, despite the name, is a mix of cp1252 and ISO-8859-1. positional arguments: inputs Input file(s) (defaults to stdin) optional arguments: -h, --help show this help message and exit -f encoding, --from encoding Source encoding (uses MySQL's "latin1" if omitted) -t encoding, --to encoding Target encoding (uses MySQL's "latin1" if omitted) -o filename, --output filename Output file (defaults to stdout) -c, --skip-invalid Omit invalid characters from output (not the default)
Python API
在 Python 代码中,只需导入名为 mysql_latin1_codec 的模块,并调用 register() 函数。将在 Python 的编码注册表中注册一个名为 mysql_latin1 的字符串编码。
import mysql_latin1_codec mysql_latin1_codec.register()
您可以使用正常的 (字节) 字符串上的 .decode() 和 .encode() 方法,您还可以将其指定为各种 I/O 函数(如 io.open())的 encoding 参数。示例
# String encoding/decoding round-trip s1 = u'foobar' s2 == text.encode('mysql_latin1').decode('mysql_latin1') assert s1 == s2 # Reading files import io with io.open('/path/to/file', 'r', encoding='mysql_latin1') as fp: for line in fp: pass
实际示例
下面的示例“修复”了包含双重编码 UTF-8 数据的转储,即实际 UTF-8 数据存储在 MySQL latin1 表中。默认情况下,mysqldump 创建 UTF-8 转储,但如果 MySQL 认为数据是 latin1,则将其再次转换,导致双重编码的数据。
$ cat backup-of-broken-database-produced-by-mysqldump.sql \ | python -m mysql_latin1_codec -f UTF-8 \ | iconv -c -f UTF-8 -t UTF-8 \ > legible-text-in-utf8.sql
此示例中的 iconv 管道删除了无效的 UTF-8 序列,同时保留了有效部分。MySQL 截断超出列最大大小的值,但如果 MySQL 不知道它正在处理 UTF-8 数据(因为数据库模式和损坏的应用程序没有告诉它这样做),它将截断字节序列,而不是字符序列。这可能导致在多字节序列中间截断时出现不完整的 UTF-8 序列。由于这些字符无论如何都无法恢复,因此在这种情况下删除它们是正确的解决方案。
在代码中,您可以像上面的示例那样做类似的事情
original = b'...' # byte string containing doubly-encoded UTF-8 data s = original.decode('UTF-8').encode('mysql_latin1').decode('UTF-8', 'replace')
另一个示例“修复”包含存储在 MySQL latin1 列中的 GB2312(简体中文)数据的转储,同样被 mysqldump 错误地解释和编码为 UTF-8
$ cat mojibake-crap.sql \ | python -m mysql_latin1_codec -f UTF-8 \ | iconv -f GB2312 -t UTF-8 \ > legible-text-in-utf8.sql
技术背景
MySQL 如何定义 latin1
当指定 latin1 时,MySQL 使用的字符集实际上不是众所周知的 latin1 字符集,官方称为 ISO-8859-1。MySQL 所称的 latin1 实际上是基于 cp-1252(也称为 windows-1252)的自定义编码。
MySQL 文档关于 西欧字符集 9§ 10.1.14.2) 包含以下内容:
latin1 是默认字符集。MySQL 的 latin1 与 Windows 的 cp1252 字符集相同。这意味着它与官方的 ISO 8859-1 或 IANA(互联网分配数字权威机构)的 latin1 相同,除了 IANA latin1 将 0x80 和 0x9f 之间的代码点视为“未定义”,而 cp1252 以及因此 MySQL 的 latin 将字符分配给这些位置。例如,0x80 是欧元符号。对于 cp1252 中的“未定义”条目,MySQL 将 0x81 转换为 Unicode 0x0081,0x8d 转换为 0x008d,0x8ff 转换为 0x008f,0x90 转换为 0x0090,0x9d 转换为 0x009d。
更多详细信息可以在 MySQL 源代码的文件 strings/ctype-latin1.c 中找到
WL#1494 notes: We'll use cp1252 instead of iso-8859-1. cp1252 contains printable characters in the range 0x80-0x9F. In ISO 8859-1, these code points have no associated printable characters. Therefore, by converting from CP1252 to ISO 8859-1, one would lose the euro (for instance). Since most people are unaware of the difference, and since we don't really want a "Windows ANSI" to differ from a "Unix ANSI", we will: - continue to pretend the latin1 character set is ISO 8859-1 - actually allow the storage of euro etc. so it's actually cp1252 Also we'll map these five undefined cp1252 character: 0x81, 0x8D, 0x8F, 0x90, 0x9D into corresponding control characters: U+0081, U+008D, U+008F, U+0090, U+009D. like ISO-8859-1 does. Otherwise, loading "mysqldump" output doesn't reproduce these undefined characters.
如您所见,此编码与 ISO-8859-1(真正的 latin1)有很大不同,但 MySQL 仍然错误地将它标记为 latin。
为什么这可能会成为一个问题
MySQL的latin1编码允许数据库列存储任意数据,而不进行任何验证。这意味着latin1文本列可以存储任何字节序列,例如UTF-8编码的文本(每个字符使用可变数量的字节)或甚至JPEG图像(根本就不是文本)。
这当然不是latin1列的正确用途。即使在现代的Unicode意识世界中,所有处理文本的正确软件都应该使用UTF-8(或其他Unicode编码),但仍很常见会遇到错误配置的数据库或编写不良的软件。大多数应用程序在存储和检索数据时使用相同的(错误的)假设,因此在许多配置中,这仍然“正常工作”,问题可能长时间未被察觉。
使这个问题更加严重的是,MySQL默认使用latin1字符编码,这主要是出于历史和向后兼容性的原因。这意味着许多现实世界的数据库可能错误地配置为在MySQL的latin1编码的列中存储数据,尽管这些列中实际存储的数据根本不是使用latin1编码的。
这可能导致各种问题,例如编码或解码错误、双重编码的文本、字符串操作的故障或错误的截断,这些都可能导致数据损坏。在许多情况下,这表现为mojibake文本。这可能是由于对字节所代表的字符的误解,或者由于双重编码造成的,例如,一个备份脚本将一个转换为UTF-8的latin1列再次转换为UTF-8。
许多工具,如Python的内置文本编码解码器以及iconv(命令行工具和C库)都不能使用这种自定义MySQL编码转换数据。这使得“恢复”例如存储在latin1列中的UTF-8数据变得非常困难,即使你知道自己在做什么以及使用了哪种实际编码。
当在命令行上调用时,此脚本将转换命令行上指定的转储文件(如果没有给出文件,则为标准输入)。数据被解释为UTF-8,并编码为MySQL的latin1,然后写入标准输出。输出是原始数据,可能需要进一步处理,例如使用iconv“重新解释”数据(例如作为UTF-8)。
我不知道你在说什么!
别担心,没关系。
项目详情
mysql-latin1-codec-2.0.tar.gz的哈希值
算法 | 哈希摘要 | |
---|---|---|
SHA256 | 009bf64f9ee0f33c5fb220c3571c5b8f62b22b7822a1512382a1b9944eed2442 |
|
MD5 | ecb565946614a65938283db17449a773 |
|
BLAKE2b-256 | 25c47221f3bb37e1a055a948515062309b773606d883069201da82ce34753550 |