> I guess you were mainly testing with Python 2. Python 3 on Linux
> does not raise any error either
In Python 3 os.fdopen delegates to io.open, which calls io.FileIO to create the raw file object. This doesn't verify a compatible mode on the file descriptor. Similarly, in Windows Python 2 os.fdopen calls VC++ _fdopen, which also doesn't verify a compatible mode.
The POSIX spec (IEEE Std 1003.1, 2013 Edition) for fdopen says that "the *application* shall ensure that the mode of the stream as expressed by the mode argument is allowed by the file access mode of the open file description to which fildes refers" [1] (emphasis mine). It happens that glibc in Linux opts to do this check for you.
If instead of closing the underlying file descriptor the program opts to close the Python file object or C FILE stream, this will attempt to write the buffered string "bbb" to the read-only fd, which should raise an EBADF error.
[1]: http://pubs.opengroup.org/onlinepubs/9699919799/functions/fdopen.html
---
The way test_openfd.py directly closes the underlying file descriptor does highlight an annoying problem in Python 2 on Windows. This should get its own issue in case someone feels like addressing it. The problem is open() and os.fdopen() create the file object with fclose as the FILE stream closer. It'd be nicer to instead use a closer on Windows that first calls _PyVerify_fd to check for a valid file descriptor. Otherwise the CRT asserts and terminates the process. For example:
import os
f = open('@test')
os.close(f.fileno())
Functions in posixmodule.c are good about first verifying the file descriptor:
>>> os.lseek(f.fileno(), 0, os.SEEK_CUR) # calls _PyVerify_fd
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
OSError: [Errno 9] Bad file descriptor
OTOH, closing the file object doesn't verify the file descriptor:
>>> f.close() # Goodbye cruel world... :'(
Stack trace:
0:000> k 8
Child-SP RetAddr Call Site
00000000`0021f3a8 00000000`68e25200 kernel32!TerminateProcessStub
00000000`0021f3b0 00000000`68e252d4 MSVCR90!invoke_watson+0x11c
00000000`0021f9a0 00000000`68e1de7e MSVCR90!invalid_parameter+0x70
00000000`0021f9e0 00000000`68ddf904 MSVCR90!close+0x9e
00000000`0021fa30 00000000`68ddf997 MSVCR90!fclose_nolock+0x5c
00000000`0021fa70 00000000`1e0ac2e5 MSVCR90!fclose+0x5f
00000000`0021fab0 00000000`1e0ac7b2 python27!close_the_file+0xa5
00000000`0021fae0 00000000`1e114427 python27!file_close+0x12
The problem doesn't exist in Python 3, for which io.FileIO's internal_close function is gated by _PyVerify_fd.