Жемчужины Python кода

В своих статьях я стараюсь рассказывать о небольших технологических штуках и проблемах, с которыми периодически сталкиваюсь. Но вот недавно я столкнулся с совершенно замечательным кодом, написанным на Python, и не могу пройти мимо, чтобы не покритиковать. Итак, есть вот такая функция:

def getkbits8(num, k, p):
    binary = bin(num)[2:]  # convert number into binary first
    leadingzeros = 8 - len(binary)  # Count the necessary leading zeros to fill byte
    binary = '0' * leadingzeros + binary  # Fill byte with leading zeros
    end = 8 - p - 1
    start = end - k + 1
    k_bit_sub_str = binary[start: end + 1]  # extract k  bit sub-string
    return int(k_bit_sub_str, 2)  # convert extracted sub-string into decimal again

Она используется следующим образом:

frequentlock = ParseDownlink._parsebinary(hexarray, 'uint8')  # Get whole register
ordict['vutrx_rx_frequentlock'] = getkbits8(frequentlock, 1, 0)  # Split register by bit position

На вход подаётся беззнаковый байт, некий индекс К и ещё один индекс Р. На выходе получается число. Я, признаюсь честно, не знаю Python и поэтому потратил много времени, чтобы понять, как работает эта функция. К слову сказать, мне нужно было переписать парсер для телеметрии Swampsat2 с Python на Java для своего проекта.

Итак, что же делает эта функция на самом деле? Ответ:

int frequentlock = dis.readUnsignedByte();
rxFrequentlock = ((frequentlock >> 0) & 0x1);

Эта функция всего-навсего выделяет биты из байта. Если нужно получить 4 старших бита, то нужно вызвать функцию вот так:

getkbits8(frequentlock, 4, 4)

Работает это так:

  • число конвертируется в строку. Например: bin(3) -> '0b11'
  • берётся строка. В моём примере - это ‘11’
  • спереди дописывается необходимое количество нулей. Так, чтобы длина стала равна 8. Это длина байта в битах.
  • выделяется подстрока нужного размера
  • она конвертируется в число и возвращается

Я не первый раз вижу настолько плохой код и поэтому оправился от шока достаточно быстро. Тем не менее меня всё не покидала мысль о мотивации программиста, который это писал. Предположим, что этот код писал новичок, и он, банально, не знал, что такое битовые операции. Но это не сходится с тем, что использованы сложные операции работы с массивами и строками. Неужели понять битовые операции было сложнее?

Второй вопрос, который я себе задал: зависит ли плохой код от языка программирования? Этот код написан на Python, и автор с лёгкостью и простым синтаксисом смог описать сложные операции. Чтобы ответить на этот вопрос, я надел перчатки и написал этот же код, но на Java:

private static int getkbits8(int num, int k, int p) {
	String binary = Integer.toBinaryString(num);
	int leadingzeros = 8 - binary.length();
	StringBuilder zeros = new StringBuilder();
	for (int i = 0; i < leadingzeros; i++) {
		zeros.append('0');
	}
	binary = zeros.toString() + binary;
	int end = 8 - p - 1;
	int start = end - k + 1;
	String k_bit_sub_str = binary.substring(start, end + 1);
	return Integer.parseInt(k_bit_sub_str, 2);
}

Если честно, то версия на Java кажется более громоздкой. И это хорошо. Если новичок задумает написать такую функцию, то он несколько раз подумает нет ли способа попроще. Этот пример лишний раз убеждает меня в том, что многословность Java, на самом деле, большой плюс. Весь Java код буквально кричит о том, что происходит выделение ресурсов и неэффективные операции, а также видно, что процессор будет выполнять странные вещи. В Python же достаточно написать '0' * leadingzeros.

Надеюсь, автор оригинального Python кода всего лишь проходил стажировку и не писал софт для настоящих спутников.