ZetCode

Python os.fdopen Function

Last modified April 11, 2025

This comprehensive guide explores Python's os.fdopen function, which creates a file object from a file descriptor. We'll cover descriptor handling, file object creation, and practical low-level I/O examples.

Basic Definitions

The os.fdopen function creates a Python file object from an existing file descriptor (fd). It provides higher-level file operations on low-level descriptors.

Key parameters: fd (file descriptor), mode (optional file mode string), buffering (optional buffer size). Returns a file object wrapping the fd.

Basic File Descriptor to File Object Conversion

This example demonstrates the simplest use of os.fdopen to convert a file descriptor into a file object. We first create a descriptor using os.open.

basic_conversion.py
import os

# Create a file descriptor
fd = os.open("example.txt", os.O_RDWR | os.O_CREAT)

# Convert descriptor to file object
file_obj = os.fdopen(fd, "r+")

# Use the file object
file_obj.write("Hello World\n")
file_obj.seek(0)
print(file_obj.read())

# Close the file object (also closes the descriptor)
file_obj.close()

The example shows the complete lifecycle: create descriptor, convert to file object, perform I/O, and close. Note that closing the file object also closes the underlying descriptor.

This approach is useful when you need both low-level descriptor operations and high-level file object methods in the same program.

Preserving File Descriptors

By default, os.fdopen closes the descriptor when the file object is closed. We can prevent this using closefd=False parameter.

preserve_descriptor.py
import os

# Create descriptor
fd = os.open("data.txt", os.O_RDWR | os.O_CREAT)

# Convert to file object without closing fd
file_obj = os.fdopen(fd, "r+", closefd=False)

# Use file object
file_obj.write("Preserved descriptor\n")
file_obj.close()

# Descriptor is still valid
os.write(fd, b"Additional data\n")
os.close(fd)  # Must close manually

This example keeps the descriptor valid after closing the file object. This is useful when you need to continue using the descriptor after file object operations.

Remember to manually close the descriptor when done to avoid resource leaks.

Working with Standard Streams

os.fdopen can wrap standard stream descriptors (0, 1, 2) for more convenient I/O operations. This example demonstrates wrapping stdin.

std_streams.py
import os
import sys

# Wrap stdin (fd 0)
stdin = os.fdopen(0, "r")  # Equivalent to sys.stdin

print("Enter your name:")
name = stdin.readline().strip()
print(f"Hello, {name}!")

# Wrap stdout (fd 1)
stdout = os.fdopen(1, "w")  # Equivalent to sys.stdout
stdout.write(f"Greetings, {name}!\n")
stdout.flush()

The example shows how to create file objects for standard input and output. This can be useful in situations where sys.stdin/stdout are not available.

Note that these file objects share the same underlying descriptors as sys.stdin/stdout, so operations affect both.

Buffering Modes with fdopen

os.fdopen supports different buffering modes like regular open(). This example demonstrates line buffering and no buffering.

buffering_modes.py
import os

# Create descriptor
fd = os.open("output.log", os.O_WRONLY | os.O_CREAT)

# Line buffered file object
line_buffered = os.fdopen(fd, "w", buffering=1)
line_buffered.write("Line 1\n")  # Flushed immediately
line_buffered.write("Line 2\n")

# No buffering (direct write)
fd2 = os.open("direct.log", os.O_WRONLY | os.O_CREAT)
unbuffered = os.fdopen(fd2, "w", buffering=0)
unbuffered.write("Immediate write")
unbuffered.close()

The first file object uses line buffering (1), flushing after each newline. The second uses no buffering (0), writing directly to disk.

Buffering choices affect performance and data safety - unbuffered is slower but ensures immediate writes.

Error Handling with fdopen

This example demonstrates proper error handling when working with file descriptors and os.fdopen. We check for invalid descriptors.

error_handling.py
import os
import errno

try:
    # Try to open invalid descriptor
    bad_fd = 999
    file_obj = os.fdopen(bad_fd, "r")
except OSError as e:
    if e.errno == errno.EBADF:
        print("Error: Bad file descriptor")
    else:
        print(f"Unexpected error: {e}")

# Safe approach
try:
    fd = os.open("data.txt", os.O_RDONLY)
    file_obj = os.fdopen(fd, "r")
    print(file_obj.read())
except OSError as e:
    print(f"File error: {e}")
finally:
    if 'file_obj' in locals():
        file_obj.close()
    elif 'fd' in locals():
        os.close(fd)

The first part shows handling invalid descriptors. The second demonstrates a complete safe approach with proper cleanup in finally block.

Always ensure descriptors and file objects are properly closed, even when errors occur.

Working with Pipes

os.fdopen is commonly used with pipes created by os.pipe(). This example shows inter-process communication using pipe descriptors.

pipe_communication.py
import os

# Create pipe
read_fd, write_fd = os.pipe()

# Convert descriptors to file objects
reader = os.fdopen(read_fd, "r")
writer = os.fdopen(write_fd, "w")

# Write to pipe
writer.write("Message through pipe\n")
writer.flush()  # Ensure data is sent

# Read from pipe
message = reader.readline()
print(f"Received: {message.strip()}")

# Clean up
writer.close()
reader.close()

The example creates a pipe, converts its descriptors to file objects, then demonstrates simple communication. Pipes are unidirectional - one for reading, one for writing.

This pattern is fundamental for inter-process communication in Python.

Non-blocking File Operations

This advanced example shows how to create non-blocking file objects from descriptors using os.fdopen after setting O_NONBLOCK flag.

non_blocking.py
import os
import errno
import fcntl

# Create descriptor with non-blocking flag
fd = os.open("fifo", os.O_RDONLY | os.O_NONBLOCK)

# Set non-blocking if not set during open
# fcntl.fcntl(fd, fcntl.F_SETFL, os.O_NONBLOCK)

# Create non-blocking file object
f = os.fdopen(fd, "r")

try:
    data = f.read()
    print(f"Read: {data}")
except IOError as e:
    if e.errno == errno.EAGAIN:
        print("No data available (non-blocking)")
    else:
        print(f"Read error: {e}")

f.close()

The example demonstrates handling non-blocking I/O operations where reads may fail with EAGAIN when no data is available immediately.

Non-blocking mode is useful for event-driven programs that can't afford to block on I/O operations.

Security Considerations

Best Practices

Source References

Author

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.

List all Python tutorials.