1. Лекция 6

26 октября 2018 г.

Заметили ошибку или есть предложение? Напишите на почту: romansdidnotcrucify@gmail.com

2. Строки

2.1. Задание строк

Строки в питоне - это такие последовательности (смысл их интуитивно понятен - набор символов).

Задавать строки можно четырьмя способами:

  1. с помощью двойных кавычек: "John";

  2. с помощью одинарных кавычек: 'Doe' (никаких отличий от двойных кавычек);

  3. с помощью тройных кавычек: '''John Doe can be multiline''' - позволяют задать многострочную строку (многострочный строковый литерал), а также использовать в её тексте кавычки, не прибегая к специальным символам в исходном коде;

  4. с помощью преобразования объектов других типов: str(12).

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

Кстати, обратите внимание на разницу в выводе (функция print, на самом деле, выводит результат метода __str__, a интерпретатор в выводе использует __repr__):

   1 >>> a = '''A multiline string
   2 ... is a string that takes multiple
   3 ... lines of text
   4 ... '''
   5 >>> a    # Интерпретатор при выводе использует результат работы метода объекта __repr__ - он представляет в виде строки внутреннюю структуру объекта 
   6 'A multiline string\nis a string that takes multiple\nlines of text\n'
   7 >>> print(a.__str__())    # __str__ задаёт строковое представление объекта - этот-то метод объекта (если он есть) и использует print
   8 A multiline string
   9 is a string that takes multiple
  10 lines of text
  11 
  12 >>> print(a.__repr__())
  13 'A multiline string\nis a string that takes multiple\nlines of text\n'
  14 

2.2. Спецсимволы (управляющие символы)

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

  1. \n - перенос строки;

  2. \t - табуляция;

  3. \r - символ возврата каретки (подробнее здесь);

  4. \', \" - символ - одинарная/двойная кавычка (вне зависимости от того, с помощью каких кавычек вы задаёте строковый литерал);

  5. \\ - собственно, backslash.

   1 >>> print('There will be a\nhuge fall')
   2 There wiil be a
   3 huge fall
   4 >>> print("We need to explore\t\t\touter space")
   5 We need to explore                      outer space
   6 >>> print("I don't want to set the world on fire\rI do!!!")    # Символ возврата каретки переносит наш "курсор" - то место, в которое происходит вывод - в начало строки
   7 I do!!! want to set the world on fire
   8 >>> print("Yep, I'm using \"double quotes\" in a double-quoted string literal")
   9 Yep, I'm using "double quotes" in a double-quoted string literal
  10 

Полную таблицу спецсимволов можно посмотреть здесь

2.3. Коды символов

В разговоре об управляющих символах уместно будет упомянуть функции, позволяющие работать напрямую с кодами символов - ord и chr:

   1 >>> ord("a")    # Получить номер символа в кодировке (ord вернёт десятичное число)
   2 97
   3 >>> ord("abc")    # Обратите внимание, что ord принимает на вход строку именно из одного символа
   4 Traceback (most recent call last):
   5   File "<stdin>", line 1, in <module>
   6 TypeError: ord() expected a character, but string of length 3 found
   7 >>> ord(a)    # И именно строку
   8 Traceback (most recent call last):
   9   File "<stdin>", line 1, in <module>
  10 TypeError: ord() expected a character, but string of length 70 found
  11 >>> ord("ы")    # Символы, не принадлежацие таблице ASCII, кодируются с помощью Юникода
  12 1099
  13 >>> chr(1099)     # chr позволяет по коду символа получить сам символ (символ Юникода тоже)
  14 'ы'
  15 >>> hex(ord("a"))    # Для примера нам потребуются шестнадцатеричные коды символов
  16 '0x61'
  17 >>> hex(ord("ы"))
  18 '0x44b'
  19 >>> print("\x61")    # \xhh - управляющая последовательность, позволяющая вывести ASCII-символ по его коду
  20 a
  21 >>> print("\x04\x4b\x61")    # Для кириллицы не подойдёт
  22 Ka
  23 >>> print("\u044b\x61")    # Символы Юникода задаются с помощью управляющих последовательностей \u и \U (за первой следуют 4 шестнадцатеричные цифры, за второй - 8)
  24 ыa
  25 

