`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
```

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 ;)

`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!

I send two weekly emails on building performant and resilient Web Applications with Python, JavaScript and PostgreSQL.
**No spam. Unscubscribe at any time.**

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 with Simple Parameters

Learn about functions with simple parameters in Python, including how the called can decide to use positional or keyword notation.

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