\n, \r и \r\n, и в старые времена надо было знать или угадывать, что используется в файле в зависимости от системы. Мы привыкли, что в python обычно все просто - splitlines()`/`split('\n') и готово, дальше под капотом работает магия. На самом деле все, конечно, гораздо сложнее 🙂 Джеймс Беннет написал отличный разбор, как все работает сейчас, почему и откуда это все взялось.Началось все с ASCII и телетайпов: LF (`0x0A`) двигал бумагу вниз, CR (`0x0D`) возвращал каретку. Два символа нужны, потому что механике требуется время, чтобы каретка успела доехать. Потом терминалы стали виртуальными, необходимость отпала, и все разбежались кто куда: CP/M → DOS → Windows оставили CR LF, Multics → Unix взяли только LF, Apple и Commodore - только CR. В питон еще в версии 2.3 завезли PEP 278 - universal newlines.
Но так-то в ASCII есть еще FF (form feed, `0x0C`) и VT (vertical tab, `0x0B`) - они как бы не "новая строка", но по факту переводят вывод на другую строку. Плюс NEL (`0x85`), который завели для совместимости с IBM-овской кодировкой EBCDIC, где был свой символ для перевода строки.
Дальше Юникод добавил
U+2028 LINE SEPARATOR и U+2029 PARAGRAPH SEPARATOR, потому что обычный newline стал двусмысленным: текстовые редакторы с автопереносом начали использовать его как разрыв абзаца, а не строки.Самое неожиданное - последние три:
U+001C, U+001D, U+001E, они же ASCII-шные FILE, GROUP и RECORD SEPARATOR, которыми когда-то разделяли записи в данных. К разрыву строк они отношения не имеют, а попали в список через алгоритм для текста со смешанным направлением письма (слева направо и справа налево). Если, например, в тексте на арабском вставка цитаты на английском, направление переключается спецсимволом, и действует он до конца абзаца. А у этих трех разделителей в свойствах как раз прописано "конец абзаца" - вот они и оказались в одном списке с настоящими переводами строк.Сейчас это все собрано в
splitlines(). А практический вывод простой: splitlines() и split('\n') - это не одно и то же. Если в данных попадется \x1c или \u2028 (а в выгрузках из всяких легаси-систем они попадаются), результат будет разный. Для парсинга структурированных форматов лучше явно указывать разделитель.Оригинал https://www.b-list.org/weblog/2026/aug/10/newlines/