Alternative Fix for Incompatible Dunder Method Error in Mypy
When a neat trick in Python becomes a side effect.
When cleaning up the code for my chess program that I’m writing in Python as a learning exercise, I fixed a problem by introducing another problem.
class Rank(list, Enum):
HIGH = [1, 8]
PAWN = [2, 7]
I created a multiple-inheritance class with list
and Enum
that allowed access to the assigned values via the in
statement without adding .value
to the end.
>>> enums.Rank.HIGH
<Rank.HIGH: (1, 8)>
>>> enums.Rank.HIGH. Value
(1, 8)
>>> 8 in enums.Rank.HIGH
True
>>> 7 in enums.Rank.HIGH
False
That was a neat trick and the code worked. The mypy static checker complained about incompatible __hash__()
methods between list
and Enum
. Since I wasn’t using that dunder method in my code, I slapped # type: ignore
to the class line to silence the error.
class Rank(list, Enum): # type: ignore
HIGH = [1, 8]
PAWN = [2, 7]
That worked until I used the Sphinx documentation generator and had to deal with the side effect of my previous fix.
I used a pair of Sphinx extensions: sphinx.ext.autodoc
to discover the doc strings in my modules and sphinx.ext.viewcode
to add source links to the code. Both extensions warned about side effects when importing the modules for processing into static HTML webpages.
If any modules have side effects on import, these will be executed when
sphinx-build
is run.
The Sphinx wouldn’t generate an entry for the enums.Rank
class.
I could force the class to appear by explicitly naming the classes with the :members:
in the enums.rst
file that specifies directives for Sphinx.
.. automodule:: enums
:members: Color, PieceType, Ranks
That worked until I noticed that the source link was missing for the class. After spending hours looking at Sphinx documentation, I concluded that this was a bug and opened a bug report.
I added this question at the end:
“Would the incompatible
__hash__()
methods be a side effect that affects the docstring for a multiple-inheritance class from appearing?”
If the answer came back that this was a side effect impacting Sphinx, I would have removed list
from the class definition and used enums.Rank.HIGH. value
in my code to access the assigned value.
Two days after I opened the bug report, Medium presented this essay in my feed, “Use Perflint — A Performance Linter for Python” by Eldad Uzman, about the perflint
plugin for the pylint
static code analyzer. This plugin looks for performance issues in the code, say, using a global variable that took longer than using a local variable.
I ran the plugin against the enums.py
file and got the warning message for the enums.Rank
class.
W8301: Use tuple instead of list for a non-mutated sequence (use-tuple-over-list)
A light bulb went off in my head. I tend to use list
for almost everything in my code. Unless the values in a list
need to be changed, a tuple
works just as well. Both are sequences that the in
statement can work with.
I changed list
to tuple
in the class definition, removed the # type: ignore
from the class line, and replaced the square brackets with parentheses.
class Rank(tuple, Enum):
HIGH = (1, 8)
PAWN = (2, 7)
The code worked, mypy
no longer complained about the incompatible __hash__()
dunder methods, and, after I had Sphinx regenerate the static HTML files, the enums.Rank
class appeared with the source link.
With the side effect gone and everything working, I closed out the bug report.
Please follow me on Medium. Your support is greatly appreciated.