Строки, f-строки, методы строк, работа с текстом
Главное правило: строки в Python — неизменяемые объекты. Любая операция над строкой создаёт новую строку.
s = "hello"
s + " world" # "hello world" — новая строка
s * 3 # "hellohellohello"
len(s) # 5
s[0] # 'h' — доступ по индексу (но не изменение!)❌ Нельзя:
s[0] = 'H' # TypeError: 'str' object does not support item assignment✅ Вместо этого:
s = "H" + s[1:] # "Hello"| Метод | Пример | Результат |
|---|---|---|
.upper() | "abc".upper() | "ABC" |
.lower() | "ABC".lower() | "abc" |
.casefold() | "Straße".casefold() | "strasse" |
.strip() | " x ".strip() | "x" |
.split() | "a,b,c".split(",") | ["a", "b", "c"] |
.join() | ",".join(["a","b"]) | "a,b" |
.replace() | "hello".replace("l", "L") | "heLLo" |
.startswith() / .endswith() | "file.txt".endswith(".txt") | True |
💡 Best practice: используйте .join() вместо + в циклах:
# Плохо (O(n²))
result = ""
for word in words:
result += word + " "
# Хорошо (O(n))
result = " ".join(words)casefold() vs lower() — сравнение без регистра для Unicodelower() — простое преобразование к нижнему регистру:
"HELLO".lower() # "hello"
"Straße".lower() # "straße" (немецкая эсцет)
"ΑΛΕΞΑΝΔΡΟΣ".lower() # "αλεξανδρος" (греческий)casefold() — агрессивное сравнение без регистра для Unicode (Python 3.0+):
"HELLO".casefold() # "hello"
"Straße".casefold() # "strasse" (эсцет → "ss")
"ΑΛΕΞΑΝΔΡΟΣ".casefold() # "αλεξανδρος"casefold()✅ Используйте casefold() для сравнения строк без учёта регистра:
# Проблема с lower()
"straße".lower() == "Strasse".lower() # False ❌
# Решение с casefold()
"straße".casefold() == "Strasse".casefold() # True ✅
# Сравнение имён пользователей, email и т.д.
def is_same_user(input1: str, input2: str) -> bool:
return input1.casefold() == input2.casefold()✅ Для ASCII-строк разницы нет:
"Hello".lower() == "Hello".casefold() # True❌ Не используйте casefold() если:
# Поиск по базе пользователей без учёта регистра
users = ["Alice", "БОРИС", "straße", "Strasse"]
search = "STRASSE"
matches = [u for u in users if u.casefold() == search.casefold()]
# matches = ["straße", "Strasse"] ✅
# С lower() не сработало бы для немецкого
matches_lower = [u for u in users if u.lower() == search.lower()]
# matches_lower = [] ❌Правило: для сравнения строк без учёта регистра всегда используйте
casefold()— это безопаснее для Unicode.
%-форматирование (устаревшее)"name: %s, age: %d" % ("Alice", 30).format() (Python 2.6+)"name: {}, age: {}".format("Alice", 30)
"name: {name}, age: {age}".format(name="Alice", age=30)name = "Alice"
age = 30
f"name: {name}, age: {age}" # "name: Alice, age: 30"
f"double age: {age * 2}" # "double age: 60"
f"{name.upper()}" # "ALICE"
f"{value:.2f}" # форматирование чисел✅ f-строки быстрее, читабельнее и поддерживают выражения внутри
{}.
string.Template — безопасное форматированиеstring.Template — альтернативный способ форматирования строк, безопасный для пользовательского ввода (модуль string).
from string import Template
# Создание шаблона
t = Template("Привет, $name! Ваш баланс: $balance руб.")
# Подстановка значений
result = t.substitute(name="Alice", balance=1000)
# "Привет, Alice! Ваш баланс: 1000 руб."
# Или через словарь
data = {"name": "Bob", "balance": 500}
result = t.substitute(data)$variable vs ${variable}# $name — простая подстановка
t = Template("Привет, $name!")
# ${variable} — когда имя переменной граничит с другими символами
t = Template("Цена: ${price}₽ (не $price₽)")
t.substitute(price=100) # "Цена: 100₽ (не $price₽)"| Метод | Поведение |
|---|---|
.substitute(**kwargs) | Выбрасывает KeyError, если переменная не найдена |
.safe_substitute(**kwargs) | Оставляет $var как есть, если переменная не найдена |
t = Template("Привет, $name!")
t.substitute(name="Alice")
# "Привет, Alice!"
t.substitute()
# KeyError: 'name' ❌
t.safe_substitute()
# "Привет, $name!" ✅ (без ошибки)string.Template✅ Хорошие случаи:
.format() с непроверенным input❌ Не используйте для:
.format() или f-строки)from string import Template
# Пользовательский шаблон (может прийти из БД или конфига)
user_template = "Уважаемый $customer, ваш заказ $order_id готов!"
# Безопасная подстановка — пользователь не может вставить вредоносный код
t = Template(user_template)
email = t.safe_substitute(customer="Иван", order_id=12345)
# "Уважаемый Иван, ваш заказ 12345 готов!"
# Сравните с опасным .format():
# user_input = "{__import__('os').system('rm -rf /')}"
# dangerous = user_template.format(**{"__import__('os').system('rm -rf /')": "x"})
# ❌ .format() может выполнить произвольный код!name = "Alice"
# f-строки (быстро, удобно, но небезопасно для user input)
f"Hello, {name}!"
# .format() (гибко, но тоже небезопасно)
"Hello, {}!".format(name)
# Template (безопасно для пользовательских шаблонов)
from string import Template
Template("Hello, $name!").substitute(name=name)Правило: используйте f-строки по умолчанию, но для пользовательских шаблонов выбирайте
string.Template.
textwrap — форматирование текстаМодуль textwrap предназначен для форматирования и обёртывания текста (разбиение на строки определённой ширины).
import textwrap
text = "Это длинный текст, который нужно разбить на несколько строк определённой ширины."
# Обёртывание текста (возвращает строку)
wrapped = textwrap.fill(text, width=40)
print(wrapped)Вывод:
Это длинный текст, который нужно
разбить на несколько строк
определённой ширины.
| Функция | Описание |
|---|---|
textwrap.fill(text, width) | Обёртывает текст, возвращает строку |
textwrap.wrap(text, width) | Обёртывает текст, возвращает список строк |
textwrap.dedent(text) | Удаляет общий ведущий пробел (незаменимо для docstrings) |
textwrap.indent(text, prefix) | Добавляет префикс к каждой строке |
textwrap.shorten(text, width) | Сокращает текст до заданной длины |
wrap() — разбиение на список строкimport textwrap
text = "Python — мощный язык программирования."
lines = textwrap.wrap(text, width=20)
print(lines)
# ['Python — мощный', 'язык', 'программирования.']dedent() — удаление общего отступаimport textwrap
# Удобно для многострочных строк в коде
text = """
Первая строка.
Вторая строка.
Третья строка.
"""
clean = textwrap.dedent(text)
print(clean)
# Первая строка.
# Вторая строка.
# Третья строка.indent() — добавление отступаimport textwrap
text = "строка 1\nстрока 2\nстрока 3"
indented = textwrap.indent(text, prefix=" ")
print(indented)
# строка 1
# строка 2
# строка 3
# С условием (Python 3.8+)
indented_if = textwrap.indent(text, prefix="> ", predicate=lambda line: True)shorten() — сокращение текстаimport textwrap
text = "Это очень длинный текст для сокращения"
shortened = textwrap.shorten(text, width=20, placeholder="...")
print(shortened)
# "Это очень..."TextWrapperКласс TextWrapper даёт больше контроля над форматированием:
from textwrap import TextWrapper
wrapper = TextWrapper(
width=50,
initial_indent="> ", # Отступ первой строки
subsequent_indent=" ", # Отступ последующих строк
break_long_words=True, # Разрывать длинные слова
replace_whitespace=True, # Заменять пробелы
drop_whitespace=True, # Удалять лишние пробелы
max_lines=3, # Максимум строк (Python 3.4+)
placeholder=" [...]" # Текст для обрезки
)
text = "Это очень длинный текст, который будет отформатирован с особыми параметрами."
result = wrapper.fill(text)
print(result)Вывод:
> Это очень длинный текст, который будет
отформатирован с особыми параметрами. [...]
import textwrap
def print_help():
help_text = """
Это справка по использованию программы.
Используйте команды: start, stop, restart.
Для подробной информации добавьте флаг --verbose.
"""
# Удаляем отступы и форматируем
clean_text = textwrap.dedent(help_text).strip()
formatted = textwrap.fill(clean_text, width=60)
print(formatted)
print_help()textwrap✅ Хорошие случаи:
❌ Не подходит для:
Правило: для простого форматирования текста используйте
textwrap.fill(), для работы с docstrings —textwrap.dedent().
\n → перевод строки, \" → кавычкаr""): все символы воспринимаются как естьpath = r"C:\Users\name\file.txt" # без экранирования обратных слешей
regex = r"\d{3}-\d{2}-\d{4}" # удобно для регулярных выраженийtext = "привет"
encoded = text.encode("utf-8") # -> b'\xd0\xbf\xd1\x80\xd0\xb8\xd0\xb2\xd0\xb5\xd1\x82'
decoded = encoded.decode("utf-8") # -> "привет"⚠️ Не смешивайте str и bytes:
"hello" + b"world" # TypeError!re)import re
pattern = r"\d{3}-\d{3}-\d{4}"
match = re.search(pattern, "Call 123-456-7890")
if match:
print(match.group()) # "123-456-7890"reimport re
text = "Alice: 30, Bob: 25, Carol: 35"
# re.search — первое совпадение
m = re.search(r"\d+", text)
m.group() # '30'
m.start() # 7
m.end() # 9
# re.findall — все совпадения в виде списка
ages = re.findall(r"\d+", text) # ['30', '25', '35']
# re.finditer — итератор match-объектов (ленивый)
for m in re.finditer(r"(\w+): (\d+)", text):
name, age = m.group(1), m.group(2)
print(f"{name} -> {age}")
# re.sub — замена
clean = re.sub(r"\s+", " ", "too many spaces") # 'too many spaces'
# re.split — разделение
parts = re.split(r",\s*", "a, b, c,d") # ['a', 'b', 'c', 'd']
# re.compile — предкомпилированный паттерн (быстрее при многократном использовании)
EMAIL_RE = re.compile(r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$')
EMAIL_RE.match("alice@example.com")LOG_RE = re.compile(
r'(?P<date>\d{4}-\d{2}-\d{2}) '
r'(?P<level>INFO|WARNING|ERROR) '
r'(?P<msg>.*)'
)
m = LOG_RE.match("2024-01-15 ERROR Database connection failed")
if m:
print(m.group('date')) # '2024-01-15'
print(m.group('level')) # 'ERROR'
print(m.group('msg')) # 'Database connection failed'
print(m.groupdict()) # словарь всех групп# re.IGNORECASE — без учёта регистра
re.search(r'python', 'Python 3.12', re.I)
# re.MULTILINE — ^ и $ совпадают с началом/концом каждой строки
re.findall(r'^\w+', 'line1\nline2\nline3', re.M)
# re.DOTALL — . совпадает с \n
re.search(r'start.*end', 'start\nmiddle\nend', re.S)
# re.VERBOSE — комментарии в паттерне
EMAIL_VERBOSE = re.compile(r"""
^ # начало строки
[a-zA-Z0-9._%+-]+ # имя пользователя
@ # символ @
[a-zA-Z0-9.-]+ # домен
\. # точка
[a-zA-Z]{2,} # TLD
$ # конец строки
""", re.VERBOSE)# Python 3 строки — Unicode (str = последовательность кодовых точек)
s = "Привет 🌍"
len(s) # 8 символов (кодовых точек)
# Информация о символе
import unicodedata
unicodedata.name('А') # 'CYRILLIC CAPITAL LETTER A'
unicodedata.category('А') # 'Lu' (Letter, uppercase)
unicodedata.normalize('NFC', 'cafe\u0301') # 'café' (NFD → NFC)
# Нормализация Unicode — важно для сравнения
s1 = 'café' # e + combining accent
s2 = 'café' # precomposed é
s1 == s2 # может быть False без нормализации!
unicodedata.normalize('NFC', s1) == unicodedata.normalize('NFC', s2) # True# Кодирование
text = "Hello, мир"
utf8_bytes = text.encode('utf-8') # b'Hello, \xd0\xbc\xd0\xb8\xd1\x80'
utf16_bytes = text.encode('utf-16')
ascii_bytes = text.encode('ascii', errors='replace') # ? вместо кириллицы
# Декодирование
decoded = utf8_bytes.decode('utf-8')
partial = b'\xff\xfe'.decode('utf-16-le', errors='ignore')
# Определение кодировки (через chardet/charset-normalizer)
from charset_normalizer import from_bytes
result = from_bytes(unknown_bytes).best()
print(result.encoding) # 'utf-8' или другой# Форматные спецификаторы
pi = 3.14159265
f"{pi:.2f}" # '3.14'
f"{pi:>10.3f}" # ' 3.142' (выравнивание правое, ширина 10)
f"{pi:<10.3f}" # '3.142 ' (левое)
f"{pi:^10.3f}" # ' 3.142 ' (по центру)
f"{pi:+.2f}" # '+3.14' (всегда знак)
# Числа
n = 1_000_000
f"{n:,}" # '1,000,000' (разделитель тысяч)
f"{n:_}" # '1_000_000' (Python 3.6+)
f"{n:e}" # '1.000000e+06'
f"{255:#x}" # '0xff' (hex с префиксом)
f"{255:08b}" # '11111111' (бинарный, 8 символов)
# Дата и время
from datetime import datetime
now = datetime.now()
f"{now:%Y-%m-%d %H:%M:%S}" # '2024-01-15 14:30:00'
f"{now:%d.%m.%Y}" # '15.01.2024'
# = для отладки (Python 3.8+)
x = 42
f"{x=}" # 'x=42'
f"{x*2=}" # 'x*2=84'
# Вложенные f-строки
width = 10
f"{'text':>{width}}" # ' text's = "Hello, World!"
# .find() и .index() — поиск подстроки
s.find("World") # 7 (позиция) или -1 если не найдено
s.index("World") # 7 или ValueError если не найдено
s.rfind("l") # 10 (поиск справа)
s.count("l") # 3 (число вхождений)
# .partition() и .rpartition() — разделение на 3 части
"host:port".partition(":") # ('host', ':', 'port')
"a/b/c".rpartition("/") # ('a/b', '/', 'c')
# .translate() — замена по таблице
table = str.maketrans("aeiou", "AEIOU") # маппинг символов
"hello world".translate(table) # 'hEllO wOrld'
# Удаление символов через translate:
remove = str.maketrans("", "", "!@#$%")
"p@ss!word".translate(remove) # 'pssword'
# .zfill() — заполнение нулями слева
"42".zfill(5) # '00042'
"42".zfill(2) # '42' (не обрезает)
# .center(), .ljust(), .rjust()
"text".center(10, "-") # '---text---'
"text".ljust(10, ".") # 'text......'
"text".rjust(10) # ' text'
# .expandtabs() — замена табов пробелами
"a\tb\tc".expandtabs(4) # 'a b c'
# Проверки
"123".isdigit() # True
"abc123".isalnum() # True
" ".isspace() # True
"Title Case".istitle() # True
"UPPER".isupper() # True
"lower".islower() # Truef"{[1, 2]}" работает, а f"{set([1,2])}" — нет? (Подсказка: __repr__)clean_text(s: str) -> str, которая удаляет все пробелы в начале и конце, заменяет последовательности пробелов на один пробел."straße".lower() == "Strasse".lower() возвращает False, а casefold() — True?string.Template для безопасной подстановки.textwrap.dedent() и ограничить ширину строки 80 символами?Вопросы ещё не добавлены
Вопросы для этой подтемы ещё не добавлены.