classification
Title: Python 3.6 has an inaccessible attribute on FileNotFoundError
Type: behavior Stage: resolved
Components: Library (Lib) Versions: Python 3.6
process
Status: closed Resolution: duplicate
Dependencies: Superseder: Inaccessible attribute characters_written on OSError instances
View: 30554
Assigned To: Nosy List: gerph, r.david.murray
Priority: normal Keywords:

Created on 2017-08-02 19:57 by gerph, last changed 2017-08-02 20:35 by r.david.murray. This issue is now closed.

Messages (2)
msg299669 - (view) Author: Charles Ferguson (gerph) Date: 2017-08-02 19:57
Whilst debugging a problem in some of my code (which turned out to be a misspelt filename), I found that I could not access one of the properties of the FileNotFoundError object.

Essentially, if you get a 'FileNotFoundError' for opening a file that does not exist, you expect to be able to enumerate the attributes on that object. And if you try to access them, that they be accessible.
However, there is an attribute - 'characters_written' - which claims to be present according to 'dir()' but not according to 'hasattr()' and trying to access it with 'getattr()' confirms that it's not really there.

Looking at the documentation at https://docs.python.org/3/library/exceptions.html#OSError I see that it is a subclass of OSError(), and that the BlockingIOError can have this attribute. But none of the other OSError subclasses are documented as having the attribute.

It is still reasonable that any attribute access could generate another exception (including an AttributeError), as their implementation may have other issues, but I do not feel that this applies here, as this is an internal exception object that probably ought not to have an issue.

Since 'characters_written' doesn't seem to have any meaning in the context of 'FileNotFound', it seems like it's an oversight from the other exception subclass.

What I conclude from this is that the documentation, hasattr() and getattr() are correct, but dir() is acting wrongly. Principle of least surprise also suggests that having inconsistent returns from these functions probably isn't correct. I guess it could be the other way around, and the documentation, hasattr and getattr could be returning incorrectly, but that feels like something of a stretch.

I would wonder if the other OSError subclasses also suffer from this extraneous attribute name, and that it's been implemented at the wrong level, but I have no evidence to suggest that's the case (just that that's something I'd probably look at if I had the time).

Reproduction code:

----
#!/usr/bin/python3.6
##
# Demonstrate oddity of FileNotFoundError.
#

try:
    fh = open('/nothing/at/all', 'r')
except Exception as ex:
    print("Exception: {}".format(ex))
    for attrname in dir(ex):
        if attrname.startswith('__'):
            # Ignore dunders for the sake of brevity
            continue

        print("  Attribute name: {}".format(attrname))
        if not hasattr(ex, attrname):
            print("         hasattr: False - surprising!")
        print("           value: {}".format(getattr(ex, attrname)))
----

On 3.6 this generates:

----
Charles@charlesmbp:~/Coding/terraspock-develop (develop)$ python python3.6.2-filenotfound.py 
Exception: [Errno 2] No such file or directory: '/nothing/at/all'
  Attribute name: args
           value: (2, 'No such file or directory')
  Attribute name: characters_written
         hasattr: False - surprising!
Traceback (most recent call last):
  File "python3.6.2-filenotfound.py", line 7, in <module>
    fh = open('/nothing/at/all', 'r')
FileNotFoundError: [Errno 2] No such file or directory: '/nothing/at/all'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "python3.6.2-filenotfound.py", line 18, in <module>
    print("           value: {}".format(getattr(ex, attrname)))
AttributeError: characters_written
----

On 2.7 this works fine, but I've not tested the other 3.x series versions as I don't have them to hand.

Testing performed on macOS using Python 3.6.2.

I find it hard to think that this is intended behaviour, but whether it's something that debugging tools (and users) would expect or find useful I don't know.
msg299670 - (view) Author: R. David Murray (r.david.murray) * (Python committer) Date: 2017-08-02 20:35
It is intended.  See issue 30554.
History
Date User Action Args
2017-08-02 20:35:42r.david.murraysetstatus: open -> closed

superseder: Inaccessible attribute characters_written on OSError instances

nosy: + r.david.murray
messages: + msg299670
resolution: duplicate
stage: resolved
2017-08-02 19:57:43gerphcreate