Skip to content

f-string evaluation of conditional expressions with != operator seems to fail #129093

Closed

Description

@JD-Veiga

Bug report

Bug description:

Hi,

I really feel puzzled by this behaviour:

>>> print(f'{True == True=}')
True == True=True
>>> print(f'{True != True=}')
True False

I expected that the output of print(f'{True != True=}') would be True != True=False.

According to docs, "[t]o display both the expression text and its value after evaluation, (useful in debugging), an equal sign '=' may be added after the expression". So print(f'{True != True=}') fails because the expression text is not fully displayed -- even "=" is omitted.

Sorry if this is a duplicate -- I was not able to find any other report here and in Cython issue tracker.

Thank you.

CPython versions tested on:

3.12, 3.13

Operating systems tested on:

macOS

Linked PRs

Activity

  1. StanFromIreland commented on Jan 20, 2025

    @StanFromIreland
    Member

    Tested on Windows 11 with 3.13 and same bug.

  2. added
    3.12only security fixes
    3.13only security fixes
    3.14bugs and security fixes
    interpreter-core(Objects, Python, Grammar, and Parser dirs)
    on Jan 20, 2025
  3. ericvsmith commented on Jan 20, 2025

    @ericvsmith
    Member

    Also:

    >>> f'{1==0=}'
    '1==0=False'
    >>> f'{1!=0=}'
    '1True'
  4. JD-Veiga commented on Jan 20, 2025

    @JD-Veiga
    Author

    Yeah! I think that any conditional expression containing != behaves in the same manner -- whatever the result of evaluating the expression is.

  5. ericvsmith commented on Jan 20, 2025

    @ericvsmith
    Member

    Yeah! I think that any conditional expression containing != behaves in the same manner -- whatever the result of evaluating the expression is.

    Yes, I was just trying to point out that the True in the last evaluated expression comes not from the source code of the expression, but rather the value. Which is also why I removed the spaces around !=.

  6. JelleZijlstra commented on Jan 20, 2025

    @JelleZijlstra
    Member

    The AST looks like this:

    >>> print(ast.dump(ast.parse("f'{1!=0=}'"), indent=True))
    Module(
     body=[
      Expr(
       value=JoinedStr(
        values=[
         Constant(value='1'),
         FormattedValue(
          value=Compare(
           left=Constant(value=1),
           ops=[
            NotEq()],
           comparators=[
            Constant(value=0)]),
          conversion=114)]))])
    

    So it does evaluate 1 != 0, but instead of outputting a constant 1!=0= (as it should) it outputs just 1. I suspect something is looking for the ! that introduces conversion specifiers and gets confused by the ! in the != operator.

    A ! inside a string literal does work correctly though:

    >>> f"{'a!b'=}"
    "'a!b'='a!b'"
    
  7. sobolevn commented on Jan 20, 2025

    @sobolevn
    Member

    This is a parsing issue:

    >>> ast.dump(ast.parse("f'{1 != 1=}'"))
    "Module(body=[Expr(value=JoinedStr(values=[Constant(value='1 '), FormattedValue(value=Compare(left=Constant(value=1), ops=[NotEq()], comparators=[Constant(value=1)]), conversion=114)]))])"
    
    >>> ast.dump(ast.parse("f'{1 == 1=}'"))
    "Module(body=[Expr(value=JoinedStr(values=[Constant(value='1 == 1='), FormattedValue(value=Compare(left=Constant(value=1), ops=[Eq()], comparators=[Constant(value=1)]), conversion=114)]))])"
  8. tomasr8 commented on Jan 20, 2025

    @tomasr8
    Member

    This seems to fix it (with test_fstring passing), but I don't fully understand why yet:

    diff --git a/Parser/lexer/lexer.c b/Parser/lexer/lexer.c
    index dbbb94a407c..3b433addbe1 100644
    --- a/Parser/lexer/lexer.c
    +++ b/Parser/lexer/lexer.c
    @@ -212,7 +212,7 @@ _PyLexer_update_fstring_expr(struct tok_state *tok, char cur)
             case '}':
             case '!':
             case ':':
    -            if (tok_mode->last_expr_end == -1) {
    +            if (tok_mode->last_expr_end == -1 || cur == '}') {
                     tok_mode->last_expr_end = strlen(tok->start);
                 }
                 break;

    I'll try to add some tests and perhaps open a PR tomorrow

  9. picnixz commented on Jan 21, 2025

    @picnixz
    Member

    Could this be related to #124363?

  10. JD-Veiga commented on Jan 21, 2025

    @JD-Veiga
    Author

    Could be, @picnixz, but I do not think so. As @JelleZijlstra pointed out:

    I suspect something is looking for the ! that introduces conversion specifiers and gets confused by the ! in the != operator.

    while #124363, as long as I understand, is related to raw strings and escape characters, which is not the case here.

    The problem here seems to be that the expression text is printed up to ! while the expression itself is correctly evaluated, such as in the following example:

    >> print(f'{(2 - 1) != 1=}')
    (2 - 1) False
    
    

    Notice that:

    >> print(f'{(2 - 1) == 1=}')
    (2 - 1) == 1=True
    
    

    Even = sign is omitted in != expressions.

  11. added a commit that references this issue on Jan 22, 2025
  12. added a commit that references this issue on Jan 22, 2025
  13. added a commit that references this issue on Jan 22, 2025
  14. pablogsal commented on Jan 22, 2025

    @pablogsal
    Member

    Thanks everyone for the investigation and thanks @tomasr8 for the analysis. Fantastic job!

  15. added 2 commits that reference this issue on Jan 22, 2025
  16. JD-Veiga commented on Jan 22, 2025

    @JD-Veiga
    Author

    Thanks to everybody. This was awesome.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    3.12only security fixes3.13only security fixes3.14bugs and security fixesinterpreter-core(Objects, Python, Grammar, and Parser dirs)topic-parsertype-bugAn unexpected behavior, bug, or error

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions