GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

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

@XuehaiPan XuehaiPan commented Jul 25, 2026 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Contributor

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

  • Issue: structseq_repr mislabels unnamed PyStructSequence fields (e.g. os.stat_result slots 7–9) #154387

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@XuehaiPan
fix(structseq): label unnamed fields in repr as <unnamed@N>
34b2b44
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.
@bedevere-app bedevere-app Bot mentioned this pull request Jul 25, 2026
structseq_repr mislabels unnamed PyStructSequence fields (e.g. os.stat_result slots 7–9) #154387
Open
@XuehaiPan 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
XuehaiPan added 5 commits July 25, 2026 18:25
@XuehaiPan
fix: fix missing import due to git rebase
23411b4
@XuehaiPan
test(structseq): cover a hidden named field in the interspersed test
9b3f8fe
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).
@XuehaiPan
fix: remove test_ prefix for functions in _testcapi
227e089
@XuehaiPan
feat(os): custom repr for os.stat_result showing time fields by name
50a2e22
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>.
@XuehaiPan
Merge remote-tracking branch 'upstream/main' into fix-structseq-unnamed
59e2238
@XuehaiPan
XuehaiPan marked this pull request as ready for review July 25, 2026 17:00
@bedevere-app bedevere-app Bot added the awaiting review label Jul 25, 2026
XuehaiPan added 3 commits July 26, 2026 13:41
@XuehaiPan
Merge remote-tracking branch 'upstream/main' into fix-structseq-unnamed
ee2e5f8
@XuehaiPan
Merge remote-tracking branch 'upstream/main' into fix-structseq-unnamed
500cdae
@XuehaiPan
ci: try fix ci
9fde8ce
XuehaiPan
XuehaiPan commented Jul 29, 2026
View reviewed changes
Comment thread Lib/test/test_class.py Resolved

Uh oh!

There was an error while loading. Please reload this page.

XuehaiPan added 9 commits August 4, 2026 14:56
@XuehaiPan
Merge remote-tracking branch 'upstream/main' into fix-structseq-unnamed
61c9236
@XuehaiPan
ci: try fix ci
8a3c65e
@XuehaiPan
Merge remote-tracking branch 'upstream/main' into fix-structseq-unnamed
49ea69f
@XuehaiPan
Merge remote-tracking branch 'upstream/main' into fix-structseq-unnamed
60d7ef1
@XuehaiPan
Merge branch 'main' into fix-structseq-unnamed
ed881b7
@XuehaiPan
Merge branch 'main' into fix-structseq-unnamed
0247aa7
@XuehaiPan
Merge branch 'main' into fix-structseq-unnamed
969e56f
@XuehaiPan
Merge remote-tracking branch 'upstream/main' into fix-structseq-unnamed
ca351cb
@XuehaiPan
Merge remote-tracking branch 'upstream/main' into fix-structseq-unnamed
89c0657
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Reviewers

No reviews

Uh oh!

There was an error while loading. Please reload this page.

Assignees

No one assigned

Labels

awaiting review

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

structseq_repr mislabels unnamed PyStructSequence fields (e.g. os.stat_result slots 7–9)

Uh oh!

There was an error while loading. Please reload this page.

1 participant

@XuehaiPan
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.
You are visitor 01402520 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.