The errno module exposes symbolic names for operating-system error codes used by files, processes, sockets, devices, and native calls. Instead of comparing unexplained integers such as 2, 13, or 111, code can use names such as ENOENT, EACCES, and ECONNREFUSED.
Modern Python maps many codes to specific OSError subclasses, including FileNotFoundError, PermissionError, BlockingIOError, and ConnectionRefusedError. The module remains important for native integration, non-blocking I/O, and portable handling of less common conditions.
Why symbolic names matter
Numeric values may differ between operating systems. A symbolic name documents the condition and improves portability.
import errno
try:
with open("config.ini", "rb") as file:
data = file.read()
except OSError as exc:
if exc.errno == errno.ENOENT:
print("File was not found")
else:
raise
When Python provides a specific exception, that form is usually clearer:
try:
with open("config.ini", "rb") as file:
data = file.read()
except FileNotFoundError:
print("File was not found")
Use errno when several codes map to one exception class or when a native library returns only an integer.
OSError attributes
OSError commonly provides errno, strerror, filename, and sometimes filename2. Not every instance has complete values, so use getattr() when the source is uncertain.
try:
source.replace(destination)
except OSError as exc:
print("code:", exc.errno)
print("message:", exc.strerror)
print("file:", exc.filename)
Do not drive application logic from strerror. Text varies by locale, platform, and operating-system version. Compare the exception type or numeric code.
Convert a number to a symbolic name
errno.errorcode maps available numeric values to their names.
import errno
code = errno.EACCES
name = errno.errorcode.get(code, "UNKNOWN_ERROR")
print(code, name)
Not every name exists on every platform. Use hasattr() or getattr() when supporting several systems.
Convert a code to text
os.strerror() returns the system message associated with a numeric code.
import errno
import os
print(os.strerror(errno.ENOSPC))
The text is useful for logs and interfaces, but not as a stable API contract. Return an application-defined error identifier separately from a localized description.
Common file errors
Frequently encountered values include ENOENT for a missing path, EACCES or EPERM for denied access, EEXIST for an existing destination, ENOTDIR when a component is not a directory, EISDIR when a file was expected, ENOSPC for a full filesystem, EROFS for read-only storage, and EXDEV for operations that cross devices.
For safer temporary files and atomic replacement patterns, see Python tempfile. Correct code must still handle permission failures, full disks, and cross-volume operations.
EXDEV across filesystems
Path.rename() and os.rename() may fail with EXDEV when source and destination are located on different filesystems. An application can copy, verify, and remove the source when that weaker semantic is acceptable.
import errno
import shutil
try:
source.replace(destination)
except OSError as exc:
if exc.errno != errno.EXDEV:
raise
shutil.copy2(source, destination)
source.unlink()
The fallback is not equivalent to an atomic rename. Add integrity checks and cleanup for partial failures.
Non-blocking operations
EAGAIN, EWOULDBLOCK, EINPROGRESS, and EALREADY appear with non-blocking sockets and file descriptors. Python often exposes them through BlockingIOError.
import errno
try:
data = sock.recv(4096)
except BlockingIOError as exc:
if exc.errno in {errno.EAGAIN, errno.EWOULDBLOCK}:
data = None
else:
raise
On some platforms EAGAIN and EWOULDBLOCK share a value. Do not busy-loop; wait for readiness with selectors or select.
Network errors
Important networking values include ECONNREFUSED, ECONNRESET, ECONNABORTED, ETIMEDOUT, EHOSTUNREACH, ENETUNREACH, EADDRINUSE, and EADDRNOTAVAIL.
The guide to Python socketserver covers server loops, concurrency, and shutdown. For HTTP clients, Python urllib.error separates HTTP responses from transport failures.
Broken pipes
EPIPE occurs when a process writes to a pipe or socket whose reader has closed. Python normally raises BrokenPipeError.
try:
connection.sendall(payload)
except BrokenPipeError:
close_session()
Stop sending, remove the descriptor from the event loop, release resources, and record a bounded diagnostic.
Interrupted system calls
EINTR means a system call was interrupted by a signal. Since PEP 475, many Python APIs retry automatically when the signal handler does not raise. Native libraries and some operations may still expose InterruptedError.
while True:
try:
return operation()
except InterruptedError:
continue
Retry only when the operation is safe. A call may have performed partial work before interruption.
Integration with ctypes
The previous guide, Python ctypes, used use_errno=True. This makes ctypes preserve a thread-local copy that can be read with get_errno().
import errno
import os
from ctypes import CDLL, get_errno
lib = CDLL("libexample.so", use_errno=True)
result = lib.open_resource()
if result == -1:
code = get_errno()
if code == errno.EACCES:
raise PermissionError(code, os.strerror(code))
raise OSError(code, os.strerror(code))
Read the code immediately after the failing native function. Another native call can overwrite it.
Platform differences
Linux, macOS, BSD, Windows, and WASI expose different subsets. errno.errorcode reflects the current environment.
import errno
quota_code = getattr(errno, "EDQUOT", None)
if quota_code is not None:
print("Disk quota failures can be identified")
When sending an error to another service, do not transmit only the integer. Include an application-defined code and optionally the symbolic name and platform.
Avoid broad OSError recovery
Converting every OSError into “file does not exist” hides permission errors, storage exhaustion, device failures, and corrupted paths.
try:
load_configuration()
except FileNotFoundError:
create_default_configuration()
Catch only conditions your code can resolve. The same applies to retry logic: a full filesystem will not improve after an immediate retry, while some transient network conditions may justify backoff.
Classify retryable failures
TRANSIENT = {
errno.EAGAIN,
errno.EINTR,
errno.ETIMEDOUT,
}
def can_retry(exc: OSError) -> bool:
return exc.errno in TRANSIENT
The decision also depends on idempotency. Retrying a write, payment request, deletion, or POST may duplicate effects. Combine codes with operation semantics, attempt limits, jitter, and exponential backoff.
Structured logging
def error_for_log(exc: OSError) -> dict:
return {
"type": type(exc).__name__,
"errno": exc.errno,
"symbol": errno.errorcode.get(exc.errno),
"message": exc.strerror,
}
Include the operation and a redacted resource identifier, not credentials or full sensitive paths.
Recommended tests
Test missing files, denied access, existing destinations, directory-versus-file mismatches, read-only storage, full disks, refused sockets, timeouts, reset connections, non-blocking operations, and symbols absent from the current platform.
Unit tests may construct OSError instances with known values, but keep some real integration tests because mappings and behavior vary by operating system.
Common mistakes
Common failures include comparing localized messages, using magic numbers, assuming every symbol exists, catching every OSError, retrying non-idempotent operations, reading native errno too late, and exposing operating-system codes as a permanent public API.
Conclusion
errno gives meaningful names to operating-system failure codes. It complements Python’s specific exception classes and remains valuable for native calls and non-blocking I/O.
Prefer specific exceptions, compare symbolic values instead of text, and design retries around both the error and operation semantics. Consult the official errno documentation and the official OSError documentation.







