【问题标题】:Proper usage of Python 3 ftp class正确使用 Python 3 ftp 类
【发布时间】:2017-12-02 22:41:22
【问题描述】:

作为一个仍在学习该语言的 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 将它们复制回我的工作站后,这两个文件都完好无损。

在我的日常工作中,我需要将 gzip 文件和 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()。

【问题讨论】:

  • 请记住,FTP 文本模式是明确的 ASCII,而大多数“文本文件”是 UTF-8 或 Latin-1 或 CP-850 或其他。另外,FTP 文本模式允许修改行尾。那么,您确定要使用文本模式吗?
  • 同时,以二进制模式传输 ASCII 始终是安全的,除非您想要另一方将行尾转换(或将 ASCII 转码为 EBCDIC 之类的),所以……什么你担心吗?

标签: python python-3.x ftp


【解决方案1】:

storlines 的 3.3 文档明确表示:

文件对象文件(以二进制模式打开)读取行直到EOF…

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

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_fileupload_text_file,并让后者调用第二个,或者是对相同功能的引用。但是,您可能想要这个。由于上述原因,以及在 J.F. Sebastian 的 cmets 中,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')

【讨论】:

  • abarnert - 谢谢!至于重复的行,这只是我用来尝试让它工作的 quick'n'dirty 脚本。我实际上是在一个类中实现整个操作,因为 ftp 传输只是其中的一小部分。也感谢 basename 方法和 with 语句 - 还没有发现这些,以及关闭文件的建议!我大约每周通读一次 PEP 8,但我仍在尝试内化 Python 风格。
  • upload_text_file 由于您概述的原因,这似乎是一个坏主意,即使 upload_ascii_file() 也不够(例如,上传和下载相同的仅 ascii 文件可能是非幂等操作(混合换行符案例))。除非有其他要求,否则按照您的建议使用按原样传输文件的图像模式更简单。
  • 另外,感谢您指出我错过的 doco 中的小细节 - storlines() 是指以二进制打开的文件对象。不知道我怎么会错过这个条目的次数:-)
  • @J.F.Sebastian:澄清一​​下:你的意思是我应该撤回可能有两个独立功能的建议,因为除非不正确,否则第二个肯定会产生误导?如果是这样,我想我同意你的看法,我会相应地编辑答案……但我想确保我首先理解你。
  • 我指的是“简而言之,您可能正在做正确的事情,以二进制模式打开文本文件并以二进制(图像)模式上传它们。”我同意前面的段落,即upload_text_file 现在不需要(甚至是有害的)。此外,upload_text_file = upload_binary_file 可能会使未来不太可能的数据迁移变得复杂。
【解决方案2】:

你的代码对我来说有点复杂,你使用了很多变量,我会这样做:

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 模式,因为可以在其上执行某种利用。

【讨论】:

  • 为什么建议用str.split('\') 替换os.path.split?这是一个非常糟糕的主意。甚至除了写成 SyntaxError 的事实之外,为什么还要让你的代码不那么可移植、不那么灵活、不那么清晰而没有任何好处呢?
  • 你是对的,os.path.split 选项更好,我忘记了第二个 \ 推动力更多的是使用更少的变量,尽管你是正确的 os 方法优于字符串操作
  • os.path.basename(path) 可能比 os.path.split(path)[1] 更明确。
  • 感谢额外的 cmets,Seth。我不喜欢不必要地使用变量,并且 abarnert 的 basename() 方法消除了一个,但我不愿在一行中嵌套太多。我对可读性很着迷,我讨厌进行代码审查,或者不得不编写包含太多此类内容的代码。
  • 我同意,我个人认为将类似的内容嵌套在一行中并不太糟糕,因为我发现方法本身的名称在确定输出应该是什么时相当清楚.你是正确的 J.F. Sebastian,这也可能是比拆分更好的解决方案。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-03-15
  • 1970-01-01
  • 2023-03-22
  • 2012-03-11
  • 2020-03-13
  • 1970-01-01
  • 2017-02-25
相关资源
最近更新 更多