Строки

Литералы строк

Литерал – запись значений некоторого типа данных в исходном коде программы. Мы уже знакомы с литералами целых чисел (например, 123), литералами чисел с плавающей точкой (например, 8.31 или 6.022e23). Пришло время разобраться с литералами строк, которые намного более разнообразны.

Для записи строк в исходном коде их содержимое нужно окружить кавычками. Можно пользоваться как одинарными кавычками, так и двойными, главное, чтобы открывающая и закрывающая строку кавычки были одинаковыми.

s1 = "hello"
s2 = 'world'

Когда в само значение строки входят одинарные или двойные кавычки, удобно воспользоваться для обозначения литерала другими кавычками:

s3 = "I'm saying"     # Одинарная кавычка не закрывает строку,
                      # а является просто символом в ней.
s4 = 'Say "hello"'

Впрочем, можно и не менять тип кавычек, а просто избавить кавычку от специального закрывающего литерал значения с помощью обратного слеша:

s3 = 'I\'m saying'
s4 = "Say \"hello\""

Особенно это полезно, когда в строку входят и одинарные, и двойные кавычки, и тогда обязательно приходится экранировать либо те, либо другие с помощью обратного слеша.

s5 = 'I\'m saying "hello"'

Экранирование и спецсимволы

Экранирование – предание символу специального значения или снятие специального значения с символа. Например, 'n' – просто буква английского алфавита, а '\n' – спецсимвол, обозначающий перевод строки. Экранирование в Python производится с помощью добавления обратного слеша перед экранируемым символом.

Смысл некоторых экранируемых символов:

  • '\0' – нулевой байт. В Python специального значения не имеет и может встречаться внутри строк, не оканчивая их.
  • '\'' – экранирование одинарной кавычки от закрывающего строку значения
  • '\"' – экранирование двойной кавычки от закрывающего строку значения
  • '\\' – так как символ обратного слеша используется для экранирования других символов, чтобы он обозначал сам себя, его тоже нужно заэкранировать
  • '\n' – перевод строки
  • '\r' – возврат каретки
  • '\t' – табуляция
  • '\v' – вертикальная табуляция

Пять символов ' ', '\r', '\n', '\t', '\v' составляют множество пробельных символов. С этим множеством работают многие методы строк.

Многострочные строки

Синтаксис Python устроен таким образом, что обычный литерал строки может располагаться только на одной строке. То есть, мы можем делать переводы внутри литерала с помощью символа ‘n’, но этот литерал должен всё равно располагаться в одной строке исходного кода.

Например:

s1 = 'Мой дядя самых честных правил,\nКогда не в шутку занемог,\nОн уважать себя заставил\nИ лучше выдумать не мог.\n'''

Как видно, представлять строки в таком виде несколько неудобно, и они могут не влезть в ширину экрана. Поэтому в языке существуют так называемые “Многострочные строки”. Такой каламбур возник из-за того, что в переводе словосочетания “Multiline strings” и “line” как строка исходного кода, и “string” как строковый тип данных оба переводятся на русский язык как “строка”.

Многострочные строки начинаются на три двойные или одинарные кавычки и заканчиваются на три кавычки того же типа:

s1 = '''Мой дядя честных правил,
Когда не в шутку занемог,
Он уважать себя заставил
И лучше выдумать не мог.
'''

Такие строки часто используются для документирования функций и модулей в самом исходном коде на языке Python.

Преобразование в строку

Кроме записи литералов в исходном коде, часто бывает нужно преобразовать в строку число или какое-то другое значение уже во время выполнения программы. Для этого мы воспользуемся функцией str:

s1 = str(123)               # "123"
s2 = str(8.31)              # "8.31"
s3 = str("abc")             # "abc", без изменений
s4 = str([1, 2, "def"])     # "[1, 2, 'def']"

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

Кроме функции str существует функция repr (от английского “representation” – представление), которая переводит объекты в такие строки, которые могли бы быть восприняты питоном как свои литералы. Такое представление часто полезно, когда нужно разглядеть всякие непечатные спецсимволы внутри строк:

message = "Your directory is:\n\tC:\\Users\\User1"
print(message)
print(repr(message))

Этот код выведет на печать следующее:

Your directory is:
        C:\Users\User1
'Your directory is:\n\tC:\\Users\\User1'

Как видно, в первом случае все символы возымели своё специальное значение, '\n' переводил печать на новую строку, '\t' делал отступ кратным восьми пробелам, '\\' обозначал одинарный обратный слеш. Во второй же инструкции print никаких спецсимволов не было, потому что repr заменил, например, '\n' на два символа '\\' и 'n'.

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

s1 = bin(255)      # "0b11111111"    в двоичном виде     (binary)
s2 = oct(255)      # "0o377"         в восьмеричном      (octal)
s2 = hex(255)      # "0xff"          в шестнадцатеричном (hexadecimal)

Если не нужен предваряющий каждое число префикс, его можно убрать с помощью среза:

s1 = bin(255)[2:]   # "11111111"

Подробнее срезы обсуждаются в данной лекции ниже.

