Обрезать строку, не заканчивая ее в середине слова

Я ищу способ обрезать строку в Python, которая не будет обрезать строку в середине слова.

Например:

Original:          "This is really awesome."
"Dumb" truncate:   "This is real..."
"Smart" truncate:  "This is really..."

Я ищу способ выполнить «умное» усечение сверху.


person Jack    schedule 30.10.2008    source источник


Ответы (9)


На самом деле я написал решение для этого в своем недавнем проекте. Я сжал большую часть его, чтобы он был немного меньше.

def smart_truncate(content, length=100, suffix='...'):
    if len(content) <= length:
        return content
    else:
        return ' '.join(content[:length+1].split(' ')[0:-1]) + suffix

Что происходит, так это то, что оператор if проверяет, меньше ли ваш контент уже точки отсечки. Если это не так, он усекается до нужной длины, разбивается на пробел, удаляет последний элемент (чтобы вы не обрезали слово), а затем снова соединяет его (прикрепляя '...') .

person Adam    schedule 30.10.2008
comment
Это очень лаконично... Я бы добавил еще один тест, чтобы избежать пустых строк, если в первых символах длины вообще нет пробелов. - person Jonas; 22.08.2011
comment
Усечение должно было учитывать длину суффикса: return ' '.join(content[:length+1-len(suffix)].split(' ')[0:-1]) + suffix - person Stan; 20.02.2012
comment
Здесь есть крайний случай, который может кого-то укусить: если content[:length+1] заканчивается пробелом, возвращаемая строка будет длиннее, чем length. То же самое касается content[:length+1-len(suffix) из комментария @Stan. - person coredumperror; 24.04.2018
comment
@Adam Хорошо ответил более 11 лет назад, и все же такой устойчивый. Спасибо, что избавили нас от множества ошибок при поиске и коде :-) - person blueDroid; 25.11.2019
comment
Это старо, но полезно. Не могли бы вы предложить добавить rstrip после соединения? ' '.join(content[:length+1].split(' ')[0:-1]).rstrip() + suffix в противном случае вы можете получить что-то вроде 'hello how are you todiajhsdfaja ...' - person Curt; 08.12.2020

Вот немного улучшенная версия последней строки решения Адама:

return content[:length].rsplit(' ', 1)[0]+suffix

(Это немного более эффективно и возвращает более разумный результат, если перед строкой нет пробелов.)

person bobince    schedule 30.10.2008
comment
Это интересно насчет rsplit. Кажется, я никогда не сталкивался с этим. - person Adam; 30.10.2008
comment
Быстрая проверка двух подходов (Python 2.4.3): Код Адама: ››› smart_truncate('Быстрая коричневая лиса перепрыгнула через ленивую собаку.', 26) Быстрая коричневая лиса перепрыгнула... С кодом bobince: › ›› smart_truncate('Быстрая коричневая лиса перепрыгнула через ленивую собаку.', 26) Быстрая бурая лиса... - person Patrick Cuff; 30.10.2008
comment
Да, я добавил length+1 к усечению, чтобы справиться с естественным разбиением усечения точно в конце слова. - person Adam; 30.10.2008
comment
Этот лучше. Но я бы поместил его под if и пропустил бы else, это больше pythonix. - person e-satis; 01.11.2008
comment
Ну, тогда воспользуемся условным выражением: def smart_truncate(content, length=100, suffix='...'): return (content if len(content) ‹= length else content[:length].rsplit(' ' , 1)[0]+суффикс) - person hughdbrown; 05.03.2009
comment
Итак, давайте убедимся, что результирующая строка не длиннее длины: return content if len(content) <= length else content[:length-len(suffix)].rsplit(' ', 1)[0] + suffix - person kraiz; 12.12.2011

Есть несколько тонкостей, которые могут быть или не быть для вас проблемами, такие как обработка вкладок (например, если вы отображаете их как 8 пробелов, но обрабатываете их как 1 символ внутри), обработка различных разновидностей разрывов и не- разрыв пробелов или разрешение переноса переносов и т. д. Если что-то из этого желательно, вы можете взглянуть на модуль textwrap. например:

