Skip to content

bug: attributes using dict comprehension are incorrectly displayed #311

@niooss-ledger

Description

@niooss-ledger

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:

Image

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:

Extract of 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): &lt;random characters&gt;</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: &lt;more random characters&gt;</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-python v1.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 []}).

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions