check50 icon indicating copy to clipboard operation
check50 copied to clipboard

Unexpected behaviour of check50.Mismatch for non-string arguments

Open mjdv opened this issue 6 years ago • 1 comments

When trying to raise Mismatch exceptions for non-string arguments, the first and last character of the string representation of the value are truncated, occasionally resulting in very cryptic error messages. Here are some examples.

>>> import check50
>>> raise check50.Mismatch(True, False)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
check50._api.Mismatch: expected "ru", not "als"
>>> raise check50.Mismatch([], "")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
check50._api.Mismatch: expected "", not ""
>>> raise check50.Mismatch(10, 20)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
check50._api.Mismatch: expected "", not ""

If the Mismatch exception class only allows string arguments, I believe this should explicitly be documented.

Additionally, while testing I ran into the following (related?) issue: once a Mismatch object has been created (but not raised), raising another Mismatch object causes a different (presumably unintended) exception.

>>> import check50
>>> check50.Mismatch("foo", "bar")
Mismatch('foo', 'bar')
>>> raise check50.Mismatch("baz", "lud")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/mees/.local/lib/python3.6/site-packages/check50/_api.py", line 405, in __init__
    super().__init__(rationale=_("expected {}, not {}").format(_raw(expected), _raw(actual)), help=help)
TypeError: 'Mismatch' object is not callable

mjdv avatar Oct 09 '19 11:10 mjdv

Problem 1 is fixed by the above commit.

Problem 2 is actually pretty funny and threw me for a loop when I saw it. For internationalization, we use python's gettext module which adds a function called _ to the global namespace. Then any time you want a string to be translated based on the locale, you just call _ on it. This is why you see _("expected {}, not {}") in the traceback. However, when the python interpreter runs in interactive mode, it defines a variable called _ in the global namespace which is equal to the last evaluated expression. You can see this in action by opening up the interpreter in interactive mode and running

>>> 1 + 2
3
>>> _
3
>>> 5 + 6
11
>>> _ 
11

Essentially, the interpreter overwrites the value of _ set by gettext (i.e. the translation function) with whatever the value of the last expression you evaluated was (in your case the previous check50.Mismatch). So if you type any expression at all after you import check50, any of the functions in check50 that create internationalized output (i.e. most of them) will fail with a similar error. You can even do something funny like:

>>> import check50
>>> def underscore_replacement(s): print("HELLO"); return s
>>> underscore_replacement
<function underscore_replacement at 0x7f09808b80e0>
>>> check50.Mismatch("foo", "bar")
HELLO
Mismatch('foo', 'bar')

This only shows up when running in interactive mode however. If you make a file called test.py containing

check50.Mismatch("foo", "bar")
check50.Mismatch("baz", "qux")

and then run python test.py everything works as expected.

cmlsharp avatar Nov 10 '19 02:11 cmlsharp