初心者必見!Python ftplib.FTP.close()の基本とエラー回避テクニック

2025-06-06

目的

FTPクライアントがFTPサーバーとの間でファイル転送などの操作を完了した後、このメソッドを呼び出すことで、確立されたTCP接続を適切に終了させます。これにより、リソースが解放され、サーバー側もクライアント側の接続終了を認識できます。

使い方

FTP オブジェクトのインスタンスに対して close() メソッドを呼び出します。

from ftplib import FTP

ftp = None
try:
    # FTPサーバーに接続する例
    ftp = FTP('your_ftp_server.com')
    ftp.login(user='your_username', passwd='your_password')

    # ファイル転送などの操作を実行...
    # 例: ftp.retrlines('LIST')

except Exception as e:
    print(f"エラーが発生しました: {e}")
finally:
    if ftp:
        print("FTP接続を閉じます。")
        ftp.close() # ここで接続を閉じます
        print("FTP接続が閉じられました。")
  1. リソースの解放: close() を呼び出すことで、ネットワークソケットや関連するシステムリソースが解放されます。これは、特に多数のFTP接続を行うアプリケーションでは非常に重要です。

  2. try...finally ブロックとの併用: ネットワーク接続のようなリソースを扱う場合、エラーが発生しても必ず接続が閉じられるように、try...finally ブロック内で close() を呼び出すことが強く推奨されます。これにより、例外が発生した場合でもリソースリークを防ぐことができます。

  3. with ステートメントの利用: ftplib.FTP オブジェクトはコンテキストマネージャとしても機能します。そのため、Pythonの with ステートメントを使用すると、close() を明示的に呼び出すことなく、安全に接続を閉じることができます。これが最も推奨される方法です。

    from ftplib import FTP
    
    try:
        with FTP('your_ftp_server.com') as ftp:
            ftp.login(user='your_username', passwd='your_password')
            print("FTP接続が確立されました。")
            # ファイル転送などの操作を実行...
            # 例: ftp.retrlines('LIST')
            print("操作が完了しました。")
        # 'with'ブロックを抜けると、自動的にftp.close()が呼び出されます。
        print("FTP接続は自動的に閉じられました。")
    except Exception as e:
        print(f"エラーが発生しました: {e}")
    


ftplib.all_errors または OSError が発生する

エラーの種類

  • OSError (特に WinError 10060 など): 接続タイムアウトやネットワークが到達不能な場合など、OSレベルのネットワークエラーが発生することがあります。
  • ftplib.all_errors: ftplib が発生させる一般的なエラーの基底クラスです。サーバーからの不正な応答や、ネットワークの問題など、さまざまなFTP関連のエラーを包括します。

原因

  • 不正な操作: 既に閉じられている接続に対して再度 close() を呼び出そうとした場合(これは通常、Pythonの with ステートメントを使用していれば避けられます)。
  • ネットワークの問題: 接続中にネットワークが切断されたり、不安定になったりした場合も、close() が正常に完了できないことがあります。
  • サーバー側が先に接続を閉じた: FTPサーバーは、一定時間アイドル状態が続いたり、何らかの理由で接続を強制的に切断したりすることがあります。この状態で close() を呼び出そうとすると、すでに接続がないためエラーになることがあります。

トラブルシューティング

  • ネットワークの安定性確認: クライアント側のネットワーク接続が安定しているか確認します。ファイアウォールやプロキシがFTP通信を妨害している可能性も考慮します。
  • サーバーログの確認: FTPサーバー側のログを確認できる場合は、サーバーが接続を閉じた理由や、何らかのエラーを記録しているかを確認します。タイムアウト設定、接続数制限などが原因であることがあります。
  • ftplib.FTP.set_debuglevel(level) の活用: デバッグレベルを設定することで、FTPクライアントとサーバー間の詳細な通信ログを確認できます。これにより、エラーがどの段階で発生しているか(例えば、close() を呼び出す前にすでに接続が切れていたかなど)を把握するのに役立ちます。
    import sys
    ftp.set_debuglevel(2) # 0:なし, 1:中程度, 2:詳細
    ftp.set_pasv(True) # 必要に応じてパッシブモードを明示的に設定
    

