How to Specify Hexadecimal and Octal Integers in Python

Python lets you write integers in bases other than ten directly in your source code. Two of the most useful are hexadecimal (base 16) and octal (base 8), and Python makes both easy with simple prefix syntax that is readable, unambiguous, and fully compatible with every integer operation in the language.

Decimal integers are fine for everyday counting, but a lot of real programming work involves bit manipulation, file permissions, memory addresses, color values, and network protocol fields. In those domains, hexadecimal and octal are not exotic curiosities — they are the natural way to express the data. Python supports both as first-class integer literals, meaning you can write them directly in code with no conversion step required.

Why Other Number Bases Exist in Python

Every integer in Python is stored the same way regardless of how you typed it. There is no such thing as a "hex variable" or an "octal variable." The prefix you use when writing a literal is just a notation convenience. Once Python reads the source, 255, 0xFF, and 0o377 are identical objects in memory — they are all the integer two hundred fifty-five.

So why bother writing them differently? Because the notation you choose communicates intent. When you write 0xFF in code that sets a bitmask, you are signaling to the next reader (and your future self) that this value is being thought about in hexadecimal terms. That context matters enormously in areas like:

  • Color codes in graphics, where #1A2B3C maps directly to 0x1A2B3C
  • Unix file permissions, where 0o755 is far clearer than 493
  • Bitwise operations on flags, registers, and protocol headers
  • Cryptography and hashing, where hex output is the universal format
  • Embedded systems and hardware register maps
Note

Python also supports binary literals using the 0b prefix (for example, 0b1010 equals 10). This article focuses on hex and octal, but the same rules apply to binary: same prefix syntax, same conversion functions, same underlying integer type.

Writing Hexadecimal Integers with 0x

To write a hexadecimal integer literal in Python, prefix the digits with 0x or 0X. After the prefix you can use the digits 0 through 9 and the letters A through F (uppercase or lowercase — Python accepts either). There is no requirement to pick one case and stick to it within a single literal, though consistency is a good habit.

# Basic hexadecimal literals
a = 0xFF       # 255
b = 0x1A       # 26
c = 0xDEAD     # 57005
d = 0x00ff     # 255 — lowercase letters, same value as 0xFF
e = 0xBEEF     # 48879

print(a)       # 255
print(b)       # 26
print(c)       # 57005

# They behave like any other integer
print(a + b)   # 281
print(c * 2)   # 114010
print(type(a)) # <class 'int'>

Notice that print() displays decimal by default. The hex prefix you typed is gone once the interpreter has parsed the source — what you have is just an int. To display it back in hex form, you need to ask for it explicitly, which is covered in the conversion section below.

Hex literals in expressions and assignments

Hex literals work anywhere a decimal integer would. You can use them in arithmetic, comparisons, list definitions, function arguments, and dictionary keys. The following example shows a bitmask pattern that is common in systems programming and network code:

# Using hex literals as bitmasks
STATUS_ACTIVE   = 0x01
STATUS_ADMIN    = 0x02
STATUS_VERIFIED = 0x04
STATUS_LOCKED   = 0x08

user_flags = STATUS_ACTIVE | STATUS_VERIFIED  # bitwise OR
print(user_flags)                              # 5

# Check a specific bit
is_admin = bool(user_flags & STATUS_ADMIN)
print(is_admin)   # False

# RGB color expressed as individual hex components
RED   = 0xFF
GREEN = 0x80
BLUE  = 0x00
color = (RED, GREEN, BLUE)
print(color)  # (255, 128, 0)
Pro Tip

Python 3.6 and later allow underscores inside integer literals to improve readability. 0xFF_FF_FF and 0xFFFFFF are identical. This is especially handy with long hex addresses or color codes: 0x00_1A_2B_3C maps cleanly to a MAC address or memory offset without losing any precision.

Writing Octal Integers with 0o

Octal uses base 8, meaning its digits run from 0 to 7. To write an octal literal in Python, prefix the digits with 0o or 0O. No digit in an octal literal can be 8 or 9 — those values do not exist in base 8, and Python will raise a SyntaxError if you try.

