GeezerVison is showing github.com — gh-154387: fix `structseq_repr` by labeling unnamed fields in repr as `<unnamed@N>` by XuehaiPan · Pull Request #154687 · python/cpython
Conversation
XuehaiPan
commented
Jul 25, 2026
•
edited
Loading
Uh oh!
There was an error while loading. Please reload this page.
Walk the members alongside the visible slots and label unnamed slots positionally as <unnamed@N>, which is correct regardless of where the unnamed fields sit among the visible ones.
Before this PR, the member slots are accessed incorrectly in repr when there are unnamed fields. It can cause a spurious SystemError when the index reaches the number of named fields (n_fields - n_unnamed_fields), or display the unnamed fields with <name>=<value> using a later field's name.
For example, a structseq type with WithUnnamed(first, <unnamed>, third, <unnamed>) + [hidden .fifth], before this PR:
>>> seq = WithUnnamed(range(5))
>>> repr(seq)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
repr(seq)
~~~~^^^^^
SystemError: In structseq_repr(), member 3 name is NULL for type WithUnnamed
# -> WithUnnamed(first=0, third=1, fifth=2, <reach NULL-terminated tp_members' end for member 3>)
After this PR:
>>> seq = WithUnnamed(range(5))
>>> seq
WithUnnamed(first=0, <unnamed@1>=1, third=2, <unnamed@3>=3)
>>> seq.fifth
4
Before this PR, for os.stat_result, the unnamed slots printed as the following field names st_atime/st_mtime/st_ctime.
>>> st = os.stat_result(range(os.stat_result.n_fields))
>>> st
os.stat_result(st_mode=0, st_ino=1, st_dev=2, st_nlink=3, st_uid=4, st_gid=5, st_size=6,
st_atime=7, st_mtime=8, st_ctime=9) # borrow the following fields' names but use the unnamed fields' values
>>> st[7]
7
>>> st.st_atime
10
>>> st = os.stat_result(tuple(range(10)) + (7.1, 8.2, 9.3))
>>> st
os.stat_result(st_mode=0, st_ino=1, st_dev=2, st_nlink=3, st_uid=4, st_gid=5, st_size=6,
st_atime=7, st_mtime=8, st_ctime=9)
>>> st[7] # index = 7 is int
7
>>> st.st_atime # index = 10 is float
7.1
After this PR, a new custom statresult_repr is added analogously to statresult_new. The custom repr shows the fields 10-12 (always initialized in statresult_new) instead of generic <unnamed@N> (introduced in this PR).
>>> st = os.stat_result(range(os.stat_result.n_fields))
>>> st
os.stat_result(st_mode=0, st_ino=1, st_dev=2, st_nlink=3, st_uid=4, st_gid=5, st_size=6,
st_atime=10, st_mtime=11, st_ctime=12) # make the field names and values match
>>> st[7]
7
>>> st.st_atime
10
>>> st = os.stat_result(tuple(range(10)) + (7.1, 8.2, 9.3))
>>> st
os.stat_result(st_mode=0, st_ino=1, st_dev=2, st_nlink=3, st_uid=4, st_gid=5, st_size=6,
st_atime=7.1, st_mtime=8.2, st_ctime=9.3)
>>> st[7] # index = 7 is int
7
>>> st.st_atime # index = 10 is float
7.1
>>> os.stat(os.curdir)
os.stat_result(st_mode=16877, st_ino=429294614, st_dev=16777232, st_nlink=58, st_uid=501, st_gid=20, st_size=1856,
st_atime=1784996316.4868717, st_mtime=1784996316.472091, st_ctime=1784996316.472091)
Without the custom statresult_repr, the generic <unnamed@N> will be used, which is not that legible.
>>> st = os.stat_result(range(os.stat_result.n_fields))
>>> st
os.stat_result(st_mode=0, st_ino=1, st_dev=2, st_nlink=3, st_uid=4, st_gid=5, st_size=6,
<unnamed@7>=7, <unnamed@8>=8, <unnamed@9>=9)
>>> os.stat(os.curdir)
os.stat_result(st_mode=16877, st_ino=429294614, st_dev=16777232, st_nlink=58, st_uid=501, st_gid=20, st_size=1856,
<unnamed@7>=1784996316, <unnamed@8>=1784996316, <unnamed@9>=1784996316)
Fixes #154387
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
structseq_repr() labeled each visible slot by indexing tp_members by slot
position, but tp_members is packed with unnamed fields skipped, so every slot
at or after the first unnamed field borrowed the name of a later hidden field.
For os.stat_result the integer time slots printed as st_atime/st_mtime/st_ctime
(which are actually separate hidden float fields), so the repr disagreed with
both st[i] and the .st_atime attribute.
Walk the members alongside the visible slots and label unnamed slots
positionally as "<unnamed@N>", which is correct regardless of where the
unnamed fields sit among the visible ones.
XuehaiPan
changed the title
gh-154387: label unnamed fields in repr as <unnamed@N>
gh-154387: fix structseq_repr by labeling unnamed fields in repr as <unnamed@N>
Jul 25, 2026
Lower n_in_sequence to 4 in the _testcapi interspersed helper so the trailing
named field ("fifth") is hidden, and expand
test_repr_with_interspersed_unnamed_fields to assert the full surface: repr
(hidden field excluded), field counts, __match_args__, tuple()/len(), and
attribute access to visible and hidden fields (the latter both provided and
defaulted to None).
os.stat_result has three unnamed integer time slots (7-9) whose values are
mirrored by the named float fields st_atime/st_mtime/st_ctime (statresult_new
fills the latter from the former). The generic struct sequence repr shows
those unnamed slots as <[email protected]>, which is uninformative.
Add statresult_repr showing the named fields (st_mode..st_size and the float
st_atime/st_mtime/st_ctime) by name, matching the attributes. Install it
through the type dict via PyObject_SetAttrString so repr() and st.__repr__()
resolve to the same function; a raw tp_repr assignment would leave the
__repr__ wrapper readied by PyStructSequence_NewType pointing at the generic
repr. Other struct sequences keep the generic <unnamed@N>.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Assignees
No one assigned
Milestone
No milestone
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
|