Глава 4. Строки

Добавлено 5 апреля 2020 в 01:17

Содержание главы

4.1 Немного скучных вещей, которые вам необходимо знать перед погружением

Знаете ли вы, что у народа острова Бугенвиль самый короткий алфавит в мире? Алфавит языка ротокас состоит всего из 12 букв: A, E, G, I, K, O, P, R, S, T, U, и V. На другом конце этой своеобразной числовой оси расположились такие языки, как китайский, японский и корейский, насчитывающие тысячи символов. Английский, конечно, содержит всего 26 букв (52, если считать буквы и верхнего, и нижнего регистров) плюс горстка знаков пунктуации !@#$%&.

Когда люди говорят «текст», они подразумевают «буквы и символы на экране компьютера». Но компьютеры не работают с буквами и символами; они работают с битами и байтами. Каждый фрагмент текста, который вы когда-либо видели на экране компьютера, на самом деле хранится в определенной кодировке. Грубо говоря, кодировка символов обеспечивает соответствие того, что вы видите на экране и того, что на самом деле хранится в памяти или на диске. Существует много различных кодировок символов, некоторые из них оптимизированы для конкретных языков, например русского, китайского или английского, другие могут быть использованы сразу для нескольких языков.

В действительности, всё гораздо сложнее. Многие символы являются общими для нескольких кодировок, но каждая кодировка может использовать свою последовательность байт для хранения их в памяти или на диске. Вы можете думать о кодировке символов, как о разновидности криптографического ключа. Всякий раз, когда вам передают последовательность байт (файл, веб-страницу, всё равно) и утверждают, что это «текст», вам необходимо понять, какая кодировка использовалась. Зная кодировку, вы сможете декодировать байты в символы. Если вам дают неправильный ключ или не дают ключа вовсе, вам не остается ничего, кроме как попытаться взломать код самостоятельно. Скорее всего, в результате вы получите кучу крякозябров (gibberish – тарабарщина, невнятная речь, но так понятнее, прим. перев.).

Все, что вы знали о строках – неверно.

Несомненно, вам приходилось видеть такие веб-страницы, со странными вопросительными знаками на месте апострофов. Обычно это означает, что автор страницы неправильно указал их кодировку, вашему браузеру осталось просто угадать ее, а результатом стала смесь ожидаемых и совершенно неожиданных символов. В английском языке это просто раздражает; в других языках результат может стать совершенно нечитаемым.

Существуют кодировки для всех основных мировых языков. Но, поскольку, языки существенно отличаются друг от друга, а память и дисковое пространство раньше были дорогими, каждая кодировка оптимизирована для конкретного языка. Под этим я подразумеваю то, что для представления символов своего языка, все кодировки используют один и тот же диапазон чисел (0-255). Например, вы вероятно знакомы с кодировкой ASCII, которая хранит символы английского языка в виде чисел от 0 до 127. (65 – заглавная «A», 97 – строчная «a» и т.д.) Английский алфавит очень простой и может быть представлен менее, чем 128 числами. Если вам известна двоичная система счисления, вы понимаете, что в байте задействовано всего 7 битов из 8.

Западноевропейские языки, такие как французский, испанский и немецкий содержат больше символов, чем английский. Точнее, они содержат символы с различными диакритическим знаками, например, испанский символ ñ. Самая распространенная кодировка для этих языков – CP-1252, также известная как «windows-1252», что связано с широким использованием ее в операционной системе Microsoft Windows. В кодировке CP-1252 символы с номерами от 0 до 127 такие же, как и в ASCII, а остальной диапазон используется для таких символов как n-с-тильдой-сверху (241), u-с-двумя-точками-сверху (252) и т.д. Однако, это всё еще однобайтная кодировка; максимально возможный номер 255 еще помещается в один байт.

А еще существуют такие языки, как китайский, японский и корейский, которые имеют так много символов, что они требуют многобайтовых кодировок. Это означает, что каждый «символ» представляется двухбайтовым числом от 0 до 65535. Но различные многобайтные кодировки всё равно имеют ту же проблему, что и различные однобайтные кодировки: каждая кодировка использует одинаковые числа для обозначения разных вещей. Отличие лишь в том, что диапазон чисел больше, потому что нужно кодировать намного больше символов.