# Basic octal literals
x = 0o7    # 7
y = 0o10   # 8
z = 0o77   # 63
w = 0o100  # 64
v = 0o755  # 493

print(x)   # 7
print(y)   # 8
print(z)   # 63
print(w)   # 64
print(v)   # 493

The place where octal shows up most naturally in Python is Unix file permission mode arguments. When you call os.chmod(), the mode is a bitmask where each group of three bits represents read, write, and execute permissions for owner, group, and others respectively. Octal expresses each three-bit group as a single digit, making the permission pattern visually obvious.

import os
import stat

# Set a file to rwxr-xr-x (owner: read/write/execute, group/others: read/execute)
os.chmod("script.sh", 0o755)

# Common permission values and what they mean
OWNER_RWX   = 0o700   # owner: read, write, execute
GROUP_RX    = 0o050   # group: read, execute
OTHERS_RX   = 0o005   # others: read, execute

full_perm = OWNER_RWX | GROUP_RX | OTHERS_RX
print(oct(full_perm))  # 0o755

# Using stat module constants is also valid, but 0o755 is widely understood
print(full_perm == stat.S_IRWXU | stat.S_IRGRP | stat.S_IXGRP |
                   stat.S_IROTH | stat.S_IXOTH)  # True
Warning

In Python 2, a plain leading zero was the octal prefix: 0755 meant octal 755. Python 3 removed this entirely. A leading zero followed by digits other than o, x, or b now raises a SyntaxError. If you see old code with bare leading zeros, do not assume it is decimal — it was octal, and it will not run in Python 3 at all.

Converting Between Bases at Runtime

Writing literals covers compile-time values. When you have an integer whose value is only known at runtime and you need to display or format it in a different base, Python provides dedicated built-in functions and format strings.

Built-in conversion functions

The hex() function takes an integer and returns a string with the 0x prefix. The oct() function does the same for octal, returning a string with the 0o prefix. For binary there is bin() with the 0b prefix. All three accept any integer, including negative numbers.

n = 255

print(hex(n))   # '0xff'
print(oct(n))   # '0o377'
print(bin(n))   # '0b11111111'

# Negative values work too
print(hex(-16)) # '-0x10'
print(oct(-8))  # '-0o10'

# The return value is a string, not an integer
result = hex(n)
print(type(result))  # <class 'str'>

Format strings for fine-grained control

When you need more control over output — such as uppercase letters, zero-padding, or stripping the prefix — Python's format mini-language gives you that control through f-strings and format().

n = 255

# Lowercase hex, no prefix
print(f"{n:x}")      # ff

# Uppercase hex, no prefix
print(f"{n:X}")      # FF

# Hex with 0x prefix
print(f"{n:#x}")     # 0xff
print(f"{n:#X}")     # 0XFF

# Zero-padded to 8 hex digits (useful for fixed-width fields)
print(f"{n:08x}")    # 000000ff
print(f"{n:#010x}")  # 0x000000ff

# Octal with and without prefix
print(f"{n:o}")      # 377
print(f"{n:#o}")     # 0o377

# Practical: formatting a list of values as hex
values = [0, 127, 128, 255]
for v in values:
    print(f"  decimal {v:3d}  ->  hex {v:#04x}  ->  octal {v:#06o}")

Parsing hex and octal strings back to integers

Going the other direction — converting a string that contains a hex or octal representation back into an integer — uses the built-in int() function with a second argument specifying the base. Passing 0 as the base tells Python to detect the base automatically from the prefix.

# Explicit base conversion
print(int("ff", 16))      # 255
print(int("0xff", 16))    # 255
print(int("FF", 16))      # 255

print(int("377", 8))      # 255
print(int("0o377", 8))    # 255

# Auto-detect base from prefix (base=0)
print(int("0xff", 0))     # 255
print(int("0o377", 0))    # 255
print(int("0b11111111", 0))  # 255
print(int("255", 0))      # 255  (plain decimal, no prefix)

