Юникод для чайников

logo
Сам я не очень люблю заголовки вроде «Покемоны в собственном соку для чайников\кастрюль\сковородок», но это кажется именно тот случай — говорить будем о базовых вещах, работа с которыми довольно часто приводить к купе набитых шишек и уйме потерянного времени вокруг вопроса — «Почему же оно не работает?». Если вы до сих пор боитесь и\или не понимаете Юникода — прошу под кат.


Зачем?


Главный вопрос новичка, который встречается с впечатляющим количеством кодировок и на первый взгляд запутанными механизмами работы с ними (например, в Python 2.x). Краткий ответ — потому что так сложилось :)

Кодировкой, кто не знает, называют способ представления в памяти компьютера (читай — в нулях-единицах\числах) цифр, буков и всех остальных знаков. Например, пробел представляется как 0b100000 (в двоичной), 32 (в десятичной) или 0x20 (в шестнадцатеричной системе счисления).

Так вот, когда-то памяти было совсем немного и всем компьютерам было достаточно 7 бит для представления всех нужных символов (цифры, строчный\прописной латинский алфавит, куча знаков и так называемые управляемые символы — все возможные 127 номеров были кому-то отданы). Кодировка в это время была одна — ASCII. Шло время, все были счастливы, а кто не был счастлив (читай — кому не хватало знака "©" или родной буквы «щ») — использовали оставшиеся 128 знаков на свое усмотрение, то есть создавали новые кодировки. Так появились и ISO-8859-1, и наши (то есть кириличные) cp1251 и KOI8. Вместе с ними появилась и проблема интерпретации байтов типа 0b1******* (то есть символов\чисел от 128 и до 255) — например, 0b11011111 в кодировке cp1251 это наша родная «Я», в тоже время в кодировке ISO-8859-1 это греческая немецкая Eszett (подсказывает Moonrise) "ß". Ожидаемо, сетевая коммуникация и просто обмен файлами между разными компьютерами превратились в чёрт-знает-что, несмотря на то, что заголовки типа 'Content-Encoding' в HTTP протоколе, email-письмах и HTML-страницах немного спасали ситуацию.

В этот момент собрались светлые умы и предложили новый стандарт — Unicode. Это именно стандарт, а не кодировка — сам по себе Юникод не определяет, как символы будут сохранятся на жестком диске или передаваться по сети. Он лишь определяет связь между символом и некоторым числом, а формат, согласно с которым эти числа будут превращаться в байты, определяется Юникод-кодировками (например, UTF-8 или UTF-16). На данный момент в Юникод-стандарте есть немного более 100 тысяч символов, тогда как UTF-16 позволяет поддерживать более одного миллиона (UTF-8 — и того больше).

Полней и веселей по теме советую почитать у великолепного Джоеля Спольски The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets.

Ближе к делу!


Естественно, есть поддержка Юникода и в Пайтоне. Но, к сожалению, только в Python 3 все строки стали юникодом, и новичкам приходиться убиваться об ошибки типа:

>>> with open('1.txt') as fh:
	s = fh.read()

>>> print s
кощей
>>> parser_result = u'баба-яга'  # присвоение для наглядности, представим себе, что это результат работы какого-то парсера
>>> parser_result + s
Traceback (most recent call last):
  File "<pyshell#43>", line 1, in <module>
    parser_result + s
UnicodeDecodeError: 'ascii' codec can't decode byte 0xea in position 0: ordinal not in range(128)

или так:
>>> str(parser_result)
Traceback (most recent call last):
  File "<pyshell#52>", line 1, in <module>
    str(parser_result)
UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-3: ordinal not in range(128)

Давайте разберемся, но по порядку.

Зачем кто-то использует Юникод?

Почему мой любимый html-парсер возвращает Юникод? Пусть возвращает обычную строку, а я там уже с ней разберусь! Верно? Не совсем. Хотя каждый из существующих в Юникоде символов и можно (наверное) представить в некоторой однобайтовой кодировке (ISO-8859-1, cp1251 и другие называют однобайтовыми, поскольку любой символ они кодируют ровно в один байт), но что делать если в строке должны быть символы с разных кодировок? Присваивать отдельную кодировку каждому символу? Нет, конечно, надо использовать Юникод.

