Пориньте у Python 3/Текст
I’m telling you this ’cause you’re one of my friends.
My alphabet starts where your alphabet ends!
Dr. Seuss, On Beyond Zebra!
Деяка нудна теорія яку ви повинні зрозуміти перед тим як зайнятись практикою
[ред.]Мало хто з людей про це думає, але тексти неймовірно складні. Взяти б для початку хоча б алфавіт. Люди з острова Бугенвіль мають найменший алфавіт в світі; письмо на їх мові, що зветься ротока, складається лише з дванадцяти літер: A, E, G, I, K, O, P, R, S, T, U та V. З іншого кінця спектру, такі мови як китайська, японська та корейська мають тисячі символів. Англійська має 26 літер, чи 52 якщо рахувати окремо великі і маленькі, і ще купку знаків пунктуації: !@#$%&.
Коли ви кажете "текст", ви напевне маєте на увазі "букви та інші символи на екрані мого комп’ютера". Але комп’ютери не працюють з символами, вони працюють з бітами і байтами. Будь-який текст який ви бачите насправді зберігається в певному кодуванні. Говорячи дуже грубо, кодування символів - це бінарне відношення між зображенням символів які ви бачите на екрані, і даними які комп’ютер насправді зберігає в пам’яті та на диску. Існує багато різноманітних кодувань символів, деякі з них оптимізовані для конкретних мов, наприклад англійської, китайської або української, а інші можуть використовуватись в багатьох мовах.
Насправді все ще трохи складніше. Багато символів є спільними для різних кодувань, але кожне кодування використовує іншу послідовність біт, для збереження тих символів у пам’яті чи на диску. Тому ви можете думати про кодування тексту, як про певний вид криптографічного ключа. Як тільки хтось дає вам послідовність байт - файл чи веб-сторінку, не важливо - і стверджує що це "текст", вам потрібно знати яке кодування вони використали, щоб мати змогу перетворити байти в символи. Якщо вони передали вам неправильний ключ, або взагалі ніякого ключа, задача розшифровки тих даних залишається для вас, і я вам не заздрю. Існує велика ймовірність що ви виберете неправильне кодування, і отримаєте абракадабру.
Звичайно ви бачили подібні веб-сторінки, з дивними знаками питання в тих місцях де повинні бути апострофи. Це зазвичай означає, що автор сторінки не вказав кодування правильно, ваш браузер спробував вгадати, і вийшла суміш очікуваних і неочікуваних символів. В англійській мові це може трошки дратувати. В інших мовах результат може бути цілком нечитабельним.
Існує кодування символів для кожної основної мови в світі. Оскільки всі мови різні, і пам’ять та дисковий простір історично були дорогими, кожне кодування символів оптимізується під конкретну мову. Під цим я маю на увазі, що кожне кодування що використовувало одні й ті ж цифри (0 - 255) для позначення символів тієї мови. Наприклад, ви напевне знайомі з кодуванням ASCII яке зберігає символи англійської мови, як числа що знаходяться між 0 та 127. (65 велике “A”, 97 маленьке “a”, і т.п.) Англійська має дуже простий алфавіт, тому він може бути повністю записаний менш ніж 128 числами. Якщо хтось із вас знає двійкову систему числення, для цього потрібно 7 з восьми біт у байті.
Західноєвропейські мови, такі як французька, іспанська та німецька мають більше символів ніж англійська. Або, точніше, вони мають букви, які комбінуються з різноманітними діакритичними знаками, такі як наприклад символ "ñ" в іспанській. Найтиповішим кодуванням для цих мов є CP-1252, також відоме як “windows-1252” тому що воно широко використовується на платформі Microsoft Windows. Кодування CP-1252 в діапазоні 0-127 має такі ж символи як і ASCII, та потім доповнює їх в діапазоні 128-255 такими символами як n-з-тильдою-зверху (241), u-з-двома-крапками-зверху (252) і т.п. Це все ще однобайтове кодування, символ з найбільшим кодом, 255, все ще можна записати в одному байті.
Крім того, є ще такі мови як китайська, японська та корейська, які мають так багато символів що потребують багатобайтових кодувань. Тобто кожен символ в них закодовано двобайтовим числом від 0 до 65535. Але різні багатобайтові кодування все ще мають таку ж спільну проблему як і однобайтові - вони використовують однакові числа для запису різних символів. Просто діапазон кодів став ширшим, тому що символів які потрібно закодувати набагато більше.
Це було майже нормальним у світі без мережі, де "текст" - це щось що ви самостійно набрали, і вряди-годи друкували. Не було багато "звичайного тексту". Код програм записувався в ASCII, а всі решта використовували текстові процесори, які описували свої власні (нетекстові) форми, які зберігали інформацію про кодування разом з оформленням та іншими даними. Люди відкривали ці документи тими ж текстовими процесорами що й автор, тому все більш-менш працювало.
Тепер подумайте про зростання глобальних мереж, таких як пошта, чи веб. Багато "звичайного тексту" літає навколо світу, створюючись на одному комп’ютері, і пересилаючись за допомогою другого комп’ютера на третій. Комп’ютери можуть бачити тільки числа, але числа можуть означати різні речі. О ні! Що робити? Ну, системи повинні створюватись так, щоб зберігати інформацію про кодування з кожним шматком "звичайного тексту". Пам’ятайте, це ключ до розшифрування чисел які читає комп’ютер, і перетворення їх в букви які можуть читати люди. Загублений ключ розшифрування означає спотворений текст, абракадабру, чи ще щось гірше.
Тепер подумайте про спробу зберігати різноманітні шматки тексту в одному місці, наприклад в одній таблиці бази даних яка зберігає всі листи які ви отримували. Вам потрібно зберігати кодування кожного щоб могти його правильно відобразити. Думаєте це важко? Спробуйте здійснити пошук по вашій базі даних, що означатиме конвертацію тексту між різними кодуваннями на льоту. Хіба це не звучить весело?
Тепер подумайте про можливість існування багатомовних документів, де символи з кількох мов використовуються поряд. (Підказка: програми які намагались працювати з такими документами зазвичай використовували спеціальні символи для перемикання "режимів". Опа, ви в кириличному кодуванні koi8-r, тому 241 означає Я; опа, тепер ви в грецькому кодуванні для Mac тому, 241 означає ώ.) І звичайно ви теж колись захочете робити пошук по таких документах.
Тепер багато плачте, тому що все що ви знали про текст - неправда. Не існує такої штуки як "простий текст".
Юнікод
[ред.]З’являється Юнікод.
Юнікод - система створена для запису кожного символа кожної мови. Юнікод представляє кожну букву, символ чи ідеограму як чотирибайтове число. Кожне число задає унікальний символ якої-небудь мови. (Не всі числа використовуються, але більш ніж 65535 з них так, тому двох байтів не достатньо). Символи що використовуються в багатьох мовах зазвичай мають один номер, якщо не існує достатньої етимологічної причини для протилежного. Кожному символу відповідає одне число, і кожному числу відповідає один символ. Кожне число завжди означає одне і те ж, бо немає "режимів" за якими потрібно слідкувати. U+0041
- завжди 'A'
, навіть якщо мова вашого тексту не містить цієї літери.
На перший погляд це виглядає як чудова ідея. Одне кодування щоб керувати ними всіма.[1] Багато мов в одному документі. Більш ніяких "змін режиму" щоб змінювати кодування посеред файла. Але в вас має виникнути законне запитання. Чотири байти? На кожен символ‽ Це виглядає страшенно витратно, особливо для таких мов як англійська чи іспанська, які потребують менше одного байта (256 символів) щоб виразити всі можливі символи мови. Насправді це марнотратство і в ієрогліфічних мовах (таких як китайська), яким ніколи не стане потрібно більше ніж два байти на символ.
Існує кодування Юнікоду яке використовує чотири байти на символ. Воно називається UTF-32, тому що 32 біти це і є 4 байти. UTF-32 - пряме кодування. Воно бере кожен номер символу Юнікоду, і записує його в цих чотирьох байтах. Це має певні переваги, найважливішою з яких є те, що ви можете знайти n-тий символ рядка за константний час, тому що n-тий символ починається з (4*n)-того байта. Це також має кілька недоліків, найочевиднішим з яких є те, що щоб зберегти кожен чортів символ потрібно аж чотири чортових байти.
І хоча є страшенно багато символів Юнікоду, виявляється що більшість людей майже ніколи не використовують символів поза першими 65535. Тому, існує інше кодування Юнікоду, назване UTF-16 (тому що 16 бітів = 2 байти). UTF-16 кодує кожен символ від 0 до 65535 двома байтами, після чого використовує деякі брудні хаки в випадках коли вам потрібно записати рідковживані символи юнікоду з "астрального простору" поза 65535-тим символом. Найбільш очевидна перевага: UTF-16 займає вдвічі менше місця ніж UTF-32, тому що кожен символ потребує лише двох байт місця замість чотирьох (звісно окрім тих які таки потребують). І ви все ще можете просто знаходити n-тий символ за константний час, якщо припустите, що рядок не містить жодних символів з "астрального простору", що є гарним припущенням аж поки воно не стає невірним.
Але існує також не такий очевидний недолік як кодування UTF-32 так і UTF-16. Різні комп’ютерні системи зберігають окремі байти різними способами. Це означає що символ U+4E2D
може зберігатись в UTF-16 як 4E 2D
та як 2D 4E
, залежно від порядку байтів в системі. (Для UTF-32 взагалі можливі чотири різні порядки запису). Поки ваші документи ніколи не покидають вашого комп’ютера, ви в безпеці - різні додатки одного і того ж комп’ютера будуть використовувати один і той самий порядок байтів. Але якщо ви захочете передати документи між системами, чи можливо у всесвітню мережу, вам потрібно буде якимось способом вказати порядок байт вашого кодування. Інакше система що отримує дані не буде знати чи двобайтова послідовність 4E 2D
означає U+4E2D
U+2D4E
.
Щоб розв’язати цю проблему, багатобайтові кодування Юнікоду описують "Мітку порядку байт" (англ. Byte order mark), яка є спеціальним недрукованим символом який ви можете вставити на початку свого документа щоб вказати в якому порядку розміщені його байти. Для UTF-16 мітка порядку байт - U+FEFF
. Якщо ви отримуєте документ, що починається з байт FF FE
, ви знаєте що байти йдуть в одному порядку, якщо FE FF
- в протилежному.
І все ж, UTF-16 не ідеальна, особливо коли вам доводиться мати справу з великою кількістю символів ASCII. Якщо подумати, то навіть китайська веб-сторінка міститиме багато ASCII символів - елементи та атрибути HTML що оточуватимуть друковані китайські ієрогліфи. Здатність знайти n-тий символ за одиничний час це добре, але все ще залишається надокучлива проблема тих символів з астральної площини, яка означає що ви не можете гарантувати те що кожен символ це рівно два байти, тому ви насправді не можете знайти n-тий символ за константний час, якщо звісно не заведете окремий індекс. І звичайно на світі море тексту ASCII...
Інші люди задумувались над цими питаннями і прийшли до рішення:
UTF-8 - система кодування Юнікоду змінної довжини. Тобто, різні символи можуть займати різну кількість байт. Для символів ASCII (A-Z, і т.п.) UTF-8 використовує лише один байт на символ. Насправді вона навіть використовує точно такі ж байти як і в ASCII: перші 128 кодів символів (0-127) в UTF-8 не відрізняються від кодів тих самих символів в ASCII. "Розширені латинські" символи, такі як ñ та ö, і кирилиця займають два байти. (Байти це не простий номер символа в таблиці Юнікоду, а хитрим чином закодований). Китайські символи, такі як 中, займають три байти. Рідко використовувані символи з "астральної площини" займають чотири байти.
Недоліки: через те що кожен символ займає різну кількість байт, знаходження n-того символа це операція складності O(N), тобто чим довший рядок, тим більше часу потрібно щоб знайти певний символ в ньому. Також, потрібні певні маніпуляції над бітами для того щоб отримати код символа з байт які його кодують, та навпаки.
Переваги: дуже ефективне кодування стандартних символів ASCII. Не гірше ніж UTF-16 для кирилиці, і розширеної латиниці. Краще ніж UTF-32 для китайських ієрогліфів. Також (і ви не мусите мені тут вірити, тому що я не збираюсь показувати вам математику), через природу необхідних операцій з бітами пропадає проблема порядку байт. Документ записаний в UTF-8 використовує однакову послідовність байт на всіх комп’ютерах.
Пірнаймо!
[ред.]В Python 3 всі рядки є послідовностями символів Юнікоду. В Python не існує такої штуки як рядок закодований в UTF-8, чи рядок закодований як cp1251. "Це рядок в UTF-8?" - неправильне запитання. UTF-8 - спосіб кодування символів як послідовностей байт. Якщо ви хочете взяти рядок і перетворити його на послідовність байт в певному кодуванні, Python 3 може допомогти в цьому. Якщо ви хочете взяти послідовність байт, і перетворити в послідовність символів Python може допомогти і в цьому. Байти не є символами: байти це байти. Символи - це абстракція. Рядок - послідовність таких абстракцій.
>>> s = '深入 Python'
Щоб створити рядок, помістіть його в лапки. Рядки в Python можуть описуватись як одиничними ('
), так і подвійними ("
) лапками.
>>> len(s)
9
Вбудована функція len()
повертає довжину рядка, тобто кількість символів. Це та ж сама функція яку ви використовували щоб знайти довжину рядка, кортежу, множини чи словника. Рядок - це як кортеж з символів.
>>> s[0]
'深'
Так само я ви можете отримати окремі елементи списку, ви можете отримувати окремі символи рядка за індексом.
>>> s + ' 3'
'深入 Python 3'
Так як і в списках, ви можете конкатенувати рядки використовуючи оператор +
.
Форматування рядків
[ред.]Давайте ще раз глянемо на humansize.py.
SUFFIXES = {1000: ['KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'],
1024: ['KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB']}
'KB'
, 'MB'
, 'KiB'
- це все рядки.
def approximate_size(size, a_kilobyte_is_1024_bytes=True):
'''Convert a file size to human-readable form.
Keyword arguments:
size -- file size in bytes
a_kilobyte_is_1024_bytes -- if True (default), use multiples of 1024
if False, use multiples of 1000
Returns: string
'''
Докстрінґи функції - теж рядки. Їх текст може містити багато рядків, тому він використовує три одинарні лапки для позначення початку і кінця.
if size < 0:
raise ValueError('number must be non-negative')
Тут ми бачимо ще один рядок, що передається в функцію як людське пояснення винятку що стався.
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)
А це ... Почекайте. Це ще що за хрінь?
Python 3 підтримує форматування значень в рядки. І хоча тут можуть використовуватись дуже складні вирази, але найпростішим використанням є вставка значення в рядок.
>>> username = 'mark'
>>> password = 'PapayaWhip'
>>> "{0}'s password is {1}".format(username, password)
"mark's password is PapayaWhip"
Тут відбувається багато всього. По-перше, виклик методу над рядковим літералом. Рядки це об’єкти, а об’єкти мають методи. По-друге, значенням виразу є рядок. По-третє, {0}
та {1}
- поля заміни, які замінюються аргументами що передаються в метод format()
.
Складені імена полів
[ред.]Попередній приклад ілюструє найпростіший випадок, де поля що підставляються - звичайні цілі. Цілі поля підстановки розглядаються як індекси в списку аргументів методу format()
. Це означає що {0}
замінюється першим аргументом (в даному випадку username
), {1}
- другим (password
), і т.д. Можна використовувати скільки завгодно аргументів. Але поля підстановки набагато потужніші.
>>> import humansize
>>> si_suffixes = humansize.SUFFIXES[1000]
>>> si_suffixes
['KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
Замість того щоб викликати якусь функцію модуля humansize
ми отримуємо описаний в ньому список суфіксів.
>>> '1000{0[0]} = 1{0[1]}'.format(si_suffixes)
'1000KB = 1MB'
А це виглядає складно, але насправді таким не є. {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
містить інформацію про запущений в даний момент інтерпретатор. Після того, як його імпортували, його можна передавати як аргумент методуformat()
. Тому поле підстановки{0}
посилається на модульsys
. sys.modules
- це словник усіх модулів які були імпортованими в даній сесії інтепретатора. Ключами є імена модулів, значеннями - самі об'єкти модулів. Тому поле підстановки{0.modules}
посилається на словник відповідних модулів.sys.modules['humansize']
- це модульhumansize
який ви недавно імпортували. Поле підстановки{0.modules[humansize]}
відповідає модулюhumansize
. Зауважте невеличку відмінність у синтаксисі. В коді Python ключами словникаsys.modules
були б рядки, щоб отримати елемент потрібно було б передавати ключ в лапках (наприклад'humansize'
). Але в полі підстановки ви опускаєте лапки навколо ключа словника. Цитуючи PEP 3101: Advanced String Formatting, "Правила розпізнавання ключів словника дуже прості. Якщо він починається з цифри, то розглядається як число, інакше використовується як рядок".sys.modules['humansize'].SUFFIXES[1000]
- список суфіксів SI: ['KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']. Тому поле підстановки{0.modules[humansize].SUFFIXES[1000]}
відповідає цьому списку.sys.modules['humansize'].SUFFIXES[1000][0]
- перший елемент списку суфіксів SI:'KB'
. Тому повною заміною поля{0.modules[humansize].SUFFIXES[1000][0]}
є двосимвольний рядок'KB'
.
Специфікатори формату
[ред.]Але зачекайте. Є ще! Давайте глянемо ще разок на дивний рядок коду з humansize.py
:
if size < multiple:
return '{0:.1f} {1}'.format(size, suffix)
{1}
замінюється другим аргументом який передається методу format()
, який є суфіксом. Але що таке {0:.1f}
? Ну, це двоє речей: вже знайома вам {0}
, і незнайома :.1f
. Друга половина (включаючи двокрапку і все після неї) описує специфікатор формату, який описує те, як змінна що підставляється повинна форматуватись.
☞ Специфікатори формату дозволяють модифікувати текст що підставляється різними корисними способами, як це робить функція
printf
мови C. Ви можете доповнювати значення зліва нулями чи пропусками, вирівнювати рядки, керувати точністю десяткових дробів, і навіть відображати числа в шістнадцятковій системі числення
Всередині поля підстановки, двокрапка (:) позначає початок специфікатору формату. Специфікатор формату ".1" означає округлення до однієї цифри після коми. Специфікатор формату "f" означає число з плаваючою крапкою. Тому отримавши значення змінної size = 698.24
та suffix = 'GB'
, відформатований рядок міститиме '698.2 GB'
:
>>> '{0:.1f} {1}'.format(698.24, 'GB')
'698.2 GB'
На рахунок всіх кривавих деталей щодо специфікаторів формату, звертайтесь до розділу Format Specification Mini-Language в офіційній документації мови Python.
Інші типові рядкові методи
[ред.]Окрім форматування, з рядками можна робити кілька інших корисних трюків.
>>> s = '''Finished files are the re-
... sult of years of scientif-
... ic study combined with the
... experience of years.'''
Ви можете вводити багаторядкові рядки прямо в інтерактивній оболонці. Для цього почніть рядок і коли натиснете Enter, оболонка запропонує вам продовжити рядок. Коли ви закриєте потрійні лапки, натиснення Enter виконає команду (в даному випадку присвоєння рядка змінній s
).
>>> s.splitlines()
['Finished files are the re-',
'sult of years of scientif-',
'ic study combined with the',
'experience of years.']
Рядковий метод splitlines()
повертає список рядків, в якому кожен рядок відповідає одному рядку даного. Зауважте що символи повернення каретки на кінці рядка не включаються.
>>> print(s.lower())
finished files are the re-
sult of years of scientif-
ic study combined with the
experience of years.
Метод lower()
переводить весь рядок в нижній регістр. Метод upper()
- в верхній.
>>> s.lower().count('f')
6
Метод count()
шукає кількість входжень підрядка в рядок. Так, в даному реченні літера 'f'
справді зустрічається 6 разів.
Ось інший поширений випадок. Нехай в нас є набір пар ключ-значення в форматі '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']
Метод рядка split()
, має один аргумент - розділювач. Метод розбиває рядок на список рядків, якщо вони були розділені розділювачем. В цьому випадку розділювачем є символ амперсанда, але він може бути довільним рядком.
>>> a_list_of_lists = [v.split('=', 1) for v in a_list if '=' in v]
>>> a_list_of_lists
[['user', 'pilgrim'], ['database', 'master'], ['password', 'PapayaWhip']]
Тепер в нас є список рядків, кожен з яких містить ключ, після якого знак рівності, а потім значення. Ми можемо використати спискові вирази, щоб пройтись по списку і поділити кожен рядок на два, покладаючись на перший знайдений знак рівності. Необов’язковим другим аргументом методу split()
є кількість бажаних поділів. 1 означатиме "ділити лише раз", тому метод split()
поверне двоелементний список. (В теорії значення теж може містити знаки рівності. Якщо ви просто застосуєте 'key=value=foo'.split('=')
, ви отримаєте трьохелементрий список ['key', 'value', 'foo']
.)
>>> a_dict = dict(a_list_of_lists)
>>> a_dict {'password': 'PapayaWhip', 'user': 'pilgrim', 'database': 'master'}
І нарешті, Python може перетворити список списків у словник, просто за допомогою функції dict()
.
☞ доробити
Зрізання рядків
[ред.]Якщо ви маєте заданий рядок, ви можете отримати будь-яку його частину як новий рядок. Це називається зрізом рядка. Зрізи рядків працюють так само як зрізи списків, що досить логічно, бо рядки - всього лише послідовності символів.
>>> 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[0:2]
поверне перші два символи рядка від a_string[0]
, аж до a_string[2]
невключно.
>>> a_string[:18]
'My alphabet starts'
Якщо лівий індекс зрізу - нуль, його можна опустити, так як він припускається за замовчуванням. Тому a_string[:18]
працює аналогічно a_string[0:18]
.
>>> a_string[18:]
' where your alphabet ends.'
Аналогічно, якщо правий індекс зрізу дорівнює довжині рядка, його можна опустити. Тому 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 називається рядком. Незмінна послідовність чисел між 0 та 255 називається байтовим об'єктом.
>>> by = b'abcd\x65'
>>> by
b'abcde'
Для запису байтового об’єкта використовують синтаксис b
(байтовий літерал). Кожен байт у літералі може бути символом ASCII чи шістнадцятковим кодом від \x00
до \xff
(0–255).
>>> type(by)
<class 'bytes'>
Типом байтового об’єкта є bytes
>>> len(by)
5
Як і у випадку списків та рядків довжину байтового об’єкта можна отримати за допомогою вбудованої функції len()
.
>>> by += b'\xff'
>>> by
b'abcde\xff'
Як і в рядках та списках оператор +
здійснює конкатенацію, результатом якої є новий байтовий об’єкт.
>>> len(by)
6
Конкатенація п’ятибайтового об’єкта з однобайтовим дає нам об’єкт з шести байт.
>>> by[0]
97
Як і в рядках та списках можна використовувати індекси для отримання окремих байтів. Елементами рядка є символи, елементи байтового об’єкта - цілі числа. Якщо точніше, цілі числа в діапазоні 0–255.
>>> by[0] = 102
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'bytes' object does not support item assignment
Об’єкт bytes
- незмінний, ми не можемо робити присвоєння окремим байтам. Якщо потрібно змінити окремі байти, можна використати зрізи та конкатенацію (які працюватимуть так само як і в рядках), чи перетворити об’єкт bytes
в об’єкт bytearray
.
>>> by = b'abcd\x65'
>>> barr = bytearray(by)
>>> barr
bytearray(b'abcde')
Щоб перетворити об’єкт bytes
в об’єкт bytearray
використайте вбудовану функцію bytearray()
.
>>> len(barr)
5
Всі методи і операції що можна виконати над об’єктом bytes
можна виконати і над об’єктом bytearray
.
>>> barr[0] = 102
>>> barr
bytearray(b'fbcde')
Єдиною відмінністю є те що можна присвоювати значення окремим байтам. Присвоюване значення повинно бути цілим в діапазоні 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
Не можна рахувати кількість входжень байтів в рядок, тому що рядок не містить байтів. Рядок - послідовність символів. Можливо ви мали на увазі "підрахувати кількість входжень рядка що отримується декодуванням цієї послідовності байт таким-то кодуванням"? Ну, тоді потрібно було написати це явно. Python 3 ніколи не перетворить байти в рядки, чи навпаки неявно.
>>> s.count(by.decode('ascii'))
1
Завдяки дивовижному співпадінню цей рядок коду перекладається на українську як "підрахувати кількість входжень рядка що отримується декодуванням цієї послідовності байт таким-то кодуванням".
Ось це і є зв’язком між рядками і байтами: байтові об’єкти мають метод decode()
що приймає кодування символів та повертає рядок, а рядки мають метод encode()
що теж приймає кодування і повертає байтовий об’єкт. В попередньому прикладі розкодуванням було досить прямолінійним - перетворити послідовність байт що задають рядок в кодуванні ASCII в відповідний рядок. Але такі самі процеси працюють і для будь-якого кодування що підтримує символи в рядку - навіть застарілі (не Unicode) кодування.
>>> a_string = '深入 Python'
>>> len(a_string)
9
a_string
- рядок. У ньому 9 символів.
>>> by = a_string.encode('utf-8')
>>> by
b'\xe6\xb7\xb1\xe5\x85\xa5 Python'
>>> len(by)
13
by
- байтовий об’єкт. У ньому 13 байт. Таку послідовність байт ми отримаємо коли візьмемо a_string
і закодуємо його в UTF-8.
>>> by = a_string.encode('gb18030')
>>> by
b'\xc9\xee\xc8\xeb Python'
>>> len(by)
11
Подібний байтовий об’єкт з 11 байт ми отримаємо якщо закодуємо a_string
в кодуванні GB18030.
>>> by = a_string.encode('big5')
>>> by b'\xb2`\xa4J Python'
>>> len(by)
11
Подібний байтовий об’єкт, що складається з зовсім іншої послідовності байт отримаємо якщо візьмемо a_string
і закодуємо її в Big5.
>>> roundtrip = by.decode('big5')
>>> roundtrip
'深入 Python'
>>> a_string == roundtrip
True
roundtrip
- рядок. Він має 9 символів. Таку послідовність символів можна отримати якщо взяти by
і розкодувати її за допомогою алгоритму кодування Big5. Цей рядок збігається з початковим.
Постскриптум: Кодування коду Python
[ред.]Python 3 припускає що ваш код - тобто кожен файл .py
записаний в кодуванні UTF-8.
☞ В Python 2, кодуванням за замовчуванням для файлів
.py
було ASCII. В Python 3, кодуванням за замовчуванням є UTF-8.
Якщо ви захочете використати для свого коду інше кодування, то можете вставити оголошення кодування в перший рядок кожного файла. Наприклад такий запис встановлює для файла кодування windows-1252:
# -*- coding: windows-1252 -*-
Технічно, опис кодування файла може розміщуватись також і на другому рядку, якщо перший - UNIX-подібна команда hash-bang.
#!/usr/bin/python3
# -*- coding: windows-1252 -*-
Для детальнішої інформації читайте PEP 263: Defining Python Source Code Encodings.
Для подальшого читання
[ред.]Про Unicode в Python:
- Python Unicode HOWTO
- What’s New In Python 3: Text vs. Data Instead Of Unicode vs. 8-bit
- PEP 261 пояснює як Python працює з астральними символами за межами Basic Multilingual Plane (тобто символами чиї порядкові номери більші ніж 65535)
Про Unicode загалом:
- The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets (No Excuses!)
- On the Goodness of Unicode
- On Character Strings
- Characters vs. Bytes
Про кодування символів в форматах файлів:
Про рядки та форматування рядків:
Зноски
[ред.]- ↑ Тут автор робить натяк на майстер-перстень з "Володаря Перснів". Хто може глянути в книжці вірш про те як три персні віддали ельфам, ще скількись там людям і гномам, і ... рядок про один перстень має стояти замість цього речення, тільки замість слова перстень - кодування