IT프로그래밍/파이썬

파이썬 FileNotFoundError 해결 : 파일이 있는데도 못 찾는 상대경로 문제

MannizDev 2026. 9. 9.

탐색기에는 파일이 보이는데 파이썬에서는 FileNotFoundError가 날 때가 있습니다. 이때는 파일을 옮기기 전에 파이썬이 어느 폴더에서 찾고 있는지부터 확인해야 합니다. 파일 이름만 적은 상대경로는 보통 코드 파일이 있는 폴더가 아니라, 프로그램의 현재 작업 폴더를 기준으로 해석되기 때문입니다.

with open으로 파일 읽고 쓰기에서 파일을 여는 방법을 익혔다면, 이번에는 실행 위치가 달라도 같은 파일을 읽는 방법을 확인해 보겠습니다. import에서 모듈을 못 찾는 문제는 ModuleNotFoundError 해결에서 따로 다룹니다.

확인 기준 : 2026년 9월 9일. Windows의 Python 3 환경에서 아래 .py 예제를 실행했고, Python 3 공식 문서의 경로와 파일 열기 동작을 확인했습니다. 터미널에서 python --version이 실행되는 상태를 전제로 합니다. 실제 출력의 드라이브와 앞쪽 폴더 이름은 PC마다 다릅니다.

 

현재 작업 폴더와 코드 폴더의 차이를 나타낸 FileNotFoundError 개념 일러스트

 

AI로 제작한 경로 개념 일러스트입니다.

먼저 오류 마지막 줄과 찾는 위치를 확인합니다

파일을 읽을 때 다음 오류가 나면, 지정한 경로에서 파일이나 그 경로의 중간 폴더를 찾지 못했다는 뜻입니다.

FileNotFoundError: [Errno 2] No such file or directory: 'data\\orders.txt'

data/orders.txt 같은 상대경로는 출발점에서 data 폴더로 들어가 파일을 찾으라는 뜻입니다. 반대로 C:/work/demo/data/orders.txt 같은 절대경로는 드라이브부터 목적지까지 적은 주소입니다. 오류 메시지의 역슬래시가 \\로 보이는 것은 문자열을 표시하는 방식이며, 실제 폴더 구분자를 두 번 넣으라는 뜻은 아닙니다.

출발점을 확인할 때는 파이썬에 기본으로 포함된 pathlib를 사용합니다. 경로를 다루는 기능을 모아 둔 모듈이며 따로 설치하지 않습니다. 그 안의 Path는 파일 주소를 다루는 값으로 만들어 줍니다. Path.cwd()는 입력 없이 현재 작업 폴더의 경로를 돌려줍니다. 폴더를 이동하거나 파일을 만드는 기능은 아닙니다.

오류가 나는 파일의 open() 바로 앞에서 먼저 실행해 보세요.

from pathlib import Path

print(Path.cwd())

from pathlib import Path는 해당 기능을 불러오는 줄입니다. print()는 Path.cwd()가 돌려준 경로를 화면에 보여 줍니다. 여기 나온 폴더가 상대경로의 출발점입니다. 코드가 저장된 폴더와 같은지 비교해 보세요.

이번에는 실제로 찾는 파일의 주소까지 출력합니다. Path(...) 안에는 open()에 넣었던 경로를 똑같이 적습니다. .absolute()는 현재 작업 폴더를 붙인 절대경로를 돌려주며, 파일을 새로 만들거나 존재 여부를 보장하지 않습니다.

from pathlib import Path

target = Path("data/orders.txt")
print("작업 폴더 :", Path.cwd())
print("찾는 파일 :", target.absolute())

출력된 찾는 파일의 위치를 탐색기와 비교하면 됩니다. 예상한 주소가 아니라면 인코딩이나 패키지 설치를 바꿀 단계가 아닙니다. 먼저 경로의 출발점을 맞춰야 합니다.

같은 파일인데 실행 폴더만 바꾸면 실패합니다

빈 실습 폴더 안에 다음 구조를 만듭니다. orders.txt는 UTF-8 텍스트 파일로 저장하고 아래 두 줄을 넣습니다.

실습 폴더/
└─ demo/
   ├─ read_bad.py
   └─ data/
      └─ orders.txt

orders.txt 내용입니다.

아메리카노 2
카페라테 1

read_bad.py에는 다음 전체 코드를 저장합니다. open()에는 앞에서 만든 경로 target을 전달합니다. 모드를 생략했으므로 읽기로 열고, with가 끝나면 파일을 닫습니다. read()는 내용을 문자열로 돌려주고, strip()은 출력 양끝의 줄바꿈과 공백을 없앱니다. 파일 내용 자체는 바꾸지 않습니다.