close() が待機しない、またはデータ損失が発生する

問題

  • 特にファイルのアップロード(storbinarystorlines)の場合に、データが途中で切れてしまう。
  • close() がすぐに戻ってきてしまい、送信したデータがサーバーに完全に届く前に接続が閉じられてしまう可能性がある。

原因

  • TCPの close() は、OSレベルでソケットを閉じることを意味し、必ずしもすべてのデータがリモートエンドに送信され、確認応答されるまでブロックするわけではありません。これは ftplib のバグとして議論されたこともあります。

トラブルシューティング

  • 処理の完了確認: ファイルのアップロードが完了した後に、サーバーから完了を示す応答(例: 226 Transfer complete)が返されることを確認する処理をコードに追加します。もし応答が返ってこない場合、転送が完了していない可能性があります。
  • ftplib の更新: ftplib のバージョンが古い場合、この種の問題が修正されている可能性がありますので、Pythonのバージョンアップを検討します。
  • socket.shutdown() の使用(高度な知識が必要): ftplib の内部実装に手を入れることになりますが、データの送受信が完了したことを明示的に示すために socket.shutdown(socket.SHUT_RDWR) を呼び出すことがより安全な方法です。しかし、これは ftplib の公開APIではないため、通常は推奨されません。

FTP.quit() と FTP.close() の違いによる混乱

問題

  • ftp.quit()ftp.close() のどちらを使うべきか、その違いが不明。

解決策と説明

  • ftp.close(): これはFTPプロトコルのコマンドを送信せず、クライアント側から一方的にTCP接続を閉じます。
  • ftp.quit(): これはFTPプロトコルの QUIT コマンドをサーバーに送信し、サーバーに接続を閉じるように要求する「礼儀正しい」方法です。サーバーは通常 221 Goodbye のような応答を返し、接続を終了します。

推奨

  • もし quit() が何らかの理由で失敗した場合(例: サーバーが応答しない)、finally ブロックや with ステートメントの終了時に close() がソケットを強制的に閉じます。
  • with ステートメントを使用すると、内部的に quit() が呼び出されてから close() が行われるため、最も推奨される方法です。
  • 通常は ftp.quit() を使用すべきです。 これにより、サーバー側もクライアント側の意図を認識し、リソースを適切に解放できます。

問題

  • close() を呼び出した後、または何らかのエラーが発生した後、FTP接続が本当に閉じられているか、またはまだ使用可能であるかを確認する方法が不明。
  • 最も確実な方法は、簡単なFTPコマンド(例: ftp.voidcmd('NOOP'))を実行してみて、エラーが発生するかどうかを確認することです。エラーが発生すれば、接続は無効になっていると判断できます。
    from ftplib import FTP, all_errors
    import socket
    
    ftp = None
    try:
        ftp = FTP('your_ftp_server.com')
        ftp.login('user', 'password')
        print("接続成功")
    
        # 接続がまだ生きているか確認
        try:
            ftp.voidcmd('NOOP') # NOOPコマンドは何もせず、応答を返す
            print("接続はまだアクティブです。")
        except (all_errors, socket.error):
            print("接続はアクティブではありません。")
    
        # 接続を閉じる
        ftp.quit()
        print("quit()実行済み")
    
        # 再度接続状態を確認
        try:
            ftp.voidcmd('NOOP')
            print("接続はまだアクティブです。(これは通常、エラーになるべき)")
        except (all_errors, socket.error):
            print("接続はアクティブではありません。(想定通り)")
    
    except all_errors as e:
        print(f"エラー: {e}")
    finally:
        if ftp and ftp.sock is not None:
            # 安全のために close() も試みる
            try:
                ftp.close()
                print("close()実行済み")
            except Exception as e:
                print(f"close()中にエラー: {e}")
    
  • ftplib.FTP オブジェクトには、接続状態を直接確認するパブリックなメソッドはありません。しかし、内部のソケットオブジェクト (ftp.sock) を利用することで、ある程度の判断が可能です。
    • ftp.sockNone であれば、接続は閉じられているか、まだ確立されていません。
    • ftp.sockNone でない場合でも、ネットワークレベルで切断されている可能性はあります。