Зачем нам новый тип «unicode»?

Вот мы и добрались до самого интересного. Что такое строка в Python 2.x? Это просто байты. Просто бинарные данные, которые могут быть чем-угодно. На самом деле, когда мы пишем что-нибудь вроде:
>>> x = 'abcd'
>>> x
'abcd'
интерпретатор не создает переменную, которая содержит первые четыре буквы латинского алфавита, но только последовательность
('a', 'b', 'c', 'd')
с четырёх байт, и латинские буквы здесь используются исключительно для обозначения именно этого значения байта. То есть 'a' здесь просто синоним для написания '\x61', и ни чуточку больше. Например:

>>> '\x61' 
'a'
>>> struct.unpack('>4b', x)  # 'x' - это просто четыре signed/unsigned char-а
(97, 98, 99, 100)
>>> struct.unpack('>2h', x)  # или два short-а
(24930, 25444)
>>> struct.unpack('>l', x)  # или один long
(1633837924,)
>>> struct.unpack('>f', x)  # или float
(2.6100787562286154e+20,)
>>> struct.unpack('>d', x * 2)   # ну или половинка double-а
(1.2926117739473244e+161,)

И всё!

И ответ на вопрос — зачем нам «unicode» уже более очевиден — нужен тип, который будет представятся символами, а не байтами.

Хорошо, я понял чем есть строка. Тогда что такое Юникод в Пайтоне?

«type unicode» — это прежде всего абстракция, которая реализует идею Юникода (набор символов и связанных с ними чисел). Объект типа «unicode» — это уже не последовательность байт, но последовательность собственно символов без какого либо представления о том, как эти символы эффективно сохранить в памяти компьютера. Если хотите — это более высокой уровень абстракции, чем байтовый строки (именно так в Python 3 называют обычные строки, которые используются в Python 2.6).

Как пользоваться Юникодом?

Юникод-строку в Python 2.6 можно создать тремя (как минимум, естественно) способами:
  • u"" литерал:
    >>> u'abc'
    u'abc'
    

  • Метод «decode» для байтовой строки:
    >>> 'abc'.decode('ascii')
    u'abc'
    

  • Функция «unicode»:
    >>> unicode('abc', 'ascii')
    u'abc'
    

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

'\x61' -> кодировка ascii -> строчная латинская "a" -> u'\u0061' (unicode-point для этой буквы)

или

'\xe0' -> кодировка c1251 -> строчная кириличная "a" -> u'\u0430'


Как из юникод-строки получить обычную? Закодировать её:

>>> u'abc'.encode('ascii')
'abc'


Алгоритм кодирования естественно обратный приведенному выше.

Запоминаем и не путаем — юникод == символы, строка == байты, и байты -> что-то значащее (символы) — это де-кодирование (decode), а символы -> байты — кодирование (encode).

Не кодируется :(

Разберем примеры с начала статьи. Как работает конкатенация строки и юникод-строки? Простая строка должна быть превращена в юникод-строку, и поскольку интерпретатор не знает кодировки, от использует кодировку по умолчанию — ascii. Если этой кодировке не удастся декодировать строку, получим некрасивую ошибку. В таком случае нам нужно самим привести строку к юникод-строке, используя правильную кодировку:

>>> print type(parser_result), parser_result
<type 'unicode'> баба-яга
>>> s = 'кощей'
>>> parser_result + s
Traceback (most recent call last):
  File "<pyshell#67>", line 1, in <module>
    parser_result + s
UnicodeDecodeError: 'ascii' codec can't decode byte 0xea in position 0: ordinal not in range(128)
>>> parser_result + s.decode('cp1251')
u'\xe1\xe0\xe1\xe0-\xff\xe3\xe0\u043a\u043e\u0449\u0435\u0439'
>>> print parser_result + s.decode('cp1251')
баба-ягакощей
>>> print '&'.join((parser_result, s.decode('cp1251')))
баба-яга&кощей   # Так лучше :)


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

Теперь использование «str» и юникод-строк. Не используйте «str» и юникод строки :) В «str» нет возможности указать кодировку, соответственно кодировка по умолчанию будет использоваться всегда и любые символы > 128 будут приводить к ошибке. Используйте метод «encode»:

