Метка: format

bin, oct, hex – системы исчисления

Вспомним основы информатики и поговорим о системах исчисления. В жизни мы привыкли к десятичной системе (base-10 или decimal), железо компьютеров оперирует в системе двоичной (base-2 или binary) – нулями и единицами. Часто приходится иметь дело с системой шестнадцатиричной (base-16 или hexadecimal), она позволяет записывать данные в 8 раз короче, чем двоичная. Реже встречается система восьмеричная (base-8 или octal). Система исчисления – это всего лишь средство представления числа, т.е. то, как мы его в строчку запишем или считаем, само число остается самим собой, независимо от системы.

Чтобы получить целое число из строки, записанной в какой-то системе исчисления, используем функцию int (второй параметр – база системы):

>>> int('42')  # по умолчанию 10 
42
>>> int('42', 10)  # тоже самое
42
>>> int('101010', 2)
42
>>> int('BEEF', 16)
48879
>>> int('7654', 8)
4012

Наоборот сделать из числа строку в какой-то системе – встроенные функции bin, oct и hex:

>>> bin(42)
'0b101010'
>>> hex(48879)
'0xbeef'
>>> oct(4012)
'0o7654'

В строке появились префиксы 0b, 0x и 0o. Как и в Си, в Python можно использовать эти префиксы для записи чисел в коде помимо обычного десятичного варианта, если это требуется:

>>> 0b11011, 0xDEAD, 0o777
(27, 57005, 511)

Однако, часто приходится иметь дело с чистыми HEX-данными без префиксов. Многие (да и я) делали вот так:

>>> hex(12345)[2:]
'3039'

Т.е. просто отрезали первые два символа. Это не очень интуитивно, да и может привести к неправильному поведению, если передать отрицательное число:

>>> hex(-12345)
'-0x3039'
>>> hex(-12345)[2:]
'x3039'

К счастью, есть встроенная функция format(value, format_spec) (не путать с str.format), которая форматирует значение, согласно спецификатору форматирования:

>>> format(3039, 'x'), format(3039, 'X'), format(3039, '#x')
('bdf', 'BDF', '0xbdf')
>>> format(120, 'b')
'1111000'
>>> format(79, 'o')
'117'
  • x — 16-ричное без префикса, маленькие букв
  • X — 16-ричное без префикса, заглавные буквы
  • #x — 16-ричное с префиксом, маленькие буквы
  • b — двоичное число без префикса и т.д.

format(value, format_spec) эквивалентная вызову type(value).__format__(value, format_spec).

🐉 Специально для канала @pyway. Подписывайтесь на мой канал в Телеграм @pyway 👈 

Quine на Python

Программисты тоже умеют развлекаться, так что давайте сегодня развлечемся и напишем quine (квайн). Квайн – это такая программа, которая выводит на экран свой же код, ни больше, ни меньше. Сразу договоримся, что пустая программа на Python, которая ничего не выводит, не считается квайном; это не интересно.

В Python у нас есть чудо-переменная, которая хранит путь к текущему интерпретируемому файлу, поэтому можно сделать так:

print(open(__file__).read())

Эта программа открывает свой же файл, читает и печатает его целиком. Но это жульничество, потому что в квайнах не принято читать файлы. Хорошо, а что если назвать файл print(__file__), записать в него print(__file__) и выполнить python "print(__file__)". Будет работать, но можешь вот без этих трюков, чисто кодом? Да без проблем!

Нам нужно что-то печатать, значит берем print:

>>> print('?')
?

Программа начинается с print…, значит и печатать будем тоже самое:

>>> print('print()')
print()

Не получается, потому что у нас тут уже два print да кавычки, а печатается только один. Так можно плодить print до бесконечности, но все равно не будет хватать одного в выводе. Будем решать поэтапно. Давайте заведем переменную s с кодом нашей программы.

>>> s='print()';print(s)
print()

Но код теперь начинается с s=, исправим:

>>> s='s=?;print(s)';print(s)
s=?;print(s)

Смотрите, уже похоже, осталось только на место знака вопроса воткнуть содержимое строки s из оригинального кода. Это самый важный момент. Используем format, а точнее s.format(s), который в определенном месте строки s вставит саму же строку s, таким образом, мы «разрываем рекурсию»:

>>> s='s={};print(s)';print(s.format(s))
s=s={};print(s);print(s)

Отлично! Но тут два недостатка: во-первых, не забыть добавить s.format(s) в саму строку s:

>>> s='s={};print(s.format(s))';print(s.format(s))
s=s={};print(s.format(s));print(s.format(s))

Во-вторых, нужно вернуть на место кавычки. Не зря я недавно рассказывал о флагах преобразования строк. Используем флаг {!r} в формате, чтобы вывести repr(s), который для строк содержит одинарные кавычки:

>>> s='s={!r};print(s.format(s))';print(s.format(s))
s='s={!r};print(s.format(s))';print(s.format(s))

Ура! Квайн готов и работает!

Вы можете сделать квайн короче, используя другой стиль форматирования строк через процент: {!r} заменяется на %r, s.format(s) на s%s, плюс экранируется процент внутри самой строки s%%s (%% понимается как сам знак процента, а не как место для подстановки):

>>> s='s=%r;print(s%%s)';print(s%s)
s='s=%r;print(s%%s)';print(s%s)

🐉 Специально для канала @pyway. Подписывайтесь на мой канал в Телеграм @pyway 👈 

Флаги преобразования

При форматировании строк доступны 3 флага преобразования объекта в строку: !r, !s и !a.

>>> x = "дом"
>>> f'{x}'
'дом'
>>> f'{x!r}'
"'дом'"
>>> f'{x!s}'
'дом'
>>> f'{x!a}'
"'\\u0434\\u043e\\u043c'"

Для фанатов format:

>>> '{0!r}'.format(x)
"'дом'"
>>> '{!r}'.format(x)
"'дом'"

Флаг !r вызывает repr(x), а флаг !s вызывает str(x). Флаг !a вызывает ascii(repr(x)). Функция ascii превращает все символы за пределами набора ASCII (включая русские буквы в юникоде) в их коды. Если флаг не указан, то по умолчанию считается, что он !s.

Для классов __repr__ и __str__ могут иметь различное определение:

class Foo:
    def __repr__(self):
        return "репр"
    def __str__(self):
        return "строка"
x = Foo()
print(f'{x!r}')  # репр
print(f'{x!s}')  # строка

Если __str__ нет, то будет вызван __repr__.

Рекомендации: __str__ должен давать нам человеко-читаемое описание объекта, а __repr__ – уникальное представление объекта, по которому можно частично или полностью восстановить состояние этого объекта или хотя бы помочь с отладкой. __str__ – для пользователей, __repr__ — для питонистов.

📎 Отличный пример для наглядности – datetime:

>>> import datetime
>>> dt = datetime.datetime(2019, 7, 27)
>>> repr(dt)
'datetime.datetime(2019, 7, 27, 0, 0)'
>>> str(dt)
'2019-07-27 00:00:00'
>>> eval(repr(dt)) == dt
True

str от datetime просто покажет нам дату и время в удобном формате; repr от datetime вернет строку, в которой будет вызов описан конструктора конкретно этого объекта, да так, что при исполнении этой строки как кода на Python функцией eval – мы получим объект datetime для той же даты. Впрочем, никто нас не обязывает делать этот трюк для каждого объекта.

🐉 Специально для канала @pyway. Подписывайтесь на мой канал в Телеграм @pyway 👈