It is a continuation position of the following article
Look at the Python built-in exception tree structure
class_tree.py
#!/usr/bin/env python
import platform
def show_class_tree(cls, depth=0):
prefix = "" if depth == 0 else "." * (depth * 3) + " "
if cls.__name__.lower() == "error":
print("{}{} ({})".format(prefix, cls.__name__, cls))
else:
print("{}{}".format(prefix, cls.__name__))
for subcls in sorted(cls.__subclasses__(), key=lambda c: c.__name__):
show_class_tree(subcls, depth+1)
if __name__ == "__main__":
print("Python Version: {}".format(platform.python_version()))
print()
show_class_tree(BaseException)
It's essentially the same as last time, but we've removed all old Python 3 protection and Python 2 support. Also, I've been using double quotes recently because black does so.
Use 3.7.7
as the comparison target. In the previous article, the last one was 3.7.1
, but there was no difference in the exception tree, so I omitted it.
My environment is MacOS 10.15.4, but there should be no difference in the Python layer.
The execution method is roughly as follows
$ python -V
Python 3.7.7
$ python class_tree.py | tee $(python -c 'import platform; print("".join(platform.python_version_tuple()) + ".txt")')
Python Version: 3.7.7
...
$ cat 377.txt
(The same content as above is saved in the text)
Python Version: 3.7.7
BaseException
... Exception
...... ArithmeticError
......... FloatingPointError
......... OverflowError
......... ZeroDivisionError
...... AssertionError
...... AttributeError
...... BufferError
...... EOFError
...... Error (<class 'locale.Error'>)
...... ImportError
......... ModuleNotFoundError
......... ZipImportError
...... LookupError
......... CodecRegistryError
......... IndexError
......... KeyError
...... MemoryError
...... NameError
......... UnboundLocalError
...... OSError
......... BlockingIOError
......... ChildProcessError
......... ConnectionError
............ BrokenPipeError
............ ConnectionAbortedError
............ ConnectionRefusedError
............ ConnectionResetError
......... FileExistsError
......... FileNotFoundError
......... InterruptedError
......... IsADirectoryError
......... ItimerError
......... NotADirectoryError
......... PermissionError
......... ProcessLookupError
......... TimeoutError
......... UnsupportedOperation
...... ReferenceError
...... RuntimeError
......... BrokenBarrierError
......... NotImplementedError
......... RecursionError
......... _DeadlockError
...... StopAsyncIteration
...... StopIteration
...... StopTokenizing
...... SubprocessError
......... CalledProcessError
......... TimeoutExpired
...... SyntaxError
......... IndentationError
............ TabError
...... SystemError
......... CodecRegistryError
...... TokenError
...... TypeError
...... ValueError
......... UnicodeError
............ UnicodeDecodeError
............ UnicodeEncodeError
............ UnicodeTranslateError
......... UnsupportedOperation
...... Verbose
...... Warning
......... BytesWarning
......... DeprecationWarning
......... FutureWarning
......... ImportWarning
......... PendingDeprecationWarning
......... ResourceWarning
......... RuntimeWarning
......... SyntaxWarning
......... UnicodeWarning
......... UserWarning
...... _OptionError
...... error (<class 're.error'>)
... GeneratorExit
... KeyboardInterrupt
... SystemExit
Since this is a comparison source, I will not comment in particular. There did not seem to be any difference in the results from 3.7.1.
$ diff 377.txt 382.txt
1c1
< Python Version: 3.7.7
---
> Python Version: 3.8.2
44d43
< ......... BrokenBarrierError
50,53d48
< ...... StopTokenizing
< ...... SubprocessError
< ......... CalledProcessError
< ......... TimeoutExpired
59d53
< ...... TokenError
79d72
< ...... _OptionError
It's quite different. It's the same as 3.8-dev last time, and I'm worried that it is unilaterally "decreasing", but I think this is due to the implementation of this script.
I noticed when I looked at What's New, but I couldn't catch all the exceptions in the first place, this script. No good child!
asyncio.CancelledError
This is not the result in the first place.
I'm just maintaining a sloppy article, so I won't dig deeper, but you shouldn't think it's exhaustive, Maru.
As I have always felt, I think it is better to challenge this exception tree branch on the assumption that it will change as a personal feeling.
First of all, as a development know-how, it is basically a place where you derive your own exception from ʻException and make the exception explicitly issued by your code clearly separated from the runtime exception by ʻexcept
. Keep it down. If you need to, for example, look at the exception class with a finer particle size than ʻOSError`, it's like writing a unit test, recognizing the possibility that it will change from version to version. If you write a test that catches exceptions in a unit test, even if the tree structure of the exception changes when you change the runtime, the unit test will immediately notice it, so you will feel more secure.
In the first place, on the premise that the theory of OOP is that parents should not have all the child classes from the beginning.
https://stackoverflow.com/questions/3862310/how-to-find-all-the-subclasses-of-a-class-given-its-name
Note that if the class definition of a subclass hasn't been executed yet - for example, if the subclass's module hasn't been imported yet - then that subclass doesn't exist yet, and subclasses won't find it.
This is a basic mistake I've made. I haven't noticed for years (^^;
Let's rewrite the script as follows
class_tree_mod.py
#!/usr/bin/env python
import platform
from threading import BrokenBarrierError
from tokenize import StopTokenizing
from warnings import _OptionError
from subprocess import SubprocessError
def show_class_tree(cls, depth=0):
prefix = "" if depth == 0 else "." * (depth * 3) + " "
if cls.__name__.lower() == "error":
print("{}{} ({})".format(prefix, cls.__name__, cls))
else:
print("{}{}".format(prefix, cls.__name__))
for subcls in sorted(cls.__subclasses__(), key=lambda c: c.__name__):
show_class_tree(subcls, depth+1)
if __name__ == "__main__":
print("Python Version: {}".format(platform.python_version()))
print()
show_class_tree(BaseException)
Take a diff
$ diff 377.txt 382.txt
1c1
< Python Version: 3.7.7
---
> Python Version: 3.8.2
No change.
So it seems correct that 3.8.2 didn't at least reduce the number of exceptions, and that they weren't imported unnecessarily when the runtime was started.
Of course, when I explicitly imported, those exceptions were loaded at runtime and could be found in the parent class __subclasses__ ()
.
This is a simple improvement.
Recommended Posts