Работа с битами в Python: операторы, маски и практические применения

Раздел: Обработка данных -> Битовые операции и кодирование

Основы побитовых операций в Python

Битовые операции позволяют напрямую манипулировать отдельными битами целых чисел. В Python используются операторы: & (И), | (ИЛИ), ^ (исключающее ИЛИ), ~ (НЕ), << (сдвиг влево), >> (сдвиг вправо). Они работают с целыми числами произвольной точности, представляя их в двоичном виде.

Эффективная работа с флагами с помощью битовых масок

Наиболее эффективный способ хранить и проверять несколько логических флагов – использовать одно целое число, где каждый бит отвечает за свой флаг. Это экономит память и позволяет выполнять проверки за одну операцию.


# Определение флагов как степеней двойки
READ = 1      # 001
WRITE = 2     # 010
EXECUTE = 4   # 100

# Установка флагов
permissions = 0
permissions |= READ   # теперь permissions = 1
permissions |= WRITE  # теперь permissions = 3 (011)

# Проверка флага
if permissions & READ:
    print("Чтение разрешено")  # выведется

# Сброс флага
permissions &= ~WRITE
print(permissions)  # 1

Python биты (биты в python)

Маска ~WRITE инвертирует биты флага записи, а операция &= сбрасывает его. Такой подход используется в системных правах, статусах модулей, настройках.

Как проверить, установлен ли определённый бит?


x = 0b101010  # 42
bit_index = 3  # проверяем бит номер 3 (с нуля)
mask = 1 << bit_index
if x & mask:
    print(f"Бит {bit_index} установлен")
else:
    print(f"Бит {bit_index} не установлен")

Используем сдвиг единицы на нужную позицию и операцию И. Результат: x & mask не равен нулю, если бит установлен.

Ошибка: забывание о нумерации битов (младший бит – 0) или путаница с направлением сдвига. Также при использовании отрицательных чисел побитовые операции ведут себя в соответствии с дополнительным кодом, что может давать неожиданные результаты.

Решение: всегда работать с неотрицательными целыми или использовать int.from_bytes для явного представления.

Как установить или сбросить конкретный бит?


flags = 0b0011
# Установить бит 2 (сделать 0b0111)
flags |= (1 << 2)
# Сбросить бит 0 (сделать 0b0110)
flags &= ~(1 << 0)
print(bin(flags))  # 0b110

Для установки используется операция ИЛИ, для сброса – И с инвертированной маской.

Типичная ошибка: забыть инвертировать маску при сбросе, что приводит к обнулению всех остальных битов.

Как инвертировать (переключить) значение бита?


value = 0b1100  # 12
# Переключить бит 2 (0b1100 -> 0b1000)
value ^= (1 << 2)
print(bin(value))  # 0b1000

Операция XOR (^) с маской переключает бит: если был 0, становится 1, и наоборот.

Как извлечь несколько последовательных битов (поле)?


# Допустим, мы хотим получить биты 2..5 (4 бита) из числа
number = 0b11110101  # 245
# Маска для 4 бит: 0b1111
# Сдвигаем число вправо на 2, затем накладываем маску
field = (number >> 2) & 0b1111
print(field)  # 0b1101 = 13

Сначала сдвигаем число так, чтобы нужные биты оказались младшими, затем обрезаем маской. Этот приём используется при работе с упакованными данными, например, в протоколах.

Как упаковать несколько значений в одно целое?


# Упакуем три числа: a=5 (3 бита), b=2 (2 бита), c=7 (4 бита)
a = 5      # 101
b = 2      # 10
c = 7      # 0111
packed = (a & 0b111) | ((b & 0b11) << 3) | ((c & 0b1111) << 5)
print(bin(packed))  # 0b111010101 (пример, зависит от порядка)

Каждое значение маскируется, чтобы не выйти за пределы отведённых бит, затем сдвигается на нужную позицию. Все части объединяются через ИЛИ. Так реализуется компактное хранение данных.

