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.

InheritsBaseException›Exception›OSError›NotADirectoryError
OS exceptionPython 3.3+Live demo
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

NameTypeRequiredDescription
errnointnoerrno.ENOTDIR (20 on Linux, macOS and Windows).
strerrorstrno'Not a directory' on POSIX; Windows os.* calls say 'The directory name is invalid'.
filenamestr | bytes | NonenoThe path that was treated as a folder.

Attributes

AttributeTypeMeaning
errnoint | Noneerrno.ENOTDIR when raised by the OS (also on Windows, translated from WinError 267).
strerrorstr | None'Not a directory', or the Windows text 'The directory name is invalid'.
filenamestr | bytes | NoneThe path that is not a folder.
winerrorintWindows 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).