2

作为一个仍在学习该语言的 python 新手,我挣扎了几天尝试使用 ftplib 中的 ftp 类(运行 Python 3.3)进行简单的 ASCII 文件传输(STOR/PUT)。

在使用 storbinary() 方法并不断收到 TypeError: "Type str doesn't support the buffer API" 之后,我发现了这个线程上的讨论,这意味着 ftplib 到 Python 3 的端口存在错误:

http://bugs.python.org/issue6822

然后我尝试使用 storbinary() 而不是 storlines(),使用使用“rb”开关打开的文件对象,它似乎工作得很好。我正在使用 Windows 系统,出于测试/学习的目的,我正在上传到我自己位于 Linux 主机上的站点。在上传 .zip 和 .txt 文件并使用 FileZilla 将它们复制回我的工作站后,这两个文件都完好无损。

在我的日常工作中,我需要将 gzipped 和 ASCII 文件上传到大型机,并且担心使用这种违反直觉的解决方法可能会让自己面临文件传输错误。当忘记切换到适当的传输模式时,我搞砸了很多手动 FTP 传输,能够使用完全相同的代码传输二进制文件和 ASCII 文件感觉令人毛骨悚然!

谁能评论我是如何实现这个库类的?

谢谢。

fileName = 'F:\\Data_Folder\\Test_File.txt'
fileParts = os.path.split(fileName)
putFile = fileParts[1]
cmd = 'STOR {}'.format(putFile)
fileObject = open(fileName, 'rb')
ftp.storbinary(cmd, fileObject)

fileName = 'F:\\Data_Folder\\Test_File.zip'
fileParts = os.path.split(fileName)
putFile = fileParts[1]
cmd = 'STOR {}'.format(putFile)
fileObject = open(fileName, 'rb')
ftp.storbinary(cmd, fileObject)

2013 年 6 月 28 日 - 回到这里是为了在这个问题上“闭环”。虽然我可以将 open(fileName, 'rb') 与 ftp.storbinary() 一起成功用于二进制和 ASCII 文本文件,并将 Windows 和 Linux 主机作为目标,但当我以大型机作为目标这样做时,文本文件出现乱码,显示为二进制文件。

通过向我的包装类添加一个开关以继续使用“rb”参数打开文件,但使用 storlines() 代替进行传输,文件将完好无损地到达目的地。我敢打赌,大型机端可能有一些配置选项可能使这种行为因一台主机而异,但我希望提及这一点会提醒遇到此线程的任何人注意“显然”的可能性安全”的 open(fileName, 'rb') 和 storbinary() 组合可能无法在所有 FTP 主机上成功,尤其是大型机系统。它可能只能通过反复试验来确定,但在某些情况下,传输 ASCII 数据的正确方法将需要 open(fileName, 'rb') 和 storlines()。

4

2 回答 2

2

3.3 文档storlines明确说:

从文件对象文件中读取行直到 EOF (以二进制模式打开)...</p>

因此,将一个以文本模式打开的文件传递给它是不应该的。

FTP 的文本 (ASCII) 模式与 Python 的文本模式不同。特别是,FTP 文本必须是 ASCII(和真正的 7 位 ASCII,而不是值 > 127 的扩展代码页)。但是 Python 文本具有明确指定的字符集,并被视为 Unicode。如果您的文件实际上是 UTF-8、Latin-1、CP-850 等,则不能使用文本模式。

最重要的是,Python 和 FTP 都允许修改文本文件的行尾。有时你想要这样,所以你可以将一个 Windows 文本文件上传到一个 linux 盒子,并让它显示为 Unix 行尾而不是 Windows(尽管这实际上可能不会发生,这取决于各种事情......)。但除此之外,您不想使用文本模式。

简而言之,您可能通过以二进制模式打开文本文件并以二进制(图像)模式上传它们来做正确的事情。


同时,您的代码按原样运行良好,但如果您正在寻找改进它的方法,那么总会有微小更改的空间。

首先,如果您复制并粘贴了两次相同的 7 行,唯一的区别是 1 行中的字符串,请将其分解为一个函数。

另外,关闭您的文件。要么添加一个显式的fileObject.close(),或者更好的是,使用一个with语句。如果您在一个短暂的脚本中只有 2 个文件,那不会有太大的不同,但这仍然是一个好主意——您以后可能会将其扩展为打开超过 2 个文件或寿命更长的东西。

如果您只想要文件的基本名称,调用basename比调用split然后访问更清楚[1]

进入挑剔,除非你有很多“遗留”或使用不同风格的包装代码,否则最好坚持使用PEP 8,而不是发明自己的风格。

最后,如果您想保留以不同方式发送文本和二进制文件的可能性,即使目前它们的实现方式相同,只需编写upload_binary_fileand upload_text_file,并让后者调用第二个,或者是对同一函数的引用。但是,您可能想要这个。由于上述原因以及 JF Sebastian 的评论,一个upload_text_file函数更可能是一个误导性的有吸引力的麻烦,而不是一个对未来扩展有用的钩子。

所以:

def upload_file(filename):
    put_file = os.path.basename(filename)
    cmd = 'STOR {}'.format(put_file)
    with open(filename, 'rb') as file_object:
        ftp.storbinary(cmd, file_object)

upload_file('F:\\Data_Folder\\Test_File.txt')
upload_file('F:\\Data_Folder\\Test_File.zip')
于 2013-05-08T18:26:36.440 回答
0

您的代码对我来说似乎有点令人费解,您使用了很多变量,这就是我的做法:

fileName = 'F:\\Data_Folder\\Test_File.txt'

fileObject = open(fileName, 'rb')

ftp.storbinary('STOR ' + os.path.split(fileName)[1], fileObject)

对于 zip 文件也是如此。二进制模式只是将您的文件作为一系列 1 和 0 传输,ASCII将其作为ASCII字符传输,最后都以相同的方式结束。我也相信通常建议您避免ASCII模式,因为可以在其上执行某种利用。

于 2013-05-08T18:33:31.357 回答