>>> print type(s), s
<type 'unicode'> кощей
>>> str(s)
Traceback (most recent call last):
  File "<pyshell#90>", line 1, in <module>
    str(s)
UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-4: ordinal not in range(128)
>>> s = s.encode('cp1251')
>>> print type(s), s
<type 'str'> кощей


«UnicodeEncodeError» — знак того, что нам нужно указать правильную кодировку во время превращения юникод-строки в обычную (или использовать второй параметр 'ignore'\'replace'\'xmlcharrefreplace' в методе «encode»).

Хочу ещё!

Хорошо, используем бабу-ягу из примера выше ещё раз:

>>> parser_result = u'баба-яга'   #1
>>> parser_result
u'\xe1\xe0\xe1\xe0-\xff\xe3\xe0'   #2
>>> print parser_result
áàáà-ÿãà   #3
>>> print parser_result.encode('latin1')  #4
баба-яга
>>> print parser_result.encode('latin1').decode('cp1251')  #5
баба-яга
>>> print unicode('баба-яга', 'cp1251')   #6
баба-яга

Пример не совсем простой, но тут есть всё (ну или почти всё). Что здесь происходит:
  1. Что имеем на входе? Байты, которые IDLE передает интерпретатору. Что нужно на выходе? Юникод, то есть символы. Осталось байты превратить в символы — но ведь надо кодировку, правда? Какая кодировка будет использована? Смотрим дальше.
  2. Здесь важной момент:
    >>> 'баба-яга'
    '\xe1\xe0\xe1\xe0-\xff\xe3\xe0'
    >>> u'\u00e1\u00e0\u00e1\u00e0-\u00ff\u00e3\u00e0' == u'\xe1\xe0\xe1\xe0-\xff\xe3\xe0'
    True
    
    как видим, Пайтон не заморачивается с выбором кодировки — байты просто превращаются в юникод-поинты:
    >>> ord('а')
    224
    >>> ord(u'а')
    224
    
  3. Только вот проблема — 224-ый символ в cp1251 (кодировка, которая используется интерпретатором) совсем не тот, что 224 в Юникоде. Именно из-за этого получаем кракозябры при попытке напечатать нашу юникод-строку.
  4. Как помочь бабе? Оказывается, что первые 256 символов Юникода те же, что и в кодировке ISO-8859-1\latin1, соответственно, если используем её для кодировки юникод-строки, получим те байты, которые вводили сами (кому интересно — Objects/unicodeobject.c, ищем определение функции «unicode_encode_ucs1»):
    >>> parser_result.encode('latin1')
    '\xe1\xe0\xe1\xe0-\xff\xe3\xe0'
    
  5. Как же получить бабу в юникоде? Надо указать, какую кодировку использовать:
    >>> parser_result.encode('latin1').decode('cp1251')
    u'\u0431\u0430\u0431\u0430-\u044f\u0433\u0430'
    
  6. Способ с пункта #5 конечно не ахти, намного удобней использовать использовать built-in unicode.
На самом деле не всё так плохо с «u''» литералами, поскольку проблема возникает только в консоле. Ведь в случае использования non-ascii символов в исходном файле Пайтон будет настаивать на использовании заголовка типа "# -*- coding: -*-" (PEP 0263), и юникод-строки будут использовать правильную кодировку.

Есть ещё способ использования «u''» для представления, например, кириллицы, и при этом не указывать кодировку или нечитабельные юникод-поинты (то есть «u'\u1234'»). Способ не совсем удобный, но интересный — использовать unicode entity codes:

>>> s = u'\N{CYRILLIC SMALL LETTER KA}\N{CYRILLIC SMALL LETTER O}\N{CYRILLIC SMALL LETTER SHCHA}\N{CYRILLIC SMALL LETTER IE}\N{CYRILLIC SMALL LETTER SHORT I}'
>>> print s
кощей


Ну и вроде всё. Основные советы — не путать «encode»\«decode» и понимать различия между байтами и символами.

Python 3

Здесь без кода, ибо опыта нет. Свидетели утверждают, что там всё значительно проще и веселее. Кто возьмется на кошках продемонстрировать различия между здесь (Python 2.x) и там (Python 3.x) — респект и уважуха.

