阿部芙蓉美とftplib

ひとり寂しい夜は 阿部芙蓉美 の曲を聴きながら、 コードリーディングをするのがベターです。

Pythonの ftplib モジュールを読んでみました。 閉じたネットワークではまだまだFTPの出番が多く、 私自身自動化ツールにFTPを使うことが多いです。 この記事でftplibの理解を深めると共に、 Pythonレベル向上にもなればなってところで。

Python (ftplib)

PythonのftplibにはLinuxのFTPクライアントコマンドのgetに 相当するメソッドを提供していません。 それほど手間は掛かりませんが、直感的にコードが書けません。 (たまに思い出したようにFTPクライアントを書くと必ず引っかかります。)

以下サンプルコードです。 (result.txtに同じファイルを2回書き出しています。)

#!/usr/bin/env python
import ftplib

ftp = ftplib.FTP('hostname', 'hogeo', 'hogepass')
wfp = open('result.txt', 'w')

ret = list()
ftp.retrlines('RETR /home/hattori/f.py', ret.append)
for l in ret:
    wfp.write(l + '\n')

ftp.retrbinary('RETR /home/hattori/f.py', wfp.write)

ftp.close()
wfp.close()

retrlinesやretrbinaryの2ndパラメータには呼び出し可能なオブジェクトを 渡します。そのオブジェクトを1行ごとに呼び出して処理します。 retrlinesはデフォルトで内部関数のprint_lineを呼び出します。 なので、ローカルファイルに保存する場合は、適切な処理に切り替える必要があります。 retrbinaryにfileobj.writeを渡すのがベタな処理方法でしょう。 少しトリッキーな感じだと、retrlinesにlist.append渡す方法も使えます。 (その後でjoin()とかしてあげればよい。)

## ftplib.py
399     def retrlines(self, cmd, callback = None):
400         '''Retrieve data in line mode.
401         The argument is a RETR or LIST command.
402         The callback function (2nd argument) is called for each line,
403         with trailing CRLF stripped.  This creates a new port for you.
404         print_line() is the default callback.'''
405         if callback is None: callback = print_line

636 def print_line(line):
637     '''Default retrlines callback to print a line.'''
638     print line

Ruby (net/ftp)

Rubyの net/ftp ライブラリはgetを実装しています。 後発の強み でしょうか。普通にgetbinaryfileとかgettextfileとかgetとかで使えます。 いいですね。

## net/ftp.rb
528     #
529     # Retrieves +remotefile+ in whatever mode the session is set (text or
530     # binary).  See #gettextfile and #getbinaryfile.
531     #
532     def get(remotefile, localfile = File.basename(remotefile),
533         blocksize = DEFAULT_BLOCKSIZE, &block) # :yield: data
534       unless @binary
535     gettextfile(remotefile, localfile, &block)
536       else
537     getbinaryfile(remotefile, localfile, blocksize, &block)
538       end
539     end

580     #
581     # Transfers +localfile+ to the server in whatever mode the session is set
582     # (text or binary).  See #puttextfile and #putbinaryfile.
583     #
584     def put(localfile, remotefile = File.basename(localfile),
585         blocksize = DEFAULT_BLOCKSIZE, &block)
586       unless @binary
587     puttextfile(localfile, remotefile, &block)
588       else
589     putbinaryfile(localfile, remotefile, blocksize, &block)
590       end
591     end

やはり Perlもありますね 。う~ん、Pythonは何故ないんだ?

またPythonにもどる

PythonのFTPクライアントモジュールとしては、 ftputil が有名(?)なようです。 uploadとdownloadメソッドがありますが、targetは指定してあげないとダメみたいですね。 Rubyのnet/ftpみたいに、デフォルトはsourceと同名のファイルに書き込めばいいのに。。。

## ftputil/ftputil.py
441     def upload(self, source, target, mode=''):
442         """
443         Upload a file from the local source (name) to the remote
444         target (name). The argument mode is an empty string or 'a' for
445         text copies, or 'b' for binary copies.
446         """
447         self.__copy_file(source, target, mode, open, self.file)
448         # the path in the stat cache is implicitly invalidated when
449         #  the file is opened on the remote host
450
451     def download(self, source, target, mode=''):
452         """
453         Download a file from the remote source (name) to the local
454         target (name). The argument mode is an empty string or 'a' for
455         text copies, or 'b' for binary copies.
456         """
457         self.__copy_file(source, target, mode, self.file, open)

EOF

ftpクライアント書くなら、Rubyかな。。。 (環境的に拡張モジュールほいほい入れれないのです。ちょとツライけど。)

続きとしては、ftpdのpython/asyncore実装であるところの pyftpdlib が おもしろいのではないかと、ひそかにコードリーディングの機会をうかがっています。

site-packages/pyftpdlib$ wc ftpserver.py
3214  13088 125307 ftpserver.py

だいたい3KLくらいですね。今の私にはやや大きめでなかなか楽しめるのでは ないかと思っています。

ex.阿部芙蓉美

本日のコードリーディングのパワーの源はウィスパーボイスの持ち主。 切なくなりつつも、元気をもらえます。

次回もすてきな歌とすてきなコードを楽しめますように、では。