Python | ファイル操作など:shutil.copy

Python
スポンサーリンク

概要(shutil.copy は「ファイルを別の場所へコピーする」高レベル関数)

shutil.copy は、指定した1つの“ファイル”を別のパスへコピーする標準ライブラリの関数です。コピー先が「ファイル名」ならその名前で書き出し、「ディレクトリ」なら元ファイル名のままその中へコピーします。中身(内容)は必ずコピーされますが、タイムスタンプなどのメタデータは基本的に完全には引き継がれません。重要なのは、コピー先ディレクトリの存在確認、権限、上書きの設計、そして例外対策です。

import shutil

# ファイルを別名でコピー
shutil.copy("report.txt", "backup/report_copy.txt")

# ディレクトリを指定すると中へコピー(同名ファイル)
shutil.copy("report.txt", "backup")
Python

基本の使い方(ここが重要)

コピー元・コピー先の指定と挙動の違い

  • コピー元(src): 実在する“ファイル”パスを渡す(ディレクトリは不可)。
  • コピー先(dst):
    • ファイルパスを渡すと、その“指定名”でコピー。
    • 既存ディレクトリを渡すと、その“中へ同名”でコピー。
import shutil

# 指定名でコピー(上書き注意)
shutil.copy("data.csv", "out/result.csv")

# ディレクトリへコピー(out/data.csv が作成される)
shutil.copy("data.csv", "out")
Python

上書きの扱い(既存ファイルは上書きされる)

dst に既存のファイルを指定すると、同名ファイルは上書きされます。誤上書きを避けたいときは事前に存在チェックや、別名付与・日付付与で回避します。

import os, shutil
dst = "out/result.csv"
if os.path.exists(dst):
    raise FileExistsError("既に存在します")
shutil.copy("data.csv", dst)
Python

例外対策(存在しない・権限なし・ディレクトリ未作成)

  • FileNotFoundError: src が無い、または dst の親ディレクトリが無い。
  • PermissionError: 書き込み権限がない、使用中でロックされている等。
  • IsADirectoryError: src がディレクトリなど不正。
import os, shutil

def safe_copy(src: str, dst: str) -> bool:
    try:
        os.makedirs(os.path.dirname(dst), exist_ok=True)
        shutil.copy(src, dst)
        return True
    except (FileNotFoundError, PermissionError, IsADirectoryError) as e:
        print("COPY ERROR:", e)
        return False
Python

関連関数との使い分け(重要ポイントの深掘り)

copy と copy2(メタデータをどこまで保つか)

  • shutil.copy: 内容をコピー。アクセス権(mode)を可能な範囲で反映。タイムスタンプなどのメタデータは十分に保持されないことがある。
  • shutil.copy2: 内容+メタデータ(mtime 等)も可能な範囲でコピー。バックアップや監査向けならこちらが適切。
import shutil

# メタデータもできる限り保つ
shutil.copy2("data.csv", "backup/data.csv")
Python

copyfile と copytree(ファイル限定か、ディレクトリ丸ごとか)

  • shutil.copyfile(src, dst): ファイル“内容のみ”をコピー。dst はファイルパス必須(ディレクトリ不可)。細かい制御が欲しいときに。
  • shutil.copytree(src_dir, dst_dir): ディレクトリを“丸ごと”再帰的にコピー。dst_dir が存在しているとエラー(Python 3.8+ は dirs_exist_ok=True で上書き許可)。
import shutil

# ファイル限定で内容コピー(メタデータは最小限)
shutil.copyfile("config.yaml", "backup/config.yaml")

# ディレクトリ丸ごと(既存ならエラー。3.8+ は引数で許可可)
shutil.copytree("templates", "project_templates")
Python

move(コピー+削除=移動)との違い

  • shutil.move: コピーして元を削除(同じボリュームではリネームに近い動作)。「複製」ではなく「移動」なので用途が別。
import shutil
shutil.move("old.txt", "archive/old.txt")  # これは“移動”。コピーではない
Python

実務設計の勘所(安全・再現性・パフォーマンス)

事前にコピー先ディレクトリを用意する

dst をファイルパスで指定するなら、親ディレクトリを作っておくのが安全。なければ os.makedirs(…, exist_ok=True)。

import os, shutil