Полезно


Раз уж мы о кодировках, порекомендую ресурс, который время-от-времени помогает побороть кракозябры — http://2cyr.com/decode/?lang=ru.

Ещё раз линк на статью Спольски — The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets.

Unicode HOWTO — официальный документ о том где, как и зачем Юникод в Python 2.x.

Спасибо за внимание. Буду благодарен за замечания в приват.

P.S. Подкинули линк на перевод Спольски — Абсолютный Минимум, который Каждый Разработчик Программного Обеспечения Обязательно Должен Знать о Unicode и Наборах Символов.
+109
17 января 2012, 18:51
412
leron 34,0

комментарии (52)

+7
digest #
Годная, полезная статья.
Я бы еще раскрыл тему как читать/писать юникодные файлы: codecs.open вместо file() и про PEP 0263.
–1
bazilxp #
по поводу файлов

#! /usr/bin/env python
# -*- coding: utf-8 -*-

операции с файлами становятся уникодными=)

ну еще со строками магия проходит вроде
регулярки работают
m=re.match('^(第)([0-9]+)(届|期)',s)

Как практика показывает, входные данные лучше отгонять в UTF-8
тогда проблемы вроде того что одни и теже данные python, c++ и так далее не сильно актуальные…
Отдельно надо выделить если происходят операции за Базой данных тут кодировки тоже важно…

З.Ы каждый программист должен начинать новый язык с UTF-8 / Unicode :) решать вопрос чтобы устойчивая программа была к любым символам
+2
digest #
Не операции становятся уникодными, а исходный текст скрипта :).
Файлы [данных] все же придется читать и декодировать руками.

> входные данные лучше отгонять в UTF-8

Ни в коем случае! UTF-8 — это внешнее представление. Входные данные надо отгонять в юникод, со всеми строками работать только в юникоде, а сохранять снова в UTF-8. Кстати, в C++ c UTF-8 вообще можно повеситься (ну или забыть работу со строками).
0
bazilxp #
Насчет входных файлов соглашусь…

У меня просто все данные в UTF-8, входные из файлов поэтому работает 2.6/2.7
Внешение представление в UTF-8 спасает…

UTF-8 для символов a-z 1byte если идет кирилица А_Я и так далее 2 byte.

С точки зрения C/C++ когда strlen(ansi) = 10 с точки зрения strlen(utf-8) 10 длина не понятна(может символы 1 +2 +2 +2 +1+1 ).

Если код не ориентирован на чёткий поиск, а склейка строк то strcpy/strcat можно пользовать…

з.ы. пора тему Unicode / UTF-8/ UTF-16 как курс или пособие, обобщение для разных языков…
–44
intation #
Чё за бред? Хабр — УГ.
+2
Monnoroch #
Толсто.
+3
krovatti #
Терпите. Скоро вас запикает НЛО.
0
couatl #
Что полезного сделали вы для хабрсообщества, чтоб его критиковать?
+3
hellhorse #
Хабрасэппуку?
0
Rome #
Это вы зря. Мне недавно пришла идея автоматического пополнения словаря с использованием нейронной сети. Идея не нова, просто самообучался. Столкнулся с проблемой кодировки, перерыл полинтернета. Нашел ответы на вопросы, но все очень разрозненно, плюс умные люди помогли, теория все равно осталась мне не понятна. А это фактически единственная статья в рунете, которая все раскладывает по полочкам.
0
medvoodoo #
Не знаю, столкнулся с юникодом в джанге, нашел решение за 5 минут. А в статье дикая мешанина из того, что такое юникод и работа с кодировками отличными от ascii в питоне.
ru.wikipedia.org/wiki/%D0%AE%D0%BD%D0%B8%D0%BA%D0%BE%D0%B4