В питоне, начиная с третьей версии, по умолчанию строки кодируются в UTF-8 (кстати, по ссылке очень хорошо объяснена предыстория вопроса; почитайте, если плохо понимаете, что есть Юникод и с чем его едят).

2.4. Строка как последовательность

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

   1 >>> for i in "QWERTY":
   2 ...     print(i)
   3 ...
   4 Q
   5 W
   6 E
   7 R
   8 T
   9 Y
  10 

Вопрос.

В кортежах элементом последовательности является элемент кортежа. В строках же элемент последовательности - строка, состоящая из одного (соответствующего) символа:

   1 >>> a = "Raxacoricofallapatorius"
   2 >>> a[5]    # Строка из одного символа
   3 'o'
   4 >>> a[5:9]    # Новая строка из 4 символов
   5 'oric'
   6 >>> a[8]
   7 'c'
   8 >>> a[8:9]
   9 'c'
  10 >>> a[8] is a[8:9]    # TODO: В чём принципиальное отличие строки от кортежа, кроме того, что каждый элемент строки - тоже строка? Нужно уточнить
  11 True
  12 >>> a[8] is 'c'
  13 True
  14 

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

   1 >>> a[6] = "v"
   2 Traceback (most recent call last):
   3   File "<stdin>", line 1, in <module>
   4 TypeError: 'str' object does not support item assignment

2.4.1. Операция in

Для строк операция in не осуществляет, как для других последовательностей, поиск первого вхождения элемента (т.е. строки из одного символа). Она осуществляет поиск подстроки в строке:

   1 >>> a
   2 'Raxacoricofallapatorius'
   3 >>> "o" in a    # Это мы уже видели на примере других последовательностей
   4 True
   5 >>> "fall" in a    # А вот это - нечто новое
   6 True
   7 >>> "gravity fall" in a
   8 False
   9 

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

2.4.2. Операция count

count осуществляет подсчёт количества вхождений подстроки в строку (что тоже является уникальным для строк поведением):

   1 >>> a.count("co")    # Потому что вместо подсчёта вхождений одного элемента последовательности мы считаем количество вхождений целой подпоследовательности
   2 2
   3 

2.5. Методы строк

