How can I ignore unicode values and and keep the format '\x##'?

Viewed 198

I'm working on creating packets to send to a device serially. I want to keep the formatting as I've typed it without having it converted to unicode characters.

my_thing = b'\xb4\x75'
print(my_thing)
(Actual Output)>>> b'\xb4u'
(Wanted Output)>>> b'\xb4\x75'

Probably a simple question but after googling and searching other questions I couldn't find what I was looking for. Thanks in advance!

2 Answers

Assuming this is only a representation/print issue, I'll suggest a custom print function:

my_thing = b'\xb4\x75'

def print_bytes_as_hex(b):
    print("b'{}'".format(''.join(r'\x{:02x}'.format(i) for i in b)))

print_bytes_as_hex(my_thing)

if you copy/eval this function's output, it should == it's input.

NOTE: the string it generates is not == to the input.

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'
Related