кстати, по работе с кодировками по ссылке есть все в одном месте (например в self.статье про ignore, replace всего одна строчка, а в доках масса примеров и даже BOM упомянут :)):
docs.python.org/howto/unicode.html
0
Monnoroch #
Непонятно, чем «символы» отличаются от байтов. В конечном счете они все равно хранятся как любая другая информация в памяти — как байты. Поясните разницу.
+5
leron #
Символ — это, например, «CYRILLIC SMALL LETTER A», а байт — это нолики и еденицы, например '\xe0'. Для того, что б байт стал символом, необходима кодировка. И в зависимоти от кодировки, этот байт (или байты) может представлять разные символы.
0
k0ldbl00d #
Определение одного символа не обязательно умещается в один байт.
0
Monnoroch #
Я и сказал «чем символы отличаются от байтов?» а не «чем символ отличается от байта?».
Я понимаю, что символ может быть больше байта, я не понимаю вот это «не обязательно».
То есть в юникоде разные символы имеют разный размер? Вообще что можно почитать по этому поводу. поменьше и попроще, чем стандарт?
+3
gribozavr #
В Unicode определяются code points. Но code point — это число + название, он не задаёт байты. Для того, чтобы преобразовать последовательность code points в байты, вам нужно выбрать некоторый Unicode Transfotmation Format (UTF) и закодировать в соответствии с его правилами.
0
Monnoroch #
Да, но число — разве оно и не есть эти самые байты? А названия — разве не однозначно соответствуют числам?
+1
gribozavr #
Нет, число — это просто целое число. Оно может быть закодировано в виде байт (причём правила различны в зависимости от UTF, и ещё и не всеми битами этих байтов).
0
whiteglasses #
Один символ может занимать несколько байт.
0
tripiz #
Похоже что косяки машинного перевода немного накладывает отпечаток…
...«Хорошо, я понял чем есть строка.»…

А так по логике вещей показалось странным, что:
байты в Unicode — это кодирование
а Unicode в батый — это де-кодирование

интуитивно хочется наоборот… хотя смысл, понятен, и зависит от того что-во-что кодировать и следовательно де-кодировать
+1
Monnoroch #
А мне именно такая логика кажется естественной.
У символа есть «код», мы получаем код из символа декодированием оного.
+1
leron #
Нет, это просто стиль такой выбран. А вобще автор совсем не русско-говорящий :)

>> интуитивно хочется наоборот

дык на самом деле и есть наоборот — байты в Unicode — это декодирование.
+1
Volshebnyi #
"string".decode('ascii') # декодируем в  юникод
u"string".encode('ascii') # переводим юникод в кодировку ascii


Все как раз наоборот
+1
Dipp #
>>
На данный момент в Юникод-стандарте есть немного более 100 тысяч символов, тогда как UTF-16 позволяет поддерживать более одного миллиона (UTF-8 — и того больше).


На сколько я понимаю, UTF-16 может кодировать максимум 65 535 символов. Возможно, вы ошиблись?
+3
gribozavr #
Тогда UTF-8 кодирует 255 символов?

Нет. «16» в UTF-16 означает размер используемого слова: каждый code point в UTF-16 кодируется одним или более 16-битными словами.
0
Dipp #
Да, вы правы. Принцип работы UTF-16 аналогичен UTF-8 с разницей лишь в размере слова.

До этого момента я свято верил в то, что wchar_t в Windows — это UTF-16. Оказывается, это UCS-2 — строго 16-битная кодировка без возможности расширения до 32 бит.
0
leron #
16 в названии совсем не значит, что используются 16 бит\2 байта\65 535 доступных комбинаций. Деталей кодировки не знаю, число взял сами знаете откуда. Посмотреть детали думаю можно там же (пока не закрыли в знак протеста против SOPA:) )
+2
MrHant #
Согласно Вики и в UTF-8 и в UTF-16 равное число возможных символов — 1,112,064
+3
gribozavr #
Совершенно верно. Но можно расширить по аналогии правила построения UTF-8 на более длинные последовательности и получить так называемые overlong sequences. Хотя в стандарте прямо написано, что они ошибочны, к сожалению некоторые «хакеры» считают, что они выше стандарта и говорят, что в UTF-8 больше кодов, чем написано в стандарте, да ещё и реализуют их поддержку в своих программах (например, в eglibc bugs.debian.org/cgi-bin/bugreport.cgi?bug=555922 ).

