When using a backslash followed by three integers, Python interprets them as an octal value:
# A backslash followed by three integers will result in an octal value:
txt = "\110\145\154\154\157"
print(txt) # Output: Hello
However, when I try to replace the octal sequence with a variable inside {}
using an f-string, like this:
# Attempting to use a variable inside an f-string:
x = 110
txt = f"\\{x}\145\154\154\157"
print(txt)
The output is \110ello
instead of Hello
.
Why does Python treat the variable x
differently inside the f-string? How can I correctly substitute a variable into an octal escape sequence?
A few issues:
The double backslash results in a literal backslash. {x}
will render the value of x
in decimal notation. That ends the dynamic part.
x
is defined as 110, not as the octal number 0o110, which in decimal is 72, not 110. The decimal number 110 has nothing to do with the character you want to render.
In the f-string you should put the dynamic part within braces so that its evaluation is the final substring you want to get. In this case that means the braces should have the expression that evaluates to the character "H".
Here is how you could do that:
x = 0o110 # Define as the intended number using octal notation
s = f"{chr(x)}\145\154\154\157" # turn that number into a character
print(s)
Realise that octal, hexadecimal, binary, ...etc, are notations of integers. Once you store the value that is intended by that notation, you just have the integer, not the notation. There is nothing "octally" about the value of x
in the above code. Once that value is assigned, it is what we would naturally call 72, because we mostly speak "decimal", but it would also be right to say it is 110 in octal base, or 1001000 in binary or 48 in hexadecimal notation. You can "talk" about the value of x
in any base you want, but it doesn't affect the value itself.
So... if you want for instance to generate a random number, then you can set a range to choose from using octal representations for the boundary values, but the value that x
will get has no "memory" of which base you used to express those values.
You can do:
x = random.randint(0o100, 0x150)
But the following program would be compiled/parsed into exactly the same byte code:
x = random.randint(64, 104) # using decimal base notation
In short, 0o100 and 64 are exactly the same integer, and which of these notations you use in your code has no impact on the actual value that it evaluates to.
Another scenario arises when you receive a number (somehow, like via user input) in the form of a character string, where each character is a digit of that number. And then maybe it is also given that these characters should be interpreted as octal digits.
# We get a string(!) from user input. This is not a numeric data type.
octal_digits = input("give number with octal notation: ")
# Then we define the base to use for interpreting these characters as a number:
x = int(octal_digits, 8) # we "parse" a character string as denoting an integer
# x has no memory of the original digits; it is just the integer that we parsed from it
print(f"the corresponding character is {chr(x)}")