Это было вполне нормально в несетевом мире, где вы набирали «текст» для себя и иногда распечатывали его. В этом мире не было ничего кроме «обычного текста» (не знаю, м.б. быть вообще не переводить “plain text” – прим. перев.). Исходный код был в кодировке ASCII, а для всего остального использовались текстовые процессоры, которые определяли свои собственные (нетекстовые) форматы, в которых, наряду с информацией о форматировании, отслеживалась и информация о кодировке. Люди читают эти документы с помощью такого же текстового процессора, какой использовался и для их создания, так что, всё более или менее работало.

Теперь подумайте о распространении глобальных сетей, таких как e-mail и web. Множество «обычного текста» перемещается вокруг планеты, создаётся на одном компьютере, передаётся через второй и принимается и отображается третьим компьютером. Компьютеры видят только числа, но числа могут иметь различное значение. О нет! Что же делать? Системы были спроектированы таким образом, чтобы передавать информацию о кодировке вместе с каждым отрывком «обычного текста». Вспомните, это криптографический ключ, устанавливающий соответствие между числами и понятными человеку символами. Потерянный ключ означает искаженный текст или кракозябры, если не хуже.

Теперь подумайте о задаче хранения различных отрывков текста в одном месте, например в одной таблице базы данных, хранящей все когда-либо полученные вами email сообщения. Вам всё ещё нужно хранить кодировку вместе с каждым отрывком текста, чтобы иметь возможность прочитать его. Думаете, это трудно? Попробуйте реализовать поиск в этой базе данных, с преобразованием на лету между множеством кодировок. Разве это не забавно?

Теперь подумайте о возможности многоязычных документов, где символы из нескольких языков находятся рядом в одном документе. (Подсказка: программы, которые пытаются делать это, обычно используют коды смены алфавита для переключения «режимов». Бац, и вы в русском режиме KOI8-R, и 241 означает Я; бац, и теперь вы в греческом режиме для Macintosh, и 241 означает ώ.) И конечно вы так же захотите осуществлять поиск по этим документам.

Теперь плачьте, т.к. всё, что вы знали о строках – неверно, и нет такого понятия «обычный текст».

4.2 Юникод

Введение в Юникод.

Юникод спроектирован для представления системой каждого символа любого языка. Юникод представляет каждую букву, символ или идеографию как 4-х байтное число.Каждое число представляет уникальный символ, используемый по крайней мере в одном из языков в мире. (используются больше чем 65535 из них, таким образом 2 байт было бы недостаточно.) У символов, которые используются в разных языках, одинаковый код, если нет веской этимологической причины. Независимо от всего, есть точно 1 код сответствующий символу, и точно 1 символ соответствующий числовому коду. Каждый код всегда означает только один символ; нет никаких «режимов». U+0041 всегда соответствует 'A', даже если в вашем языке нету символа 'A'.

На первый взгляд, это великолепная идея. Одна кодировка для всего. Множество языков в одном документе. Не надо больше никаких «переключений режимов» для смены кодировок. Но у вас должен возникнуть очевидный вопрос. Четыре байта? На каждый символ? Это кажется ужасно расточительным, особенно для таких языков, как английский или испанский, в которых нужно меньше одного байта (256 чисел) для представления любого возможного символа. На самом деле, это расточительно даже для иероглифических языков (таких как китайский), в которых никогда не нужно больше, чем два байта на символ.

Существует кодировка Юникод, которая использует четыре байта на символ. Она называется UTF-32, так как 32 бита = 4 байта. UTF-32 – прямолинейная кодировка; каждому символу Юникод (4-байтовому числу) соответствует символ с определенным номером. В этом есть свои преимущества, самое важное из которых заключается в том, что вы можете найти N-ый символ в строке за постоянное время, так как N-ый символ начинается в 4*N байте. Но эта кодировка также имеет и недостатки, самый очевидный из которых – для хранения каждого символа требуется четыре байта.

