The "MRO" in the subject is in scare quotes because it doesn't actually seem to be the MRO but something related.
I noticed this in Django, and the following test code is a very stripped
down version of corresponding Django code, converted to Flask because a)
Flask is more lightweight and b) it is a different framework, so that
rules out that Django does something weird.
#v+
import flask
from collections import UserList
class RenderableMixin:
def render(self):
s = ""
for i in self:
s += f" * {i}\n"
return s
__str__ = render
class RenderableErrorMixin(RenderableMixin):
pass
class ErrorList(UserList, list, RenderableErrorMixin):
pass
app = flask.Flask(__name__)
@app.get("/")
def home():
print("home")
e = ErrorList(['a', 'b', 'c', 'd'])
s = ""
s += f"{type(e)}\n"
s += f"{type(e).__mro__}\n"
s += f"{e.__str__}\n"
s += f"{e}\n"
return flask.Response(s, mimetype="text/plain")
#v-
If I run that in on the command line via "flask run" it returns (as
intended):
<class 'app.ErrorList'>
(<class 'app.ErrorList'>, <class 'collections.UserList'>, <class
'collections.abc.MutableSequence'>, <class 'collections.abc.Sequence'>, <class
'collections.abc.Reversible'>, <class 'collections.abc.Collection'>, <class
'collections.abc.Sized'>, <class 'collections.abc.Iterable'>, <class
'collections.abc.Container'>, <class 'list'>, <class
'app.RenderableErrorMixin'>, <class 'app.RenderableMixin'>, <class 'object'>)
<bound method RenderableMixin.render of ['a', 'b', 'c', 'd']>
* a
* b
* c
* d
So, here, e.__str__ refers to the render() method of RenderableMixin and
calling str(e) calls that method. This is actually weird since <class
'list'> appears before <class 'app.RenderableMixin'> in the MRO. Maybe
«__str__ = render» overwrites the normal order?
This also happens if the script is just a "normal" script without Flask.
But if I run the same script via mod_wsgi in Apache, it returns:
<class 'app.ErrorList'>
(<class 'app.ErrorList'>, <class 'collections.UserList'>, <class
'collections.abc.MutableSequence'>, <class 'collections.abc.Sequence'>, <class
'collections.abc.Reversible'>, <class 'collections.abc.Collection'>, <class
'collections.abc.Sized'>, <class 'collections.abc.Iterable'>, <class
'collections.abc.Container'>, <class 'list'>, <class
'app.RenderableErrorMixin'>, <class 'app.RenderableMixin'>, <class 'object'>)
<method-wrapper '__str__' of ErrorList object at 0x75a198841450>
['a', 'b', 'c', 'd']
So here e.__str__ refers to the __str__ method of list, and str(e)
returns the default representation of a list.
Looking just at the MRO, this is actually what I would expect, since
<class 'list'> appears before <class 'app.RenderableMixin'>.
However, it is clearly not what the Django authors intended and also not
what happens outside of mod_wsgi. Also that didn't happen in Ubuntu
22.04 (Python 3.10), it seems to be new to Ubuntu 24.04 (Python 3.12).
What's going on here?
hjp
--
_ | Peter J. Holzer | Story must make more sense than reality.
|_|_) | |
| | | [email protected] | -- Charles Stross, "Creative writing
__/ | http://www.hjp.at/ | challenge!"
signature.asc
Description: PGP signature
-- https://mail.python.org/mailman3//lists/python-list.python.org
