Правильная длина строки неанглийских символов в Python3

Мне дается строка символов иврита (и некоторых других арабских символов. Я не знаю ни одного из них) в файле

צוֹר‎

Когда я загружаю эту строку из файла в Python3

fin = open("filename")
x = next(fin).strip()

Длина x кажется равной 5

>>> len(x)
5

Его кодировка unicode utf-8

>>> x.encode("utf-8")
b'\xd7\xa6\xd7\x95\xd6\xb9\xd7\xa8\xe2\x80\x8e'

Однако в браузерах видно, что длина этих символов иврита равна 3.

Как правильно подобрать длину? И почему это происходит?

Я знаю, что Python 3 по умолчанию использует юникод, поэтому я не ожидал, что возникнет такая проблема.


person Yo Hsiao    schedule 18.12.2017    source источник
comment
понятно, что длина этих еврейских символов равна 3 — Понятно, что компьютер с вами не согласен, можете пояснить свою позицию?   -  person Josh Lee    schedule 18.12.2017
comment
Я не знаю, сколько там символов — я не читаю на иврите. Но я знаю, что там есть 5 кодовых точек юникода. Попробуйте это в Python3: for ch in 'צוֹר‎': print(unicodedata.name(ch))   -  person Robᵩ    schedule 18.12.2017
comment
Связано: stackoverflow.com/questions/2247205/   -  person Robᵩ    schedule 18.12.2017
comment
Попробуйте также разбить текст на кластеры графем pypi.python.org/pypi/uniseg   -  person Josh Lee    schedule 18.12.2017
comment
@joshlee Я использую курсор мыши для выбора / выделения, и воспринимаемое количество символов равно 3.   -  person Yo Hsiao    schedule 18.12.2017


Ответы (4)


Причина в том, что включенный текст содержит управляющий символ \u200e, который является невидимым символом, используемым в качестве слева. Метка -to-right (часто используется при смешивании нескольких языков для разграничения между написанием слева направо и справа налево). Кроме того, он включает в себя гласный «символ» (маленькая точка над вторым символом, которая показывает, как его произносить).

Например, если вы замените метку LTR пустой строкой, вы получите длину 4:

>> x = 'צוֹר'
>> x
'צוֹר\u200e' # note the control character escape sequence
>> print(len(x))
5

>> print(len(x.replace('\u200e', ''))
4

Если вам нужна длина строго буквенного символа и только пробельных символов, вы можете сделать что-то вроде re.sub из всех не пробельных не словесных символов:

>> print(len(re.sub('[^\w\s]', '', x)))
3
person lemonhead    schedule 18.12.2017
comment
Хороший ответ! Дополнительный вопрос: если у меня есть x = "צוֹר abc (123)" и я хочу использовать индекс для доступа к 123, как я могу это сделать? Наивно «а» соответствует 4, а «1» — 9. Предложенная вами замена также удаляет знаки препинания. - person Yo Hsiao; 18.12.2017
comment
Хм, ну это зависит от того, что вы хотите сделать. Правильные индексы для необработанного текста будут 6 и 9 из-за управляющих символов и символов ударения. Если вам нужна версия текста, которая явно исключает только метки без пробелов и управляющие символы, вы можете сделать что-то вроде (заимствование из ответа @MichaelButscher): ''.join(c for c in x if unicodedata.category(c) not in ['Mn', 'Cf']) - person lemonhead; 18.12.2017
comment
Исправление: должны быть индексы 6 и 11 выше ^ - person lemonhead; 18.12.2017

Символы Unicode имеют разные категории. В твоем случае:

>>> import unicodedata
>>> s = b'\xd7\xa6\xd7\x95\xd6\xb9\xd7\xa8\xe2\x80\x8e'.decode("utf-8")
>>> list(unicodedata.category(c) for c in s)
['Lo', 'Lo', 'Mn', 'Lo', 'Cf']
  • Lo: Буква, другая (не прописная, не строчная и т.п.). Это "настоящие" персонажи.
  • Mn: Отметить, без пробела. Это какой-то тип акцентного символа в сочетании с предыдущим символом.
  • Cf: Управление, формат. Здесь он переключается обратно на направление записи слева направо
person Michael Butscher    schedule 18.12.2017
comment
Хороший способ выделить реальных персонажей. Если у меня есть другие слова после символов иврита, и я хочу их правильно проиндексировать (учитывая только настоящие символы), есть ли способ сделать это? - person Yo Hsiao; 18.12.2017
comment
@YoHsiao Я вижу только способ перебрать кодовые точки и посмотреть на каждую или сначала преобразовать их, используя подход лимонных головок, чтобы получить положение отфильтрованных реальных символов и слов. - person Michael Butscher; 18.12.2017

Вы пробовали использовать io библиотеку?

>>> import io
>>> with io.open('text.txt',  mode="r", encoding="utf-8") as f:
     x = f.read()
>>> print(len(x))

Вы также можете попробовать codecs:

>>> import codecs
>>> with codecs.open('text.txt', 'r', 'utf-8') as f:
     x = f.read()
>>> print(len(x))
person Dawid Laszuk    schedule 18.12.2017
comment
Спасибо за совет! Но эти два дают идентичные результаты: 5. На самом деле, если вы откроете его в редакторе, который правильно его декодирует, перемещение курсора покажет, что есть некоторые базовые символы, которые изменяются в обратном направлении. Другими словами, нажатие правой кнопки перемещает курсор вперед и назад, но не всегда вперед. Обратная модификация аналогична юникоду для акцентов. - person Yo Hsiao; 18.12.2017
comment
Использование io и кодеков необходимо в Python 2, но, как правило, не в Python 3. - person Josh Lee; 18.12.2017
comment
@JoshLee я так и думал, поскольку все поддельные примеры, которые я сделал, работали из коробки. Просто думал выкинуть его туда. - person Dawid Laszuk; 18.12.2017

Откройте файл с кодировкой utf-8.

fin = open('filename','r',encoding='utf-8')

or

with open('filename','r',encoding='utf-8') as fin:
    for line1 in fin:
        print(len(line1.strip()))
person Ahmad Yoosofan    schedule 24.12.2017