6. Практическая работа со строками
В главе 5 вы узнали основы работы со строками: создание строк, доступ к символам через индексацию и срезы, а также использование базовых строковых методов. Теперь мы расширим эту базу и рассмотрим более сложные операции со строками, которые вы будете постоянно использовать в реальных программах на Python.
В этой главе мы сосредоточимся на практических приемах обработки строк, которые решают повседневные задачи программирования: разбиение текста на части и сборка его обратно, создание форматированного вывода, который выглядит профессионально. Эти навыки необходимы, когда вы обрабатываете ввод пользователя, создаете отчеты, читаете файлы данных или пишете любую программу, работающую с текстом.
6.1) Разбиение и объединение строк
Одна из самых распространенных задач при обработке текста — разбить строку на более мелкие части или, наоборот, объединить несколько частей в одну строку. Python предоставляет мощные методы для обеих операций.
6.1.1) Разбиение строк с помощью split()
Метод split() разбивает строку на список (list) более мелких строк на основе разделителя (separator, также называемого делимитером). Это чрезвычайно полезно для обработки структурированного текста, такого как CSV-данные, пользовательский ввод с несколькими значениями или предложения, разбиваемые на слова.
Базовое разбиение по пробельным символам:
Когда вы вызываете split() без аргументов, строка разбивается по любому пробельному символу (пробелы, табуляции, переводы строки), и пустые строки автоматически удаляются из результата:
# split_basic.py
sentence = "Python is a powerful programming language"
words = sentence.split()
print(words) # Output: ['Python', 'is', 'a', 'powerful', 'programming', 'language']
print(len(words)) # Output: 6Обратите внимание, как split() разумно обрабатывает несколько пробелов:
# split_whitespace.py
messy_text = "Python is awesome"
words = messy_text.split()
print(words) # Output: ['Python', 'is', 'awesome']Несмотря на то, что между словами несколько пробелов, split() рассматривает любое количество пробельных символов как один разделитель и создает аккуратный список.
Разбиение по конкретному разделителю:
Вы можете явно указать, по какому символу или строке нужно разбивать, передав его в качестве аргумента:
# split_separator.py
csv_data = "apple,banana,cherry,date"
fruits = csv_data.split(',')
print(fruits) # Output: ['apple', 'banana', 'cherry', 'date']
date_string = "2024-03-15"
parts = date_string.split('-')
print(parts) # Output: ['2024', '03', '15']
year = parts[0]
month = parts[1]
day = parts[2]
print(f"Year: {year}, Month: {month}, Day: {day}") # Output: Year: 2024, Month: 03, Day: 15Важное отличие: когда вы указываете разделитель, split() воспринимает его буквально и создаёт пустые строки, если разделители идут подряд:
# split_empty_strings.py
data = "apple,,cherry"
result = data.split(',')
print(result) # Output: ['apple', '', 'cherry']
print(len(result)) # Output: 3Пустая строка '' посередине представляет собой «ничего» между двумя подряд идущими запятыми. Такое поведение отличается от разбиения по пробелам без аргументов.
Ограничение количества разбиений:
Вы можете контролировать, сколько раз будет произведено разбиение, передав второй аргумент (maxsplit):
# split_maxsplit.py
text = "one:two:three:four:five"
parts = text.split(':', 2) # Split only on the first 2 colons
print(parts) # Output: ['one', 'two', 'three:four:five']В итоге создается не более 3 частей (maxsplit + 1), потому что разбиение прекращается после указанного числа срабатываний. Остаток строки остаётся целым.
Практический пример: обработка пользовательского ввода
# process_input.py
user_input = input("Enter your full name: ")
# User enters: "Alice Marie Johnson"
name_parts = user_input.split()
if len(name_parts) >= 2:
first_name = name_parts[0]
last_name = name_parts[-1] # Last element
print(f"First name: {first_name}") # Output: First name: Alice
print(f"Last name: {last_name}") # Output: Last name: Johnson
else:
print("Please enter at least a first and last name")6.1.2) Объединение строк с помощью join()
Метод join() делает противоположное split(): он объединяет список (или любой другой итерируемый объект) строк в одну строку, вставляя между элементами разделитель. Синтаксис может показаться непривычным — метод вызывает строка-разделитель, а список передается как аргумент.
Базовое объединение:
# join_basic.py
words = ['Python', 'is', 'awesome']
sentence = ' '.join(words)
print(sentence) # Output: Python is awesome
csv_line = ','.join(['apple', 'banana', 'cherry'])
print(csv_line) # Output: apple,banana,cherryСтрока, у которой вызывается join() (например, ' ' или ','), становится разделителем между элементами.
Почему синтаксис именно separator.join(list):
Этот синтаксис становится логичным, если посмотреть с точки зрения разделителя: «Я хочу соединить эти элементы, вставляя себя между каждой парой». Он также позволяет элегантно комбинировать вызовы и делает разделитель очень заметным в коде.
Объединение с разными разделителями:
# join_separators.py
items = ['eggs', 'milk', 'bread', 'butter']
# Разделитель — запятая
print(', '.join(items)) # Output: eggs, milk, bread, butter
# Разделитель — перевод строки (каждый элемент на своей строке)
print('\n'.join(items))
# Output:
# eggs
# milk
# bread
# butter
# Разделитель — дефис
print('-'.join(items)) # Output: eggs-milk-bread-butter
# Без разделителя (конкатенация)
print(''.join(items)) # Output: eggsmilkbreadbutterВажно: join() работает только со строками:
Все элементы итерируемого объекта должны быть строками. Если вы попытаетесь объединить числа или другие типы, возникнет ошибка:
# join_error.py
numbers = [1, 2, 3, 4]
# result = ','.join(numbers) # This would cause: TypeError: sequence item 0: expected str instance, int foundЧтобы объединить элементы другого типа, сначала преобразуйте их в строки. В главе 34 мы рассмотрим более элегантный способ, но пока можно конвертировать каждый элемент вручную:
# join_numbers.py
numbers = [1, 2, 3, 4]
# Convert each number to a string manually
string_numbers = [str(numbers[0]), str(numbers[1]), str(numbers[2]), str(numbers[3])]
result = ','.join(string_numbers)
print(result) # Output: 1,2,3,4Практический пример: построение путей к файлам
# build_path.py
path_parts = ['home', 'user', 'documents', 'report.txt']
# On Unix-like systems (Linux, macOS)
unix_path = '/'.join(path_parts)
print(unix_path) # Output: home/user/documents/report.txt
# On Windows
windows_path = '\\'.join(path_parts)
print(windows_path) # Output: home\user\documents\report.txtПримечание: в главе 26 мы познакомимся с модулем os.path, который обеспечивает более удобную кроссплатформенную работу с путями, но этот пример демонстрирует саму идею использования join().
6.1.3) Комбинирование split() и join() для обработки текста
Эти два метода прекрасно работают вместе для трансформации текста. Комбинируя их, вы можете очищать «грязный» ввод, конвертировать между форматами, извлекать и переставлять данные:
# transform_text.py
# Заменить несколько пробелов одним пробелом
messy = "Python is really cool"
clean = ' '.join(messy.split())
print(clean) # Output: Python is really cool
# Преобразовать строку, разделенную запятыми, в строку, разделенную пробелами
csv_data = "apple,banana,cherry"
space_separated = ' '.join(csv_data.split(','))
print(space_separated) # Output: apple banana cherry
# Удалить все пробелы
text_with_spaces = "H e l l o"
no_spaces = ''.join(text_with_spaces.split())
print(no_spaces) # Output: Hello6.1.4) Другие методы разбиения
Python предоставляет дополнительные методы разбиения для особых случаев:
rsplit() — разбиение справа:
# rsplit_example.py
path = "folder/subfolder/file.txt"
# Обычный split с maxsplit
parts = path.split('/', 1)
print(parts) # Output: ['folder', 'subfolder/file.txt']
# rsplit разбивает начиная с правого края
parts = path.rsplit('/', 1)
print(parts) # Output: ['folder/subfolder', 'file.txt']Это полезно, когда нужно отделить последнюю часть строки от всего, что перед ней.
splitlines() — разбиение по переводам строки:
# splitlines_example.py
multiline = "Line 1\nLine 2\nLine 3"
lines = multiline.splitlines()
print(lines) # Output: ['Line 1', 'Line 2', 'Line 3']
# Работает с разными стилями окончания строк
mixed_lines = "Line 1\nLine 2\r\nLine 3\rLine 4"
all_lines = mixed_lines.splitlines()
print(all_lines) # Output: ['Line 1', 'Line 2', 'Line 3', 'Line 4']Метод splitlines() распознает все стандартные варианты переноса строки (\n, \r\n, \r) и разбивает по ним, делая его более надежным, чем split('\n') при обработке текста из разных источников.
6.2) Форматирование строк с помощью f-строк
Создание форматированного вывода — одна из самых частых задач в программировании. Нужно объединять текст со значениями переменных, выравнивать колонки, форматировать числа и создавать удобочитаемый вывод для пользователей. F-строки (formatted string literals) в Python — самый современный, наглядный и мощный способ сделать это.
6.2.1) Базовый синтаксис f-строк
F-строка — это строковый литерал с префиксом f или F, внутри которого можно помещать выражения в фигурных скобках {}. Python вычисляет эти выражения и преобразует их результаты в строку:
# fstring_basic.py
name = "Alice"
age = 30
greeting = f"Hello, {name}! You are {age} years old."
print(greeting) # Output: Hello, Alice! You are 30 years old.Выражения внутри {} могут быть любыми корректными выражениями Python:
# fstring_expressions.py
x = 10
y = 20
result = f"The sum of {x} and {y} is {x + y}"
print(result) # Output: The sum of 10 and 20 is 30
price = 19.99
quantity = 3
total = f"Total cost: ${price * quantity}"
print(total) # Output: Total cost: $59.976.2.2) Почему f-строки лучше старых подходов
До появления f-строк (в Python 3.6) программисты использовали конкатенацию строк или метод format(). Сравним подходы.
Конкатенация строк (старый, склонный к ошибкам способ):
# concatenation_example.py
name = "Bob"
age = 25
# Требуется преобразование чисел в строки и множество операторов +
message = "Hello, " + name + "! You are " + str(age) + " years old."
print(message) # Output: Hello, Bob! You are 25 years old.Этот подход многословен, склонен к ошибкам (если забыть str(), возникнет ошибка) и плохо читается при большом числе переменных.
F-строки (современный, аккуратный подход):
# fstring_clean.py
name = "Bob"
age = 25
message = f"Hello, {name}! You are {age} years old."
print(message) # Output: Hello, Bob! You are 25 years old.F-строки автоматически преобразуют значения в строки, лучше читаются и на самом деле работают быстрее, чем другие подходы.
6.2.3) Выражения и вызовы методов в f-строках
Внутри f-строк можно использовать сложные выражения, вызовы методов и даже функций:
# fstring_methods.py
name = "alice"
print(f"Capitalized: {name.capitalize()}") # Output: Capitalized: Alice
print(f"Uppercase: {name.upper()}") # Output: Uppercase: ALICE
print(f"Length: {len(name)}") # Output: Length: 5
# Арифметика и сравнения
x = 10
print(f"Is {x} even? {x % 2 == 0}") # Output: Is 10 even? True
# Индексация и срезы
text = "Python"
print(f"First letter: {text[0]}") # Output: First letter: P
print(f"First three: {text[:3]}") # Output: First three: Pyt6.2.4) Форматирование чисел в f-строках
F-строки поддерживают спецификаторы формата (format specifiers), позволяющие управлять тем, как значения отображаются. Синтаксис: {expression:format_spec}.
Управление количеством знаков после запятой для float:
# fstring_decimals.py
pi = 3.14159265359
print(f"Default: {pi}") # Output: Default: 3.14159265359
print(f"2 decimals: {pi:.2f}") # Output: 2 decimals: 3.14
print(f"4 decimals: {pi:.4f}") # Output: 4 decimals: 3.1416
print(f"No decimals: {pi:.0f}") # Output: No decimals: 3Спецификатор формата .2f означает «отформатировать как число с плавающей точкой с 2 знаками после запятой». Буква f означает «fixed-point notation» (фиксированная точка).
Форматирование с разделителями тысяч:
# fstring_thousands.py
large_number = 1234567890
print(f"No separator: {large_number}") # Output: No separator: 1234567890
print(f"With commas: {large_number:,}") # Output: With commas: 1,234,567,890
print(f"With underscores: {large_number:_}") # Output: With underscores: 1_234_567_890
# Комбинирование с количеством знаков после запятой
price = 1234567.89
print(f"Price: ${price:,.2f}") # Output: Price: $1,234,567.89Форматирование процентов:
# fstring_percentage.py
ratio = 0.847
print(f"Ratio: {ratio}") # Output: Ratio: 0.847
print(f"Percentage: {ratio:.1%}") # Output: Percentage: 84.7%
print(f"Percentage: {ratio:.2%}") # Output: Percentage: 84.70%Спецификатор % умножает число на 100 и добавляет знак процента.
6.2.5) Практические примеры с f-строками
Создание форматированных отчетов:
# report_example.py
product = "Laptop"
price = 899.99
quantity = 5
tax_rate = 0.08
subtotal = price * quantity
tax = subtotal * tax_rate
total = subtotal + tax
print(f"Product: {product}") # Output: Product: Laptop
print(f"Price: ${price:.2f}") # Output: Price: $899.99
print(f"Quantity: {quantity}") # Output: Quantity: 5
print(f"Subtotal: ${subtotal:.2f}") # Output: Subtotal: $4499.95
print(f"Tax (8%): ${tax:.2f}") # Output: Tax (8%): $360.00
print(f"Total: ${total:.2f}") # Output: Total: $4859.95Создание удобочитаемых сообщений для пользователя:
# user_messages.py
username = "Alice"
login_count = 42
last_login = "2024-03-15"
welcome = f"Welcome back, {username}!"
stats = f"You've logged in {login_count} times. Last login: {last_login}"
print(welcome) # Output: Welcome back, Alice!
print(stats) # Output: You've logged in 42 times. Last login: 2024-03-156.2.6) Отладка (debugging) с помощью f-строк
В Python 3.8 был добавлен спецификатор =, удобный для отладки: он показывает и выражение, и его значение:
# fstring_debug.py
x = 10
y = 20
z = x + y
print(f"{x=}") # Output: x=10
print(f"{y=}") # Output: y=20
print(f"{z=}") # Output: z=30
print(f"{x + y=}") # Output: x + y=30Это невероятно удобно для быстрого просмотра значений переменных во время разработки, без необходимости дважды печатать имя переменной.
6.2.7) Экранирование фигурных скобок в f-строках
Если вам нужны буквальные фигурные скобки внутри f-строки, удвойте их:
# fstring_escape.py
value = 42
# Одиночные скобки — это места для выражений
print(f"Value: {value}") # Output: Value: 42
# Двойные скобки дают буквальные фигурные скобки
print(f"Use {{value}} as a placeholder") # Output: Use {value} as a placeholder
print(f"The value is {value}, shown as {{value}}") # Output: The value is 42, shown as {value}6.3) Форматирование с помощью format() и спецификаторов формата
Хотя f-строки — современный предпочитаемый подход, метод format() все еще широко используется и предоставляет несколько возможностей, которые полезно понимать. Именно на format() опираются f-строки, поэтому знание format() помогает лучше понять и f-строки.
6.3.1) Базовый синтаксис format()
Метод format() использует фигурные скобки {} как заполнители в строке, а значения для подстановки передаются как аргументы:
# format_basic.py
template = "Hello, {}! You are {} years old."
message = template.format("Alice", 30)
print(message) # Output: Hello, Alice! You are 30 years old.
# Множественное использование format()
greeting = "Hello, {}!".format("Bob")
print(greeting) # Output: Hello, Bob!6.3.2) Позиционные и именованные аргументы
Вы можете управлять тем, какой аргумент куда подставляется, используя номера позиций или имена.
Позиционные аргументы:
# format_positional.py
# Порядок по умолчанию (0, 1, 2, ...)
template = "{} + {} = {}"
result = template.format(5, 3, 8)
print(result) # Output: 5 + 3 = 8
# Явное указание позиций
template = "{0} + {1} = {2}"
result = template.format(5, 3, 8)
print(result) # Output: 5 + 3 = 8
# Переупорядочивание и переиспользование
template = "{2} = {0} + {1}"
result = template.format(5, 3, 8)
print(result) # Output: 8 = 5 + 3
# Переиспользование одного и того же значения
template = "{0} times {0} equals {1}"
result = template.format(7, 49)
print(result) # Output: 7 times 7 equals 49Именованные аргументы:
# format_keyword.py
template = "Hello, {name}! You are {age} years old."
message = template.format(name="Alice", age=30)
print(message) # Output: Hello, Alice! You are 30 years old.
# Можно смешивать с позиционными (позиционные должны идти первыми)
template = "{0}, your score is {score} out of {1}"
result = template.format("Alice", 100, score=95)
print(result) # Output: Alice, your score is 95 out of 1006.3.3) Спецификаторы формата в format()
Спецификаторы формата в format() работают так же, как и в f-строках, с разделителем ::
# format_specifiers.py
pi = 3.14159265359
print("{:.2f}".format(pi)) # Output: 3.14
print("{:.4f}".format(pi)) # Output: 3.1416
# С именованными аргументами
print("{value:.2f}".format(value=pi)) # Output: 3.14
# Несколько значений с разными форматами
template = "{name}'s score is {score:.1f}%"
result = template.format(name="Bob", score=87.654)
print(result) # Output: Bob's score is 87.7%6.3.4) Когда использовать format() вместо f-строк
В целом f-строки предпочтительнее, но format() полезен в некоторых ситуациях.
1. Строки-шаблоны (template strings), определенные отдельно от данных:
# format_templates.py
# Шаблон определен один раз и используется многократно с разными данными
email_template = "Dear {name},\n\nYour order #{order_id} has shipped.\n\nThank you!"
# Используем шаблон с разными покупателями
message1 = email_template.format(name="Alice", order_id=12345)
message2 = email_template.format(name="Bob", order_id=12346)
print(message1)
# Output:
# Dear Alice,
#
# Your order #12345 has shipped.
#
# Thank you!
print(message2)
# Output:
# Dear Bob,
#
# Your order #12346 has shipped.
#
# Thank you!2. Когда шаблон поступает из внешних источников:
# format_external.py
# Шаблон может поступать из файла конфигурации или базы данных
# (Читать файлы мы научимся в главе 24)
user_template = input("Enter message template: ")
# User enters: "Hello, {name}! Welcome to {place}."
message = user_template.format(name="Charlie", place="Python")
print(message) # Output: Hello, Charlie! Welcome to Python.С f-строками шаблон должен находиться в вашем коде, потому что выражения вычисляются немедленно. С format() шаблон может быть обычной строкой, полученной откуда угодно.
6.3.5) Доступ к атрибутам объектов и ключам словарей
Метод format() может напрямую получать доступ к атрибутам объектов и ключам словарей:
# format_access.py
# Доступ к словарю
person = {"name": "Alice", "age": 30, "city": "Boston"}
message = "Name: {0[name]}, Age: {0[age]}, City: {0[city]}".format(person)
print(message) # Output: Name: Alice, Age: 30, City: Boston
# С именованным аргументом
message = "{p[name]} is {p[age]} years old".format(p=person)
print(message) # Output: Alice is 30 years oldПримечание: о атрибутах объектов мы поговорим в главе 30, но этот пример показывает, что format() может работать с вложенными структурами данных.
6.4) Выравнивание и округление чисел в форматированном выводе
Вывод, выглядящий профессионально, часто требует аккуратного выравнивания и форматирования чисел. И f-строки, и format() предоставляют мощные средства для создания хорошо оформленных таблиц, отчетов и других видов вывода.
6.4.1) Выравнивание текста
Вы можете управлять шириной и выравниванием значений с помощью спецификаторов формата:
# alignment_basic.py
# Синтаксис: {value:width}
# По умолчанию строки выравниваются по левому краю, числа — по правому
name = "Alice"
print(f"|{name}|") # Output: |Alice|
print(f"|{name:10}|") # Output: |Alice | (выравнивание по левому краю, ширина 10)
print(f"|{name:>10}|") # Output: | Alice| (выравнивание по правому краю)
print(f"|{name:^10}|") # Output: | Alice | (выравнивание по центру)Спецификаторы выравнивания:
<: выравнивание по левому краю (по умолчанию для строк)>: выравнивание по правому краю (по умолчанию для чисел)^: выравнивание по центру
Выравнивание чисел:
# alignment_numbers.py
value = 42
print(f"|{value}|") # Output: |42|
print(f"|{value:5}|") # Output: | 42| (по умолчанию по правому краю)
print(f"|{value:<5}|") # Output: |42 | (по левому краю)
print(f"|{value:^5}|") # Output: | 42 | (по центру)6.4.2) Пользовательские символы заполнения
Можно указать символ, которым будет заполняться свободное место:
# alignment_fill.py
name = "Bob"
print(f"|{name:*<10}|") # Output: |Bob*******|
print(f"|{name:*>10}|") # Output: |*******Bob|
print(f"|{name:*^10}|") # Output: |***Bob****|
# Полезно для создания визуальных разделителей
print(f"{name:=^20}") # Output: ========Bob=========Синтаксис: {value:fill_char align width}.
6.4.3) Совмещение выравнивания и форматирования чисел
Можно сочетать ширину, выравнивание и числовое форматирование:
# alignment_combined.py
price = 19.99
quantity = 5
total = price * quantity
# Выравнивание по правому краю, ширина 10, 2 знака после запятой
print(f"Price: ${price:>10.2f}") # Output: Price: $ 19.99
print(f"Quantity: {quantity:>10}") # Output: Quantity: 5
print(f"Total: ${total:>10.2f}") # Output: Total: $ 99.95
# С символом заполнения для наглядного эффекта
print(f"Total: ${total:>10.2f}".replace(' ', '.')) # Output: Total:....$.....99.95В этой главе вы познакомились с важнейшими приемами работы со строками, которые будете использовать практически в каждой программе на Python: разбиение и объединение строк для обработки текста, создание форматированного вывода с помощью f-строк и спецификаторов формата, выравнивание и форматирование чисел для профессионального отображения данных.
Эти навыки формируют основу работы с текстовыми данными в Python. Теперь вы можете обрабатывать ввод пользователя, создавать форматированные отчеты, работать с данными из файлов (об этом мы поговорим в главе 24). По мере дальнейшего изучения Python вы будете постоянно пользоваться этими приёмами обработки строк, поэтому тренируйтесь до тех пор, пока они не станут для вас естественными.