from pathlib import Path

print("작업 폴더 :", Path.cwd())
target = Path("data/orders.txt")
print("찾는 파일 :", target.absolute())
with open(target, encoding="utf-8") as file:
    print(file.read().strip())

터미널을 demo 폴더에서 열고 실행합니다.

python read_bad.py

이번에는 잘 읽힙니다. 앞쪽 경로를 C:\work로 줄여 표현하면 다음과 같습니다.

작업 폴더 : C:\work\demo
찾는 파일 : C:\work\demo\data\orders.txt
아메리카노 2
카페라테 1

이제 터미널에서 한 단계 위로 올라간 뒤 같은 코드 파일을 실행합니다. cd ..는 터미널의 작업 폴더를 상위 폴더로 바꾸는 명령입니다.

cd ..
python demo/read_bad.py

두 경로 출력과 오류의 마지막 줄은 다음처럼 달라집니다. 중간의 호출 기록은 생략했습니다.

작업 폴더 : C:\work
찾는 파일 : C:\work\data\orders.txt
FileNotFoundError: [Errno 2] No such file or directory: 'data\\orders.txt'

파일은 여전히 demo/data에 있습니다. 그런데 파이썬은 C:\work\data에서 찾습니다. **실행할 .py 파일을 지정했다고 해서 작업 폴더까지 그 파일의 폴더로 바뀌는 것은 아닙니다.**

VSCode의 실행 버튼과 터미널에서 결과가 다를 때도 같은 두 경로를 출력해 보세요. 실행 방식과 설정에 따라 작업 폴더가 달라질 수 있으므로, 특정 버튼이 항상 코드 폴더에서 실행된다고 가정하지 않는 편이 좋습니다.

코드 파일 옆의 데이터를 읽으려면 기준 폴더를 고정합니다

이번 예제는 read_good.py와 함께 배포한 data/orders.txt를 읽는 상황입니다. 따라서 작업 폴더보다 코드 파일이 있는 폴더를 기준으로 삼는 것이 맞습니다.

일반적인 .py 파일 실행에서는 __file__에 그 코드 파일의 경로가 들어 있습니다. Path(__file__).resolve()는 이를 절대경로로 풀고 심볼릭 링크도 해석합니다. 그 뒤의 .parent는 파일이 들어 있는 상위 폴더를 뜻합니다. 이 과정은 주소를 계산할 뿐, 작업 폴더나 원본 파일을 바꾸지 않습니다.

demo 폴더에 read_good.py를 만들고 다음 코드를 저장합니다.

from pathlib import Path

base_dir = Path(__file__).resolve().parent
target = base_dir / "data" / "orders.txt"
print("찾는 파일 :", target)
with open(target, encoding="utf-8") as file:
    print(file.read().strip())

핵심 두 줄을 나눠 읽으면 이렇습니다.

  • base_dir = ...는 오른쪽에서 계산한 코드 폴더 경로를 base_dir에 저장합니다.
  • base_dir / "data" / "orders.txt"의 /는 Path 값에 폴더와 파일 이름을 연결하는 표기입니다. 여기서는 숫자 나눗셈이 아닙니다.
  • target은 demo/data/orders.txt의 절대경로입니다. 실제로 파일을 여는 시점은 그 아래 open()입니다.

실습 폴더에서 다음을 실행합니다.

python demo/read_good.py

demo 안으로 이동해서 실행해도 됩니다.

cd demo
python read_good.py

직접 실행했을 때 두 경우 모두 같은 파일을 읽었습니다. 예시 주소로 표시한 결과입니다.

찾는 파일 : C:\work\demo\data\orders.txt
아메리카노 2
카페라테 1
코드 demo에서 실행 한 단계 위에서 실행
Path("data/orders.txt") 성공 FileNotFoundError
코드 폴더에 data/orders.txt 연결 성공 성공

다만 이 방법이 없는 파일을 찾아내는 것은 아닙니다. demo/data/orders.txt를 지우거나 이름을 바꾸면 수정한 코드도 실패합니다. 해결한 것은 출발점이 달라지는 문제입니다.

사용자가 터미널에서 선택한 폴더의 파일을 읽는 프로그램이라면 현재 작업 폴더를 기준으로 삼는 것이 의도에 맞을 수 있습니다. 코드와 묶인 데이터인지, 사용자가 선택하는 데이터인지에 따라 기준을 정하세요.

경로가 맞아 보이는데도 안 될 때 확인할 것

