В предыдущей части мы узнали, что компьютер может выполнять команды (приказы, функции), руководствуясь параметрами. Вы наверняка заметили, что в параметрах мы часто указывали, с файлом какого типа мы хотим произвести действие: с изображением, pdf-документом, текстом. То есть на самом деле наш сценарий состоит не только из действий, но и из данных, над которыми эти действия выполняются.
В этом параграфе мы поговорим о данных с точки зрения их хранения в компьютере и преобразования командами. Здесь данные для нас — любая информация в компьютере в любом виде: изображение, звук, текст, pdf-документ, логин в форме заявки на странице интернет-магазина и т. д.
Также мы приведём небольшие отрывки кода на Python, которые преобразуют данные. Вы можете пропускать их при чтении — сосредоточьтесь только на результатах выполнения. Ничего страшного, если они покажутся вам непонятными, цель главы не в этом. А про основы Python мы ещё поговорим!
Числа
Компьютеры когда-то назывались электронно-вычислительными машинами (ЭВМ). Это подчёркивало, что данные внутри программ тех времён в основном числа, а команды — операции с ними. ЭВМ таким образом экономили огромное количество времени для учёных и инженеров, моделируя ситуации из реальной жизни или абстрактной математической модели. Числа обычно считаются самым простым типом данных, который обрабатывается скриптами.
В компьютере числа хранятся в двоичной системе счисления (в ней есть только две цифры: 0 и 1). Она такая же позиционная, как и десятеричная, к которой мы все привыкли. То есть правила всех действий, включая сложение и умножение столбиком, остаются абсолютно такими же.
Может показаться, что раз компьютер оперирует только двумя цифрами, то это ограничивает его возможности. Но это не так. Например, мы используем десятеричную систему счисления, в которой 10 цифр, — но мы же не ограничены в выборе только 10 вариантами. Всё-таки из цифр мы можем сконструировать любое число, например 343 935 112
.
Выбор именно двоичной системы для компьютеров в первую очередь продиктован простотой создания — чем меньше состояний у системы, тем проще её моделировать. Посмотрите, как прекрасна таблица умножения в двоичной системе:
0 * 0 = 0
.
1 * 0 = 0
.
1 * 1 = 1
.
Если бы люди выбрали двоичную систему счисления в качестве основной, нам бы никогда не пришлось учить таблицу умножения.
Двигаемся дальше. Мы часто в больших числах выделяем разряды по 3 цифры, чтобы было удобней читать, например: 2 726 382 293
.
Компьютер тоже выделяет разряды, но не по 3, а по 8. Вот как можно записать число выше в двоичной системе: 10100010 10000001 01001010 11010101
Один разряд в двоичном числе обычно называют битом, а группу из 8 разрядов — байтом.
В своих скриптах мы используем, естественно, привычные для нас числа, но внутри вычисления происходят всё равно в двоичной системе.
Целые и вещественные числа
В компьютерах эти два вида чисел разделены: это данные разных типов.
У целых чисел нет дробной части (то, что после запятой), а у вещественных она может присутствовать. Это разделение кажется немного искусственным, однако оно основано на том, как целые и дробные числа хранятся в памяти.
В Python длина (точность) целых чисел ничем не ограничена (кроме размеров памяти, конечно).
Например, мы можем возвести 5 в 517-ю степень:
>>> 5 ** 517
23307314785000646072784266610821668241801473545626271544598494791269619953344119702925546532501336885504723760174487817041914775297579070814019265092475264660878939975996191804672512732082610196692020482390824733641919428939339084750099021321885071069679886560204219422928768932301614464518761290398229849521777054145309400479124661842433852143585681915283203125
У вещественных чисел в Python ограничена точность: мы можем представить только какое-то определённое количество разрядов после запятой. Обычно это несколько десятков разрядов, что кажется избыточным для любых вычислений.
Например, если вы поделите 23 на 34, вы, скорее всего, запишете как ответ с точностью до 4–5 знаков после запятой, а не будете переписывать всё:
>>> 23 / 34
0.6764705882352942 // обратите внимание, что в качестве десятичного разделителя в языках программирования используется `.`, а не `,`
Из-за фиксации количества разрядов все вычисления с вещественными числами примерные, а не точные:
>>> 0.3 - 0.1 - 0.1 - 0.1
-2.7755575615628914e-17
Мы должны были получить 0, а тут что-то странное.
Всё хорошо. e-17
означает, что надо перенести запятую (точку) на 17 разрядов влево, то есть мы получили -0.000000000000000027755575615628914
, что почти 0. Это иногда надо учитывать, именно из-за таких случаев бывают ошибки вида «Вы должны 0 рублей, 0 копеек, оплатите ваш долг до...» в СМС от банка. То есть число формально не точный 0, но, округляя до сотых число, в котором первые 17 разрядов заполнены нулями, а всего разрядов 33, мы получаем 0.00.
С точки зрения работы с числами компьютер можно считать продвинутым калькулятором. Только действия можно записывать не по одному, а все сразу:
>>> (12 + 6) * 134 / 3.4 + 20.6
730.0117647058823
Но можно и хранить промежуточные результаты:
>>> in_brackets = 12 + 6
>>> in_brackets * 134 / 3.4 + 20.6
730.0117647058823
Строки и тексты. Вопросы интерпретации
Из всех типов данных только числа являются для компьютера естественными: всё, что он делает, — операции с числами: сложение, умножение, перемещение из одного места в другое и т. д.
Возникает закономерный вопрос: «А как быть с более сложными данными?» Например, с текстами, изображениями, звуками.
Можно отчасти пошутить, отчасти сказать почти правду: никак.
Дело в том, что вопрос остальных данных — это вопрос интерпретации их для человека и скриптов, которые он пишет. Это вопрос кодирования и договорённостей.
Например, строка привет
хранится как последовательность шести чисел (1087, 1088, 1080, 1074, 1077, 1090):
>>> example_string = "привет"
>>> for letter in example_string:
print(ord(letter))
1087
1088
1080
1074
1077
1090
То есть внутри какие-то двоичные числа. Но компьютер заботливо отображает их в виде символов.
Символы кириллицы кодируются как в алфавите, друг за другом:
>>> s = "абвгд"
>>> for c in s:
print(ord(c))
1072
1073
1074
1075
1076
Строчные буквы сразу после заглавных:
>>> ord('Я')
1071
>>> ord('а')
1072
Не будем сейчас поднимать вопрос о букве ё
, которая стоит не на своём месте:
>>> ord('д')
1076
>>> ord('е')
1077
>>> ord('ё')
1105
>>> ord('ж')
1078
Дело в том, что при кодировке кириллицы буква ё
оказалась смещена в конец, как и все буквы с диакритическими знаками. Хотя буква й
находится на своём месте.
>>> ord('й')
1081
Кто определяет, какие символы какими числами кодируются? Сейчас практически всеми системами используется таблица Unicode, там есть все национальные алфавиты, иероглифы, эмодзи, пиктограммы и т. д. — несколько десятков тысяч символов. Например, там даже есть несколько снеговичков. Один из них — с кодом 9731.
>>> ord('☃')
9731
Если нам нужно отсортировать список фамилий, то сортировка может идти по числам, которыми закодированы символы, а мы будем воспринимать это как сортировку по алфавиту:
>>> s = ['Сидоров', 'Петров', 'Иванов']
>>> sorted(s)
['Иванов', 'Петров', 'Сидоров']
Мы можем превратить строку привет
в Привет
, отняв 32 от кода первого символа. Или воспользоваться специальной функцией Python под названием capitalize
, которая сделает это за нас:
>>> "заголовок новости".capitalize()
'Заголовок новости'
Но «под капотом» всё равно происходит замена одних чисел на другие.
Как видите, все данные (не только текст), в компьютере «оцифрованы» — превращены в последовательность чисел (байт) с определённой структурой.
Форматы данных на основе текста. Слои интерпретации
Хорошо, а если мы хотим работать со структурированными данными? Например, с таблицами. У таблицы есть строки и столбцы, как кодировать их?
Мы можем принять за основу обычный текст и договориться, что каждая строка будет с новой строки, а столбцы будут отделяться друг от друга точкой с запятой:
Расходы;Месяц;Комментарий
1250;январь;
10500;февраль;Очень большое превышение. Почему?
Формально это просто кусок данных, записанных в виде текста, но эти данные имеют структуру (разделение на строки и столбцы), поэтому некоторые символы отвечают за данные, а некоторые — за разметку (в данном случае — ;
).
Такой формат данных называется CSV (Comma-separated values: значения, разделённые запятыми). Его поддерживает большое количество программ, включая Excel. На рисунке сверху открыт именно этот CSV-файл. Разделитель в нём ;
— он такой по умолчанию в русскоязычной Windows, а в других регионах может быть другой.
Получается, у нас уже два слоя интерпретации.
Слой №1
В памяти компьютера находится последовательность чисел:
1056, 1072, 1089, 1093, 1086, 1076, 1099, 59, 1052, 1077, 1089, 1103, 1094, 59, 1050, 1086,
1084, 1084, 1077, 1085, 1090, 1072, 1088, 1080, 1081, 10, 49, 50, 53, 48, 59, 1103, 1085, 1074,
1072, 1088, 1100, 59, 10, 49, 48, 53, 48, 48, 59, 1092, 1077, 1074, 1088, 1072, 1083, 1100, 59,
1054, 1095, 1077, 1085, 1100, 32, 1073, 1086, 1083, 1100, 1096, 1086, 1077, 32, 1087, 1088, 1077,
1074, 1099, 1096, 1077, 1085, 1080, 1077, 46, 32, 1055, 1086, 1095, 1077, 1084, 1091, 63
Каждое число интерпретируется как код символа в таблице Unicode. Мы получаем текст:
Расходы;Месяц;Комментарий
1250;январь;
10500;февраль;Очень большое превышение. Почему?
Слой №2
Но это ещё не конец, потому что в тексте есть структура, а не чистые данные. Нужно прочесть эту структуру, разделить всё на строки и столбцы и получить в итоге таблицу, как мы привыкли её видеть:
Больше слоёв
Слоёв интерпретации может быть ещё больше. Возьмем обычный Word-документ, в котором есть фраза «Сегодня необычайно приятная погода!»:
Сохраним его под именем weather.docx
. Формат docx
— это на самом деле архив. Давайте переименуем его в weather.zip
, чтобы его открыл архиватор.
Как видим, там очень большое количество файлов и папок. Нас интересует только один из файлов — document.xml
.
В нем содержится вот такой текст (мы добавили отступы для удобства чтения, так-то он хранится в одной строке):
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<w:document
xmlns:wpc="http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas"
xmlns:cx="http://schemas.microsoft.com/office/drawing/2014/chartex"
xmlns:cx1="http://schemas.microsoft.com/office/drawing/2015/9/8/chartex"
xmlns:cx2="http://schemas.microsoft.com/office/drawing/2015/10/21/chartex"
xmlns:cx3="http://schemas.microsoft.com/office/drawing/2016/5/9/chartex"
xmlns:cx4="http://schemas.microsoft.com/office/drawing/2016/5/10/chartex"
xmlns:cx5="http://schemas.microsoft.com/office/drawing/2016/5/11/chartex"
xmlns:cx6="http://schemas.microsoft.com/office/drawing/2016/5/12/chartex"
xmlns:cx7="http://schemas.microsoft.com/office/drawing/2016/5/13/chartex"
xmlns:cx8="http://schemas.microsoft.com/office/drawing/2016/5/14/chartex"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:aink="http://schemas.microsoft.com/office/drawing/2016/ink"
xmlns:am3d="http://schemas.microsoft.com/office/drawing/2017/model3d"
xmlns:o="urn:schemas-microsoft-com:office:office"
xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math"
xmlns:v="urn:schemas-microsoft-com:vml"
xmlns:wp14="http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing"
xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing"
xmlns:w10="urn:schemas-microsoft-com:office:word"
xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
xmlns:w14="http://schemas.microsoft.com/office/word/2010/wordml"
xmlns:w15="http://schemas.microsoft.com/office/word/2012/wordml"
xmlns:w16cid="http://schemas.microsoft.com/office/word/2016/wordml/cid"
xmlns:w16se="http://schemas.microsoft.com/office/word/2015/wordml/symex"
xmlns:wpg="http://schemas.microsoft.com/office/word/2010/wordprocessingGroup"
xmlns:wpi="http://schemas.microsoft.com/office/word/2010/wordprocessingInk"
xmlns:wne="http://schemas.microsoft.com/office/word/2006/wordml"
xmlns:wps="http://schemas.microsoft.com/office/word/2010/wordprocessingShape" mc:Ignorable="w14 w15 w16se w16cid wp14">
<w:body>
<w:p w:rsidR="00295D78" w:rsidRDefault="00730203">
<w:r>
<w:t>Сегодня необычайно приятная погода!</w:t>
</w:r>
</w:p>
<w:p w:rsidR="00730203" w:rsidRPr="00730203" w:rsidRDefault="00730203">
<w:bookmarkStart w:id="0" w:name="_GoBack"/>
<w:bookmarkEnd w:id="0"/>
</w:p>
<w:sectPr w:rsidR="00730203" w:rsidRPr="00730203">
<w:pgSz w:w="11906" w:h="16838"/>
<w:pgMar w:top="1134" w:right="850" w:bottom="1134" w:left="1701" w:header="708" w:footer="708" w:gutter="0"/>
<w:cols w:space="708"/>
<w:docGrid w:linePitch="360"/>
</w:sectPr>
</w:body>
</w:document>
Это формат XML
, он тоже сделан на основе текста, там есть разметка данных и сами данные. Про него мы будем говорить позже. Сейчас отметим только, что там всё строится на основе открывающихся и закрывающихся тегов:
<пример>Это как на указателях въезда и выезда из города</пример>
.
Внутри этой разметки мы видим наш абзац:
<w:t>Сегодня необычайно приятная погода!</w:t>
Получается, что мы можем на основе одних форматов данных делать другие и получать слои интерпретации. Это как в сказке:
На море, на океане есть остров, на том острове дуб стоит, под дубом сундук зарыт, в сундуке — заяц, в зайце — утка, в утке — яйцо, в яйце — игла — смерть Кощея. А у нас так: есть документ, он — архив, в архиве — папка, в папке — документ, в документе — структура, внутри структуры — данные.
Как видите, получается очень многослойная структура. Наверняка у вас сейчас есть вопрос: «И что мне делать со всей этой информацией?» Ответ простой: просто принять её к сведению и держать в голове, что некоторые вещи устроены сложнее, чем может показаться на первый взгляд.
В последнем параграфе главы мы настроим окружение для работы с Python и подготовим всё необходимое для запуска первых команд.