ftplib.FTP.close() は、FTPサーバーへの接続を閉じるために使用されます。PythonでFTP操作を行う際、リソースの解放とクリーンな終了のために、このメソッドを適切に呼び出すことが非常に重要です。

ここでは、様々なシナリオでの close() の使い方と、推奨されるプラクティスについて具体的なコード例を交えて説明します。

基本的な使い方(try...finally ブロック)

FTP操作が成功しても失敗しても、必ず接続を閉じるようにするための基本的な方法です。

from ftplib import FTP, all_errors
import socket # ネットワーク関連のエラーをキャッチするため

ftp = None # ftp オブジェクトを初期化
try:
    # 1. FTPサーバーに接続
    print("FTPサーバーに接続中...")
    ftp = FTP('ftp.example.com') # 例: 実際のFTPサーバーアドレスに置き換えてください
    print("ログイン中...")
    ftp.login(user='your_username', passwd='your_password') # 実際のユーザー名とパスワードに置き換えてください
    print(f"接続成功!ウェルカムメッセージ: {ftp.getwelcome()}")

    # 2. FTP操作を実行(例: ファイルリストの取得)
    print("\nファイルリストを取得中...")
    files = ftp.nlst() # サーバー上のファイルとディレクトリのリストを取得
    for file in files:
        print(f"- {file}")
    print("ファイルリストの取得完了。")

except all_errors as e:
    # ftplibが引き起こすFTP関連の全てのエラーを捕捉
    print(f"\nFTP操作中にエラーが発生しました: {e}")
except socket.error as e:
    # ネットワーク接続関連のエラーを捕捉(例: 接続拒否、タイムアウト)
    print(f"\nネットワーク接続エラーが発生しました: {e}")
except Exception as e:
    # その他の予期せぬエラーを捕捉
    print(f"\n予期せぬエラーが発生しました: {e}")
finally:
    # 3. 接続を閉じる(必ず実行される)
    if ftp: # ftp オブジェクトがNoneでないことを確認
        try:
            print("\nFTP接続を閉じます...")
            # ここで close() を呼び出す
            ftp.close()
            print("FTP接続が正常に閉じられました。")
        except all_errors as e:
            # close() 自体が失敗する可能性も考慮
            print(f"close()中にFTP関連のエラーが発生しました: {e}")
        except Exception as e:
            print(f"close()中に予期せぬエラーが発生しました: {e}")
    else:
        print("\nFTP接続は確立されませんでした。")

print("\nプログラムが終了しました。")

解説

  • if ftp:finally ブロック内で、ftp オブジェクトが実際に作成され、None でないことを確認してから close() を試みます。これにより、接続自体が失敗した場合に AttributeError が発生するのを防ぎます。
  • try...except...finally:
    • try:FTP接続から操作まで、エラーが発生する可能性のあるコードを配置します。
    • except all_errors as eftplib 固有のエラーをキャッチします。
    • except socket.error as e:ネットワーク接続に関する低レベルのエラーをキャッチします。
    • finallytry ブロック内で例外が発生したかどうかにかかわらず、常に実行されます。ここで ftp.close() を呼び出すことで、リソースのリークを防ぎます。
  • ftp = None: try ブロックに入る前に ftpNone で初期化しておくことで、接続に失敗した場合でも finally ブロックで NameError が発生するのを防ぎます。

with ステートメントを使った推奨される方法

ftplib.FTP はコンテキストマネージャを実装しているため、Pythonの with ステートメントを使用すると、close() を明示的に呼び出す必要がなくなり、より簡潔で安全なコードになります。with ブロックを抜ける際に、自動的に quit()(サーバーに終了を通知)と close()(ソケットを閉じる)が呼び出されます。

from ftplib import FTP, all_errors
import socket

