When you type something like thing = b"\xb4"
, what is stored in Python the code object, after compiled, is just the actual number 0xb4 (180 in decimal). This has nothing to do with Unicode at all - actually, the separation of bytes and text-strings in Python 3 was done exactly so that bytes values are one thing and text is another, and one needs an "encoding" to relate one to the other.
Your value would be just the same if you'd do:
In [155]: thing = bytes([180])
In [156]: thing
Out[156]: b'\xb4'
This only becomes a character in Python 3 if converted to a string via an explicit encoding:
In [157]: print(thing.decode("latin1"))
´
What happens is that for some similarity with Python 2 and C language itself, the byte values that happen to be mapped to the [32, 128] range - are printed as ASCII characters. So, 0x75 corresponds to the character ASCII u
- but the internal representation of both numbers in
my_thing = b'\xb4\x75'
is still one byte numeric value for each - no matter what their representation with print
is. And when you send this bytes object in a binary packet, both numbers 0xb4 and 0x75 will be sent as numeric values.
This is easy to verify if you either iterate through the bytes-string, which yields numeric values in the [0, 256] range - or write the values to a file and check that it actually only contains 2 bytes:
In [158]: my_thing = b"\xb4\x75"
In [159]: my_thing
Out[159]: b'\xb4u'
In [160]: list(my_thing)
Out[160]: [180, 117]
In [161]: open("test.bin", "wb").write(my_thing)
Out[161]: 2
In [162]: !ls -la test.bin
-rw-rw-r--. 1 gwidion gwidion 2 Jun 13 17:46 test.bin
(the "2" in the last line in this listing is the byte-size for the file as spelled by Linux shell's "ls")
So, the only problem you are having, if any, is to visualize your values on the Python side, before they are sent - for that purpose then, of viewing the values in the host console, either as a print on the TTY, or displaied in a GUI or generated web-page, you do the oposite thing you think is taking place:
Call a method that gives you a text object representing the HEX digits of your bytes object as text - so it can be easily inspected. The bytes object itself has the .hex()
method that fits this purpose:
In [165]: my_thing.hex()
Out[165]: 'b475'
So, there you are - the hexdigits for the 2 bytes you are ready to send as a packet, viewed as text - while the contents of my_thing
itself are unmodified.
This does not have the "\xYY"
prefix, so it is nicer to look at - and, if you will type a lot of values, there is also a bytes method that will convert each pair of hex-digits into a byte - and is much more convenient for typing literal values. The bytes.fromhex
class method. It allows you to type:
In [166]:my_thing = bytes.fromhex("b475")
And this is equivalent to b"\xb4\x75"
.
If for some reason you really need the \x
prefix to each pair of digits, a one-liner of Python code can manipulate it to generate a string containing a byte-string literal that could be fed to eval
, for example - but using bytes.fromhex
would still be more readable:
converter = lambda seq: 'b"{}"'.format("".join(f"\\x{value:x}" for value in seq))
And on the interactive session:
In [168]: my_thing = b"\xb4\x75"
...:
...: converter = lambda seq: 'b"{}"'.format("".join(f"\\x{value:x}" for value in seq))
...: print(converter(my_thing))
b"\xb4\x75"
This is just a printout of a text string - a sequence of text characters including the character "b", '"', and so on. To have the bytes literal back one needs to apply eval on that:
In [169]: eval(converter(my_thing))
Out[169]: b'\xb4u'