Все, что можно сделать с помощью модуля textwrap в Python
Узнайте обо всем, что можно сделать с помощью модуля textwrap в Python, включая форматирование, обертывание текста, обрезку и многое другое.
В Python есть множество возможностей для форматирования строк и текста, включая f-строки, функцию format(), шаблоны и многое другое. Однако есть один модуль, о котором мало кто знает, и называется он textwrap.
Этот модуль создан специально для того, чтобы помочь вам с обводкой строк, отступами, обрезкой и многим другим, и в этой статье мы рассмотрим все, для чего его можно использовать.
Shorten
Начнем с очень простой, но очень полезной функции из модуля textwrap, которая называется shorten:
from textwrap import shorten
shorten("This is a long text or sentence.", width=10)
# 'This [...]'
shorten("This is a long text or sentence.", width=15)
# 'This is a [...]'
shorten("This is a long text or sentence.", width=15, placeholder=" <...>")
# 'This is a <...>'
Как следует из названия, shorten позволяет обрезать текст до определенной длины (ширины), если указанная строка слишком длинная. По умолчанию в качестве вставки для обрезанного текста используется […], но это можно изменить с помощью аргумента placeholder.
Wrap
Более интересной функцией этого модуля является wrap. Очевидно, что она используется для разбиения длинного текста на строки одинаковой длины, но есть и другие возможности:
from textwrap import wrap
s = '1234567890'
wrap(s, 3)
# ['123', '456', '789', '0']
В этом примере мы разбиваем строку на равные куски, что может быть полезно для пакетной обработки, а не только для форматирования.
Однако использование этой функции имеет некоторые недостатки:
s = '12\n3 45678\t9\n0'
wrap(s, 3)
# ['12', '3 ', '456', '78', '9 0']
# the first ("12") element "includes" newline
# the 4th element ("78") "includes" tab
wrap(s, 3, drop_whitespace=False, tabsize=1)
# ['12 ', '3 ', '456', '78 ', '9 0']
При использовании wrap следует быть осторожным с пробелами – выше показано поведение с символами новой строки, табуляции и пробела. Видно, что первый элемент ( 12) “включает” новую строку, а 4-й элемент ( 78) “включает” табуляцию, однако они отбрасываются по умолчанию, поэтому эти элементы содержат только 2 символа вместо 3.
Мы можем указать ключевой аргумент drop_whitespace, чтобы сохранить их и поддерживать правильную длину чанков.
Это может быть очевидно, но wrap также отлично подходит для переформатирования целых файлов под определенную ширину строки:
with open("some-text.md", "r", encoding="utf-8") as f:
formatted = wrap(f.read(), width=80) # List of lines
formatted = fill(f.read(), width=80) # Single string that includes line breaks
# ... write it back
Мы также можем использовать функцию fill, которая является сокращением для “\n”.join(wrap(text, …)). Разница между этими двумя функциями заключается в том, что wrap даст нам список строк, которые нам нужно будет объединить самостоятельно, а fill даст нам одну строку, которая уже объединена с помощью новых строк.
TextWrapper
Модуль textwrap также включает в себя более мощную функцию обертывания версий, которая представляет собой класс TextWrapper:
import textwrap
w = textwrap.TextWrapper(width=120, placeholder=" <...>")
for s in list_of_strings:
w.wrap(s)
# ...
Этот класс и его метод wrap отлично подходят, если нам нужно вызвать wrap с одними и теми же параметрами несколько раз, как показано выше.
А пока мы рассматриваем TextWrapper, давайте попробуем использовать еще несколько ключевых аргументов:
user = "John"
prefix = user + ": "
width = 50
wrapper = TextWrapper(initial_indent=prefix, width=width, subsequent_indent=" " * len(prefix))
messages = ["...", "...", "..."]
for m in messages:
print(wrapper.fill(m))
# John: Lorem Ipsum is simply dummy text of the
# printing and typesetting industry. Lorem
# John: Ipsum has been the industry's standard dummy
# text ever since the 1500s, when an
# John: unknown printer took a galley of type and
# scrambled it to make a type specimen
Здесь мы видим использование initial_indent и subsequent_indent для отступа первой строки абзаца и последующих, соответственно. Есть еще несколько вариантов, которые вы можете найти в документации.
Кроме того, поскольку TextWrapper – это класс, мы можем расширить его и полностью переопределить некоторые из его методов:
from textwrap import TextWrapper
class DocumentWrapper(TextWrapper):
def wrap(self, text):
split_text = text.split('\n')
lines = [line for par in split_text for line in TextWrapper.wrap(self, par)]
return lines
text = """First line,
Another, much looooooonger line of text and/or sentence"""
d = DocumentWrapper(width=50)
print(d.fill(text))
# First line,
# Another, much looooooonger line of text and/or
# sentence
Это хороший пример изменения метода обертки для сохранения существующих переносов строк и их правильной печати.
Indentation
Наконец, textwrap также включает две функции для отступов, первая из которых – dedent:
# Ugly formatting:
multiline_string = """
First line
Second line
Third line
"""
from textwrap import dedent
multiline_string = """
First line
Second line
Third line
"""
print(dedent(multiline_string))
# First line
# Second line
# Third line
# Notice the leading blank line...
# You can use:
multiline_string = """\
First line
Second line
Third line
"""
# or
from inspect import cleandoc
cleandoc(multiline_string)
# 'First line\nSecond line\nThird line'
По умолчанию многострочные строки в Python учитывают все отступы, используемые в строке, поэтому мы должны использовать уродливое форматирование, показанное в первой переменной в приведенном выше фрагменте. Но мы можем использовать функцию dedent для улучшения форматирования – мы просто делаем отступ для значения переменной, как нам нравится, а затем вызываем dedent для нее перед использованием.
В качестве альтернативы можно использовать inspect.cleandoc, которая также удаляет ведущую новую строку. Однако эта функция кодирует пробелы как специальные символы (\n и \t), так что вам, возможно, придется переформатировать ее снова.
Естественно, если есть функция вычитания, то должна быть и функция отступа:
from textwrap import indent
indented = indent(text, " ", lambda x: not text.splitlines()[0] in x)
Мы просто указываем текст и строку, в которой каждая строка будет отступать (здесь просто 4 пробела, но мы можем, например, использовать >>>, чтобы это выглядело как REPL). Кроме того, мы можем указать предикат, который будет решать, должна ли строка быть отступом или нет. В примере выше лямбда-функция делает так, что первая строка строки (абзац) не имеет отступа.
Заключительные размышления
textwrap – это простой модуль с несколькими функциями/методами, но он еще раз показывает, что Python действительно поставляется с “батарейками в комплекте” для вещей, которые не обязательно должны быть стандартной библиотекой, но они могут сэкономить вам столько времени, когда они вам понадобятся.
Если вы часто работаете с текстом, то я также рекомендую ознакомиться с целым разделом документации, посвященным работе с текстом. Там вы найдете множество модулей и функций, о которых даже не подозревали. 😉