try:
    print("FTPサーバーに接続中(withステートメント使用)...")
    # 'with' ブロックに入ると自動的にFTP接続が確立される
    with FTP('ftp.example.com') as ftp: # 例: 実際のFTPサーバーアドレスに置き換えてください
        print("ログイン中...")
        ftp.login(user='your_username', passwd='your_password') # 実際のユーザー名とパスワードに置き換えてください
        print(f"接続成功!ウェルカムメッセージ: {ftp.getwelcome()}")

        # FTP操作を実行(例: カレントディレクトリの表示)
        print("\nカレントディレクトリを表示中...")
        current_dir = ftp.pwd()
        print(f"現在のディレクトリ: {current_dir}")

        # ファイルをアップロードする例 (ダミーファイルを作成)
        local_filename = "test_upload.txt"
        with open(local_filename, "w") as f:
            f.write("This is a test file for FTP upload.\n")
            f.write("Hello, FTP world!\n")

        print(f"\nファイルをアップロード中: {local_filename}")
        with open(local_filename, 'rb') as fp:
            # storbinary はファイルをバイナリモードでアップロード
            # 'STOR' はFTPのアップロードコマンド
            ftp.storbinary(f'STOR {local_filename}', fp)
        print(f"{local_filename} のアップロード完了。")

        # 'with' ブロックの終了時に、自動的に ftp.quit() と ftp.close() が呼び出される
        print("\n'with' ブロックが終了します。接続は自動的に閉じられます。")

except all_errors as e:
    print(f"\nFTP操作中にエラーが発生しました: {e}")
except socket.error as e:
    print(f"\nネットワーク接続エラーが発生しました: {e}")
except FileNotFoundError:
    print("\nアップロードするファイルが見つかりませんでした。")
except Exception as e:
    print(f"\n予期せぬエラーが発生しました: {e}")

print("\nプログラムが終了しました。")

解説

  • 推奨される方法: ほとんどのFTP操作において、この with ステートメントの使用が最も推奨されるプラクティスです。
  • 自動的なクリーンアップ:with ブロックの実行が正常に完了したか、途中で例外が発生したかにかかわらず、ブロックを抜ける際に ftp.quit()(FTPプロトコルの終了コマンド)が実行され、その後ソケットが閉じられます(ftp.close() 相当)。これにより、コードが大幅に簡潔になり、リソースリークのリスクも軽減されます。
  • with FTP(...) as ftp::この構文を使用することで、FTP接続のライフサイクルが with ブロックによって管理されます。

quit() と close() の両方を理解する

FTP.quit() はサーバーに「もう終了します」と礼儀正しく伝えるFTPコマンドです。一方、FTP.close() はクライアント側のTCP接続を強制的に閉じます。with ステートメントは、内部で quit() を実行し、その後 close() を行います。

from ftplib import FTP, all_errors
import socket
import time

ftp = None
try:
    print("FTPサーバーに接続中...")
    ftp = FTP('ftp.example.com')
    ftp.login(user='your_username', passwd='your_password')
    print(f"接続成功!ウェルカムメッセージ: {ftp.getwelcome()}")

    # 意図的にエラーを起こす例(存在しないディレクトリに移動しようとする)
    print("\n存在しないディレクトリに移動を試みます(エラーの例)...")
    ftp.cwd('non_existent_directory_123')
    print("この行は表示されないはずです。") # ここには到達しない

except all_errors as e:
    print(f"\nFTPエラーが発生しました: {e}")
    # エラーが発生しても、接続を閉じようとします。

finally:
    if ftp:
        print("\nfinallyブロック: 接続状態を確認中...")
        try:
            # サーバーにQUITコマンドを送信(推奨される礼儀正しい終了)
            print("ftp.quit() を呼び出します...")
            ftp.quit()
            print("ftp.quit() が正常に実行されました。")
        except all_errors as e:
            # quit() が失敗した場合(例: サーバーが既に切断している)
            print(f"ftp.quit()中にエラーが発生しました: {e} -> 強制的に close() を試みます。")
            try:
                ftp.close() # 強制的にソケットを閉じる
                print("ftp.close() が正常に実行されました。")
            except Exception as ex:
                print(f"ftp.close()中にもエラーが発生しました: {ex}")
        except Exception as e:
            print(f"ftp.quit()中に予期せぬエラーが発生しました: {e} -> 強制的に close() を試みます。")
            try:
                ftp.close()
                print("ftp.close() が正常に実行されました。")
            except Exception as ex:
                print(f"ftp.close()中にもエラーが発生しました: {ex}")
    else:
        print("\nfinallyブロック: FTP接続は確立されませんでした。")

