В Python убрать расширение файла из строки надёжнее всего методом removesuffix(".txt"): он снимает ровно это окончание и ничего больше. Если расширение заранее неизвестно — бери os.path.splitext(name)[0] или Path(name).stem, а вот rstrip(".txt") для этой задачи не годится никогда: он молча съедает лишние буквы, и report.txt превращается в repor.
print("report.txt".removesuffix(".txt"))
print("report.txt".rstrip(".txt"))
report
repor
Ниже — почему так происходит, чем отличаются четыре правильных способа и что делать с archive.tar.gz, .gitignore и файлами вообще без расширения.
Почему "report.txt".rstrip(".txt") возвращает repor?
Потому что rstrip принимает не окончание строки, а набор символов. Аргумент ".txt" он разбирает на три разных символа — точку, t и x — и отгрызает с конца всё, что попадает в этот набор, пока не встретит символ вне набора. Имя report заканчивается на t, эта буква тоже в наборе, поэтому она уходит следом за расширением. Порядок символов в аргументе не важен вообще: rstrip(".txt") и rstrip("xt.") работают одинаково.
name = "report.txt"
print(name.rstrip(".txt"))
repor
Почему rstrip удаляет лишние символы: разбор по шагам
| Шаг |
Строка |
Последний символ |
Есть в наборе ., t, x? |
| 1 |
report.txt |
t |
да, отрезаем |
| 2 |
report.tx |
x |
да, отрезаем |
| 3 |
report.t |
t |
да, отрезаем |
| 4 |
report. |
. |
да, отрезаем |
| 5 |
report |
t |
да, отрезаем |
| 6 |
repor |
r |
нет — стоп |
Самое неприятное здесь то, что иногда rstrip выдаёт правильный ответ — и баг живёт в коде месяцами, пока не попадётся «неудачное» имя:
files = ["report.txt", "test.txt", "photo.txt", "notes.txt"]
print([f.rstrip(".txt") for f in files])
['repor', 'tes', 'photo', 'notes']
| Имя файла |
Что вернул rstrip(".txt") |
Что ожидали |
Совпало? |
report.txt |
repor |
report |
нет |
test.txt |
tes |
test |
нет |
photo.txt |
photo |
photo |
да, случайно |
notes.txt |
notes |
notes |
да, случайно |
Кириллица маскирует проблему ещё лучше: "отчёт.txt".rstrip(".txt") вернёт отчёт, потому что русская т — это другой символ, не латинская t. На русских именах код выглядит рабочим, а на первом же английском ломается.
Как в Python убрать расширение файла из строки методом removesuffix()
str.removesuffix() появился в Python 3.9 и делает ровно то, что от него ждут: если строка заканчивается указанным окончанием, метод отрезает его целиком; если нет — возвращает строку нетронутой. Пример на Python 3.12:
print("report.txt".removesuffix(".txt"))
print("test.txt".removesuffix(".txt"))
print("report.csv".removesuffix(".txt"))
report
test
report.csv
Посмотри на третью строку: report.csv остался как есть, потому что окончание не совпало. Это и есть правильное поведение для задачи «удалить подстроку с конца строки» — метод либо снимает точный суффикс, либо честно ничего не делает. У него есть зеркальный брат removeprefix().
Один нюанс: метод регистрозависимый.
name = "IMG_2026.JPG"
print(name.removesuffix(".jpg"))
if name.lower().endswith(".jpg"):
name = name[:-len(".jpg")]
print(name)
IMG_2026.JPG
IMG_2026
На Python 3.8 и старше метода просто нет, и вместо результата будет вот такая строка — тогда бери os.path.splitext() или связку endswith() со срезом, как выше:
AttributeError: 'str' object has no attribute 'removesuffix'
Оба метода разобраны в карточке справочника «Убрать точный префикс или суффикс». Соседний приём — отрезать хвост по разделителю — тренируется в бесплатной задаче «Ссылка без якоря».
os.path.splitext(): имя и расширение по отдельности
Когда нужны обе части — и имя, и расширение — берётся os.path.splitext(). Он режет по последней точке и всегда возвращает кортеж из двух строк. Вот пример:
import os
print(os.path.splitext("report.txt"))
base, ext = os.path.splitext("prices_2026.csv")
print(base, ext)
('report', '.txt')
prices_2026 .csv
Важная деталь: splitext смотрит на имя файла, а не на весь путь, поэтому точки в названиях папок его не сбивают.
import os
print(os.path.splitext("data/2026.otchet/notes"))
('data/2026.otchet/notes', '')
Если из полного пути нужно только имя без расширения, добавь os.path.basename():
import os
path = "/home/anna/docs/report.txt"
print(os.path.basename(path))
print(os.path.splitext(os.path.basename(path))[0])
report.txt
report
pathlib: Path("report.txt").stem и with_suffix()
Если в коде уже есть пути, короче всего pathlib: .stem — это имя файла без расширения, .suffix — расширение, .name — имя целиком, а with_suffix() меняет расширение на другое, не клея строки руками.
from pathlib import Path
p = Path("/home/anna/docs/report.txt")
print(p.name)
print(p.stem)
print(p.suffix)
print(p.with_suffix(".csv"))
report.txt
report
.txt
/home/anna/docs/report.csv
Обрати внимание: stem вернул только имя, без папок. Если результат дальше пойдёт в open(), тебе нужен не stem, а p.with_suffix("") — он сохраняет весь путь. Подробнее про объекты путей — в справочнике: Path objects.
Что выбрать: срез, removesuffix, splitext или pathlib?
Критерий простой. Знаешь расширение заранее — removesuffix. Нужны обе части — splitext. Уже работаешь с путями — Path.stem. Расширение неизвестно, а тянуть модули не хочется — rsplit(".", 1)[0]. Жёсткий срез вроде name[:-4] оставь для случаев, когда длина расширения гарантирована и не изменится.
| Способ |
Что делает |
Когда брать |
name.removesuffix(".txt") |
снимает точное окончание или ничего |
суффикс известен, Python 3.9+ |
os.path.splitext(name) |
отдаёт ('report', '.txt') |
нужны обе части сразу |
Path(name).stem |
отдаёт 'report' |
в коде уже есть pathlib |
name.rsplit(".", 1)[0] |
режет по последней точке |
расширение заранее неизвестно |
name[:-4] |
режет ровно 4 символа |
длина расширения фиксирована |
name.rstrip(".txt") |
грызёт набор символов |
никогда для этой задачи |
Про срез стоит сказать отдельно — он ломается тише всех:
print("report.txt"[:-4])
print("photo.jpeg"[:-4])
report
photo.
У .jpeg пять символов, а срез отрезал четыре — в имени осталась точка. Срезы вообще полезно разобрать целиком: «Срезы строк».
А rsplit(".", 1)[0] — безопасный однострочник: он режет по последней точке и не падает, если точки нет вовсе. Не путай его с split(".")[0], который режет по первой:
print("prices.2026.csv".rsplit(".", 1)[0])
print("README".rsplit(".", 1)[0])
print("prices.2026.csv".split(".")[0])
prices.2026
README
prices
Что вернёт splitext() для файла archive.tar.gz?
Вернёт ('archive.tar', '.gz'), и это не баг. Двойное расширение — это соглашение между людьми, а не правило файловой системы: для Python расширение начинается после последней точки, поэтому .tar остаётся частью имени. По той же причине Path("archive.tar.gz").stem даёт archive.tar. Если нужно снять оба куска сразу, назови их явно через removesuffix(".tar.gz") или посмотри на список Path(...).suffixes.
import os
from pathlib import Path
print(os.path.splitext("archive.tar.gz"))
print(Path("archive.tar.gz").stem)
print(Path("archive.tar.gz").suffixes)
print("archive.tar.gz".removesuffix(".tar.gz"))
('archive.tar', '.gz')
archive.tar
['.tar', '.gz']
archive
Работает и цепочка, если хвосты нужно снимать по одному:
print("report.tar.gz".removesuffix(".gz").removesuffix(".tar"))
report
Файл без расширения и имя, которое начинается с точки
.gitignore, .env, README — это имена целиком, расширения у них нет. И splitext, и pathlib считают ведущую точку частью имени, а не разделителем, и оба возвращают пустое расширение.
import os
from pathlib import Path
print(os.path.splitext(".gitignore"))
print(repr(Path(".gitignore").stem))
print(repr(Path(".gitignore").suffix))
print(os.path.splitext("README"))
('.gitignore', '')
'.gitignore'
''
('README', '')
А вот самописный ".gitignore".split(".")[0] вернёт пустую строку — и это уже настоящая потеря данных, которую легко не заметить в цикле по сотне файлов.
Как убрать перенос строки \n в конце, не задев данные?
Для одного гарантированного переноса в конце годятся оба варианта — и rstrip("\n"), и removesuffix("\n"), — но ведут они себя по-разному, когда переносов два: rstrip снимет оба, removesuffix — ровно один. Здесь rstrip не опасен, потому что в наборе всего один символ и внутри данных он не встречается. Беда начинается ровно тогда, когда в аргумент strip-семейства попадает больше одного символа, как в ".txt".
line = "report.txt\n"
print(repr(line.rstrip("\n")))
print(repr(line.removesuffix("\n")))
print(repr("100\n\n".rstrip("\n")))
print(repr("100\n\n".removesuffix("\n")))
'report.txt'
'report.txt'
'100'
'100\n'
Заодно про соседний миф: сам по себе хвостовой \n разбор числа не ломает. float() отбрасывает пробельные символы по краям строки, поэтому float("77.59\n") спокойно вернёт 77.59. Строку "77,59\n" в число не превращает запятая, а не перенос. Все настоящие причины этой ошибки разобраны отдельно — could not convert string to float. Аккуратная чистка пробелов тренируется в бесплатной задаче «Аккуратные пробелы».
Частые ошибки
rstrip(".txt") вместо removesuffix(".txt"). Классика: rstrip удалил буквы из имени файла, и в логах вместо report лежит repor. Фикс — removesuffix(".txt"), а если Python старше 3.9, то os.path.splitext(name)[0].
split(".")[0] вместо rsplit(".", 1)[0]. На prices.2026.csv первый вариант отдаст prices и потеряет год, а на .gitignore — пустую строку.
- Жёсткий срез
name[:-4]. Работает на .txt и .csv, ломается на .jpeg, .xlsx и файлах без расширения. Если очень нужен срез, считай длину: name[:-len(ext)].
- Забытый регистр.
"IMG_2026.JPG".removesuffix(".jpg") вернёт строку без изменений. Сравнивай через name.lower().endswith(".jpg").
- Ожидание, что
splitext снимет .tar.gz целиком. Не снимет — вернёт ('archive.tar', '.gz'). Для двойных расширений пиши суффикс явно.
- Путаница
stem и with_suffix(""). Path("/home/anna/docs/report.txt").stem — это report без папок; если строка дальше используется как путь, нужен with_suffix("").
- Чтение списка имён из файла без
encoding="utf-8". Имена приедут кракозябрами, и любые срезы будут работать не по тем символам. Разбор — в статье про кракозябры и UnicodeDecodeError.
Практика: почисти список имён файлов из выгрузки
Собери всё вместе — список имён из выгрузки чистится одной строкой:
from pathlib import Path
files = ["report.txt", "prices_2026.csv", "archive.tar.gz", ".gitignore", "README"]
print([Path(f).stem for f in files])
['report', 'prices_2026', 'archive.tar', '.gitignore', 'README']
Прочитай вывод внимательно: archive.tar и .gitignore — это не ошибки, а ровно то поведение, которое мы разобрали выше. Если хочется закрепить строковые методы руками и с проверкой, начни с бесплатных задач по строкам — код запускается прямо в браузере.
Мини-резюме
rstrip(".txt") снимает набор символов ., t, x, а не окончание, поэтому report.txt превращается в repor.
- Иногда
rstrip угадывает верный ответ (photo.txt, отчёт.txt) — из-за этого баг долго остаётся незамеченным.
- Расширение известно заранее —
name.removesuffix(".txt"), Python 3.9 и новее.
- Нужны и имя, и расширение —
os.path.splitext(name); работаешь с путями — Path(name).stem.
- Расширение неизвестно —
name.rsplit(".", 1)[0]; split(".")[0] для этого не годится.
archive.tar.gz даёт ('archive.tar', '.gz'), у .gitignore расширения нет — оба случая нормальны.
- Заменить расширение —
Path(name).with_suffix(".csv"), а не склейка строк.
В Python убрать расширение файла из строки надёжнее всего методом
removesuffix(".txt"): он снимает ровно это окончание и ничего больше. Если расширение заранее неизвестно — бериos.path.splitext(name)[0]илиPath(name).stem, а вотrstrip(".txt")для этой задачи не годится никогда: он молча съедает лишние буквы, иreport.txtпревращается вrepor.print("report.txt".removesuffix(".txt")) print("report.txt".rstrip(".txt"))Ниже — почему так происходит, чем отличаются четыре правильных способа и что делать с
archive.tar.gz,.gitignoreи файлами вообще без расширения.Почему
"report.txt".rstrip(".txt")возвращаетrepor?Потому что
rstripпринимает не окончание строки, а набор символов. Аргумент".txt"он разбирает на три разных символа — точку,tиx— и отгрызает с конца всё, что попадает в этот набор, пока не встретит символ вне набора. Имяreportзаканчивается наt, эта буква тоже в наборе, поэтому она уходит следом за расширением. Порядок символов в аргументе не важен вообще:rstrip(".txt")иrstrip("xt.")работают одинаково.name = "report.txt" print(name.rstrip(".txt"))Почему rstrip удаляет лишние символы: разбор по шагам
.,t,x?report.txttreport.txxreport.ttreport..reporttreporrСамое неприятное здесь то, что иногда
rstripвыдаёт правильный ответ — и баг живёт в коде месяцами, пока не попадётся «неудачное» имя:files = ["report.txt", "test.txt", "photo.txt", "notes.txt"] print([f.rstrip(".txt") for f in files])rstrip(".txt")report.txtreporreporttest.txttestestphoto.txtphotophotonotes.txtnotesnotesКириллица маскирует проблему ещё лучше:
"отчёт.txt".rstrip(".txt")вернётотчёт, потому что русскаят— это другой символ, не латинскаяt. На русских именах код выглядит рабочим, а на первом же английском ломается.Как в Python убрать расширение файла из строки методом
removesuffix()str.removesuffix()появился в Python 3.9 и делает ровно то, что от него ждут: если строка заканчивается указанным окончанием, метод отрезает его целиком; если нет — возвращает строку нетронутой. Пример на Python 3.12:print("report.txt".removesuffix(".txt")) print("test.txt".removesuffix(".txt")) print("report.csv".removesuffix(".txt"))Посмотри на третью строку:
report.csvостался как есть, потому что окончание не совпало. Это и есть правильное поведение для задачи «удалить подстроку с конца строки» — метод либо снимает точный суффикс, либо честно ничего не делает. У него есть зеркальный братremoveprefix().Один нюанс: метод регистрозависимый.
name = "IMG_2026.JPG" print(name.removesuffix(".jpg")) if name.lower().endswith(".jpg"): name = name[:-len(".jpg")] print(name)На Python 3.8 и старше метода просто нет, и вместо результата будет вот такая строка — тогда бери
os.path.splitext()или связкуendswith()со срезом, как выше:Оба метода разобраны в карточке справочника «Убрать точный префикс или суффикс». Соседний приём — отрезать хвост по разделителю — тренируется в бесплатной задаче «Ссылка без якоря».
os.path.splitext(): имя и расширение по отдельностиКогда нужны обе части — и имя, и расширение — берётся
os.path.splitext(). Он режет по последней точке и всегда возвращает кортеж из двух строк. Вот пример:import os print(os.path.splitext("report.txt")) base, ext = os.path.splitext("prices_2026.csv") print(base, ext)Важная деталь:
splitextсмотрит на имя файла, а не на весь путь, поэтому точки в названиях папок его не сбивают.import os print(os.path.splitext("data/2026.otchet/notes"))Если из полного пути нужно только имя без расширения, добавь
os.path.basename():import os path = "/home/anna/docs/report.txt" print(os.path.basename(path)) print(os.path.splitext(os.path.basename(path))[0])pathlib:Path("report.txt").stemиwith_suffix()Если в коде уже есть пути, короче всего
pathlib:.stem— это имя файла без расширения,.suffix— расширение,.name— имя целиком, аwith_suffix()меняет расширение на другое, не клея строки руками.from pathlib import Path p = Path("/home/anna/docs/report.txt") print(p.name) print(p.stem) print(p.suffix) print(p.with_suffix(".csv"))Обрати внимание:
stemвернул только имя, без папок. Если результат дальше пойдёт вopen(), тебе нужен неstem, аp.with_suffix("")— он сохраняет весь путь. Подробнее про объекты путей — в справочнике: Path objects.Что выбрать: срез, removesuffix, splitext или pathlib?
Критерий простой. Знаешь расширение заранее —
removesuffix. Нужны обе части —splitext. Уже работаешь с путями —Path.stem. Расширение неизвестно, а тянуть модули не хочется —rsplit(".", 1)[0]. Жёсткий срез вродеname[:-4]оставь для случаев, когда длина расширения гарантирована и не изменится.name.removesuffix(".txt")os.path.splitext(name)('report', '.txt')Path(name).stem'report'pathlibname.rsplit(".", 1)[0]name[:-4]name.rstrip(".txt")Про срез стоит сказать отдельно — он ломается тише всех:
print("report.txt"[:-4]) print("photo.jpeg"[:-4])У
.jpegпять символов, а срез отрезал четыре — в имени осталась точка. Срезы вообще полезно разобрать целиком: «Срезы строк».А
rsplit(".", 1)[0]— безопасный однострочник: он режет по последней точке и не падает, если точки нет вовсе. Не путай его сsplit(".")[0], который режет по первой:print("prices.2026.csv".rsplit(".", 1)[0]) print("README".rsplit(".", 1)[0]) print("prices.2026.csv".split(".")[0])Что вернёт
splitext()для файлаarchive.tar.gz?Вернёт
('archive.tar', '.gz'), и это не баг. Двойное расширение — это соглашение между людьми, а не правило файловой системы: для Python расширение начинается после последней точки, поэтому.tarостаётся частью имени. По той же причинеPath("archive.tar.gz").stemдаётarchive.tar. Если нужно снять оба куска сразу, назови их явно черезremovesuffix(".tar.gz")или посмотри на списокPath(...).suffixes.import os from pathlib import Path print(os.path.splitext("archive.tar.gz")) print(Path("archive.tar.gz").stem) print(Path("archive.tar.gz").suffixes) print("archive.tar.gz".removesuffix(".tar.gz"))Работает и цепочка, если хвосты нужно снимать по одному:
print("report.tar.gz".removesuffix(".gz").removesuffix(".tar"))Файл без расширения и имя, которое начинается с точки
.gitignore,.env,README— это имена целиком, расширения у них нет. Иsplitext, иpathlibсчитают ведущую точку частью имени, а не разделителем, и оба возвращают пустое расширение.import os from pathlib import Path print(os.path.splitext(".gitignore")) print(repr(Path(".gitignore").stem)) print(repr(Path(".gitignore").suffix)) print(os.path.splitext("README"))А вот самописный
".gitignore".split(".")[0]вернёт пустую строку — и это уже настоящая потеря данных, которую легко не заметить в цикле по сотне файлов.Как убрать перенос строки
\nв конце, не задев данные?Для одного гарантированного переноса в конце годятся оба варианта — и
rstrip("\n"), иremovesuffix("\n"), — но ведут они себя по-разному, когда переносов два:rstripснимет оба,removesuffix— ровно один. Здесьrstripне опасен, потому что в наборе всего один символ и внутри данных он не встречается. Беда начинается ровно тогда, когда в аргументstrip-семейства попадает больше одного символа, как в".txt".line = "report.txt\n" print(repr(line.rstrip("\n"))) print(repr(line.removesuffix("\n"))) print(repr("100\n\n".rstrip("\n"))) print(repr("100\n\n".removesuffix("\n")))Заодно про соседний миф: сам по себе хвостовой
\nразбор числа не ломает.float()отбрасывает пробельные символы по краям строки, поэтомуfloat("77.59\n")спокойно вернёт77.59. Строку"77,59\n"в число не превращает запятая, а не перенос. Все настоящие причины этой ошибки разобраны отдельно — could not convert string to float. Аккуратная чистка пробелов тренируется в бесплатной задаче «Аккуратные пробелы».Частые ошибки
rstrip(".txt")вместоremovesuffix(".txt"). Классика: rstrip удалил буквы из имени файла, и в логах вместоreportлежитrepor. Фикс —removesuffix(".txt"), а если Python старше 3.9, тоos.path.splitext(name)[0].split(".")[0]вместоrsplit(".", 1)[0]. Наprices.2026.csvпервый вариант отдастpricesи потеряет год, а на.gitignore— пустую строку.name[:-4]. Работает на.txtи.csv, ломается на.jpeg,.xlsxи файлах без расширения. Если очень нужен срез, считай длину:name[:-len(ext)]."IMG_2026.JPG".removesuffix(".jpg")вернёт строку без изменений. Сравнивай черезname.lower().endswith(".jpg").splitextснимет.tar.gzцеликом. Не снимет — вернёт('archive.tar', '.gz'). Для двойных расширений пиши суффикс явно.stemиwith_suffix("").Path("/home/anna/docs/report.txt").stem— этоreportбез папок; если строка дальше используется как путь, нуженwith_suffix("").encoding="utf-8". Имена приедут кракозябрами, и любые срезы будут работать не по тем символам. Разбор — в статье про кракозябры и UnicodeDecodeError.Практика: почисти список имён файлов из выгрузки
Собери всё вместе — список имён из выгрузки чистится одной строкой:
from pathlib import Path files = ["report.txt", "prices_2026.csv", "archive.tar.gz", ".gitignore", "README"] print([Path(f).stem for f in files])Прочитай вывод внимательно:
archive.tarи.gitignore— это не ошибки, а ровно то поведение, которое мы разобрали выше. Если хочется закрепить строковые методы руками и с проверкой, начни с бесплатных задач по строкам — код запускается прямо в браузере.Мини-резюме
rstrip(".txt")снимает набор символов.,t,x, а не окончание, поэтомуreport.txtпревращается вrepor.rstripугадывает верный ответ (photo.txt,отчёт.txt) — из-за этого баг долго остаётся незамеченным.name.removesuffix(".txt"), Python 3.9 и новее.os.path.splitext(name); работаешь с путями —Path(name).stem.name.rsplit(".", 1)[0];split(".")[0]для этого не годится.archive.tar.gzдаёт('archive.tar', '.gz'), у.gitignoreрасширения нет — оба случая нормальны.Path(name).with_suffix(".csv"), а не склейка строк.