def truncate(text, max_size):
    if len(text) <= max_size:
        return text
    return textwrap.wrap(text, max_size-3)[0] + "..."

Поведение по умолчанию для слов, превышающих max_size, заключается в их разрыве (что делает max_size жестким ограничением). Вы можете перейти к мягкому пределу, используемому некоторыми другими решениями здесь, передав break_long_words=False для wrap(), и в этом случае он вернет слово целиком. Если вы хотите такое поведение, измените последнюю строку на:

    lines = textwrap.wrap(text, max_size-3, break_long_words=False)
    return lines[0] + ("..." if len(lines)>1 else "")

Есть несколько других параметров, таких как expand_tabs, которые могут представлять интерес в зависимости от того, какое именно поведение вы хотите.

person Brian    schedule 30.10.2008

>>> import textwrap
>>> textwrap.wrap('The quick brown fox jumps over the lazy dog', 12)
['The quick', 'brown fox', 'jumps over', 'the lazy dog']

Вы просто берете первый элемент этого, и все готово...

person Antonio    schedule 29.12.2013
comment
textwrap.shorten("Hello world", width=10, placeholder="...") создаст "Hello..." docs.python.org/3.5/library/textwrap.html - person Salami; 10.12.2015
comment
Я только что попробовал этот, и он сломался в середине кластера графем, так что он даже не выполняет правильную разбивку символов, не говоря уже о разбиении слов. - person Trejkaz; 03.05.2017

def smart_truncate(s, width):
    if s[width].isspace():
        return s[0:width];
    else:
        return s[0:width].rsplit(None, 1)[0]

Тестирование:

>>> smart_truncate('The quick brown fox jumped over the lazy dog.', 23) + "..."
'The quick brown fox...'
person Vebjorn Ljosa    schedule 30.10.2008
comment
Примечание. Если ширина › len(s), вы получаете выход за пределы s[width]. Вероятно, вам нужна дополнительная проверка для случая, когда усечение не требуется. - person Brian; 30.10.2008

В Python 3.4+ вы можете использовать textwrap.shorten. На примере ОП:

>>> import textwrap
>>> original = "This is really awesome."
>>> textwrap.shorten(original, width=20, placeholder="...")
'This is really...'

textwrap.shorten(текст, ширина, **kwargs)

Свернуть и обрезать данный текст, чтобы он соответствовал заданной ширине.

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

person marcanuy    schedule 14.11.2017

Для Python 3.4+ я бы использовал textwrap.shorten. .

Для более старых версий:

def truncate(description, max_len=140, suffix='…'):    
    description = description.strip()
    if len(description) <= max_len:
        return description
    new_description = ''
    for word in description.split(' '):
      tmp_description = new_description + word
      if len(tmp_description) <= max_len-len(suffix):
          new_description = tmp_description + ' '
      else:
          new_description = new_description.strip() + suffix
          break
    return new_description
person Jorge Barata    schedule 26.11.2020

Если вы действительно предпочитаете усекать полное предложение, а не слово, вот с чего начать:

def smart_truncate_by_sentence(content, length=100, suffix='...',):
    if not isinstance(content,str): return content
    if len(content) <= length:
        return content
    else:
        sentences=content.split('.')
        cs=np.cumsum([len(s) for s in sentences])
        n = max(1,  len(cs[cs<length]) )
        return '.'.join(sentences[:n])+ '. ...'*(n<len(sentences))
person CPBL    schedule 21.01.2021

person    schedule
comment
я всегда люблю решения на основе регулярных выражений :) - person Corey Goldberg; 30.10.2008
comment
это (по крайней мере, лучшее решение) работает даже для строк без пробелов (тогда оно обрезает границу слова), хотя в этом случае он не добавляет суффикс :) - person Robin Manoli; 06.05.2015