pythonsyntaxinteger

Why can hexadecimal python integers access properties but not regular ints?


Decimal (i.e. non-prefixed) integers in Python seem to have fewer features than prefixed integers.

If I do 1.real I get a SyntaxError: invalid decimal literal. However, if I do 0x1.real, then I get no error and 1 is the result. (Same for 0b1.real and 0o1.real, though in Python2 01.real gives a syntax error as).


Solution

  • It's because the 1. lead-in is being treated as a floating-point literal and r is not a valid decimal digit.

    Hex literals, of the form you show, are integers, so are not ambiguous in being treated as a possible floating point (there is no 0x1.1).

    If you use (1).real to specify that the literal is just the 1, it works fine.

    The following (annotated) transcript may help:

    >>> 0x1.real     # Can only be integer, so works as expected.
    1
    
    >>> 1.real       # Treated as floating point, "r" is invalid,
      File "<stdin>", line 1
        1.real
             ^
    SyntaxError: invalid syntax
    
    >>> (1).real     # Explicitly remove ambiguity, works.
    1
    
    >>> 0x1.1        # No float hex literal in this form.
      File "<stdin>", line 1
        0x1.1
            ^
    SyntaxError: invalid syntax
    

    For completeness, the lexical tokens for numerics (integral and float) can be found in the Python docs:

    integer      ::=  decinteger | bininteger | octinteger | hexinteger
    decinteger   ::=  nonzerodigit (["_"] digit)* | "0"+ (["_"] "0")*
    bininteger   ::=  "0" ("b" | "B") (["_"] bindigit)+
    octinteger   ::=  "0" ("o" | "O") (["_"] octdigit)+
    hexinteger   ::=  "0" ("x" | "X") (["_"] hexdigit)+
    nonzerodigit ::=  "1"..."9"
    digit        ::=  "0"..."9"
    bindigit     ::=  "0" | "1"
    octdigit     ::=  "0"..."7"
    hexdigit     ::=  digit | "a"..."f" | "A"..."F"
    
    floatnumber   ::=  pointfloat | exponentfloat
    pointfloat    ::=  [digitpart] fraction | digitpart "."
    exponentfloat ::=  (digitpart | pointfloat) exponent
    digitpart     ::=  digit (["_"] digit)*
    fraction      ::=  "." digitpart
    exponent      ::=  ("e" | "E") ["+" | "-"] digitpart
    

    You can see there that the floats do not allow for hexadecimal prefixes, which is why it can safely assume the . in 0x1.real is not part of the literal value.

    That's not the case for 1.real, it assumes that the . is preceding a fractional part of a float.

    A clever-enough lexer could, of course, detect an invalid decimal digit immediately after the . and therefore assume it's a method to call on an integer. But that introduces complexity and may have other problematic edge cases.