Проблема: неправильный сдвиг или маска могут привести к наложению значений. Нужно тщательно рассчитывать позиции.

Использование битовых операций для кодирования

Битовые операции часто применяются для сжатия, шифрования (например, простой шифр XOR) и работы с двоичными протоколами. Операция XOR позволяет обратимо зашифровать данные: c = a ^ b, затем a = c ^ b.

Расширенные примеры работы с битами

1. Функция для вывода двоичного представления с разделением на байты

Пример

def bin_view(x, width=8):
    """Возвращает строку с двоичным представлением числа, разделяя по width бит."""
    binary = bin(x & ((1 << (width * 8)) - 1))[2:].zfill(width * 8)
    return ' '.join(binary[i:i+width] for i in range(0, len(binary), width))

print(bin_view(0x4A, 8))   # 01001010
print(bin_view(0x1F3, 8))  # 00000001 11110011
01001010
00000001 11110011

Функция полезна для отладки при работе с байтовыми потоками.

2. Кодирование и декодирование RGB цвета в 24-битное число

Пример

def rgb_to_int(r, g, b):
    return (r << 16) | (g << 8) | b

def int_to_rgb(color):
    r = (color >> 16) & 0xFF
    g = (color >> 8) & 0xFF
    b = color & 0xFF
    return r, g, b

col = rgb_to_int(255, 128, 64)
print(f"Число: {col}, 0x{col:06X}")  # 0xFF8040
r, g, b = int_to_rgb(col)
print(f"R={r}, G={g}, B={b}")
Число: 16744512, 0xFF8040
R=255, G=128, B=64

3. Простое шифрование XOR с ключом

Пример

def xor_cipher(data, key):
    # data - байтовая строка, key - целое число (0-255)
    return bytes(b ^ key for b in data)

plaintext = b"Hello"
key = 0xAA
cipher = xor_cipher(plaintext, key)
decrypted = xor_cipher(cipher, key)
print("Зашифровано:", cipher.hex())
print("Расшифровано:", decrypted)
Зашифровано: e22fcfc4c7
Расшифровано: b'Hello'

Из-за обратимости XOR такой шифр применяется в простых маскирующих алгоритмах.

4. Определение чётности числа через побитовое И

Пример

numbers = [12, 13, 0, -4]
for n in numbers:
    if n & 1:
        print(f"{n} нечётное")
    else:
        print(f"{n} чётное")
12 чётное
13 нечётное
0 чётное
-4 чётное

Младший бит равен 1 для нечётных чисел. Отрицательные числа в Python представлены с бесконечной точностью, но младшие биты работают аналогично.

5. Быстрая проверка, является ли число степенью двойки

Пример

def is_power_of_two(n):
    return n > 0 and (n & (n - 1)) == 0

print(is_power_of_two(8))   # True
print(is_power_of_two(10))  # False
print(is_power_of_two(0))   # False

У чисел, являющихся степенью двойки, в двоичном виде только один установленный бит. Выражение n & (n-1) обнуляет младший бит.

6. Извлечение байтов из многобайтового числа

Пример

def get_bytes(value, num_bytes=4):
    return [(value >> (8 * i)) & 0xFF for i in range(num_bytes)]

print(get_bytes(0x12345678, 4))  # [0x78, 0x56, 0x34, 0x12]
[120, 86, 52, 18]

Порядок little-endian (младший байт первым). Для big-endian нужно изменить порядок обхода.

7. Использование битовых операций для оптимизации проверки пересечения множеств

Пример

# Если множества представлены битовыми масками (до 64 элементов)
set_a = 0b1101  # {0,2,3}
set_b = 0b1011  # {0,1,3}
# Пересечение: побитовое И
intersection = set_a & set_b  # 0b1001 (элементы 0 и 3)
print(bin(intersection))
# Объединение:
union = set_a | set_b  # 0b1111
# Разность:
diff = set_a & ~set_b  # 0b0100 (элемент 2)

Операции над битовыми масками выполняются за константное время, что быстрее итеративных методов.

Биты в Python - comments

En
Python биты (python)