dst = "backup/2025-12/report.txt"
os.makedirs(os.path.dirname(dst), exist_ok=True)
shutil.copy("report.txt", dst)
Python

上書きポリシーを決める(重複対策)

業務では“既存なら”どうするかが重要。タイムスタンプや連番を付けて衝突を避ける、既存ならエラーにする、比較(ハッシュ)して同一ならスキップする等、方針を明確に。

import os, shutil
from datetime import datetime

def copy_with_timestamp(src: str, dst_dir: str) -> str:
    os.makedirs(dst_dir, exist_ok=True)
    base = os.path.splitext(os.path.basename(src))[0]
    stamp = datetime.now().strftime("%Y%m%d-%H%M%S")
    dst = os.path.join(dst_dir, f"{base}-{stamp}.txt")
    shutil.copy(src, dst)
    return dst
Python

メタデータが大事なら copy2、丸ごとなら copytree

監査ログやバックアップで“更新時刻”が重要なら copy2。テンプレート一式の複製などは copytree が本筋。単一ファイルなら copy/copy2 を選ぶ。

大量コピーはエラーに強く(1件ずつ try/except、途中経過ログ)

大量のファイルを処理する場合、エラーは必ず混ざる前提で、1件ずつ例外を握りつつ進めると運用が安定します。

例題で身につける(定番から一歩先まで)

例題1:CSV を全てバックアップ(ディレクトリへ同名コピー)

import os, shutil

def backup_csv(src_dir: str, dst_dir: str) -> int:
    os.makedirs(dst_dir, exist_ok=True)
    count = 0
    for name in os.listdir(src_dir):
        src = os.path.join(src_dir, name)
        if os.path.isfile(src) and name.lower().endswith(".csv"):
            shutil.copy(src, dst_dir)  # dst_dir に同名でコピー
            count += 1
    return count

print(backup_csv("data", "backup"))
Python

例題2:メタデータ保持でレポートをバックアップ(copy2)

import os, shutil

def backup_reports(src_dir: str, dst_dir: str) -> None:
    os.makedirs(dst_dir, exist_ok=True)
    for name in os.listdir(src_dir):
        src = os.path.join(src_dir, name)
        if os.path.isfile(src) and name.endswith(".txt"):
            shutil.copy2(src, os.path.join(dst_dir, name))

backup_reports("reports", "backup/reports")
Python

例題3:拡張子別にコピー先を分ける(安全な作成と上書き管理)

import os, shutil

def copy_by_ext(src_dir: str, dst_root: str) -> None:
    for name in os.listdir(src_dir):
        src = os.path.join(src_dir, name)
        if os.path.isfile(src):
            ext = os.path.splitext(name)[1].lstrip(".").lower() or "_none"
            dst_dir = os.path.join(dst_root, ext)
            os.makedirs(dst_dir, exist_ok=True)
            dst = os.path.join(dst_dir, name)
            shutil.copy(src, dst)

copy_by_ext("downloads", "sorted")
Python

例題4:差分コピー(同じ内容ならスキップ)

import os, shutil, hashlib

def sha256(path: str, buf: int = 1 << 20) -> str:
    h = hashlib.sha256()
    with open(path, "rb") as f:
        while True:
            chunk = f.read(buf)
            if not chunk: break
            h.update(chunk)
    return h.hexdigest()

def copy_if_changed(src: str, dst: str) -> bool:
    os.makedirs(os.path.dirname(dst), exist_ok=True)
    if os.path.exists(dst):
        if sha256(src) == sha256(dst):
            return False  # 同一ならコピーしない
    shutil.copy(src, dst)
    return True

copied = copy_if_changed("data.csv", "backup/data.csv")
print("copied:", copied)
Python

まとめ

shutil.copy は「単一ファイルのコピー」を高レベルに安全に行うための標準関数です。コピー先がファイルかディレクトリかで挙動が変わる点、既存ファイルは上書きされる点、例外(存在・権限・親未作成)への備えが重要。メタデータ保持が必要なら shutil.copy2、ディレクトリ丸ごとは shutil.copytree、内容のみなら shutil.copyfile を使い分ける。コピー先の事前作成、上書きポリシー、差分判定などを設計に組み込めば、初心者でも堅牢で実務品質の“コピー処理”を書けます。

タイトルとURLをコピーしました