print("\nプログラムが終了しました。")
  • これにより、より堅牢な終了処理を実現できます。ただし、前述の通り with ステートメントを使用すれば、これら両方の処理が自動的に、かつより安全に行われます。
  • もし quit() が何らかの理由で失敗した場合(例えば、既にネットワークが切断されている、サーバーが応答しないなど)、その except ブロック内で ftp.close() を呼び出して、少なくともクライアント側のソケットを確実に閉じようとしています。
  • この例では、finally ブロック内で最初に ftp.quit() を試みています。これは、FTPプロトコルに従ってサーバーにセッション終了を通知する、より「友好的」な方法です。


ftplib.FTP.close() はFTPサーバーへの接続を閉じるための直接的なメソッドですが、Pythonにおけるリソース管理のベストプラクティスを考慮すると、より推奨される代替手段が存在します。これらの代替手段は、コードの簡潔さ、堅牢性、およびエラーハンドリングの改善に貢献します。

主な代替方法は以下の2つです。

  1. with ステートメントの使用(コンテキストマネージャ)
  2. ftplib.FTP.quit() の使用(FTPプロトコルレベルでの終了)

それぞれ詳しく見ていきましょう。

with ステートメントの使用(最も推奨される方法)

ftplib.FTP オブジェクトは、Pythonのコンテキストマネージャプロトコルを実装しています。これにより、with ステートメントと組み合わせて使用することで、リソースの取得と解放を自動的に行うことができます。with ブロックを抜ける際に、ftplib.FTP オブジェクトは内部的に quit() メソッドを呼び出し、その後 close() メソッドを呼び出すため、明示的に close() を記述する必要がなくなります。

利点

  • 可読性
    リソースのライフサイクルがコード上で明確になります。
  • 堅牢性
    例外が発生しても、with ブロックを抜ける際に必ずリソースが解放されます(quit()close() が実行されます)。これにより、リソースリークを防ぎます。
  • コードの簡潔さ
    try...finally ブロックで close() を手動で呼び出す手間が省けます。

コード例

from ftplib import FTP, all_errors
import socket # ネットワーク関連のエラーをキャッチするため

try:
    print("FTPサーバーに接続中(withステートメント使用)...")
    # 'with' ブロックに入ると、FTP接続が確立されます
    with FTP('ftp.example.com') as ftp: # 実際のFTPサーバーアドレスに置き換えてください
        print("ログイン中...")
        ftp.login(user='your_username', passwd='your_password') # 実際のユーザー名とパスワードに置き換えてください
        print(f"接続成功!ウェルカムメッセージ: {ftp.getwelcome()}")

        # ここでFTP操作を実行します
        print("\nファイルリストを取得中...")
        files = ftp.nlst()
        for file in files:
            print(f"- {file}")
        print("ファイルリストの取得完了。")

        # ファイルをアップロードする例
        local_filename = "upload_test.txt"
        with open(local_filename, "w") as f:
            f.write("This is a test file to upload via FTP.\n")
        
        print(f"\nファイルをアップロード中: {local_filename}")
        with open(local_filename, 'rb') as fp:
            ftp.storbinary(f'STOR {local_filename}', fp)
        print(f"{local_filename} のアップロード完了。")

    # 'with' ブロックを抜けると、自動的に ftp.quit() が実行され、その後 ftp.close() が実行されます。
    print("\n'with' ブロックを抜けました。FTP接続は自動的に閉じられました。")

except all_errors as e:
    print(f"\nFTP操作中にエラーが発生しました: {e}")
except socket.error as e:
    print(f"\nネットワーク接続エラーが発生しました: {e}")
except FileNotFoundError:
    print("\n操作に必要なローカルファイルが見つかりませんでした。")
except Exception as e:
    print(f"\n予期せぬエラーが発生しました: {e}")

print("\nプログラムが終了しました。")