И да, не называйте юникодные code points символами. Не каждый code point можно нарисовать в виде символа и даже есть такие code points, которые называются noncharacters. См. также en.wikipedia.org/wiki/Mapping_of_Unicode_characters
+1
MrHant #
ммм… Code point. Пасиб, проще с такой терминологией работать.
Какой-то русский аналог у этого названия есть?
0
gribozavr #
Не слышал, но возможно «кодовая позиция Unicode»?
+1
MrHant #
прочитал ссылку на баг в eglibc. мдааа…
Ещё один кейс на проверку в программах (я тестировщик). Правда не очень понятно кого винить потом проблеме — своих разработчиков, или разработчиков билиотеки которую они использовали.
Особенно «порадовал» комментарий — «Nobody has ever shown any evidence why this is a bad idea.»
Стандарты видимо люди тоже просто так придумывали…
+7
Moonrise #
> в тоже время в кодировке ISO-8859-1 это греческая "ß".
В ISO-8859-1 нет греческих букв. Это немецкая Eszett. (на правах занудства)
0
leron #
Спасибо за занудство:)
0
mrjj #
Хорошая статья, советую еще раскрыть тему локалей, без этого материал неполный.

Жаль что не смотря на теорию проблема кодировок это сверхбольное место питона, которое каждый раз съедает у меня массу сил и времени. Заткнулся какой ни будь встроенный модуль для работы с gzip на русскоязычных именах файлах и рабочий день коту под хвост.

В python 3000 демонстрировать особо нечего, для самого питона проблема решена на корню как в java, все строки — юникод, на практике ад с портированием библиотек и, имхо, эта ветка сдохнет и на ее место доэволюционирует 2.x, х/з как при этом решится проблема со строками.
+1
svetlov #
Помер как раз 2.х
3.3 выкатят к осени.
Трудности с портированием есть, но «адом» я их не назову.
+2
bndr #
Мне очень понравилась тема Unicode в Dive into Python 3
diveintopython3.ep.io/strings.html
–1
SowingSadness #
Честно говоря, до этой статьи я понимал что такое кодировки и как с ними работать.
Очень тяжелая статья для новичков, которая лишь внесет кашу в головы.
0
pento #
Увидел сначала сколько букв — испугался.
Когда осилил, понял, что не так уж и много. Читается нормально.
Полезная статья!
+1
sev #
В вводной забыли про EBCDIC, они с ASCII практически ровесники.
0
HomoErectus #
Нужная статья, основная полезность направление decode / encode.
Некоторая мнемоника:
1. Для себя сделал ассоциации типа decode более тяжелое по звучанию соответственно создает тяжелый utf,
encode легкое создает легковесный байт.
2. Байты это непонятный код, который чтобы увидеть надо декодировать в символы (utf), а чтобы символы превратить в код (т.е. байты), их соответственно нужно закодировать (encode).
0
aspect #
На виндах у петона вечные проблемы с юникодом, что у третьего, что у второго. На линуксе с этим проще.
0
mrjj #
Да и не только с юникодом.
Да, я по это причине все пытаюсь переползти на макось но пока не складывается.
+2
lightman #
А чем проблемность юникода в Windows вызвана?
–2
aspect #
видимо тем, что родная для винды CP1251
0
qmax #
не волнуйтесь, в линуксе тоже есть вечные проблемы с вводом/выводом изнутри библиотек.
например, stderr направленный в файл — работает, а на консоль — падает, а на appengine — генерит servererror.
0
qmax #
Правильно ли я понимаю, что если везде и всюду в константах и на входе программы будет только юникод (вместо байтстрок) — это избавит от проблем вида «UnicodeDecodeError» в крайне неожиданных местах?
(например, от лютых кабздецов, когда appengine-приложение падает в «server error» из-за того, что логгер не может нарисовать лог.)
0
elmm #
А в каком формате сам python внутри хранит строки в 2.х и 3.х?
0
Savvy #
Строки (str)? Естественно, байты char[]
Unicode — платформозависимо, обычно UCS-2(4)
Хотя что понимать под «хранит внутри» — я о памяти запущенной программы.
0
maxmoriss #
Забавно, вот что по ссылке на перевод статьи Спольски :)
0
maxmoriss #
картинка для предыдущего комментария img.skitch.com/20120320-mb9qxs4tukfin98ria2wb7rjt3.png

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