Строки

Строки

Строки предоставляют возможность хранить и оперировать с данными, представленными в виде последовательности символов. Язык Python из "коробки" имеет широкую поддержку строк и позволяет обращаться с символами из самых разных алфавитов.

Тип str

Для взаимодействия со строками в языке есть встроенный тип str. Его литералом являются одиночные ' или двойные кавычки ". Символы (код), заключенный между кавычками будет восприниматься Python, как строка:

>>> s1 = 'Привет! Я строка!'
>>> s2 = "Привет! Я тоже строка!"
>>> type(s1)
<class 'str'>
>>> type(s2)
<class 'str'>

Строка может быть пустой: '' или "".

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

  1. Воспользоваться в качестве литерала одним видом кавычек, а внутри строки пользоваться вторым видом кавычек;
  2. Экранировать кавычки внутри строки с помощью символа экранирования (escape character) \, он же называется обратной косой чертой.

Например:

>>> s4 = '\'Я внутри одинарных кавычек\', а "я внутри двойных"'
>>> s4
'\'Я внутри одинарных кавычек\', а "я внутри двойных"'
>>> print(s4)
'Я внутри одинарных кавычек', а "я внутри двойных"

Здесь в качестве литерала взяты одинарные кавычки, для первой части фразы используется экранирование, а для второй двойные.

Более того, для удобного написания многострочной строки (простите), можно воспользоваться тройными одинарными или тройными двойными кавычками, причём внутри такой строки экранирование не понадобится:

>>> multiline_str = '''Я первая строка
... I'm the second line
... А я третья!'''
>>> multiline_str
"Я первая строка\nI'm the second line\nА я третья!"
>>> print(multiline_str)
Я первая строка
I'm the second line
А я третья!