解説
with FTP(...) as ftp: の行がこの方法の核心です。これにより、FTP オブジェクトが __enter__ メソッド(接続確立)と __exit__ メソッド(接続終了)を自動的に呼び出すようになります。__exit__ メソッドの内部で、quit()close() が適切に処理されます。

ftplib.FTP.quit() の使用

ftplib.FTP.quit() は、FTPサーバーに対して QUIT コマンドを送信し、FTPプロトコルレベルでセッションの終了を要求するメソッドです。これは close() とは異なり、単にTCPソケットを閉じるだけでなく、サーバーに「私は終了します」というメッセージを伝える「礼儀正しい」終了方法です。ftplib.FTP オブジェクトの __exit__ メソッド(with ステートメントで使われる)は、内部的に quit() を呼び出してから close() を呼び出します。

利点

  • サーバー側のリソース解放
    サーバーがクライアントの接続終了を認識し、関連するリソースを適切に解放するのに役立ちます。
  • プロトコル準拠
    FTPサーバーに対して正式な終了プロセスを通知します。

注意点

  • quit() が失敗した場合でも、最終的にはソケットを閉じるために close()(または with ステートメントによる自動クリーンアップ)が必要です。
  • quit() はネットワーク通信を伴うため、サーバーが応答しない場合やネットワークに問題がある場合は、all_errorssocket.error が発生する可能性があります。

コード例(try...finally ブロック内で quit() を優先):

from ftplib import FTP, all_errors
import socket

ftp = None
try:
    print("FTPサーバーに接続中...")
    ftp = FTP('ftp.example.com')
    ftp.login(user='your_username', passwd='your_password')
    print(f"接続成功!ウェルカムメッセージ: {ftp.getwelcome()}")

    # FTP操作を実行
    print("\n現在の作業ディレクトリ: ", ftp.pwd())

    # 例: 意図的に存在しないディレクトリに移動を試み、エラーを発生させる
    # print("\n存在しないディレクトリへ移動を試みます(エラー発生例)...")
    # ftp.cwd('non_existent_folder_xyz') # この行はコメントアウトして正常終了させるか、有効にしてエラーハンドリングをテストしてください

except all_errors as e:
    print(f"\nFTP操作中にエラーが発生しました: {e}")
except socket.error as e:
    print(f"\nネットワーク接続エラーが発生しました: {e}")
except Exception as e:
    print(f"\n予期せぬエラーが発生しました: {e}")
finally:
    # 接続が確立されている場合にのみ処理
    if ftp:
        try:
            # 優先的に quit() を呼び出す
            print("\nFTP接続を終了します(QUITコマンド送信)...")
            ftp.quit()
            print("FTP接続が正常に終了しました(QUITコマンド完了)。")
        except all_errors as e:
            # QUITコマンド送信中にエラーが発生した場合
            print(f"quit()中にFTP関連のエラーが発生しました: {e}")
            print("強制的にソケットを閉じます(close())。")
            try:
                ftp.close() # quit() が失敗しても close() を試みる
                print("ftp.close() が正常に実行されました。")
            except Exception as ex:
                print(f"ftp.close()中に予期せぬエラーが発生しました: {ex}")
        except Exception as e:
            # その他の quit() 中のエラー
            print(f"quit()中に予期せぬエラーが発生しました: {e}")
            print("強制的にソケットを閉じます(close())。")
            try:
                ftp.close()
                print("ftp.close() が正常に実行されました。")
            except Exception as ex:
                print(f"ftp.close()中にも予期せぬエラーが発生しました: {ex}")
    else:
        print("\nFTP接続は確立されませんでした。")

print("\nプログラムが終了しました。")

解説
この例では、finally ブロック内で ftp.quit() を優先して呼び出し、それが失敗した場合に備えて ftp.close() をフォールバックとして使用しています。しかし、前述の with ステートメントを使用すれば、これらの複雑なエラーハンドリングを自動的に処理してくれるため、通常はこの手動での quit() および close() の組み合わせは推奨されません。

Pythonで ftplib を使用してFTP接続を閉じる最も推奨される方法は、with ステートメントを使用することです。 これにより、コードが簡潔になり、エラーハンドリングが自動化され、リソースが確実に解放されます。