views:

1356

answers:

6

Is this the best way to convert a Python number to a hex string?

number = 123456789
hex(number)[2:-1].decode('hex')

Sometimes it doesn't work and complains about Odd-length string when you do 1234567890.

Clarification:

I am going from int to hex.

Also, I need it to be escaped.

IE: 1234567890 -> '\x49\x96\x02\xd2' not '499602D2'

Also, it needs to be able to take any Python integer. IE. something larger than an Int.

Edit:

Here is the best solution so far I have cobbled together from Paolo and Devin's post.

def hexify(num):
    num = "%x" % num

    if len(num) % 2:
        num = '0'+num

    return num.decode('hex')
+4  A: 

You can use string formatting:

>>> number = 123456789
>>> hex = "%X" % number
>>> hex
'75BCD15'
Paolo Bergantino
Ah nice, now how do I get it so it will output '\x75\xbc\d1\x05'?
Unknown
You can use lowercase x to make it display in lowercase, I am not sure about the other part...
Paolo Bergantino
Well hex(number)[2:-1].decode('hex') does that except it messes up sometimes.
Unknown
I'm pretty sure you mean [2:], not [2:-1] — the latter cuts off the last digit.
Ben Blank
Ben: Oh yeah, that's another problem, sometimes hex() sticks an 'L' at the end.
Unknown
Then hopefully you'll like my answer, which doesn't use hex() and so doesn't have that problem. :-)
Ben Blank
+1  A: 

Sometimes it doesn't work and complains about Odd-length string when you do 1234567890.

Because it doesn't make sense. How do you fit 'AAB' in a space that takes either 2 or 4 digits? Each byte is two hex characters. When you have an odd number of hex characters, the desired result is ambiguous. Do you want it to be the equivalent of 0AAB or AAB0? If you know which one you want it to be equivalent to, just add that character to the right place before decoding.

i.e. (('0' + foo) if len(foo) % 2 else foo).decode('hex') where foo is a string of the form returned by %x.

Devin Jeanpierre
Wrong way. I'm trying to go from int to hex, not hex to int.
Unknown
Sorry, I both misread and misremembered stuff. The answer is changed now.
Devin Jeanpierre
(To me) it makes more sense to add a leading 0. See my answer. The code can easily be adjusted if a trailing space is preferred.
Stephan202
Yours doesn't answer his question either, though. It returns '\\x0F' instead of '\x0F'. The real answer is a one-liner-- `(('0' + foo) if len(foo) % 2 else foo).decode('hex')`
Devin Jeanpierre
@Devin I (incorrectly?) assumed that he wants to have a literal backslash instead of the actual hex value in the string.
Stephan202
Who knows. I've given up on this question anyway.
Devin Jeanpierre
A: 

As Paolo mentioned, string formatting is the way to go. Note that you can choose between lower and upper case letters:

>>> hex = lambda n: '%X' % n
>>> hex(42)
'2A'
>>> hex = lambda n: '%x' % n
>>> hex(42)
'2a'
>>> def escaped(n):
...     s = hex(n)
...     if len(s) & 1:
...          s = '0' + s
...     return ''.join(chr(int(s[i:i + 2], 16)) for i in range(0, len(s), 2))
...
>>> escaped(123)
'{'
>>> escaped(1234)
'\x04\xd2'
>>> escaped(12348767655764764654764445473245874398787989879879873)
'!\x01^\xa4\xdf\xdd(l\x9c\x00\\\xfa*\xf3\xb4\xc4\x94\x98\xa9\x03x\xc1'

Note that escaped adds a leading zero in case of an odd number of hex digits. This solution works for strings of any length.

Stephan202
Yes I know but I need it to convert to '\x2a' not '2a'
Unknown
Thank you for the input. It is almost the same as the snippet I just made, but I didn't want it to be escaped, so I guess it would need a char() instead of ''.join('\\x")
Unknown
Alright, I changed the return statement.
Stephan202
A: 

If you know how long your output strings should be, string formatting will work. For example, to get four-character strings, you need a formatted length of eight:

>>> "{0:08x}".format(123456789).decode("hex")
'\x07[\xcd\x15'
>>> "{0:08x}".format(1234567890).decode("hex")
'I\x96\x02\xd2'

This will prepend zeroes if your number doesn't "fill up" the string. For example, with six-character strings:

>>> "{0:012x}".format(123456789).decode("hex")
'\x00\x00\x07[\xcd\x15'
>>> "{0:012x}".format(1234567890).decode("hex")
'\x00\x00I\x96\x02\xd2'

Edit:

To "detect" the length of target strings, you can use the math.log function:

>>> def int2str(n):
        l = int(math.ceil(math.log(n, 256) / 2) * 4)
        return ("{0:0{1}x}").format(n, l).decode("hex")

>>> int2str(123456789)
'\x07[\xcd\x15'
>>> int2str(1234567890)
'I\x96\x02\xd2'
Ben Blank
Is there a way to automatically detect the length based on the size of the number?
Unknown
it's called a logarithm
hop
+4  A: 

I'm not sure exactly what you want, but have you looked at the struct module?

Given

>>> hex(123456789)
'0x75bcd15'

You can do:

>>> struct.pack('i', 123456789)
'\x15\xcd[\x07'

Note that '\x5b' == '['.

Also, you can reverse the endianness:

>>> struct.pack('>i', 123456789)
'\x07[\xcd\x15'

Edit: I'm not sure what you mean by "bigger than a long", since AFAIK longs in python are unbounded (except by memory). However, you can deal with bigger integers by just dividing and concatenating. e.g. given:

>>> n = 123456789012345678901234567890

the target is:

>>> hex(n)
'0x18ee90ff6c373e0ee4e3f0ad2L'

So:

>>> s = ''
>>> while n >= 2**32:
...  n, r = divmod(n, 2**32)
...  s = struct.pack('>l', r) + s
... 
>>> s = struct.pack('>l', n) + s

See that s matches the result of hex(n) above:

>>> s
'\x00\x00\x00\x01\x8e\xe9\x0f\xf6\xc3s\xe0\xeeN?\n\xd2'
John Fouhy
I need something that can take a number bigger than an Int or long.
Unknown
A: 

one of the surest way, for arbitary numbers, is to use the 'array' module like this:

from array import array
binArr = array('B')

while(data):
    d = data & 0xFF
    binArr.append(d)
    data >>= 8

hexStr = binArr.tostring()
Vishal