Хотя в Юникоде существует огромное количество символов, на самом деле большинство людей никогда не используют те, номера которых выше первых 65535 (216). Поэтому существует другая кодировка Юникода, называемая UTF-16 (очевидно, что 16 бит = 2 байта). UTF-16 кодирует каждый символ в номерах 0–65535; для представления же редко используемых «запредельных» символов с номерами выше 65535 приходится прибегать к некоторым уловкам. Самое очевидное преимущество: UTF-16 дважды эффективнее по потреблению памяти, нежели UTF-32, так как каждый символ требует 2 байта вместо 4-х (кроме случаев с теми самыми «запредельными» символами). И, как и в случае с UTF-32, можно легко отыскать нужный N-ый символ в строке за постоянное время, если вы уверены, что текст не содержит «запредельных» символов; и всё хорошо, если это действительно так.

Тем не менее существуют неочевидные недостатки, как UTF-32, так и UTF-16. На различных компьютерных платформах отдельные байты хранятся по-разному. Это означает, что символ U+4E2D может быть сохранён в UTF-16 либо как 4E 2D, либо как 2D 4E (задом наперёд), в зависимости от используемого порядка байт: big-endian или little-engian. (Для UTF-32 видов порядков даже больше.) Пока вы храните свои документы исключительно у себя на компьютере, вы в безопасности – различные приложения на одном компьютере всегда используют один и тот же порядок. Но в тот момент, когда вы захотите передать документы на другой компьютер в Интернете или иной сети, вам понадобится способ пометки документа, какой у вас используется порядок байт. В противном случае, принимающая документ система не имеет понятия, что представляет последовательность байт "4E 2D": U+4E2D или U+2D4E.

Для решения этой проблемы многобайтовые кодировки Юникода имеют “отметку о порядке байт” (BOM, «Byte Order Mark»), которая представляет собой специальный непечатный символ, который вы можете включить в начало документа для сохранения информации о используемом порядке байт. Для UTF-16, эта отметка имеет номер U+FEFF. Если вы принимаете документ с UTF-16, который начинается с байт FF FE, – это однозначно оповещает о прямом порядке; если же начинается с байт FE FF, следовательно порядок обратный.

На самом деле, UTF-16 не идеален, особенно если вы имеете дело с большим количеством символов ASCII. Вы не думали о том, что даже китайская веб-страница может содержать большое количество символов ASCII – все элементы и атрибуты, окружающие печатные китайские символы (и на них тоже тратится 2 байта, хотя они и умещаются в один). Возможность искать N-ый символ в строке за постоянное время заманчива, однако до сих пор существует надоевшая всем проблема с теми "запредельными" символами, которая заключается в том, что вы не можете гарантировать, что каждый символ хранится точно в двух байтах, вследствие чего поиск за постоянное время также становится невозможным (если вы только не имеете отдельный индекс по символам). Открою вам секрет: и до сих пор в мире существует огромное число ASCII текстов...

Кое-кто до вас тоже задумывался над этой проблемой и пришёл вот к такому решению:

UTF-8

UTF-8 – это кодировка Юникода с переменным числом байт. Это означает, что различные символы занимают разное число байт. Для символов ASCII (A-Z, цифр и т.п.) UTF-8 использует только 1 байт на символ (действительно, а больше и не требуется). Причём и на деле для них зарезервированы точно те самые номера, как и в ASCII; первые 128 символов (0–127) таблицы UTF-8 неотличимы от той же части ASCII. “Расширенные” латинские символы, такие как ñ и ö занимают два байта. (эти байты не просто код Unicode, как в UTF-16; здесь применяется некоторая манипуляция битами.) Китайские символы, такие как 中 занимают три байта. Самые редко используемые символы – четыре.

Недостатки: так как каждый символ занимает различное число байт, поиск N-го символа обладает сложностью O(N), что означает, что время поиска пропорционально длине строки. Кроме того, манипуляция питами, применяемая для кодирования символов в байты, также увеличивает время поиска. (прим. перев. в кодировке с фиксированным числом байт на символ время поиска составляет O(1), то есть оно не зависит от длины строки).

Преимущества: крайне эффективное кодирование наиболее часто используемых символов ASCII. Хранение расширенных символов не хуже, чем в UTF-16. Лучше, чем UTF-32 для китайских символов. Также (не хочу грузить вас математикой, так что вам придётся поверить мне на слово), в связи с самой природой манипуляции битами, проблемы с порядком байт просто не существует. Документ, закодированный в UTF-8, использует один и тот же порядок байт на любом компьютере!

