IsADirectoryError
[Errno 21] Is a directory — you handed a folder to something that wants a file. On Windows the same mistake usually surfaces as PermissionError instead.
IsADirectoryError(errnoerrno — errno.EISDIR (21 on Linux, macOS and Windows).type: int · default: null, strerror[, filename])
Raised by
open(folder), os.remove(folder) on Linux, os.rename(file, folder)
Message
[Errno 21] Is a directory: 'path'
Quick fix
filter with Path.is_file(); remove folders with shutil.rmtree
Watch out
Windows raises PermissionError for open(folder)
Demo
Live evaluation
What Linux and macOS raise for open() on a folder: OSError with errno EISDIR — the constructor turns it into IsADirectoryError.
Try:
Inputs
pathstr | Noneempty → None
Code
import errno raise OSError(errno.EISDIR, 'Is a directory', 'logs')
Uncaught exception
IsADirectoryError: [Errno 21] Is a directory: 'logs'
The Trigger snippet raises OSError but the traceback names IsADirectoryError: errno.EISDIR selected the subclass. That is also why the demo builds the error explicitly — a real open() on a folder only produces this on Linux/macOS; Windows reports PermissionError: [Errno 13] for the same call.
Constructor
| Name | Type | Required | Description |
|---|---|---|---|
| errno | int | no | errno.EISDIR (21 on Linux, macOS and Windows). |
| strerror | str | no | 'Is a directory'. |
| filename | str | bytes | None | no | The directory path that was used as a file. |
Attributes
| Attribute | Type | Meaning |
|---|---|---|
| errno | int | None | errno.EISDIR when raised by the OS; None for the one-argument form. |
| strerror | str | None | 'Is a directory'. |
| filename | str | bytes | None | The folder that was passed where a file was expected. |
Common patterns
Read only the files in a folder
listdir()/iterdir() return folders too. Filter before opening.
from pathlib import Path for p in Path('data').iterdir(): if p.is_file(): process(p.read_text())
Delete a file or a folder
os.remove is for files; folders need os.rmdir (empty) or shutil.rmtree (with contents).
import shutil from pathlib import Path p = Path(target) if p.is_dir(): shutil.rmtree(p) else: p.unlink(missing_ok=True)
Catch the Linux and the Windows variant
The same mistake is IsADirectoryError on POSIX and PermissionError on Windows.
try: text = open(path).read() except (IsADirectoryError, PermissionError): text = None
Examples
1. errno EISDIR maps to it
import errno
OSError(errno.EISDIR, 'Is a directory', 'logs')
Returns
IsADirectoryError(21, 'Is a directory')2. Raised with a path
import errno
raise IsADirectoryError(errno.EISDIR, 'Is a directory', 'logs')
Returns
IsADirectoryError: [Errno 21] Is a directory: 'logs'3. open() on a folder, portably
import os
os.mkdir('uploads')
try:
open('uploads')
except (IsADirectoryError, PermissionError) as e:
r = f'{e.filename} is a folder'
r
Returns
'uploads is a folder'4. Filter with is_file()
from pathlib import Path
Path('data').mkdir()
Path('data/a.txt').write_text('A')
Path('data/archive').mkdir()
sorted(p.name for p in Path('data').iterdir() if p.is_file())
Returns
['a.txt']5. rglob() yields folders too
from pathlib import Path
Path('data/sub').mkdir(parents=True)
Path('data/a.txt').write_text('A')
Path('data/sub/b.txt').write_text('B')
sorted(p.as_posix() for p in Path('data').rglob('*'))
Returns
['data/a.txt', 'data/sub', 'data/sub/b.txt']6. shutil.rmtree removes a folder
import os, shutil
os.mkdir('build')
open('build/app.o', 'w').close()
shutil.rmtree('build')
os.path.exists('build')
Returns
FalsePitfalls
1. Opening every entry of listdir()
os.listdir() returns sub-folder names as well as files. The first folder breaks the loop (IsADirectoryError on Linux/macOS, PermissionError on Windows).
All entries
import os from pathlib import Path os.mkdir('data') Path('data/a.txt').write_text('A') os.mkdir('data/old') try: texts = [open(f'data/{n}').read() for n in sorted(os.listdir('data'))] except (IsADirectoryError, PermissionError) as e: texts = f'failed on {e.filename}' texts
'failed on data/old'
Files only
import os from pathlib import Path os.mkdir('data') Path('data/a.txt').write_text('A') os.mkdir('data/old') texts = [p.read_text() for p in sorted(Path('data').iterdir()) if p.is_file()] texts
['A']
2. os.remove() on a folder
os.remove/os.unlink delete files only. On a folder Linux raises IsADirectoryError, macOS and Windows raise PermissionError. Use shutil.rmtree (or os.rmdir for an empty folder).
os.remove
import os os.mkdir('cache') try: os.remove('cache') except (IsADirectoryError, PermissionError): r = 'not removed' r
'not removed'
shutil.rmtree
import os, shutil os.mkdir('cache') shutil.rmtree('cache') os.path.exists('cache')
False
When to use
Use it
- Catching a folder passed where a file path was expected (together with PermissionError for Windows)
- Raising it from your own API when given a directory instead of a file
Reach for something else
- Walking a tree → filter with Path.is_file() / os.walk() files lists
- Deleting folders → shutil.rmtree() or os.rmdir()
- Relying on it on Windows → the same mistake is PermissionError there
Notes
CPython impl
Objects/exceptions.c — OSError(EISDIR, …) returns IsADirectoryError
Catch via
except OSError, or (IsADirectoryError, PermissionError) for portable code
Platforms
open(folder): Linux/macOS IsADirectoryError, Windows PermissionError. os.remove(folder): Linux IsADirectoryError, macOS/Windows PermissionError
FAQ
The path you gave to open(), os.remove() or similar is a folder, not a file. Common causes: a loop over os.listdir() that also returns sub-folders, a path variable missing its file name (data/ instead of data/file.csv), or a save path that points at an existing folder.
History
3.3
Added, together with the other OSError subclasses (PEP 3151).