Кодировка Unicode, одиночные символы, длина строки

Символы в Python – это элементы Универсального набора символов Unicode, в который входят символы почти всех алфавитов мира, большая часть иероглифов различных языков и даже многие специальные символы, например, математические обозначения. Так как число таких символов заметно превышает 256, одного байта уже недостаточно для представления одного символа. Это приводит к тому, самой в распространённой кодировке, кодирующей символы из набора Unicode, а именно, в кодировке UTF-8, один символ может кодироваться переменным числом байт (от 1 до 6). К счастью, Python берёт проблемы работы с такими многобайтовыми кодировками на себя, и один символ в Python – это один символ стандарта Unicode, а длина строки – это число символов Unicode, а не число байт.

В языке C функция вычисления длины строки работала достаточно долго из-за того, что ей приходилось проходить по всей строке и считать символы. В Python функция len для строк работает быстро, так как длина строки изначально хранится в объекте строки.

s = 'hello world!'
print(len(s))  # 12

Символы

Отдельного типа данных для символов в Python3 нет. Символы – это строки длиной 1.

s = 'abc'
print(type(s))   # str
print(len(s))    # 3

c = s[0]         # 'a'
print(type(c))   # str
print(len(c))    # 1

Функция ord возвращает номер символа в наборе символов Unicode, а chr наоборот, принимает номер символа и возвращает сам символ:

n = ord('A')     # 65
c = chr(65)      # 'A'

Чтобы пройти по всем символам строки, можно просто подставить её в цикл for и перебрать все символы по порядку:

for c in s:
    print(c)

В таком случае переменная c последовательно переберёт все символы строки s.

Так же можно перебрать и все индексы символов этой строки с помощью range от длины строки s:

for i in range(len(s)):
    print(s[i])

Операторы ==, !=, <, <=, >, >=, in, not in, +, *

Операторы == и != сравнивают содержимое двух строк. Две строки считаются равными, если их длины равны и все символы в них совпадают. При этом регистр букв имеет значение, то есть, 'a' и 'A' – это разные символы.

Операторы <, <=, >, >= сравнивают две строки в алфавитном порядке, причём “алфавитом” считается порядок символов, заданный стандартом Unicode. Так, например, цифры в нём стоят до заглавных английских букв, заглавные английские буквы до прописных английских, буквы и иероглифы остальных языков позже английских. Кроме того, есть проблема с русской буквой 'ё', которая идёт позже остальных букв русского алфавита. Но в целом, операторы <, <=, >, >= очень полезны.

Оператор in

sub in s   # True, если в s есть подстрока, равная sub
           # False иначе

Оператор in для строк работает не совсем так, как для списков. Если для списка он ищет один элемент, то для строк он будет искать подстроку, то есть несколько идущих подряд символов, совпадающих с искомой строкой:

print('yt' in 'Python')  # True
print('ty' in 'Python')  # False

Оператор + создаёт новую строку из двух существующих и копирует в неё эти две строки одна за другой:

s1 = 'abc'
s2 = 'def'

s3 = s1 + s2  # 'abcdef'
s4 = s1 * 3   # 'abcabcabc'

Важно понимать, что соединять большое количество строк в цикле с помощью инструкции вида s = s + s1, где s1 – небольшая строка, присоединяемая в цикле к концу строки s, крайне неэффективно. В такой конструкции каждый раз будет создаваться новая строка, в которую будет копироваться всё содержимое старой, плюс небольшая добавка из новой. Эффективно соединять большое количество строк помогает метод join, который мы рассмотрим ниже.

Доступ по индексу

Конструкция вида

с = s[i]

запишет в переменную c i-ый символ строки s. Причём, в данном случае под символом поднимается не просто байт строки, а полноценный символ многобайтовой кодировки Unicode. То есть, даже если строка состоит из букв, размер каждой из которых более одного байта, всё равно s[i] будет i-ым символом. Для строк, так же как и для списков, отрицательные индексы обозначают элементы с конца строки.

_images/indices.png

Строки в Python неизменяемы. То есть, нельзя изменять их длину, добавляеть или удалять подстроки или символы, присваивать новые значения символам в строке.

s = 'abc'
s[0] = 'A'  # Ошибка

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

Срезы строк

Для строк срезы работают точно так же, как и для списков, то есть, создают новую строку из символов данной строки с заданными в срезе индексами.

s= 'abcdef'

# Срезы - это копии подстрок
# Первый индекс включается, втрой нет
s1 = s[1:5]  # 'bcde'

# Как один, так и оба индекса могут быть отрицательными
s1 = s[-5:5]  # 'bcde'
s1 = s[1:-1]  # 'bcde'
s1 = s[-5:-1]  # 'bcde'

# Если первый индекс не указан, с начала
s1 = s[:4]  # 'abcd'

# Если второй индекс не указан, до конца
s1 = s[2:]  # 'bcde'

s1 = s[:]   # 'abcdef', та же строка

# Если индекс начала больше или равен индексу конца,
# то результат -- пустая строка
s1 = s[4:2]  # ''

Как видно, срезы с двумя аргументами, s[a:b], проходят по тем же индексам, что и range(a, b). Аналогия полная, кроме случаев, когда в s[a:b] не указан один или оба индекса.

Срезы с тремя аргументами работают аналогично range(a, b, step).

s = 'abcdef'

s1 = s[0:4:2]  # 'ac'
s1 = s[::2]    # 'ace', срез по чётным индексам
s1 = s[1::2]   # 'bdf', по нечётным
# если первый индекс не указан, то при отрицательном шаге
# движение начнётся с конца строки, если не указан второй --
# продолжится до начала.
s1 = s[::-1]   # 'fedcba', в обратном порядке

Методы строк

Методы lower и upper возвращают новую строку, в которой все буквы переведены в нижний (или верхний в случае upper) регистр, а небуквенные символы оставлены без изменений.

s = 'abc, Def'
s1 = s.upper()  # 'ABC, DEF'
s1 = s.lower()  # 'abc, def'

Метод isupper возвращает истину в случае, если все буквы в строке в верхнем регистре и в строке есть хотя бы одна буква (здесь имеются в виду именно буквы, а не символы). Если это условие не выполняется, возвращается False. Метод islower работает аналогично для нижнего регистра. Эти методы обычно применяются для проверки условий в if.

s.isupper()
s.islower()

Методы isdigit, isalpha, isalnum проверяют, что строка не пуста и целиком состоит из символов определённого класса: десятичных цифр для isdigit, букв для isalpha, и цифр или букв для isalnum.

s.isdigit()
s.isalpha()
s.isalnum()

Метод find ищет индекс крайнего левого вхождения подстроки sub в строку, к которой он применён. Если указан именованный аргумент start, поиск начинается с индекса start, если указан индекс end, поиск ведётся до end, не включая его.

ind = s.find(sub)  # первое вхождение, поиск с начала до конца
ind = s.find(sub, start=i0)  # первое вхождение, поиск с индекса i0 до конца
ind = s.find(sub, end=i1)
ind = s.find(sub, start=i0, end=i1)

Метод rfind (от английского right find) работает аналогично find, однако ищет первое вхождение подстроки справа. Аргументы start и end указывают левую и правую границы поиска.

ind = s.rfind(sub)
ind = s.rfind(sub, start=i0)
ind = s.rfind(sub, end=i1)
ind = s.rfind(sub, start=i0, end=i1)

Методы index и rindex работают так же, как find и rfind и принимают такие же именованные аргументы start и end, но, если строка не найдена, произойдёт ошибка.

ind = s.index(sub)
ind = s.rindex(sub)

Метод split

Одним из самых важных и часто используемых методов является split, который позволяет разбить одну строку на список из нескольких строк по символам-разделителям.

Метод split без аргументов разбивает строку по пробельным символам:

s = ' abc  def\n gh'
L = s.split()  # ['abc', 'def', 'gh']

Очень часто метод split используется для ввода нескольких значений на одной строке. Пример для ввода двух целых чисел через один или несколько пробелов:

x, y = input().split()  # Разбили одну введённую строку на две
x = int(x)  # Переводим строку в число
y = int(y)

В случае, если в строке таким образом вводится неизвестное количество чисел, можно сохранить результат split в переменную и затем обработать эти строки по одной в цикле.

Метод join

Метод join предназначен для эффективного соединения большого числа строк. Мы уже обсудили, что если присоединять строки к результирующей строке по одной, это может привести к проблемам с производительностью. Теперь же у нас есть метод join, который сразу вычисляет, сколько памяти необходимо под результирующую строку, выделяет память один раз, и последовательно заполняет её содержимым соединяемых строк. Для соединения списка строк мы будем пользоваться следующей конструкцией:

L_str = ['abc', 'def', 'ghi']
s = ''.join(L_str)   # 'abcdefghi'

В качестве объекта, к которому применяется метод join, выступает строка-соединитель, которая будет стоять между соединяемыми строками. Однако обычно нам нужно просто склеить несколько строк вместе, и соединитель в этом случае будет пустой строкой, как в примере выше, но можно использовать, например, запятую с пробелом:

L_str = ['abc', 'def', 'ghi']
s = ', '.join(L_str)   # 'abc, def, ghi'

Метод format

Иногда неудобно соединять несколько строк с помощью оператора +. Например, если мы хотим получить сроку вида

s = 'cos(1.0471975511965976) = 0.5000000000000001'

то мы могли бы сгенерировать её следующим образом:

import math
x = math.pi / 3
s = 'cos(' + str(x) + ') =' + str(cos(x))

Как видно, у нас оформление строки, то есть 'cos(' и ') =', перемешалось с вычислениями, да ещё и результаты вычислений надо переводить в строки с помощью str, чтобы не было ошибки в операторе +.

Воспользуемся методом format, который позволяет подставлять в указанные места строк значения своих аргументов:

import math
x = math.pi / 3
s = 'cos({}) = {}'.format(x, cos(x))

В этом случае содержание строки будет точно таким же. Метод format подставил x вместо первой подстроки '{}', а результат cos(x) вместо второй '{}'.