4.3 Погружение

В языке программирования Python 3 все строки представляют собой последовательность Unicode символов. В Python нет такого понятия, как строка в кодировке UTF-8, или строка в кодировке CP-1251. Некорректным является вопрос: "Это строка в UTF-8?". UTF-8 – это способ закодировать символы в последовательность байт. Если Вы хотите взять строку и превратить её в последовательность байт в какой-либо кодировке, то Python 3 может помочь вам в этом. Если же Вы желаете превратить последовательность байт в строку, то и здесь Python 3 вам пригодится. Байты – это не символы, байты – это байты. Символы – это абстракция. А строка – это последовательность таких абстракций.

>>> s = '深入 Python'    ①
>>> len(s)               ②
9
>>> s[0]                 ③
'深'
>>> s + ' 3'             ④
'深入 Python 3'
  1. Строка 1. Чтобы создать строку окружите её кавычками. В Python строки можно создавать как с помощью одинарных ('), так и с помощью двойных кавычек (").
  2. Строка 2. Стандартная функция len() возвращает длину строки, т.е. количество символов в ней. Эта же функция используется для определения длины списков, кортежей, множеств и словарей. Строка в данном случае похожа на кортеж символов.
  3. Строка 4. Так же как и со списками, Вы можете получить произвольный символ из строки, зная его индекс.
  4. Строка 6. Так же как и со списками, Вы можете объединять строки, используя оператор +.

Форматирование строк

Строки можно создавать как с помощью одинарных, так и с помощью двойных кавычек.

Давайте взглянем еще раз на humansize.py:

SUFFIXES = {1000: ['KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'],         ①
            1024: ['KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB']}

def approximate_size(size, a_kilobyte_is_1024_bytes=True):
    '''Преобразует размер файла в удобочитаемую для человека форму.         ②

    Ключевые аргументы:
    size -- размер файла в байтах
    a_kilobyte_is_1024_bytes -- если True (по умолчанию), используются степени 1024
                                если False, используются степени 1000

    Возвращает: текстовую строку (string)

    '''                                                                     ③
    if size < 0:
        raise ValueError('число должно быть неотрицательным')               ④

    multiple = 1024 if a_kilobyte_is_1024_bytes else 1000
    for suffix in SUFFIXES[multiple]:
        size /= multiple
        if size < multiple:
            return '{0:.1f} {1}'.format(size, suffix)                       ⑤

    raise ValueError('число слишком большое')
  1. Строка 1. 'KB', 'MB', 'GB'… – это все строки.
  2. Строка 5. Комментарии к функции – это тоже строка. Комментарии к функции могут быть многострочными, поэтому используются тройные кавычки вначале и в конце строки.
  3. Строка 14. Эти тройные кавычки заканчивают комментарии к функции.
  4. Строка 16. Здесь еще одна строка, которая передается конструктору исключения как удобочитаемый текст ошибки.
  5. Строка 22. Здесь… ого, это ещё что такое?

Python 3 поддерживает форматирование значений в строки. Форматирование может включать очень сложные выражение. Самое простое использование – это вставка значения в поле подстановки в строке.

>>> username = 'mark'
>>> password = 'PapayaWhip'                             ①
>>> "{0}'s password is {1}".format(username, password)  ②
"mark's password is PapayaWhip"
  1. Строка 2. Вы же не думаете, что мой пароль действительно PapayaWhip
  2. Строка 3. Здесь много чего происходит. Во первых, вызывается метод format(…) для строки. Строки – это объекты, а у объектов есть методы. Во вторых, значением всего выражения будет строка. В третьих, {0} и {1} являются полями, которые заменяются аргументами, переданными методу format().

Составные имена полей

Предыдущий пример показал простейший способ форматирования строк: поля в строке представляют из себя целые числа. Эти числа в фигурных скобках означают порядковые номера аргументов в списке параметрах метода format(). Это означает, что {0} заменяется первым аргументом (в данном случаем username), а {1} заменяется на второй аргумент (password), и так далее. У вас может быть столько номеров полей, сколько аргументов есть у метода format(). А аргументов может быть сколько угодно. Но имена полей гораздо более мощный инструмент, чем может показаться на первый взгляд.

>>> import humansize
>>> si_suffixes = humansize.SUFFIXES[1000]      ①
>>> si_suffixes
['KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
>>> '1000{0[0]} = 1{0[1]}'.format(si_suffixes)  ②
'1000KB = 1MB'
  1. Строка 2. Вместо того, чтобы вызывать какие-либо функции модуля humansize, Вы просто используете один из словарей, которые в этом модуле определены: список суффиксов СИ (степени 1000)
  2. Строка 5. Этот кусок выглядит сложным, хотя это и не так. {0} ссылается на первый аргумент, переданный методу format() (переменная si_suffixes). Но si_suffixes – это список. Поэтому {0[0]} ссылается на первый элемент этого списка: 'KB'. В тоже время {0[1]} ссылается на второй элемент того же списка: 'MB'. Всё, что находится за фигурными скобками (включая 1000, знак равенства, и пробелы) остается нетронутым. В результате мы получим строку '1000KB = 1MB'.

{0} заменяется на первый аргумент метода format(). {1} заменяется на второй аргумент.

Этот пример показывает, что при форматировании в именах полей можно получить доступ к элементам и свойствам структур данных, используя (почти) синтаксис Python. Это называется «составные имена полей». Следующие составные имена полей работают:

  • передача списка и получение доступа к элементу списка по его индексу (как в предыдущем примере);
  • передача словаря и получение доступа к значению словаря по его ключу;
  • передача модуля и получение доступа к его переменным и функциям по их именам;
  • передача экземпляра класса и получение доступа к его свойствам и методам по их именам;
  • любая комбинация выше перечисленного.

И чтобы взорвать ваш мозг, вот пример который использует все вышеперечисленные возможности:

>>> import humansize
>>> import sys
>>> '1MB = 1000{0.modules[humansize].SUFFIXES[1000][0]}'.format(sys)
'1MB = 1000KB'

Вот как это работает:

  • Модуль sys содержит информацию об работающем интерпретаторе Python. Так как вы его импортировали, то можете использовать в качестве аргумента метода format(). То есть поле {0} ссылается на модуль sys.
  • sys.modules представляет из себя словарь со всеми модулями, которые на данный момент импортированы интерпретатором Python. Ключи этого словаря – это строки с именами модулей; значения – объекты, представляющие импортированные модули. Таким образом поле {0.modules} ссылается на словарь импортированных модулей.
  • sys.modules['humansize'] – это объект, представляющий собой модуль humansize, который вы только что импортировали. Таким образом составное поле {0.modules[humansize]} ссылается на модуль humansize. Заметьте, что синтаксис здесь отличается. В синтаксисе Python ключи словаря sys.modules являются строками, и чтобы обратиться к значениям словаря необходимо окружить кавычками имя модуля (например 'humansize'). Вот цитата из PEP 3101: Расширенное форматирование строк: "Правила для парсинга ключей очень простые. Если он начинается с цифры, то его нужно интерпретировать как число. Иначе – это строка".
  • sys.modules['humansize'].SUFFIXES – это словарь, определенный в самом начале модуля humansize. Поле {0.modules[humansize].SUFFIXES} ссылается на этот словарь.
  • sys.modules['humansize'].SUFFIXES[1000] – это список суффиксов системы СИ: ['KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']. Таким образом поле {0.modules[humansize].SUFFIXES[1000]} ссылается на этот список.
  • А sys.modules['humansize'].SUFFIXES[1000][0] – это первый элемент списка суффиксов: 'KB'. Таким образом окончательное составное поле {0.modules[humansize].SUFFIXES[1000][0]} заменяется на строку из двух символов KB.

Описатели формата

Постойте! Есть еще кое-что. Давайте взглянем на еще одну странную строку из humansize.py:

if size < multiple:
    return '{0:.1f} {1}'.format(size, suffix)

{1} заменяется на второй аргумент метода format(), то есть на значение переменной suffix. Но что означает {0:.1f}? Здесь две вещи: {0}, о которой вы уже знаете и :.1f, о которой вы еще не слышали. Вторая половина (двоеточие и все что после него) описывает формат, который уточняет каким образом замещающее значение должно быть отформатировано.

Описатель формата позволяет вам модифицировать замещающий текст многими полезными способами, как функция printf() в языке программирования C. Вы можете добавить заполнение нулями или пробелами, горизонтальное выравнивание текста, контролировать точность после запятой и даже конвертировать числа в 16-ричную систему.

Внутри замещаемого поля символ двоеточие (:) и всё, что идет после него, означает описатель формата. Описатель формата ".1" означает "округлить до десятых" (то есть показывать только один знак после запятой). Описатель формата "f" означает "число с фиксированной запятой" (fixed-point number) (в отличие от экспоненциального или какого-либо другого представления десятичных чисел). Таким образом если переменная size имеет значение 698.24, а suffix – 'GB', форматированная строка получится '698.2 GB', потому что число 698.24 округлено до одного знака после запятой, и к нему добавлен суффикс.

>>> '{0:.1f} {1}'.format(698.24, 'GB')
'698.2 GB'

За всеми деталями описателей формата обратитесь в раздел "Format Specification Mini-Language" официальной документации Python 3.

Другие общие методы строк

Помимо форматирования строки позволяют делать множество полезных трюков.

>>> s = '''Finished files are the re-  ①
... sult of years of scientif-
... ic study combined with the
... experience of years.'''
>>> s.splitlines()                     ②
['Finished files are the re-',
 'sult of years of scientif-',
 'ic study combined with the',
 'experience of years.']
>>> print(s.lower())                   ③
finished files are the re-
sult of years of scientif-
ic study combined with the
experience of years.
>>> s.lower().count('f')               ④
6
  1. Строка 1. В интерактивной оболочке Python вы можете вводить многострочный текст. Такой текст начинается с тройного символа кавычек. А когда Вы нажмете ENTER интерактивная оболочка предложит вам продолжить вводить текст. Заканчиваться многострочный текст должен также тройным символом кавычек. Когда вы нажмете ENTER интерактивная оболочка Python выполнит команду (запишет текст в переменную s).
  2. Строка 5. Метод splitlines() берет многострочный текст и возвращает список строк, по одной на каждую строку оригинального текста. Заметьте, что символы перевода строки не добавляются в результирующие строки.
  3. Строка 10. Метод lower() переводит все символы строки в нижний регистр. (Аналогично метод upper() переводит строку в верхний регистр.)
  4. Строка 15. Метод count() подсчитывает количество появлений подстроки. Да, в этом предложении 6 букв "f".

Вот еще один часто встречающийся случай. Пусть у вас есть список пар ключ-значение в виде key1=value1&key2=value2, и вы хотите разделить их и получить словарь в виде {key1: value1, key2: value2}.

>>> query = 'user=pilgrim&database=master&password=PapayaWhip'
>>> a_list = query.split('&')                            ①
>>> a_list
['user=pilgrim', 'database=master', 'password=PapayaWhip']
>>> a_list_of_lists = [v.split('=', 1) for v in a_list]  ②
>>> a_list_of_lists
[['user', 'pilgrim'], ['database', 'master'], ['password', 'PapayaWhip']]
>>> a_dict = dict(a_list_of_lists)                       ③
>>> a_dict
{'password': 'PapayaWhip', 'user': 'pilgrim', 'database': 'master'}
  1. Строка 2. Метод split() принимает один аргумент, разделитель, и разбивает строку по разделителям на список строк. В данном случае разделителем выступает аперсанд (&), но разделитель может быть каким угодно.
  2. Строка 5. Теперь у вас есть список строк, каждая из которых состоит из ключа, знака '=' и значения. Мы можем использовать генераторы списков чтобы пройтись по всему списку и разбить каждую строку в месте первого символа '=' на две строки: ключ и значение. (Теоретически значение также может содержать знак равенства. Если просто сделаем 'key=value=foo'.split('='), то получим список из трех элементов ['key', 'value', 'foo'].)
  3. Строка 8. Наконец Python может превратить этот список в словарь используя функцию dict().

Предыдущий пример похож на грамматический разбор параметров в URL, в реальной жизни такой разбор намного сложнее. Если Вам необходимо работать с параметрами URL, то лучше использовать функцию urllib.parse.parse_qs(), которая умеет обрабатывать некоторые неочевидные специфические случаи.

Разрезание строк

Как только Вы создали строку, Вы можете получить любую её часть как новую строку. Это называется разрезание строк. Разрезание работает также как срезы для списков, что вполне логично, так как строки – это те же последовательности символов.

>>> a_string = 'My alphabet starts where your alphabet ends.'
>>> a_string[3:11]           ①
'alphabet'
>>> a_string[3:-3]           ②
'alphabet starts where your alphabet en'
>>> a_string[0:2]            ③
'My'
>>> a_string[:18]            ④
'My alphabet starts'
>>> a_string[18:]            ⑤
' where your alphabet ends.'
  1. Строка 2. Вы можете получить любую часть строки, так называемый "срез", указав два индекса. Возвращаемое значение представляет из себя новую строку, содержащую все символы оригинальной строки в том же порядке, начиная с первого указанного индекса.
  2. Строка 4. Как и при работе со срезами списков, индексы для срезов строк могут быть отрицательными.
  3. Строка 6. Индексация символов в строке начинается с нуля, поэтому a_string[0:2] вернет первые два элемента строки, начиная с a_string[0] (включительно) и заканчивая (не включительно) a_string[2].
  4. Строка 8. Если срез начинается с индекса 0, то этот индекс можно опустить. Таким образом a_string[:18] – это тоже самое, что и a_string[0:18].
  5. Строка 10. Аналогично, если последний индекс – это длина строки, то его можно не ставить. То есть a_string[18:] означает тоже самое, что и a_string[18:44], так как в строке 44 символа. Здесь наблюдается приятная симметрия. В нашем примере строка содержит 44 символа, a_string[:18] возвращает первые 18 символов, а a_string[18:] возвращает все кроме первых 18 символов. Фактически a_string[:n] всегда возвращает первые n символов, а a_string[n:] возвращает оставшуюся часть, независимо от длины строки.

Строки против последовательности байт

Байты – это байты; символы – это абстракция. Неизменяемая последовательность Unicode символов называется строкой (string). Неизменяемая последовательность чисел-от-0-до-255 называется объект bytes.

>>> by = b'abcd\x65'  ①
>>> by
b'abcde'
>>> type(by)          ②
<class 'bytes'>
>>> len(by)           ③
5
>>> by += b'\xff'     ④
>>> by
b'abcde\xff'
>>> len(by)           ⑤
6
>>> by[0]             ⑥
97
>>> by[0] = 102       ⑦
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'bytes' object does not support item assignment
  1. Строка 1. Чтобы создать объект bytes, используйте синтаксис "байтовых строк" b''. Каждый байт в байтовой строке может быть либо ASCII символом, либо закодированным шестнадцатеричным числом от \x00 до \xff (0-255).
  2. Строка 4. Тип байтовой строки – bytes.
  3. Строка 6. По аналогии со списками и строками вы можете определить длину байтовой строки с помощью встроенной функции len().
  4. Строка 8. По аналогии со списками и строками вы можете объединять байтовые строки с помощью оператора +. Результат будет новым объектом с типом bytes.
  5. Строка 11. Объединение 5-байтового и однобайтового объекта даст в результате 6-ти байтовый объект.
  6. Строка 13. По аналогии со списками и строками вы можете получить конкретный байт из байтовой строки по его индексу. Элементами обычной строки выступают строки, а элементами байтовой строки являются целые числа. А именно целые числа от 0 до 255.
  7. Строка 15. Байтовая строка неизменяема. Вы не можете изменять какие-либо байты в ней. Если у вас возникла необходимость изменить отдельные байты, то вы можете либо использовать разрезание строки и оператор конкатенации (+), который действует так же, как и со строками, либо конвертировать объект bytes в объект bytearray.
>>> by = b'abcd\x65'
>>> barr = bytearray(by)  ①
>>> barr
bytearray(b'abcde')
>>> len(barr)             ②
5
>>> barr[0] = 102         ③
>>> barr
bytearray(b'fbcde')
  1. Строка 2. Для конвертирования объекта bytes в изменяемый объект bytearray используйте встроенную функцию bytearray().
  2. Строка 5. Все методы и операторы, которые вы использовали с объектами типа bytes, также подходят к объектам bytearray.
  3. Строка 7. Единственное отличие состоит в том, что вы можете изменить значение отдельного байта при работе с объектом bytearray. Записываемое значение должно быть целым числом от 0 до 255.

Единственное, чего Вы не можете делать, это смешивать байты и строки.

>>> by = b'd'
>>> s = 'abcde'
>>> by + s                       ①
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: can't concat bytes to str
>>> s.count(by)                  ②
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: Can't convert 'bytes' object to str implicitly
>>> s.count(by.decode('ascii'))  ③
1
  1. Строка 3. Нельзя соединять байты и строки. Это два разных типа данных.
  2. Строка 7. Вы не можете подсчитать частоту встречаемости последовательности байтов в строке, потому что в строке вообще нет байтов. Строка – это последовательность символов. Возможно вы имеете в виду «подсчитать количество вхождений строки, полученной декодированием последовательности байт из конкретной кодировки»? Тогда это необходимо указать точно. Python 3 не будет автоматически конвертировать байты в строки или строки в байты.
  3. Строка 11. По случайному совпадению эта строка кода означает «подсчитать количество вхождений строки, полученной декодированием последовательности байт из конкретной кодировки».

Здесь появляется связь между строками и байтами: объект типа bytes имеет метод decode(), аргументом которого является кодировка, и который возвращает строку. В свою очередь строка имеет метод encode(), аргументом которого является кодировка, и который возвращает объект bytes. В предыдущем примере декодирование было относительно простым: последовательность байт в кодировке ASCII преобразовывалась в строку. Но этот процесс подходит для любой кодировки, которая поддерживает символы строки, даже устаревшие (не-Unicode) кодировки.

>>> a_string = '深入 Python'         ①
>>> len(a_string)
9
>>> by = a_string.encode('utf-8')    ②
>>> by
b'\xe6\xb7\xb1\xe5\x85\xa5 Python'
>>> len(by)
13
>>> by = a_string.encode('gb18030')  ③
>>> by
b'\xc9\xee\xc8\xeb Python'
>>> len(by)
11
>>> by = a_string.encode('big5')     ④
>>> by
b'\xb2`\xa4J Python'
>>> len(by)
11
>>> roundtrip = by.decode('big5')    ⑤
>>> roundtrip
'深入 Python'
>>> a_string == roundtrip
True
  1. Строка 1. Это строка. В ней 9 символов.
  2. Строка 4. Это объект типа bytes. В нем 13 байт. Эта последовательность байт получена кодированием строки a_string в кодировке UTF-8.
  3. Строка 9. Это объект типа bytes. В нем 11 байт. Эта последовательность байт получена кодированием строки a_string в кодировке GB18030.
  4. Строка 14. Это объект типа bytes. В нем 11 байт. Эта последовательность байт получена кодированием строки a_string в кодировке Big5.
  5. Строка 19. Это строка. Она состоит из девяти символов. Она представляет из себя последовательность символов, которые получены после декодирования by, используя алгоритм кодировки Big5. Полученная строка совпадает с первоначальной.

P.S. Кодировка в исходном коде Python

Python 3 предполагает, что ваш исходный код (т.е. каждый файл .py) записан в кодировке UTF-8.

В Python 2, кодировкой по умолчанию для файлов .py была кодировка ASCII. В Python 3 кодировка по умолчанию – UTF-8.

Если вы хотите использовать другую кодировку в вашем коде, вы можете разместить объявление кодировки на первой строке каждого файла. Например, для кодировки windows-1252 объявление выглядит следующим образом:

# -*- coding: windows-1252 -*-

Объявление кодировки также может располагаться во второй строке файла, если первой строкой является путь к интерпретатору Python.

#!/usr/bin/python3
# -*- coding: windows-1252 -*-

За дополнительной информацией обращайтесь к PEP 263: Defining Python Source Code Encodings.

Материалы для дальнейшего чтения

Об Unicode в Python:

Об Unicode вообще:

О кодировании символов в других форматах:

О строках и форматировании строк:

Источник:

  • Mark Pilgrim. Dive Into Python 3

Теги

PythonОбучениеПрограммированиеЯзыки программирования

На сайте работает сервис комментирования DISQUS, который позволяет вам оставлять комментарии на множестве сайтов, имея лишь один аккаунт на Disqus.com.

В случае комментирования в качестве гостя (без регистрации на disqus.com) для публикации комментария требуется время на премодерацию.