Заметьте, что во второй строке экранирование кавычки не понадобилось (I'm).

Также, вы могли заметить, что при вызове в интерактивном режиме строка отображается в виде, в котором представлена в программе, и только при печати print() отображается ожидаемо. В случае печати чисел это было не заметно.

Напоследок, небольшой список часто используемых экранированных последовательностей (escape sequence):

  • символ новой строки \n
  • табуляция \t, с помощью табуляции можно получать удобные для чтения таблицы
  • кавычки \', \"
  • обратная косая \\

Доступ к символам и срезы строк

Так же, как и список, строка это упорядоченная последовательность. Если список это последовательность объектов произвольного типа, то строка это последовательность символов.

Можно узнать длину строки, получить символ на определённой позиции и даже получить срез строки:

>>> s = "Hello, World!"
>>> len(s)
13
>>> s[0]
'H'
>>> s[7:]
'World!'
>>> s[::2]
'Hlo ol!'

Конкатенация и неизменяемость строк

Простейшая операция над двумя строками это конкатенация - приписывание второй строки в конец первой:

>>> str_1 = "ABC"
>>> str_2 = "def"
>>> str_1 + str_2
'ABCdef'
>>> str_2 + str_1
'defABC'

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

>>> str_1
'ABC'
>>> str_1 * 10
'ABCABCABCABCABCABCABCABCABCABC'
>>> 5 * str_1
'ABCABCABCABCABC'
>>> str_1
'ABC'
>>> str_2
'def'
>>> (str_1 + str_2) * 5
'ABCdefABCdefABCdefABCdefABCdef'

Строки являются неизменяемым типом в Python. При попытке изменения символа на какой-то позиции произойдёт ошибка:

>>> s = 'ваза'
>>> s[0] = 'б'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'str' object does not support item assignment

На самом деле, в примерах по конкатенации Python создавал новые объекты строк. Даже операция инкрементирования создаёт новую строку, в чём можно убедиться, узнав их идентификатор (в данном случае это равносильно адресу в памяти):

>>> s = 'a'
>>> id(s)
4465232176
>>> s += 'b'
>>> s
'ab'
>>> id(s)
4466564720

Сравнение строк

Строки в Python можно сравнивать между собой.

Сначала посмотрим на сравнение символов.

>>> '0' < '1'
True
>>> 'a' < 'b'
True
>>> '.' < ','
False

И если с символами 0, 1, a, b результат интуитивен, то последнее сравнение (знаков препинания) неочевидно.

Не будем гадать. Существует кодовая таблица символов, которая приписывает каждому символу порядковый номер. При сравнении символов сравниваются их порядковые номера.

В языке Python, чтобы узнать порядковый номер символа, воспользуйтесь функцией ord(s)

>>> '.' < ','
False
>>> ord('.')
46
>>> ord(',')
44
>>> 46 < 44
False

Сразу приведём функцию, обратную функции ord, это функция chr(i)

>>> ord('a')
97
>>> chr(97)
'a'

Историческим стандартом кодовых таблиц символов является таблица ASCII (American standard code for information interchange). Изначально таблица содержала 128 символов (по 7 бит на символ), затем она была расширена до 256 символов, где первые 128 символов остались без изменений, а вторая половина отводилась под национальные алфавиты, что породило множество национально-специфичных кодировок.

Современный стандарт - кодировка Unicode. Python работает с ней. Целью данной кодировки является создать "алфавит" для любых естественных и выдуманных языков и символов вообще. В Unicode первые 128 символов - символы ASCII.

Рекомендуется ознакомиться с таблицей ASCII. Например, здесь. Вы заметите, что символы алфавитов расположены в алфавитном порядке. Как раз за этим и кроется "интуитивность" сравнения 'a' < 'b'.

В Unicode также сохраняется этот удобный паттерн и для других языков, но стоит быть осторожным

>>> 'б' < 'ё'
True
>>> 'Б' < 'Ё'
False

Поскольку, скажем, буквы расположены непрерывно в ASCII, то вы можете проверять, является ли символ буквой (строчные и заглавные случаи нужно рассматривать отдельно).

>>> 'a' <= '0' <= 'z'
False
>>> 'a' <= 'y' <= 'z'
True

Аналогично этому примеру попробуйте написать условие проверки символа на десятичную цифру.

Итак, отдельные символы сравниваются по их порядковым номерам. Как же сравниваются строки? Всё просто. Так же, как слова отсортированы в словаре в алфавитном порядке, так же и в машинном представлении, только алфавитом является кодовая таблица (в случае Python это Unicode).

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

>>> 'aa' < 'aaa'
True
>>> 'ab' < 'aaa'
False

В первом примере первые два символа строк совпадают, однако в строке aa два символа, а в строке aaa — три. В этом случае меньшей строкой является та, что короче.

Во втором примере первые два символа совпадают, вторые же нет. Поскольку порядок ord('b') > ord('a'), то получаем False.

Некоторые методы строк

У строк в Python огромное количество методов. Не верите? Вот они:

str.capitalize()
str.casefold()
str.center(width[, fillchar])
str.count(sub[, start[, end]])
str.encode(encoding="utf-8", errors="strict")
str.endswith(suffix[, start[, end]])
str.expandtabs(tabsize=8)
str.find(sub[, start[, end]])
str.format(*args, **kwargs)
str.format_map(mapping)
str.index(sub[, start[, end]])
str.isalnum()
str.isalpha()
str.isascii()
str.isdecimal()
str.isdigit()
str.isidentifier()
str.islower()
str.isnumeric()
str.isprintable()
str.isspace()
str.istitle()
str.isupper()
str.join(iterable)
str.ljust(width[, fillchar])
str.lower()
str.lstrip([chars])
static str.maketrans(x[, y[, z]])
str.partition(sep)
str.replace(old, new[, count])
str.rfind(sub[, start[, end]])
str.rindex(sub[, start[, end]])
str.rjust(width[, fillchar])
str.rpartition(sep)
str.rsplit(sep=None, maxsplit=-1)
str.rstrip([chars])
str.split(sep=None, maxsplit=-1)
str.splitlines([keepends])
str.startswith(prefix[, start[, end]])
str.strip([chars])
str.swapcase()
str.title()
str.translate(table)
str.upper()
str.zfill(width)

Мы разберём только некоторые из них (для остальных есть help(str.method_name) :-)

Поиск

Метод str.find ищет подстроку в строке и возвращает индекс начала найденной подстроки. Если вхождение не найдено, вернётся -1:

>>> s = 'Hello, World!'
>>> s.find('World')
7
>>> s[7]
'W'
>>> s.find('Universe')
-1

Этот метод имеет два необязательных аргумента start и end. Если их указать, то поиск будет осуществляться в срезе строки s[start:end]:

>>> s
'Hello, World!'
>>> s.find('o')
4
>>> s[3:6]
'lo,'
>>> s.find('o', 7)
8
>>> s[7:10]
'Wor'

И, как видно, str.find осуществляет поиск первого вхождения подстроки, начиная слева.

Чтобы осуществить поиск подстроки, начиная справа (т.е. с конца) строки, можно воспользоваться методом str.rfind. Сравните:

>>> s
'Hello, World!'
>>> s.rfind('o')
8
>>> s.find('o')
4

Метод str.rfind имеет тот же интерфейс, что и str.find: он имеет два необязательных аргумента, чтобы задать диапазон поиска и возвращает -1, если подстрока не найдена.

Подсчёт

Методом str.count можно подсчитать количество вхождений подстроки в строку:

>>> s = 'Пингвины не любят окна.'
>>> s.count('а')
1
>>> s.count('ин')
2
>>> s.count('яблоки')
0

Диапазон поиска можно указать так же, как в str.find.

Замена

Для замены подстроки в строке существует метод str.replace:

>>> src = 'Пингвины не любят окна.'
>>> replaced = src.replace('Пингвины', 'Даже окна')
>>> src
'Пингвины не любят окна.'
>>> replaced
'Даже окна не любят окна.'

Так как строки в Python неизменяемые, то str.replace на базе исходной строки создает и возвращает новую.

У этого метода есть дополнительный параметр - количество производимых замен. Если этот параметр выставлен в -1 (значение по умолчанию), то произойдёт замена всех вхождений.

>>> s = 'aaaaa'
>>> s.replace('a', 'b')
'bbbbb'
>>> s.replace('a', 'b', 3)
'bbbaa'

Разбиение и объединение

По существу, вы уже знакомы с этими операциями и применяли их.

Можно разбивать строку на основе подстроки с помощью str.split. Результатом этой операции является список. Например, может стоять задача по разбиению предложения на слова:

>>> sentence = 'Пингвины не любят окна.'
>>> sentence.split()
['Пингвины', 'не', 'любят', 'окна.']
>>> sentence2 = 'вставка, выбор, пузырёк, подсчёт, Хоар, слияние'
>>> sentence2.split(', ')
['вставка', 'выбор', 'пузырёк', 'подсчёт', 'Хоар', 'слияние']

В первом случае в качестве подстроки для разбиения используется значение по умолчанию: разбиение по символам, обозначающих пустое пространство (пробелы, табуляция, перенос строки). Во втором случае разбиение задано явно - по подстроке ', '.

Больше примеров:

>>> sentence3 = 'вставка -- выбор -- пузырёк -- подсчёт -- Хоар -- слияние'
>>> sentence3.split()
['вставка', '--', 'выбор', '--', 'пузырёк', '--', 'подсчёт', '--', 'Хоар', '--', 'слияние']
>>> sentence3.split('--')
['вставка ', ' выбор ', ' пузырёк ', ' подсчёт ', ' Хоар ', ' слияние']
>>> sentence3.split(' -- ')
['вставка', 'выбор', 'пузырёк', 'подсчёт', 'Хоар', 'слияние']

У str.split есть ещё один необязательный аргумент - количество разбиений.

Итак, str.split разбивает строку по подстроке и возвращает список строк. Обратная операция это объединение массива строк в одну строку, она осуществляется с помощью str.join:

>>> sentence3 = 'вставка -- выбор -- пузырёк -- подсчёт -- Хоар -- слияние'
>>> sort_algs = sentence3.split(' -- ')
>>> sort_algs
['вставка', 'выбор', 'пузырёк', 'подсчёт', 'Хоар', 'слияние']
>>> ''.join(sort_algs)
'вставкавыборпузырёкподсчётХоарслияние'
>>> ' '.join(sort_algs)
'вставка выбор пузырёк подсчёт Хоар слияние'
>>> ' + '.join(sort_algs)
'вставка + выбор + пузырёк + подсчёт + Хоар + слияние'

Этот метод более гибкий для входных данных и позволяет объединять не только список строк, но и любой другой итерируемый объект. Главное, чтобы этот объект содержал только строки:

>>> ' '.join(range(10))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: sequence item 0: expected str instance, int found
>>> ' '.join(map(str, range(10)))
'0 1 2 3 4 5 6 7 8 9'