Skip to content

Fix cryptic TypeError from ClassLabel.int2str on a string input - #8416

Open
hdimer wants to merge 2 commits into
huggingface:mainfrom
hdimer:fix-classlabel-int2str-str
Open

Fix cryptic TypeError from ClassLabel.int2str on a string input#8416
hdimer wants to merge 2 commits into
huggingface:mainfrom
hdimer:fix-classlabel-int2str-str

Conversation

@hdimer

@hdimer hdimer commented Aug 8, 2026

Copy link
Copy Markdown

ClassLabel.int2str("1") raises a cryptic TypeError: '<=' not supported between instances of 'int' and 'str' instead of a clear error.

int2str guards its input with isinstance(values, Iterable), but a str is iterable, so a string is not wrapped as a single value; the code then iterates over the string's characters and compares 0 <= <char> < num_classes, raising the TypeError. The sibling str2int already special-cases str; int2str was missing the symmetric guard.

Two commits here:

  1. Top-level str. Rejected with the method's existing ValueError — a string is not a valid integer label (str2int is the string → int direction).
  2. Non-integer elements (after review feedback from @shashvat-singham). The guard above only inspects values itself, so a non-integer inside the iterable still reached the range check and raised the same TypeError (int2str(["1"])). An out-of-range float didn't even get that far — it passed the comparison and then broke the error message's own {v:d} formatting with Unknown format code 'd' for object of type 'float'. Both now report the offending value.

The element check duck-types the comparison rather than testing isinstance(v, (int, np.integer)), because a type whitelist would reject inputs that work today and that the error message itself advertises: iterating a torch tensor yields 0-d Tensors rather than ints, and floats are accepted. "Not comparable to an int" is the property that actually matters, and it covers str, None and arbitrary objects without enumerating types.

Unchanged: ints, lists/tuples of ints, floats, numpy arrays and scalars, torch tensors, and bytes (a genuine iterable of ints — ClassLabel(num_classes=100).int2str(b"\x01") returns ['1']).

Out of scope, both pre-existing and both surfaced in review — happy to fold either in if wanted: str2int([1]) returns [1] rather than rejecting it, and 0-d inputs fail in the for itself before any check can run (int2str(torch.tensor(1))TypeError: iteration over a 0-d tensor).

Testing

Added pytest.raises(ValueError) cases for int2str("1"), int2str(["1"]) and int2str([2.5]) to test_classlabel_int2str; each fails on the unpatched code. tests/features/test_features.py passes (201 passed, 2 skipped); ruff check and ruff format --check clean.


Used AI assistance on this; I reviewed and tested the change myself.

`int2str` guards on `isinstance(values, Iterable)`, but a str is iterable, so
a string like "1" was treated as an iterable of characters and failed with
`TypeError: '<=' not supported between instances of 'int' and 'str'`. Mirror
str2int, which special-cases str, and reject a str with the method's existing
clear ValueError.
@hdimer
hdimer marked this pull request as ready for review August 9, 2026 17:45

@shashvat-singham shashvat-singham left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Checked out the branch and ran it. The top-level case is fixed — int2str("1") now raises the clear ValueError instead of iterating the string's characters.

The cryptic TypeError this PR is aimed at is still reachable one level down, though. The guard only inspects values itself, but the loop below it compares each element:

for v in values:
    if not 0 <= v < self.num_classes:

so a string inside the iterable still hits exactly the error you're removing:

>>> cl = ClassLabel(names=["neg", "pos"])
>>> cl.int2str(["1"])
TypeError: '<=' not supported between instances of 'int' and 'str'

which is the same message from the same line as the original report, just reached via a list. Given the issue is "cryptic TypeError from int2str on a string input", I think a reviewer will ask why "1" is handled and ["1"] isn't.

Related, bytes slips through and produces a misleading message, because iterating it yields ints:

>>> cl.int2str(b"1")
ValueError: Invalid integer class label 49

Validating inside the loop instead would cover all three at once — something like raising if not isinstance(v, (int, np.integer)) before the range check, which also gives you the offending value in the message.

One note on the "as str2int does" in the comment: str2int guards the top level the same way, but it has the mirror-image hole in its own loop and fails silently rather than loudly:

>>> cl.str2int([1])
[1]

It returns the int straight back instead of rejecting it. Not this PR's problem, but worth knowing the symmetry being invoked isn't quite there today — if you do go with per-element validation here, the same treatment in str2int would make the pair actually consistent.

…e check

The str guard added in the previous commit only covered a top-level str, so a
non-integer *inside* the iterable still reached `0 <= v < self.num_classes` and
raised the same cryptic `TypeError` (`int2str(["1"])`). An out-of-range float
got no further: it passed the comparison and then broke the error message's own
`{v:d}` formatting with "Unknown format code 'd' for object of type 'float'".

Treat "not comparable to an int" as "not a label" and fall through to the
existing ValueError, so both cases report the offending value. Duck-typing the
comparison rather than checking `isinstance(v, (int, np.integer))` keeps the
inputs the error message advertises: iterating a torch tensor yields 0-d
Tensors, not ints, and floats are accepted today.
@hdimer

hdimer commented Aug 15, 2026

Copy link
Copy Markdown
Author

Thanks, you're right about ["1"] — fixed in 5500a38, along with a related one you didn't hit: an out-of-range float never got as far as your case, it passed the comparison and then broke the error message's own {v:d} formatting.

>>> cl.int2str([2.5])
ValueError: Unknown format code 'd' for object of type 'float'

Both now report the offending value (Invalid integer class label '1' / 2.5).

I didn't go with isinstance(v, (int, np.integer)) though, because it rejects things that work today. Iterating a torch tensor yields 0-d Tensors rather than ints, so int2str(torch.tensor([0, 1])) would start failing, and floats would too — both are inputs the method's own error message advertises. So I duck-typed it instead: if v isn't comparable to an int, it isn't a label. That covers str, None and anything else non-comparable without enumerating types.

On bytes I left it alone. b"1" is a genuine iterable of ints, and ClassLabel(num_classes=100).int2str(b"\x01") returns ['1'] today, so "label 49" is honest about what the iterable yields. Rejecting it would be the same trade I just turned down.

Two more that your comment effectively surfaced, both pre-existing and both out of scope here unless a maintainer wants them folded in:

  • str2int([1]) returning [1], as you noted.
  • 0-d inputs. int2str(np.int64(1)) is rejected outright, and int2str(torch.tensor(1)) raises TypeError: iteration over a 0-d tensor — the for fails before any check can run.

Happy to take either in a follow-up.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants