pythondictionarymultiple-inheritancepython-subinterpreters

str() of a dict subclass does not return "{}" per the MRO


With a class that inherits from dict, why does str() not use dict.__str__ when dict is earlier in the MRO of the class?

Python 3.10.12 (main, Sep 11 2024, 15:47:36) [GCC 11.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> class A:
...  def __str__(self):
...   return "A"
...
>>> class B(dict, A):
...  pass
...
>>> B.__mro__
(<class '__main__.B'>, <class 'dict'>, <class '__main__.A'>, <class 'object'>)
>>> str(B())
'A'
>>> str(dict())
'{}'
>>>

When calling str(B()), why is dict.__str__ not called in preference to A.__str__ ?

Is it somehow related to dict having a slot_wrapper instead of a function?

>>> dict.__str__
<slot wrapper '__str__' of 'object' objects>
>>> A.__str__
<function A.__str__ at 0x75be4ea8a0e0>
>>> B.__str__
<function A.__str__ at 0x75be4ea8a0e0>

EDIT re: Sub-Interpreters

In a Python 3.12 sub-interpreter the behavior is different.

This is what started my investigation. A Django application running in Apache with mod_wsgi exposed the difference.

Inspecting dict.__str__:

Python 3.10.12, main AND sub interpreters:

<slot wrapper '__str__' of 'object' objects>

Python 3.12.3, main interpreter:

<slot wrapper '__str__' of 'object' objects>

Python 3.12.3, sub interpreter:

<slot wrapper '__str__' of 'dict' objects>

This added dict.__str__ is what was making Django forms display errors as "{}" or "[]", instead of "".

The fix was to add this line into the Apache site definition:

WSGIApplicationGroup %{GLOBAL}

which, per modwsgi documentation:

>> ... forces the WSGI application to run in the main Python interpreter...

The config was already using the directives:

WSGIDaemonProcess WSGIProcessGroup WSGIScriptAlias

EDIT

This is a bug in Python 3.12.4 and earlier. See gh-117482


Solution

  • dict.__str__ is inherited from object, not implemented by dict. (The implementation is basically return repr(self) - it's not dict-specific.)

    dict might come before A in B.__mro__, but A comes before object, so A's __str__ implementation is found before object's implementation.