# Useful when reading hex strings from user input or config files
raw = input("Enter a value (hex, octal, or decimal): ")
value = int(raw, 0)
print(f"Interpreted as: {value}")
Pro Tip

Using int(value, 0) is a clean way to write utilities that accept user-supplied integers in any base. It correctly handles 0x, 0o, and 0b prefixes and falls back to decimal for plain numeric strings — all in one call with no branching logic.

Practical Patterns and Common Mistakes

A few patterns come up repeatedly once you start working with hex and octal integers in real code. The examples below show some of the idioms worth knowing, along with the mistakes that trip up new Python programmers.

Combining hex literals with bitwise operations

# Reading individual bytes from a 32-bit integer
value = 0xDEADBEEF

byte0 = (value >> 0)  & 0xFF   # least significant byte: 0xEF = 239
byte1 = (value >> 8)  & 0xFF   # 0xBE = 190
byte2 = (value >> 16) & 0xFF   # 0xAD = 173
byte3 = (value >> 24) & 0xFF   # 0xDE = 222

print(f"Bytes: {byte3:#04x} {byte2:#04x} {byte1:#04x} {byte0:#04x}")
# Bytes: 0xde 0xad 0xbe 0xef

# Reassemble from bytes
reconstructed = (byte3 << 24) | (byte2 << 16) | (byte1 << 8) | byte0
print(hex(reconstructed))  # 0xdeadbeef

Hex in byte sequences

When working with bytes objects — common in networking, file parsing, and cryptography — hex escape sequences inside byte literals use the same digit characters as hex integer literals, just with a different syntax:

# bytes literals use \x escapes (not the 0x prefix)
raw = b"\xDE\xAD\xBE\xEF"
print(raw)           # b'\xde\xad\xbe\xef'
print(len(raw))      # 4

# Convert bytes to a hex string for display
print(raw.hex())         # deadbeef
print(raw.hex(" "))      # de ad be ef  (Python 3.8+, separator argument)

# Convert a hex string back to bytes
restored = bytes.fromhex("deadbeef")
print(restored == raw)   # True

Common mistakes to avoid

The error that catches people coming from Python 2 is the bare leading zero: writing 0755 expecting it to be octal. Python 3 will not accept that — it is a SyntaxError. Always write 0o755.

A subtler mistake is mixing up hex() (which returns a string) with a hex literal (which is an integer). If you do arithmetic on the result of hex(), you will get a TypeError because strings do not support addition the way integers do. Keep your types straight: literals and int() give you integers, while hex(), oct(), and format strings give you strings.

# This is fine — hex literal is an int
result = 0xFF + 1
print(result)  # 256

# This raises TypeError — hex() returns a string
bad = hex(255) + 1  # TypeError: can only concatenate str (not "int") to str

# Correct approach if you need to convert and then compute
s = hex(255)          # '0xff' (a string)
n = int(s, 16)        # 255 (an integer)
print(n + 1)          # 256

Key Takeaways

  1. Use 0x for hexadecimal and 0o for octal: These prefixes tell Python the base of your integer literal. The resulting value is a plain int — there is no special "hex type" or "octal type." Letters A through F are valid in hex literals and can be upper or lowercase.
  2. All integer operations work on all bases: Hex and octal literals participate in arithmetic, comparisons, bitwise operations, and everything else exactly as decimal integers do. The prefix is notation, not type.
  3. Use hex(), oct(), and format strings to convert at runtime: The hex() and oct() functions return strings. For output control — padding, prefix, case — use f-strings with format specifiers like :x, :X, :#010x, and :o.
  4. Parse unknown-base strings with int(s, 0): When you receive a string that might be hex, octal, binary, or decimal, passing base 0 to int() lets Python detect the base from the prefix automatically.
  5. Python 3 requires the 0o prefix for octal: A bare leading zero is a syntax error in Python 3. Code like 0755 from Python 2 must be rewritten as 0o755.

Once hex and octal literals become part of your toolkit, a lot of code involving colors, permissions, protocol headers, and bit manipulation becomes significantly cleaner to write and far easier to read. The values mean what they look like, and that clarity pays off every time someone has to understand what the code is doing.

back to articles