
Что такое бинарные (или битовые) операции и как из можно использовать в Python?
В фреймворке PyQt (и PySide тоже) часто встречается настройка чего-либо с помощью так называемых флагов.
widget.setWindowFlags(Qt.Window)
Взаимодействие нескольких флагов делается с помощью бинарных (или побитовых) операторов.
Несколько флагов можно указать с помощью оператора "|"
list_item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)
исключить флаг из уже имеющегося набора можно так
list_item.setFlags(list_item.flags() ^ Qt.ItemIsEnabled)
Добавить новый флаг к имеющимся можно так
list_item.setFlags(list_item.flags() | Qt.ItemIsEnabled)
А проверка наличия делается так
is_enabled = item.flags() & Qt.ItemIsEnabled > 0
Почему именно так? Всё дело в том как именно работают побитовые операторы. Всего есть 6 основных операторов:
| OR
& AND
^ XOR (исключающее OR)
~ NOT (унарная операция)
>> сдвиг вправо
<< сдвиг влево
Эти операторы работают с числами в двоичном представлении. Условно говоря, они ставят числа в двоичном виде
друг над другом и по очереди обрабатывают каждый столбик с битами.
Например, берём два числа, и сразу смотрим как оно выглядит в двоичном виде
(Python отбрасывает ведущие нули, так что рядом допишу более удобную форму)
>>> a = 3
>>> bin(a)
'0b11' # 011
>>> b = 6
>>> bin(b)
'0b110' # 110
Оператор OR
В результат пишет 1 если в одном из элементов есть 1
>>> 3|6
7
в двоичном виде это выглядит так (запишем столбиком)
_011
|110
=111
В каждом столбце был найден 1, поэтому в результате все биты равны 1
Оператор AND
В результат ставит 1 только если оба бита равны 1
>>> 3&6
2
Бинарный вид
_011
&110
=010
Только на 2й позиции оба бита равны 1.
Оператор XOR
Пишет 1 на бит результата, для которого только один из соответствующих битов операндов равен 1.
>>> 3^6
5
_011
&110
=101
Оператор NOT
Заменяет каждый бит на противоположный. Эта операция унарная, то есть поддерживает только один операнд.
>>> ~3
-4
~011
=100
Здесь всё понятно. Но давайте попробуем другое число:
~50
=-51
~110010
=-110011
Вот тут не очень понятно что произошло) Это связано со способом представления отрицательных чисел в двоичном виде.
Ведь мы не можем в память записать отрицательные биты. Для этого используется ведущий 0 или 1.
Но это тема не поместится в пост, советую поискать информацию в интернете самостоятельно). Если кратко и из
документации, то:Побитовая операция НЕ для числа x соответствует -(x+1)
Сдвиг
Здесь всё просто. Все биты сдвигаются на указанное количество шагов подставляя нули
>>> 3 << 1
6
011 << 1
110
Кстати, преобразовать бинарное представление обратно в число можно с помощью функции int() указав разрядность системы исчисления.
>>> int('11001', 2)
25
Лично я на практике встречал использование побитовых операторов в двух ситуациях (их конечно намного больше).
1. Сдвиг, который соответствует некоторой математической операции (арифметический сдвиг) но работает несравнимо быстрей.
Например сдвиг влево равен выражению a*2**b
a<<b == a*2**b
А также определение знака числа или нахождение модуля без условного оператора, что также очень быстро делается. И другие операции.
2. Числовые маски.
Что это такое?
Создаем несколько переменных, в которых в бинарном представлении все ячейки заполнены нулями кроме одной позиции. И у каждой переменной используется своя уникальная позиция для бита 1.
FLAG1 = int('001', 2) # 2
FLAG2 = int('010', 2) # 4
FLAG3 = int('100', 2) # 8
Теперь с помощью оператора OR можем объединять все биты в одну маску
>>> flags = FLAG1 | FLAG2
3 # 011
А после проверить, входит ли определённый флаг в состав битов маски?
>>> flags & FLAG3
0 # 000 нет совпадений
>>> flags & FLAG2
2 # 010 совпал второй бит
Если результат больше 0 то флаг присутствует в маске. Если результат 0 то такого флага нет.
Чтобы получить тип bool можем писать так
bool(flags & FLAG2)
или так
flags & FLAG2 > 0
и, очевидно, так
if flags & FLAG1:
...
Где могут пригодиться такие маски? Один пример был про флаги в Qt фреймворке. Также такой способ часто используют в организации прав доступа к ресурсам.
READ = int('001', 2) # 2
WRITE = int('010', 2) # 4
DELETE = int('100', 2) # 8
USER = READ
MODERATOR = READ | WRITE
ADMIN = READ | WRITE | DELETE
can_write = ADMIN & WRITE
Не сложно представить альтернативу на простом Python
READ = 1
WRITE = 2
DELETE = 3
USER = [READ]
MODERATOR = [READ, WRITE]
ADMIN = [READ, WRITE, DELETE]
can_write = WRITE in ADMIN
Оператор in работает довольно шустро, но всё равно медленней чем побитовый оператор.