확장자와 실제 파일 이름을 먼저 비교합니다. 탐색기에서 확장자가 숨겨져 있으면 orders.txt.txt를 orders.txt로 착각할 수 있습니다. 폴더 이름의 공백과 철자도 출력한 주소와 비교하세요. Linux 등 대소문자를 구분하는 파일 시스템에서는 Orders.txt와 orders.txt가 다릅니다.

Windows 경로는 역슬래시의 특수 의미를 피합니다. 보통 문자열 안의 \n은 줄바꿈, \t는 탭으로 해석됩니다. C:\new\test.txt를 그대로 보통 문자열에 적으면 의도와 다른 글자가 들어갈 수 있습니다. r을 앞에 붙인 원시 문자열이나 /를 사용합니다. 아래 주소는 예시이므로 실제 파일 주소로 바꿔야 합니다.

from pathlib import Path

target = Path(r"C:\work\demo\data\orders.txt")
# 또는 같은 경로를 슬래시로 적습니다.
target = Path("C:/work/demo/data/orders.txt")

원시 문자열도 따옴표 바로 앞을 홀수 개의 역슬래시로 끝낼 수는 없습니다. 파일 경로를 직접 이어 붙이기보다 Path의 /로 구성하면 이런 실수를 줄일 수 있습니다.

오류 이름이 바뀌었다면 원인도 다시 구분합니다. PermissionError는 권한 문제 등을, UnicodeDecodeError는 파일을 읽는 문자 변환 문제를 살펴봐야 합니다. encoding="utf-8"은 문자 해석 규칙이며, 잘못된 파일 주소를 고치는 옵션이 아닙니다.

저장할 때 발생했다면 상위 폴더도 있어야 합니다

읽기가 아니라 결과를 저장하는 줄에서 FileNotFoundError가 날 수도 있습니다. 쓰기 모드 "w"는 파일을 만들 수 있지만, 빠진 상위 폴더까지 만들지는 않습니다.

Path의 mkdir()는 폴더를 만드는 기능입니다. parents=True는 필요한 중간 폴더도 만들고, exist_ok=True는 해당 폴더가 이미 있어도 계속 진행하게 합니다. 코드 파일 옆에 output/result.txt를 저장하는 전체 예제는 다음과 같습니다.

from pathlib import Path

base_dir = Path(__file__).resolve().parent
target = base_dir / "output" / "result.txt"
target.parent.mkdir(parents=True, exist_ok=True)

with open(target, "w", encoding="utf-8") as file:
    file.write("완료")

print("저장 위치 :", target)

이 예제는 같은 이름의 result.txt가 있으면 내용을 완료로 덮어씁니다. 별도 실습 폴더에서 실행하세요. 검증에서는 상위 폴더가 없을 때의 실패를 먼저 확인한 뒤, 폴더를 만들고 같은 내용을 저장해 다시 읽었습니다.

읽기 오류를 없애려고 "r"을 "w"로 바꾸지는 마세요. 찾아야 할 파일 대신 빈 파일을 만들거나 기존 내용을 지울 수 있습니다.

주피터에서 __file__ 오류가 나면 기준을 직접 지정합니다

앞의 __file__ 예제는 일반 .py 파일 실행을 전제로 합니다. 주피터 노트북 셀이나 대화형 실행 환경에는 이 이름이 없어 다음 오류가 날 수 있습니다.

NameError: name '__file__' is not defined

이 경우에는 Path.cwd()로 실제 작업 폴더를 확인하거나 데이터 폴더의 절대경로를 직접 지정합니다. 아래 코드는 __file__ 없이 쓸 수 있는 형태이며, 주소는 자신의 파일 위치로 바꿉니다.

from pathlib import Path

base_dir = Path("C:/work/demo")
target = base_dir / "data" / "orders.txt"
with open(target, encoding="utf-8") as file:
    print(file.read())

경로를 수정한 뒤에는 단순히 오류가 사라졌는지만 보지 말고, 출력한 주소와 읽힌 내용이 원하는 파일의 것인지 함께 확인하세요. 다른 폴더에 우연히 같은 이름의 파일이 있으면 오류 없이 엉뚱한 데이터를 읽을 수도 있습니다.

파일이 없을 때 안내하고 다음 작업을 이어가는 처리는 파이썬 try except 사용법을 참고하면 됩니다. 다만 예외를 잡아서 메시지를 숨기는 것과 파일 경로를 바로잡는 것은 별개입니다. 먼저 찾는 파일의 주소가 맞는지부터 확인하세요.

참고한 공식 문서

댓글

💲 추천 글