У строк очень много различных методов, что связано с головными болями, которые разработчики языка имели при работе с ABC/Паскалем:

   1 >>> a = "asd aSDFSDv  zxcdfg%^ 567567 GFHFG oikpi"
   2 >>> dir(a)
   3 ['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mod__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmod__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'capitalize', 'casefold', 'center', 'count', 'encode', 'endswith', 'expandtabs', 'find', 'format', 'format_map', 'index', 'isalnum', 'isalpha', 'isdecimal', 'isdigit', 'isidentifier', 'islower', 'isnumeric', 'isprintable', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'maketrans', 'partition', 'replace', 'rfind', 'rindex', 'rjust', 'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill']
   4 

Эти методы делятся на несколько групп.

2.5.1. Поиск

Помимо уже упомянутых in и count, следует знать также об index и find:

   1 >>> a
   2 'asd aSDFSDv  zxcdfg%^ 567567 GFHFG oikpi'
   3 >>> a.index("SDF")    # Найти индекс первого вхождения подстроки в строку
   4 5
   5 >>> a.index("SDF!")    # Если такой подстроки в строке не содержится, выбросить исключение
   6 Traceback (most recent call last):
   7   File "<stdin>", line 1, in <module>
   8 ValueError: substring not found
   9 >>> a.find("SDF")    # find тоже находит первое вхождение подстроки в строку
  10 5
  11 >>> a.find("SDF!")    # Однако в случае неуспеха не выбрасывает ошибку, а возвращает -1
  12 -1
  13 

2.5.2. Проверки

Названия методов из этой группы начинаются на is. Сами эти методы призваны проверять различные свойства строк:

   1 >>> b = "123we"
   2 >>> b.isalnum()    # "is alphanumeric" - содержит ли строка только буквы и цифры
   3 True
   4 >>> b = "123we!"
   5 >>> b.isalnum()
   6 False
   7 >>> b = "123we_"    # К слову, нижнее подчёркивание буквой не считается
   8 >>> b.isalnum()
   9 False
  10 >>> b = "йцукен"    # Буквами считаются и буквы в Юникоде
  11 >>> b.isalnum()
  12 True
  13 >>> "1234".isdigit()    # isdecimal, isdigit, isnumeric - проверка, состоит ли строка из цифр (с некоторыми тонкостями)
  14 True
  15 >>> "1234".isdecimal()
  16 True
  17 >>> "1234F".isdecimal()
  18 False
  19 >>> "1234F".isdigit()
  20 False
  21 >>> b = "123qwe"
  22 >>> b.isidentifier()    # Проверка, может ли строка быть идентификатором (именем объекта), т.е. является ли она последовательностью букв, цифр и _, начинающейся не с цифры
  23 False
  24 >>> b = "_123qwe"
  25 >>> b.isidentifier()
  26 True
  27 >>> "asd".islower()    # islower - состоит ли строка только из строчных букв
  28 True
  29 >>> "aSd".islower()
  30 False
  31 >>> "ASD".isupper()    # isupper - состоит ли строка только из прописных букв
  32 True
  33 >>> "AsD".isupper()
  34 False
  35 >>> "word".isprintable()    # isprintable - состоит ли строка только из печатных символов
  36 True
  37 >>> "word\n".isprintable()
  38 False
  39 >>> "\n\t      \t\n".isspace()    # isspace - состоит ли строка только из пробельных символов
  40 True
  41 >>> "\n\t   !   \t\n".isspace()
  42 False
  43 >>> "Avalidtitle".istitle()    # istitle - проверяет, состоит ли строка только из слов, разделённых пробелами, состоящих только из букв и начинающихся с прописной буквы каждое
  44 True
  45 >>> "A nonvalid title".istitle()
  46 False
  47 >>> "A_non-valid_title".istitle()
  48 False
  49 >>> "Anon-validtitle".istitle()
  50 False
  51 >>> "Anon123alidtitle".istitle()
  52 False
  53 >>> "ANonValidTitle".istitle()
  54 False
  55 >>> "A Valid Title".istitle()
  56 True
  57 

Про разницу между isdecimal, isdigit и isnumeric можно почитать здесь.

2.5.3. Методы форматирования

   1 >>> s = "qWe"
   2 >>> s.lower()    # lower - преобразование всех букв в строчные
   3 'qwe'
   4 >>> s.upper()    # upper - преобразование всех букв в прописные
   5 'QWE'
   6 >>> a.capitalize()    # capitalize - преобразование первой буквы в прописную
   7 'Asd asdfsdv  zxcdfg%^ 567567 gfhfg oikpi'
   8 >>> a.title()    # title - преобразование первых букв всех слов в прописные
   9 'Asd Asdfsdv  Zxcdfg%^ 567567 Gfhfg Oikpi'
  10 >>> s.swapcase()    # swapcase - сменить регистр всех букв в строке
  11 'QwE'
  12 >>> a.casefold()    # casefold - привести строку к такому виду, в котором её возможно сравнивать (>, <) с другими строками вне зависимости от регистра букв
  13 'asd asdfsdv  zxcdfg%^ 567567 gfhfg oikpi'
  14 

2.5.4. replace

Гораздо более интересный метод - replace. Он позволяет заменить вхождения подстроки на заданную строку:

   1 >>> a
   2 'asd aSDFSDv  zxcdfg%^ 567567 GFHFG oikpi'
   3 >>> a.replace("SD","----")    # По умолчанию заменяются все вхождения
   4 'asd a----F----v  zxcdfg%^ 567567 GFHFG oikpi'
   5 >>> a.replace("SD","----",1)    # Но можно и ограничить количество замен
   6 'asd a----FSDv  zxcdfg%^ 567567 GFHFG oikpi'
   7 

2.5.5. Работа с пробельными символами

Как убрать лишние пробелы? Методами lstrip, rstrip и strip:

   1 >>> b = "   \t   za warudo   \t   "
   2 >>> b.lstrip()
   3 'za warudo   \t   '
   4 >>> b.rstrip()
   5 '   \t   za warudo'
   6 >>> b.strip()
   7 'za warudo'
   8 

Кроме того, есть метод expandtabs, являющийся наследием второго питона и позволяющий раскрыть символы табуляции в строке по определённым правилам.

2.5.6. zfill

Метод zfill позволяет дополнить строку, состоящую из цифр, нулями слева, чтобы получить строку длины не меньше указанной:

   1 >>> s = "1"
   2 >>> s.zfill(10)
   3 '0000000001'
   4 >>> s = "-1"
   5 >>> s.zfill(10)    # Вот и отличие от ljust: zfill интерпретирует строку именно как число (но это работает только с целыми десятичными числами)
   6 '-000000001'
   7 

2.6. split и join

Самые интересные и полезные методы работы со строками я даже выделил в отдельный раздел.

2.6.1. split

Метод split позволяет разбить строку на части по какому-либо символу (по умолчанию - по целым подпоследовательностям, состоящим из пробельных символов). При этом результатом его работы будет список подстрок исходной строки:

   1 >>> a
   2 'asd aSDFSDv  zxcdfg%^ 567567 GFHFG oikpi'
   3 >>> a.split()    # Разбить строку на подстроки по пробельным символам (последовательность пробельных символов как бы считается за один символ)
   4 ['asd', 'aSDFSDv', 'zxcdfg%^', '567567', 'GFHFG', 'oikpi']
   5 >>> nn = "123 32 2134 346 657 56 1234    436"    # Это очень удобно для анализа ввода с клавиатуры
   6 >>> [int(n) for n in nn.split()]
   7 [123, 32, 2134, 346, 657, 56, 1234, 436]
   8 >>> nn.split("3")    # Можно и указать подстроку, по которой выполнять разбиение
   9 ['12', ' ', '2 21', '4 ', '46 657 56 12', '4    4', '6']
  10 >>> a = "a,,d,r,,,e,,r"    # Если вы указываете подстроку, по которой разбивать данную, вы можете получить в массиве и пустые строки
  11 >>> a.split(",")
  12 ['a', '', 'd', 'r', '', '', 'e', '', 'r']
  13 >>> a.split(",,")    # Эта подстрока, конечно, не обязана состоять из одного символа
  14 ['a', 'd,r', ',e', 'r']
  15 

Метод split очень, ОЧЕНЬ часто используется при анализе входных данных с клавиатуры.

Частный случай работы метода split, с некоторыми нюансами (например, независимость от используемого в ос символа новой строки), представляет метод splitlines:

   1 >>> s = "This is a long\n text with\n multiple\n line endings\n"
   2 >>> s.splitlines()    # splitlines позволяет разбить строку по символам переноса строки (какой бы символ для этого ни использовался в ос)
   3 ['This is a long', ' text with', ' multiple', ' line endings']
   4 

2.6.2. join

Можно выполнить и обратную операцию - объединить несколько строк в одну:

   1 >>> sn = nn.split()
   2 >>> "__".join(sn)    # Таким образом можно задать тот разделитель, которым нужно соединять строки
   3 '123__32__2134__346__657__56__1234__436'
   4 >>> "".join(sn)    # Т.к. join - метод строки, то, даже если нам не нужен разделитель, хотя бы пустую строку указать придётся
   5 '123322134346657561234436'
   6 

Может возникнуть вопрос: зачем это делать с помощью отдельного метода, если есть операция конкатенации +?

  • /!\ В интернетах если не продолжаются до сих пор, то, по крайней мере, легко находятся старые холивары на тему того, какой метод эффективнее, и не всегда наиболее оцененный комментарий - верный. Так что будьте аккуратны, сверяйтесь с несколькими источниками и не забывайте проверять даты публикаций.

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

Кстати, посмотрите, что мы уже знаем про форматирование строк.

2.7.1. Модификатор r

Если вы не хотите, чтобы управляющие последовательности в вашей строке обрабатывались (скажем, вы хотите напечатать \n именно как два символа, а не как перенос строки), укажите перед открывающей кавычкой модификатор r (от raw - сырой, необработанный):

   1 >>> a = '\n\n\n\n'    # Управляющие последовательности интерпретируются
   2 >>> print(a)
   3 
   4 
   5 
   6 
   7 
   8 >>> a = r'\n\n\n\n'    # Управляющие последовательности не интерпретируются
   9 >>> print(a)
  10 \n\n\n\n
  11 

С модификатором r, впрочем, есть некоторые тонкости.

2.7.2. Форматные строки

В питоне версии 3.6 (который не так уж давно появился) был добавлен такой весьма удобный инструмент, как форматные строки.

Идея форматных строк восходит к концепции шаблонов и похожа на то, как форматируются строки в C (в питоне C-подобный язык форматирования тоже поддерживается, но он считается устаревшим и потому не главным).

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

Непонятно, с чего здесь начать, поэтому покажу всё на примерах:

   1 >>> f"____===={10:8}====____"    # Помимо текста снаружи них, в фигурных скобках указывается то выражение, которое должно быть отформатировано; через двоеточие после него можно указать минимальное количество символов, которое должна занимать получаемая подстрока
   2 '____====      10====____'
   3 >>> f"____===={10:08}====____"    # Можно не только указать минимальную ширину подстроки, но и заполнитель, который будет использован
   4 '____====00000010====____'
   5 >>> f"____===={10:<8}====____"    # Можно выровнять подстроку по левому краю
   6 '____====10      ====____'
   7 >>> f"____===={10:^8}====____"    # А можно и по центру (по правому краю она выравнивается по умолчанию)
   8 '____====   10   ====____'
   9 >>> f"____===={10.123456789192:^8}====____"    # Обратите внимание, впрочем, что здесь указывается именно минимальная, но не максимальная допустимая ширина подстроки
  10 '____====10.123456789192====____'
  11 >>> f"____===={10.123456789192:22}====____"
  12 '____====       10.123456789192====____'
  13 >>> f"____===={10.123456789192:22.20}====____"    # Для чисел можно указать, сколько знаков после запятой должно быть выведено
  14 '____==== 10.123456789191999761====____'
  15 >>> n = 100500    # Хуже того, в фигурных скобках могут фигурировать объявленные ранее переменные
  16 >>> f"____{n}____{n:8}____{a}"
  17 '____100500____  100500____\\n\\n\\n\\n'
  18 >>> f"____{n}____{n // 3 :8}____{a}"    # И целые выражения; т.е. сначала для каждого форматируемого фрагмента выполняется eval, и только потом происходит форматирование
  19 '____100500____   33500____\\n\\n\\n\\n'
  20 >>> f"____{n}____{nnn // 3 :8}____{a}"    # Следующие из этого ошибки и потенциальные угрозы - такие же, как у eval
  21 Traceback (most recent call last):
  22   File "<stdin>", line 1, in <module>
  23 NameError: name 'nnn' is not defined

Повторюсь, что, т.к. при обработке форматных строк выполняется, по сути, eval, применять их в продакшен-коде не рекомендуется точно так же и по тем же причинам, что и eval.

2.7.3. Строковый метод format

Форматные строки появились только в Python 3.6, а в предыдущих версиях для форматирования строк можно было пользоваться только таким инструментом, как строковый метод format.

Работает это так:

   1 >>> "{1}, {0}, {1}, {2}".format(10,20,30)    # В фигурных скобках указывается индекс параметра, переданного в format, который нужно подставить в данное место строки
   2 '20, 10, 20, 30'
   3 >>> "qqwe {} sdfsdf {}".format(10,20)    # Можно оставить фигурные скобки пустыми, и тогда выбор параметра будет осуществлён автоматически (они будут взяты просто по порядку)
   4 'qqwe 10 sdfsdf 20'
   5 >>> "qqwe {1} sdfsdf {}".format(10,20,30)    # Единственное, смешивать эти два подхода нельзя
   6 Traceback (most recent call last):
   7   File "<stdin>", line 1, in <module>
   8 ValueError: cannot switch from manual field specification to automatic field numbering
   9 >>> "qqwe {} sdfsdf {2}".format(10,20,30)
  10 Traceback (most recent call last):
  11   File "<stdin>", line 1, in <module>
  12 ValueError: cannot switch from automatic field numbering to manual field specification
  13 >>> "qqwe {:8} sdfsdf {}".format(10,20)    # Для форматируемого параметра можно указать дополнительные свойства, например, опять-таки, минимальную ширину занимаемой им подтроки
  14 'qqwe       10 sdfsdf 20'
  15 

Метод format, в отличие от форматных строк, безопасен: никакого eval при форматировании не выполняется, всё, что должно быть вычислено, вычисляется ещё на этапе передачи параметров в метод.

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

Тем не менее, на сервере ejudge на данный момент уже установлен Python 3.7, так что у вас должны работать оба способа форматирования строк.

2.8. Байтовые строки

2.8.1. Что такое строка с точки зрения питона

Строка в питоне - это последовательность символов.

Если вы заметили, я несколько раз за сегодня оговаривался и вместо "символы" говорил "буквы". Это было неспроста: символы в питоне - это не то же, что символ в C.

Символ в питоне - это то, что выводится на экран.

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

   1 >>> import sys    # С помощью специального метода посмотрим, сколько байт занимают наши строки
   2 >>> sys.getsizeof('12345')    # Кроме собственно символов, в строку входит также служебная информация
   3 54
   4 >>> sys.getsizeof('123456')    # Добавим ASCII-символ (пока что вся наша строка состоит только из них)
   5 55
   6 >>> sys.getsizeof('123456w')    # При этом размер, как и ожидалось, увеличивается на 1
   7 56
   8 >>> sys.getsizeof('123456wы')    # А вот при добавлении руской буквы получаем значительное увеличение размера
   9 90
  10 >>> a = "waffle345вафля"    # Почему? Потому что теперь нужно всю строку хранить, выделяя на каждый символ по 2 байта
  11 >>> a[0]    # Такое поведение позволяет при обращении со строками не думать о том, какой кодировке на самом деле принадлежит символ
  12 'w'
  13 >>> a[10]    # Кроме того, оно позволяет обеспечить совместимость с самыми разными кодировками, которые могут использоваться в операционной системе (в одной только windows, в зависимости от ситуации, используется не менее 4 различных кодировок)
  14 'а'
  15 

Чтение из файла, и даже чтение с клавиатуры - в общем-то, непростые операции. Так, например, в линуксе при чтении с клавиатуры ASCII-символы кодируются одним байтом, а символы Юникода могут кодироваться некоторые двумя, а некоторые - четырьмя байтами. А в питоне нужно выбрать единую кодировку для всей считываемой строки.

2.8.2. bytes

В питоне есть специальный тип данных - байтовая строка (он же - байтовый массив). Он чем-то похож на массив символов в C:

   1 >>> b = b"qwerqwer"    # Обозначается он как строковый литерал с модификатором `b`
   2 >>> type(b)
   3 <class 'bytes'>
   4 >>> a = 'asdfasdf'    # Для сравнения - строка
   5 >>> type(a)
   6 <class 'str'>
   7 >>> b    # Когда символов несколько - bytes ведёт себя как строка
   8 b'qwerqwer'
   9 >>> b * 3
  10 b'qwerqwerqwerqwerqwerqwer'
  11 >>> b + b'ret'
  12 b'qwerqwerret'
  13 >>> b[0]    # Но вот при индексации мы получим не сами символы (либо части символов, если речь о символах Юникода), а их числовые значения
  14 113
  15 >>> b[1]
  16 119
  17 >>> b[1] = 6    # Тип bytes - неизменяемый, прямо как строка
  18 Traceback (most recent call last):
  19   File "<stdin>", line 1, in <module>
  20 TypeError: 'bytes' object does not support item assignment

Зачем нужен тип bytes? Для чтения бинарных файлов.

Что произойдёт, если загнать в bytes русские буквы?

   1 >>> ф = '-абвг'    # К слову, идентификаторы не обязаны состоять именно из ascii-символов
   2 >>> a = '-абвг'    # Но мы изгаляться всё же не будем
   3 >>> bytes(a)    # Если строка содержит не-ASCII-символы, для преобразования её в bytes требуется указать кодировку, с помощью которой будет произведено кодирование
   4 Traceback (most recent call last):
   5   File "<stdin>", line 1, in <module>
   6 TypeError: string argument without an encoding
   7 >>> bytes(a, 'UTF-8')    # При этом в различных кодировках получим и различное байтовое представление: в UTF-8 на каждый символ в нашем примере будет отведено по 2 байта
   8 b'-\xd0\xb0\xd0\xb1\xd0\xb2\xd0\xb3'
   9 >>> bytes(a, 'KOI8-R')    # А в некоторых других кодировках - по одному
  10 b'-\xc1\xc2\xd7\xc7'
  11 >>> bytes(a, 'CP1251')
  12 b'-\xe0\xe1\xe2\xe3'
  13 

Олдфаги помнят (а дело было лет 10-20 назад) такое слово, как "бнопня". Оно было широко известно в среде системных администраторов:

   1 >>> a = "Вопрос"    # Присылают вам письмо с вопросом, но не указывают, нарушая правила, кодировку, которую используют
   2 >>> a.encode("cp1251")    # Допустим, пользователь использовал кодировку cp1251
   3 b'\xc2\xee\xef\xf0\xee\xf1'
   4 >>> a.encode("cp1251").decode("koi8-r")    # А вы, как администратор на линуксе, со своей KOI8-кодировкой вместо "Вопрос" увидите:
   5 'бНОПНЯ'
   6 

Кроме сказанного, данные типа bytes поддерживают все строковые методы:

   1 >>> dir(bytes)
   2 ['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mod__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmod__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'capitalize', 'center', 'count', 'decode', 'endswith', 'expandtabs', 'find', 'fromhex', 'hex', 'index', 'isalnum', 'isalpha', 'isdigit', 'islower', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'maketrans', 'partition', 'replace', 'rfind', 'rindex', 'rjust', 'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill']
   3 

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

   1 >>> b = b"12345678999"
   2 >>> b.hex()    # Обратите внимание на повторяющееся "39" в конце строки. И вообще видно, что цифры 1-9 в кодировке имеют номера с 31 по 39
   3 '3132333435363738393939'
   4 

2.8.3. bytearray

Bytearray - это bytes, но изменяемый.

   1 >>> b = bytearray("QWERTY")    # При создании bytearray из строки тоже нужно указывать кодировку
   2 Traceback (most recent call last):
   3   File "<stdin>", line 1, in <module>
   4 TypeError: string argument without an encoding
   5 >>> b = bytearray("QWERTY", "UTF-8")
   6 >>> b
   7 bytearray(b'QWERTY')
   8 >>> print(b)
   9 bytearray(b'QWERTY')
  10 >>> b[0]    # При индексации вы так же будете получать численное значение символа, а не сам символ
  11 81
  12 >>> b[1]
  13 87
  14 >>> b[4] = 32    # Вы можете изменять bytearray - однако помните, что вы работаете с численными значениями
  15 >>> b
  16 bytearray(b'QWER Y')
  17 >>> b[4] = '!'    # Присвоить напрямую желаемый символ не получится
  18 Traceback (most recent call last):
  19   File "<stdin>", line 1, in <module>
  20 TypeError: an integer is required
  21 >>> dir(bytearray)    # У bytearray тоже есть все строковые (и не только) методы
  22 ['__add__', '__alloc__', '__class__', '__contains__', '__delattr__', '__delitem__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mod__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmod__', '__rmul__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', 'append', 'capitalize', 'center', 'clear', 'copy', 'count', 'decode', 'endswith', 'expandtabs', 'extend', 'find', 'fromhex', 'hex', 'index', 'insert', 'isalnum', 'isalpha', 'isdigit', 'islower', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'maketrans', 'partition', 'pop', 'remove', 'replace', 'reverse', 'rfind', 'rindex', 'rjust', 'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill']
  23 

Таким образом, с bytearray вы можете работать:

  1. как со строками;
  2. как со списками;
  3. как с байтовыми строками.

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


LecturesCMC/PythonIntro2018/06_Strings/Conspect (последним исправлял пользователь RomanKrivonogov 2018-11-01 06:50:03)