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!"

Attachment: signature.asc
Description: PGP signature

-- 
https://mail.python.org/mailman3//lists/python-list.python.org

Reply via email to