Python hex Function: Syntax, Behavior, and Examples
python hex function: Learn how Python's hex() built-in converts integers to hexadecimal strings, including handling negative numbers, formatting options, and common pi...
The Python hex function is a built-in that converts an integer to a lowercase hexadecimal string prefixed with 0x. For negative integers, it returns -0x followed by the hexadecimal representation of the absolute value. This function is part of the standard library and requires no imports. It is the direct way to get a hexadecimal string from an integer, but it is not the only way.
The hex Function's Signature and Return Value
The signature is hex(x), where x must be an integer or an object that implements the __index__() method. The return value is always a string. For a positive integer, the string starts with 0x. For zero, it is 0x0. For a negative integer, the string starts with -0x followed by the hex digits of the absolute value. The hexadecimal digits are always lowercase.
>>> hex(255) '0xff' >>> hex(0) '0x0' >>> hex(-255) '-0xff'
The function does not accept floats or strings. Passing a float raises TypeError because the function is defined only for integer-like values. This is a deliberate design choice to avoid ambiguity about how fractional parts should be represented.
Converting Integers to Hexadecimal Strings
The most common use is converting an integer to its hexadecimal representation for logging, debugging, or data serialization. The output includes the 0x prefix, which makes it clear that the string is hexadecimal.
value = 1024 hex_value = hex(value) print(hex_value) # '0x400'
For large integers, the conversion works the same way. Python integers have arbitrary precision, so hex() can handle numbers far beyond 64-bit limits.
big = 2**128 print(hex(big)) # '0x100000000000000000000000000000000'
The returned string is a regular Python string, so you can slice it, concatenate it, or pass it to any string method. If you need the hexadecimal digits without the 0x prefix, you can slice from index 2.
hex_value = hex(255) digits = hex_value[2:] # 'ff'
Handling Negative Numbers and Zero
Negative integers produce a string that starts with a minus sign, then 0x, then the hex digits of the absolute value. The sign is not part of the hex digits themselves. This is consistent with how Python represents negative integers in other bases.
>>> hex(-42) '-0x2a'
Zero is a special case: hex(0) returns '0x0'. There is no leading minus sign and no empty digit group. When you slice away the prefix, you get '0', which is correct for zero.
If you need to represent a negative number as a two's complement value, hex() is not the right tool. Two's complement representation depends on the bit width and is usually produced with bitwise operations or format() with a width specifier. For example, format(-1 & 0xFF, '02x') gives 'ff', but that is a different concept.
Using hex() with Custom Objects
The hex() function works with any object that defines the __index__() method. This method must return an integer. This is the same protocol used by bin() and oct(), and it is also used for slicing and indexing operations. By implementing __index__, you can make your custom numeric type convertible to a hexadecimal string.
class ColorChannel: def __init__(self, value): self.value = value def __index__(self): return self.value channel = ColorChannel(200) print(hex(channel)) # '0xc8'
This is useful when you have a wrapper type that represents an integer but should not be directly treated as one in arithmetic. The __index__ method gives hex() a clear way to extract the integer value without relying on int() conversion, which might have different semantics.
Comparing hex() with format() and f-strings
Python offers several ways to produce hexadecimal strings. The hex() function always includes the 0x prefix and uses lowercase digits. The format() function and f-strings give you more control over prefix, case, and width.
value = 255 print(hex(value)) # '0xff' print(format(value, 'x')) # 'ff' print(format(value, 'X')) # 'FF' print(f'{value:#x}') # '0xff' print(f'{value:02x}') # 'ff' (but with width 2, no effect here)
The # option in format specifiers adds the 0x prefix. Without it, you get only the digits. If you need uppercase digits, use 'X' instead of 'x'. The hex() function has no uppercase variant, so you must use format() or str.upper() if you need uppercase.
| Method | Prefix | Case | Width control |
|---|---|---|---|
hex() | Always 0x | Lowercase | None |
format(x, 'x') | No prefix | Lowercase | Yes |
format(x, 'X') | No prefix | Uppercase | Yes |
f'{x:#x}' | With # | Lowercase | Yes |
f'{x:#X}' | With # | Uppercase | Yes |
For most cases where you need a plain hex string without a prefix, format() is more direct than calling hex() and slicing. For cases where you want the prefix and lowercase, hex() is the simplest.
Common Pitfalls and Edge Cases
The most frequent mistake is passing a float to hex(). The function is not defined for floats, and you will get a TypeError. If you have a float that represents an integer, convert it with int() first, but be aware that this truncates any fractional part.
# Raises TypeError: 'float' object cannot be interpreted as an integer # hex(3.14)
Passing a string that looks like a number also fails. hex() does not parse strings. Use int() with a base if you need to parse a hexadecimal string.
Another edge case is the __index__ method returning a non-integer. The method must return an integer; otherwise, Python raises a TypeError. This is a common source of bugs when implementing custom numeric types.
For very large integers, the resulting string can be long. The memory used is proportional to the number of hexadecimal digits, which is about n / 4 bytes for an integer of n bits. This is rarely a problem, but it is worth remembering if you convert extremely large numbers in a tight loop.
Performance and Memory Considerations
The hex() function is implemented in C and is quite fast. It does allocate a new string on every call, so if you are converting the same integer repeatedly, you might want to store the result rather than call hex() each time. This is a micro-optimization, but it can matter in hot paths.
For large integers, the conversion time grows with the number of bits. The algorithm is linear in the bit length, so doubling the integer size roughly doubles the conversion time. There is no way to avoid this cost if you need the full hexadecimal representation.
If you only need a short prefix or a few digits, consider using bitwise operations to extract specific nibbles instead of converting the whole integer. For example, (value >> 4) & 0xF gives the second nibble. This can be more efficient when you only need a portion of the hex representation.
Practical Example: Converting RGB Values to Hex Colors
A common real-world use of hexadecimal conversion is building CSS color strings from RGB components. Each channel is an integer from 0 to 255. You can use hex() and slice off the prefix, but you must also pad each channel to two digits.
def rgb_to_hex(r, g, b): def channel_hex(value): return hex(value)[2:].zfill(2) return f'#{channel_hex(r)}{channel_hex(g)}{channel_hex(b)}' print(rgb_to_hex(255, 87, 51)) # '#ff5733'
The zfill(2) ensures that values below 16 produce a leading zero, such as 0x0a becoming '0a'. This function works for any integer in the 0–255 range. If you need uppercase hex digits, you can use format(value, '02X') instead, which avoids the slicing and padding entirely.
def rgb_to_hex_upper(r, g, b): return f'#{r:02X}{g:02X}{b:02X}'
The second version is more concise and uses the format specifier directly. It also avoids the intermediate hex() call. This example shows that while hex() is useful, there are situations where format() is more appropriate. Choose the tool that matches the exact output you need.