The Built-In bin(), oct() and hex() Functions

by Christoph Schiessl on Python

Today, I want to explore three built-ins at once: the bin(), the oct(), and the hex() functions. It makes sense to talk about all three at once because they are very similar, and if you learn about one, then your knowledge easily transfers to the other two.

All three take a single parameter, which must be an int object or interpretable as an int if it is a different type — we get to details of the conversion logic later.

Python 3.12.3 (main, Apr 21 2024, 14:06:33) [GCC 13.2.1 20230801] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> bin(255), bin(-255)
('0b11111111', '-0b11111111')
>>> oct(255), oct(-255)
('0o377', '-0o377')
>>> hex(255), hex(-255)
('0xff', '-0xff')

As you can see, bin() returns a string that is a binary number (i.e., a number that uses two distinct digits), oct() returns a string that is an octal number (i.e., a number that uses eight distinct digits), and finally, hex() returns a string that is a hexadecimal number (i.e., a number that uses sixteen distinct digits).

Note that we only have ten digits — 0 through 9 — available, but we need sixteen for hexadecimal numbers. Hence, these hexadecimal numbers use the letters a through f to denote the remaining six digits.

Furthermore, all returned strings are prefixed with 0b, 0o, or 0x, making them valid Python expressions. For negative integers, the minus sign goes before the prefix.

Python 3.12.3 (main, Apr 21 2024, 14:06:33) [GCC 13.2.1 20230801] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> 0b11111111 == 255
True
>>> -0b11111111 == -255
True
>>> 0o377 == 255
True
>>> -0o377 == -255
True
>>> 0xff == 255
True
>>> -0xff == -255
True

How to Read Binary, Octal, and Hexadecimal Numbers

Decimal numbers use ten distinct digits; therefore, they are also called base 10 numbers. Now, look at a decimal number like 255 and read it from the right to the left — which is, read from the least significant to the most significant digit. Then the right-most digit represents 5, the digit in the middle represents 50, and the left-most digit represents 200. To get the final number, you calculate the sum: 5 + 50 + 200 == 255.

A more generic way to look at this is the following: The right-most digit represents 5 * 10 ** 0 == 5 * 1 == 5, the digit in the middle represents 5 * 10 ** 1 == 5 * 10 == 50, and the left-most digit represents 2 * 10 ** 2 == 2 * 100 == 200. So, the exponent starts with zero for the least significant digit and is then incremented each time you parse the next, more significant digit.

I'm using a 10 in this calculation because, so far, we have been talking about a base 10 number. However, the same calculation also applies to numbers with a different base. Generally speaking, for a base n number, you would replace the 10 with n in the calculation above.

We have already established that binary numbers use two distinct digits, so they are base 2. Similarly, octal numbers are base 8, and hexadecimal numbers are base 16 because they use eight and sixteen distinct digits, respectively.

Take, for example, the octal number 377. If you have to parse this by hand, you would go from left to right and calculate as follows: 7 * 8 ** 0 == 7 * 1 == 7, 7 * 8 ** 1 == 7 * 8 == 56, and 3 * 8 ** 2 == 3 * 64 == 192. This gives a final number of 7 + 56 + 192 == 255.

Again, this calculation is not specific to any number system. If you wanted to, you could invent a base 42 number ;)

Conversion of objects other than int

I mentioned before that the parameter of bin(), oct(), and hex() must be an int or interpretable as an int. Well, most objects are not losslessly convertible to an int.

Python 3.12.3 (main, Apr 20 2024, 16:22:09) [GCC 13.2.1 20230801] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> bin(object())
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'object' object cannot be interpreted as an integer
>>> oct(1.6) # not using normal int conversion!
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'float' object cannot be interpreted as an integer

Ordinary integer conversion, if available, is lossless because the digits after the floating point are truncated (i.e., int(1.6) == 1). Instead, a special procedure normally used for indexing is utilized. As it turns out, the operator module defines an index() operator that delegates to the object's magic __index__() method behind the scenes. This magic method, if implemented, is supposed to provide the ability to do lossless conversion. We can easily demonstrate this:

Python 3.12.3 (main, Apr 20 2024, 16:22:09) [GCC 13.2.1 20230801] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import operator
>>> operator.index(object()) # doesn't work with arbitrary objects
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'object' object cannot be interpreted as an integer
>>> operator.index(1.6)      # doesn't work with floats
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'float' object cannot be interpreted as an integer
>>> '__index__' in dir(255)
True
>>> operator.index(255)      # works with ints that implement __index__()
255
>>> class Foo:
...   def __index__(self) -> int:
...     return 255
...
>>> operator.index(Foo())    # works with custom objects that implement __index__()
255

Last but not least, we can also demonstrate all of this together:

Python 3.12.3 (main, Apr 20 2024, 16:22:09) [GCC 13.2.1 20230801] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> class Foo:
...   def __index__(self):
...     print("Foo.__index__() has been called!")
...     return 255
...
>>> bin(Foo())
Foo.__index__() has been called!
'0b11111111'
>>> oct(Foo())
Foo.__index__() has been called!
'0o377'
>>> hex(Foo())
Foo.__index__() has been called!
'0xff'

That's everything for today. Thank you for reading, and see you soon!

Christoph Schiessl

Hi, I'm Christoph Schiessl.

I help you build robust and fast Web Applications.


I'm available for hire as a freelance web developer, so you can take advantage of my more than a decade of experience working on many projects across several industries. Most of my clients are building web-based SaaS applications in a B2B context and depend on my expertise in various capacities.

More often than not, my involvement includes hands-on development work using technologies like Python, JavaScript, and PostgreSQL. Furthermore, if you already have an established team, I can support you as a technical product manager with a passion for simplifying complex processes. Lastly, I'm an avid writer and educator who takes pride in breaking technical concepts down into the simplest possible terms.

Continue Reading?

Here are a few more Articles for you ...


The Built-In chr() and ord() Functions

Discover Python's built-in functions chr() and ord() for handling Unicode characters and converting between integers and characters.

By Christoph Schiessl on Python

Function Definition Basics

Explore Python's function definition statement and discover its features with this series of articles. Get started with this simple introduction.

By Christoph Schiessl on Python

The Built-In sum() Function

In this article, we explore Python's built-in sum() function, its parameters, and some extreme use cases it wasn't even designed for.

By Christoph Schiessl on Python

Web App Reverse Checklist

Ready to Build Your Next Web App?

Get my Web App Reverse Checklist first ...


Software Engineering is often driven by fashion, but swimming with the current is rarely the best choice. In addition to knowing what to do, it's equally important to know what not to do. And this is precisely what my free Web App Reverse Checklist will help you with.

Subscribe below to get your free copy of my Reverse Checklist delivered to your inbox. Afterward, you can expect one weekly email on building resilient Web Applications using Python, JavaScript, and PostgreSQL.

By the way, it goes without saying that I'm not sharing your email address with anyone, and you're free to unsubscribe at any time. No spam. No commitments. No questions asked.