NotADirectoryError
[Errno 20] Not a directory — a file sits where a folder was expected: you listed it, removed it with rmdir, or used it as a path component.
NotADirectoryError(errnoerrno — errno.ENOTDIR (20 on Linux, macOS and Windows).type: int · default: null, strerror[, filename])
Raised by
os.listdir(file), os.scandir(file), os.rmdir(file), Path.iterdir()
Message
[Errno 20] Not a directory: 'path'
Quick fix
check Path.is_dir(); look for a stray file with the folder name
Watch out
open('file.txt/x'): Linux/macOS ENOTDIR, Windows FileNotFoundError
Demo
Live evaluation
What POSIX systems raise for os.listdir() on a file: OSError with errno ENOTDIR, turned into NotADirectoryError by the constructor.
Try:
Inputs
pathstr | Noneempty → None
Code
import errno raise OSError(errno.ENOTDIR, 'Not a directory', 'notes.txt')
Uncaught exception
NotADirectoryError: [Errno 20] Not a directory: 'notes.txt'
OSError(errno.ENOTDIR, …) comes back as NotADirectoryError — look at the class name in the result, not the one in the code. The demo builds it explicitly because the real message differs by OS: Linux says [Errno 20] Not a directory, Windows says [WinError 267] The directory name is invalid. The class is the same on both.
Constructor
| Name | Type | Required | Description |
|---|---|---|---|
| errno | int | no | errno.ENOTDIR (20 on Linux, macOS and Windows). |
| strerror | str | no | 'Not a directory' on POSIX; Windows os.* calls say 'The directory name is invalid'. |
| filename | str | bytes | None | no | The path that was treated as a folder. |
Attributes
| Attribute | Type | Meaning |
|---|---|---|
| errno | int | None | errno.ENOTDIR when raised by the OS (also on Windows, translated from WinError 267). |
| strerror | str | None | 'Not a directory', or the Windows text 'The directory name is invalid'. |
| filename | str | bytes | None | The path that is not a folder. |
| winerror | int | Windows only: 267 (ERROR_DIRECTORY). |
Common patterns
Check before listing
When a path may be a file or a folder, branch on is_dir().
from pathlib import Path p = Path(target) files = list(p.iterdir()) if p.is_dir() else [p]
Walk instead of recursing by hand
os.walk separates folders from files, so you never call listdir on a file.
import os for root, dirs, files in os.walk('project'): for name in files: handle(os.path.join(root, name))
Fail early with a clear message
Validate a configured output folder once at start-up.
from pathlib import Path out = Path(config['output_dir']) if out.exists() and not out.is_dir(): raise SystemExit(f'{out} is a file, expected a folder')
Examples
1. errno ENOTDIR maps to it
import errno
OSError(errno.ENOTDIR, 'Not a directory', 'notes.txt')
Returns
NotADirectoryError(20, 'Not a directory')2. os.listdir() on a file
import os
from pathlib import Path
Path('notes.txt').write_text('hi')
try:
os.listdir('notes.txt')
except OSError as e:
r = (type(e).__name__, e.filename)
r
Returns
('NotADirectoryError', 'notes.txt')3. os.rmdir() on a file
import os
from pathlib import Path
Path('notes.txt').write_text('hi')
try:
os.rmdir('notes.txt')
except NotADirectoryError as e:
r = e.filename
r
Returns
'notes.txt'4. Path.iterdir() on a file
from pathlib import Path
Path('notes.txt').write_text('hi')
try:
list(Path('notes.txt').iterdir())
except NotADirectoryError:
r = 'not a folder'
r
Returns
'not a folder'5. is_dir() avoids it
from pathlib import Path
Path('notes.txt').write_text('hi')
p = Path('notes.txt')
list(p.iterdir()) if p.is_dir() else [p.name]
Returns
['notes.txt']6. Path through a file, portably
from pathlib import Path
Path('notes.txt').write_text('hi')
try:
open('notes.txt/draft')
except (NotADirectoryError, FileNotFoundError) as e:
r = e.filename
r
Returns
'notes.txt/draft'Pitfalls
1. A stray file with the folder name
open('backup', 'w') by mistake (missing file name) leaves a FILE called backup. Later code that expects the folder fails. Validate the type, not just existence.
exists() check
import os from pathlib import Path Path('backup').write_text('') if os.path.exists('backup'): try: r = os.listdir('backup') except NotADirectoryError: r = 'crashed: backup is a file' r
'crashed: backup is a file'
is_dir() check
import os from pathlib import Path Path('backup').write_text('') r = os.listdir('backup') if os.path.isdir('backup') else 'backup is a file — rename it' r
'backup is a file — rename it'
2. Hand-rolled recursion calls listdir on files
A recursive walk that lists every entry it finds will eventually list a file. os.walk() separates folders from files for you.
Recursive listdir
import os from pathlib import Path os.makedirs('proj/src') Path('proj/readme.md').write_text('# proj') def count_dirs(folder): return 1 + sum(count_dirs(f'{folder}/{n}') for n in os.listdir(folder)) try: r = count_dirs('proj') except NotADirectoryError as e: r = f'listdir on a file: {e.filename}' r
'listdir on a file: proj/readme.md'
os.walk
import os from pathlib import Path os.makedirs('proj/src') Path('proj/readme.md').write_text('# proj') sum(1 for _ in os.walk('proj'))
2
When to use
Use it
- Catching a file passed where a folder was expected
- Raising it from your own API that requires a directory
Reach for something else
- Unknown path type → branch on Path.is_dir() first
- Tree traversal → os.walk() or Path.rglob() instead of hand-rolled listdir recursion
Notes
CPython impl
Objects/exceptions.c — OSError(ENOTDIR, …) returns NotADirectoryError; Windows ERROR_DIRECTORY (267) is translated to ENOTDIR
Catch via
except OSError
Platforms
os.listdir / os.rmdir / os.scandir on a file raise it everywhere; the message is [Errno 20] Not a directory on POSIX and [WinError 267] The directory name is invalid on Windows
FAQ
A path that must be a folder is a regular file. Typical cases: os.listdir()/os.scandir()/Path.iterdir() on a file, os.rmdir() on a file, or a path that goes through a file like "config.json/key". A frequent root cause is a file accidentally created with the folder name.
History
3.3
Added, together with the other OSError subclasses (PEP 3151).