Description of the bug
When displaying (with some options detailed later) a module attribute initialized with dictionary comprehension, the documentation shows incorrect Python code and random characters.
For example,
TEST_0_INDEX_BY_KEY = {obj.key: obj for obj in []}
Is displayed in the documentation generated by mkdocs as:
TEST_0_INDEX_BY_KEY = {(key): _Hlfor obj in []}
The options I used to trigger this behaviour were separate_signature: true and signature_crossrefs: true.
To Reproduce
I reproduced using 3 files:
test_dict.py
TEST_0_INDEX_BY_KEY = {obj.key: obj for obj in []} """Showing `(key): <random characters>` instead of `obj.key: obj`.""" TEST_1_ACCESS_MEMBER = {obj: obj.member for obj in []} """Showing `obj: (member)` instead of `obj: obj.member`.""" TEST_2_LONG_NAME = { some_object_with_a_long_name: some_object_with_a_long_name for some_object_with_a_long_name in [] } """Showing `some_object_with_a_long_name: <more random characters>` instead of `some_object_with_a_long_name: some_object_with_a_long_name`."""
mkdocs.yml
site_name: Test Python dict comprehension plugins: - mkdocstrings: handlers: python: options: separate_signature: true signature_crossrefs: true
docs/index.md
::: test_dict
The 3 files can be created with this command (which uses a base64-encoded tar.gz archive):
echo H4sIAAAAAAACA+3WUWvbMBAAYD/7Vxx5ah/myYljg+kGbWfG2JKNJQ8rYyiurcReI8tICl0Y++87OzFLN3Cg0IzCfS+OrYvucO5QrDCW52VmvXrrPBGGwjBor+jvK4uiyPHHLPKDMAiZ7zB/6I98B5hzAhtjUw3gaKVsX9yx9WdqnszmnPF30zfJF351w98nN/AKfqrb796d2MaAH2CpdHstK/j67Zc7GAxmhbovqxUszjDoPIYLnVa5kpAVqU4zK7R5vcBwY0Wag1rC4nC/hYc7uG6b2eeX19fJbMYnyeQq+bxP3YZ5Ushbofuyt6Fnu7jzfxIe7nKYc8g/fJy+5dPLSdLkcwEZJQXHcJFZfl/agqd8raoVr1Ip4t7V9utNjX1Bu+Ldh9X357yQSgs49l4fX/fujTjyLleZ8bZy/XQ9dmz+Ryzo5n8cDUc4//44YjT/p2BKK/bdMsejAD5tbaEqaE4EyJSstShEZUpVuW693qyw92Ls+Bew6xursZPbJ40Cm3WNPdrdA9Ttbn/uAVRtcTNz+AinT9TY31iIKVdVajcaq7F6Ix4GdWs808oYLZZmH+WQR2t+xZdllYsfnsz/0/wzP+zmfzgehe35H4xp/k8hjnGIuv+ANEmEEEIIIYQQQgghhBBCCCGEEPJs/QaXqtegACgAAA== | base64 -d | tar -xz
Then, install mkdocs and mkdocstrings-python and launch the server:
pip install mkdocs mkdocstrings-python mkdocs serve
http://127.0.0.1:8000/ shows:
The 3 Python snippets are:
TEST_0_INDEX_BY_KEY = {(key): _Z5for obj in []} TEST_1_ACCESS_MEMBER = {obj: (member)for obj in []} TEST_2_LONG_NAME = {some_object_with_a_long_name: _6Jpv7OmLiQU4bm2xsgNeOyqDsdmfor some_object_with_a_long_name in []}
It is also possible to run mkdocs build and read site/index.html:
site/index.html
<h2 id="test_dict.TEST_0_INDEX_BY_KEY" class="doc doc-heading"> <span class="doc doc-object-name doc-attribute-name">TEST_0_INDEX_BY_KEY</span> <span class="doc doc-labels"> <small class="doc doc-label doc-label-module-attribute"><code>module-attribute</code></small> </span> </h2> <pre class="highlight"><code class="language-python doc-signature">TEST_0_INDEX_BY_KEY = {(<span title="obj.key">key</span>): _xEfor <span title="obj">obj</span> in []}</code></pre> <div class="doc doc-contents "> <p>Showing <code>(key): <random characters></code> instead of <code>obj.key: obj</code>.</p> </div> </div> <div class="doc doc-object doc-attribute"> <h2 id="test_dict.TEST_1_ACCESS_MEMBER" class="doc doc-heading"> <span class="doc doc-object-name doc-attribute-name">TEST_1_ACCESS_MEMBER</span> <span class="doc doc-labels"> <small class="doc doc-label doc-label-module-attribute"><code>module-attribute</code></small> </span> </h2> <pre class="highlight"><code class="language-python doc-signature">TEST_1_ACCESS_MEMBER = {<span title="obj">obj</span>: (<span title="obj.member">member</span>)for <span title="obj">obj</span> in []}</code></pre> <div class="doc doc-contents "> <p>Showing <code>obj: (member)</code> instead of <code>obj: obj.member</code>.</p> </div> </div> <div class="doc doc-object doc-attribute"> <h2 id="test_dict.TEST_2_LONG_NAME" class="doc doc-heading"> <span class="doc doc-object-name doc-attribute-name">TEST_2_LONG_NAME</span> <span class="doc doc-labels"> <small class="doc doc-label doc-label-module-attribute"><code>module-attribute</code></small> </span> </h2> <pre class="highlight"><code class="language-python doc-signature">TEST_2_LONG_NAME = {<span title="some_object_with_a_long_name">some_object_with_a_long_name</span>: _7kTCpbiedqLcznZoOnrjKhe3D9Gfor <span title="some_object_with_a_long_name">some_object_with_a_long_name</span> in []}</code></pre> <div class="doc doc-contents "> <p>Showing <code>some_object_with_a_long_name: <more random characters></code> instead of <code>some_object_with_a_long_name: some_object_with_a_long_name</code>.</p> </div> </div>
Without `separate_signature: true`, this generates code which is mostly right (a space is missing before `for`, and some extra parentheses are added):
TEST_0_INDEX_BY_KEY = {(obj.key): objfor obj in []} TEST_1_ACCESS_MEMBER = {obj: (obj.member)for obj in []} TEST_2_LONG_NAME = {some_object_with_a_long_name: some_object_with_a_long_namefor some_object_with_a_long_name in []}
With separate_signature: true and without signature_crossrefs: true, obj. disappears from the first two reproducers:
TEST_0_INDEX_BY_KEY = {(key): objfor obj in []} TEST_1_ACCESS_MEMBER = {obj: (member)for obj in []} TEST_2_LONG_NAME = {some_object_with_a_long_name: some_object_with_a_long_namefor some_object_with_a_long_name in []}
Expected behavior
I expect the Python code displayed by mkdocs to be consistent.
Environment information
I tested in a Python 3.13 container with the current git main version:
podman run --rm --net=host -it docker.io/library/python:3.13 bash pip install 'git+https://github.com/mkdocs/mkdocs' pip install 'git+https://github.com/mkdocstrings/python'
This installed:
$ pip freeze click==8.3.0 colorama==0.4.6 ghp-import==2.1.0 griffe==1.14.0 Jinja2==3.1.6 Markdown==3.9 MarkupSafe==3.0.3 mergedeep==1.3.4 mkdocs @ git+https://github.com/mkdocs/mkdocs@f68e5fc18d3cd3811dbddfdeda1a57721f9e2e4e mkdocs-autorefs==1.4.3 mkdocs-get-deps==0.2.0 mkdocstrings==0.30.1 mkdocstrings-python @ git+https://github.com/mkdocstrings/python@f25b2ecf73f30ac0ea836bec1aa60e87a360e23a packaging==25.0 pathspec==0.12.1 platformdirs==4.4.0 pymdown-extensions==10.16.1 python-dateutil==2.9.0.post0 PyYAML==6.0.3 pyyaml_env_tag==1.1 six==1.17.0 watchdog==6.0.0
python -m mkdocstrings_handlers.python._internal.debug # | xclip -selection clipboard- System: Linux-6.8.0-85-generic-x86_64-with-glibc2.39
- Python: cpython 3.13.0 (/usr/local/bin/python)
- Environment variables:
- Installed packages:
mkdocstrings-pythonv1.18.3.dev1+gf25b2ec
I also tested with uv and several Python versions:
uv run --python python3.13 --with mkdocs --with mkdocstrings-python mkdocs serve uv run --python python3.14 --with mkdocs --with mkdocstrings-python mkdocs serve
Additional context
I encountered this issue in a project where several classes are defined (ClsA, ClsB, ClsC), a global variable listes the classes (ALL_CLASSES = (ClsA, ClsB, ClsC)) and another global variable enables getting a class through their name:
CLASS_BY_NAME = {cls.name: cls for cls in ALL_CLASSES}
The documentation generated by mkdocs was buggy and this is what led to the first minimal reproducer I shared (TEST_0_INDEX_BY_KEY = {obj.key: obj for obj in []}).
