确认Python 2.6的ftplib不支持Unicode文件名?有什么替代方案?
有人能确认一下,Python 2.6 的 ftplib 模块是否不支持 Unicode 文件名吗?或者说,Unicode 文件名必须经过特殊编码才能在 ftplib 模块中使用吗?
以下的邮件交流似乎支持我的结论,即 ftplib 模块只支持 ASCII 文件名。
ftplib 应该使用 UTF-8 而不是 latin-1 编码吗?http://mail.python.org/pipermail/python-dev/2009-January/085408.html
有没有推荐的第三方 Python FTP 模块可以支持 Unicode 文件名?我在网上搜索这个问题没有找到答案 [1], [2]。
官方的 Python 文档没有提到 Unicode 文件名 [3]。
谢谢,Malcolm
[1] ftputil 是在 ftplib 的基础上封装的,它也继承了 ftplib 只支持 ASCII 的特性吗?
[2] Paramiko 的 SFTP 库支持 Unicode 文件名,但我现在具体想找的是 ftp(而不是 sftp)支持,跟我们当前的项目有关。
[3] http://docs.python.org/library/ftplib.html
解决方法:
可以使用 encodings.idna.ToASCII 和 .ToUnicode 方法将 Unicode 路径名转换为 ASCII 格式。如果你把所有的远程路径名和 dir/nlst 方法的输出都用这些函数包裹起来,就可以在使用标准 ftplib 的同时保留 Unicode 路径名(也能在不支持 Unicode 路径的文件系统上保留 Unicode 文件名)。不过,这种方法的缺点是,服务器上的其他进程在引用你上传到服务器的文件时也必须使用 encodings.idna。顺便提一下,我知道这有点滥用 encodings.idna 库。
谢谢 Peter 和 Bob 的评论,我觉得非常有帮助。
5 个回答
为了解决这个问题,我使用了以下代码
ftp.storbinary("STOR " + target_name.encode( "utf-8" ), open(file_name, 'rb'))
这个代码假设FTP服务器支持RFC 2640,http://www.ietf.org/rfc/rfc2640.txt,这个标准允许使用utf-8格式的文件名。在我的情况下,我使用了SwiFTP服务器在安卓设备上,它能够成功地传输带有正确名称的文件。
我个人觉得,跟ftp连接的另一端相比,我更担心的是那边的情况,而不是这个库的支持。ftp协议本身就不太稳定,尤其是在文件名上搞创意的时候。
根据RFC 959的说法:
Pathname is defined to be the character string which must be
input to a file system by a user in order to identify a file.
Pathname normally contains device and/or directory names, and
file name specification. FTP does not yet specify a standard
pathname convention. Each user must follow the file naming
conventions of the file systems involved in the transfer.
我理解这意味着文件名应该尽量简单,符合最低的要求。现在使用DOS服务器、Vax和IBM大型机的情况几乎没有,所以你很可能会连接到Windows或Unix系统,这样的共同要求还是比较高的。不过,去猜测远程网站希望接受哪种编码方式,我觉得这风险挺大的。
ftplib
这个库对 Unicode(即中文、日文等字符的编码方式)是完全不认识的。它的设计是用字节字符串来处理文件名,当你请求目录列表时,它也会返回字节字符串。这些字符串就是从服务器传来的原始字节。
如果你在 Python 2.x 中把一个 Unicode 字符串传给 ftplib
,它会在发送到底层的 socket 对象时被强制转换成字节。这种转换使用的是 Python 的“默认”编码方式,也就是为了安全起见使用 US-ASCII(美国标准信息交换码),对于非 ASCII 字符会抛出异常。
你提到的 python-dev 信息是关于 Python 3.x 的,在这个版本中,字符串默认就是 Unicode。这就让像 ftplib
这样的模块处于一个棘手的境地,因为虽然它们现在在前端使用 Unicode 字符串,但实际的协议还是基于字节的。因此,必须额外进行编码和解码,如果没有明确指定使用什么编码,就有很大可能会选择错误的编码。
在 Python 3.x 中,ftplib
默认选择 ISO-8859-1 编码,以便在 Unicode 字符串中保留每个字节作为一个字符。不幸的是,这在目标服务器使用 UTF-8 编码来处理文件名的常见情况下,会导致意想不到的结果(无论 FTP 服务本身是否知道文件名是 UTF-8 编码,这通常是它不知道的)。像这样的情况有很多,Python 的标准库在处理 Unicode 字符串时被粗暴修改,导致了负面的后果;我认为 Python 3 的内置功能仍然存在一些问题。