Fix cryptic TypeError from ClassLabel.int2str on a string input - #8416
Fix cryptic TypeError from ClassLabel.int2str on a string input#8416hdimer wants to merge 2 commits into
Conversation
`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.
shashvat-singham
left a comment
There was a problem hiding this comment.
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 49Validating 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.
|
Thanks, you're right about Both now report the offending value ( I didn't go with On Two more that your comment effectively surfaced, both pre-existing and both out of scope here unless a maintainer wants them folded in:
Happy to take either in a follow-up. |
ClassLabel.int2str("1")raises a crypticTypeError: '<=' not supported between instances of 'int' and 'str'instead of a clear error.int2strguards its input withisinstance(values, Iterable), but astris iterable, so a string is not wrapped as a single value; the code then iterates over the string's characters and compares0 <= <char> < num_classes, raising theTypeError. The siblingstr2intalready special-casesstr;int2strwas missing the symmetric guard.Two commits here:
str. Rejected with the method's existingValueError— a string is not a valid integer label (str2intis the string → int direction).valuesitself, so a non-integer inside the iterable still reached the range check and raised the sameTypeError(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 withUnknown 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-dTensors rather than ints, and floats are accepted. "Not comparable to an int" is the property that actually matters, and it coversstr,Noneand 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 theforitself before any check can run (int2str(torch.tensor(1))→TypeError: iteration over a 0-d tensor).Testing
Added
pytest.raises(ValueError)cases forint2str("1"),int2str(["1"])andint2str([2.5])totest_classlabel_int2str; each fails on the unpatched code.tests/features/test_features.pypasses (201 passed, 2 skipped);ruff checkandruff format --checkclean.Used AI assistance on this; I reviewed and tested the change myself.