user12205982

user12205982

Я Сергей Смирнов. Живу и путешествую по Азии: Бали, Таиланд, Вьетнам. Снимаю влоги о жизни за границей и делюсь опытом применения ИИ в работе. Мои истории: Telegram — https://t.me/+lK9JzeZRRLo0MWRi, YouTube — https://www.youtube.com/@1smirnovsergey, Instagram — https://www.instagram.com/1smirnovsergey/
На Пикабу
в топе авторов на 542 месте
рейтинг 0 подписчиков 0 подписок 52 поста 0 в горячем
0

Хотели убрать ID-, а потеряли букву D: почему strip меняет код целиком⁠⁠

Есть учебная выгрузка с обозначениями ID-D42, ID-42 и D42. Нужно снять ровно начальную метку ID-, если она есть. Ожидаемые результаты — D42, 42 и D42.

На первый взгляд для этого подходит строка:

code = "ID-D42"

print(code.strip("ID-"))

Но программа печатает 42. Буква D, которая была частью самого обозначения, исчезла вместе с меткой. Исключения нет, результат выглядит правдоподобно. Такую ошибку легко пропустить, если проверять только пример ID-42.

Что попросили у Python

У strip аргумент задаёт набор отдельных символов, которые можно снимать с обоих краёв строки. Здесь это I, D и дефис. Метод снимает их, пока не встретит другой символ. После начального ID- он видит ещё одну D и снимает её тоже. Похожий lstrip работает только слева, но смысл аргумента остаётся тем же: набор символов, а не целая метка. Это поведение описано в документации строк Python: https://docs.python.org/3/library/stdtypes.html#str.strip.

Чтобы удалить один точный префикс, подходит removeprefix, доступный с Python 3.9. Описание метода: https://docs.python.org/3/library/stdtypes.html#str.removepr....

for code in ["ID-D42", "ID-42", "D42", "42-ID"]:

result = code.removeprefix("ID-")

print(repr(code), "->", repr(result))

Результат:

'ID-D42' -> 'D42'

'ID-42' -> '42'

'D42' -> 'D42'

'42-ID' -> '42-ID'

Теперь обозначение без начальной метки сохраняется. Конечное -ID тоже не трогаем: задача касалась только начала.

Проверка, которая отличает две операции

Для такой очистки полезны не десять похожих примеров, а несколько разных условий:

- Метка есть, а следующий символ совпадает с одной из её букв: ID-D42.

- Метки нет, но строка начинается с D: D42.

- Похожая метка стоит в конце: 42-ID.

- Метка повторяется: ID-ID-42. Одно применение removeprefix оставит ID-42.

- Строка пустая: результат тоже пустой.

Последние два случая заставляют уточнить требование. Нужно удалить одну метку или все подряд? Пустой результат допустим или это ошибка входных данных? Сам метод этого не решает.

Если в исходной выгрузке встречаются пробелы, другой регистр или кириллические буквы, это отдельные условия. Не стоит незаметно добавлять к исправлению ещё несколько преобразований: можно изменить значимый код. Сначала сохраняем исходный столбец и проверяем результат на небольшой копии данных.

В примере все обозначения придуманы. Текст подготовлен с помощью ИИ; код выполнен локально, результаты сверены. Это разбор поведения Python, без заявления о реальном сбое у клиента.

Показать полностью
0

Задача длилась 26 часов, а отчёт показал два: где потерялись сутки⁠⁠

В учебном отчёте есть длительность обработки — 26 часов. Чтобы показать её в часах, скрипт берёт duration.seconds / 3600 и получает 2. Вычисление выполняется без ошибки, но целые сутки в него не попали.

Это локальный пример, подготовленный с помощью ИИ. Здесь нет измерений рабочего сервиса и нет вывода о скорости какого-либо продукта.

from datetime import timedelta

duration = timedelta(hours=26)

print(duration.days)

print(duration.seconds)

print(duration.seconds / 3600)

print(duration.total_seconds() / 3600)

Получим четыре строки: 1, 7200, 2.0, 26.0. Поле seconds — секундная часть нормализованной длительности. Полная длительность включает ещё дни и микросекунды. Метод total_seconds() учитывает их вместе. Это различие прямо отмечено в документации timedelta: https://docs.python.org/3/library/datetime.html#datetime.tim....

Почему ошибка долго остаётся незаметной? На длительности в десять минут оба варианта дают одинаковый ответ. Набор коротких тестов подтверждает только короткие интервалы. Добавление ещё одного примера на полчаса ничего не проверит за пределами суток.

Нужны значения по обе стороны границы. Для 23 часов ожидается 23, для 24 — 24, для 26 — 26. Отдельно стоит решить, может ли вход быть отрицательным. Например, если из конца ошибочно вычли начало в обратном порядке, результат не должен выглядеть как почти полный положительный день.

for hours in (23, 24, 26, -1):

delta = timedelta(hours=hours)

print(hours, delta.seconds / 3600, delta.total_seconds() / 3600)

Последняя строка — -1 23.0 -1.0. Это следствие представления отрицательной длительности через дни и секундный остаток. Замена неправильного расчёта на правильный не решает, что делать с отрицательным входом: для одного отчёта это допустимое смещение, для другого — ошибка исходных данных.

Не стоит сразу превращать результат в int. Для 90 минут получится 1.5 часа; отбрасывание дроби превратит их в один час. Сначала определяют требуемую величину, затем отдельно правило округления для отображения.

Пример касается уже заданной длительности. Он не разбирает часовые пояса, перевод часов, календарные месяцы или измерение времени выполнения программы. Эти задачи требуют своих правил.

В проверке отчёта полезно хранить исходный интервал рядом с отображаемым числом. Если после замены формулы сутки появились, убедись, что следующая стадия форматирования не теряет их снова: часы длительности могут быть больше 23.

Показать полностью
2

Две строки с одинаковой датой: почему сортировке нужен понятный второй ключ⁠⁠

Для списка событий требуется сначала показать новые записи. Помощник добавляет ORDER BY created_at DESC, а затем LIMIT. На первый взгляд задача решена. Но если у нескольких событий одна дата, какой из них должен попасть первым?

Это учебный пример на вымышленных записях, подготовленный с помощью ИИ. Он выполнен в отдельной базе SQLite в памяти. Реальные журналы и персональные данные не используются.

Записываем ожидаемый порядок

import sqlite3

with sqlite3.connect(":memory:") as db:

db.execute("CREATE TABLE events(id INTEGER PRIMARY KEY, created_at TEXT)")

db.executemany("INSERT INTO events VALUES (?, ?)", [

(1, "2026-10-01T10:00:00"),

(2, "2026-10-01T10:00:00"),

(3, "2026-10-02T10:00:00"),

])

rows = db.execute(

"SELECT id FROM events ORDER BY created_at DESC, id DESC LIMIT 3"

).fetchall()

print(rows)

Результат:

[(3,), (2,), (1,)]

Мы явно договорились: при одинаковом времени первым идёт больший идентификатор. Уникальный id завершает порядок для этого набора. Это выбранное правило интерфейса, а не утверждение, что больший id всегда означает более позднее реальное событие.

Документация SQLite (https://www.sqlite.org/lang_select.html#orderby) не обещает определённый порядок строк, у которых равны все выражения ORDER BY. Если повторный запуск случайно показывает тот же порядок, это ещё не превращает наблюдение в гарантию.

Отдельная проблема — меняющиеся данные

Второй ключ помогает однозначно упорядочить текущий набор. Он не гарантирует отсутствие пропусков при перелистывании через OFFSET, если между запросами появляются или исчезают записи.

Для такого списка сначала выбирают требуемое поведение: нужен неизменный снимок, живая лента или продолжение после конкретной пары ключей. Это разные задачи. Не стоит обещать «исправленную пагинацию» после одного добавленного id.

В нашем примере все даты записаны в одном формате и предполагают одну временную шкалу. Разные часовые пояса и форматы нельзя незаметно смешивать в строковой сортировке.

Небольшая проверка перед выпуском

Создайте несколько строк с одинаковым основным ключом. Заранее задайте, какая должна идти первой. Проверьте результат целиком и на границе LIMIT. Затем отдельно рассмотрите вставку новой записи между запросами страниц.

Полезный вопрос к предложенному ИИ запросу: «Каким правилом разрешаются равные значения?» Если ответ — «база обычно возвращает так», правило пока не определено.

Показать полностью

Скрипт упал при сохранении отчёта. Почему старый файл тоже пропал⁠⁠

Есть небольшой отчёт report.txt. Скрипт должен пересчитать его и сохранить новую версию. Если открыть итоговый файл с режимом w, прежнее содержимое стирается ещё до того, как новый отчёт готов. Ошибка посередине записи оставит обрывок.

Проверим это в отдельной временной папке. Все данные учебные; материал подготовлен с помощью ИИ. Пример не использует рабочие файлы.

from pathlib import Path

from tempfile import TemporaryDirectory

import os

with TemporaryDirectory() as folder:

root = Path(folder)

direct = root / "direct.txt"

direct.write_text("old complete report", encoding="utf-8")

try:

with direct.open("w", encoding="utf-8") as f:

f.write("half a report")

raise RuntimeError("generation failed")

except RuntimeError:

pass # только для демонстрации: продолжаем сравнение

print("direct:", direct.read_text(encoding="utf-8"))

target = root / "report.txt"

pending = root / "report.pending"

target.write_text("old complete report", encoding="utf-8")

try:

pending.write_text("half a report", encoding="utf-8")

raise RuntimeError("generation failed")

except RuntimeError:

pending.unlink() # убираем только свой незавершённый файл

print("before replace:", target.read_text(encoding="utf-8"))

pending.write_text("new complete report", encoding="utf-8")

assert pending.read_text(encoding="utf-8") == "new complete report"

os.replace(pending, target)

print("after replace:", target.read_text(encoding="utf-8"))

Получится:

direct: half a report

before replace: old complete report

after replace: new complete report

В первом случае итоговый путь существует, но полный отчёт потерян. Во втором ошибка возникает до замены: читатель по итоговому пути по-прежнему получает старую версию. Новый файл попадает на этот путь только после подготовки и проверки.

Вместо сравнения с заранее известной строкой реальный отчёт потребует своей проверки: обязательных столбцов, числа строк или контрольной суммы выгрузки. Сам факт успешной записи не проверяет содержание.

os.replace заменяет существующий файл назначения. Успешное переименование атомарно; между разными файловыми системами операция может завершиться ошибкой. Поэтому здесь оба файла лежат в одной папке. Это поведение описано в документации Python: https://docs.python.org/3/library/os.html#os.replace.

У примера один писатель и отдельная папка. Для нескольких процессов фиксированное имя report.pending не годится: нужны уникальные временные файлы и согласование изменений. Иначе последняя запись может затереть чужую версию.

Атомарная замена также не равна гарантии сохранности при отключении питания. Здесь не проверяются сброс данных на диск и восстановление после сбоя ОС. Резервная копия тоже не создаётся: прежнее содержимое после успешной замены отдельно не хранится.

Для проверки своего скрипта полезны оба исхода: ошибка до замены должна оставлять старый результат целым, успешная замена — давать полный новый. Одного exists() для этого недостаточно.

Показать полностью

В настройке написано false, а Python включает режим. Разбираем четыре значения⁠⁠

Скрипт получает настройку как текст. В ней написано false. Кажется естественным вызвать bool(value) и получить выключенный режим. Но для непустой строки результат будет True — даже если внутри написано false или 0.

Это учебный разбор с помощью ИИ. Он не меняет настройки приложения; все примеры выполняются с локальными переменными.

for value in ('false', '0', '', False):

print(repr(value), '->', bool(value))

Результат:

'false' -> True

'0' -> True

'' -> False

False -> False

Кавычки здесь существенны. 'false' — строка, False — логическое значение. В правилах проверки истинности Python: https://docs.python.org/3/library/stdtypes.html#truth-value-... пустая строка относится к ложным значениям. Непустая строка истинна; её содержимое bool не переводит.

Для текстового формата нужен разбор допустимых слов. В этом примере разрешим только true/false, любой регистр и пробелы по краям. Неизвестное слово не будем молча превращать в выключенный режим.

def parse_flag(value):

if not isinstance(value, str):

raise TypeError('Ожидается строка')

allowed = {'true': True, 'false': False}

normalized = value.strip().lower()

if normalized not in allowed:

raise ValueError('Допустимы только true и false')

return allowed[normalized]

for value in (' FALSE ', 'true', 'flase', '', None):

try:

print(repr(value), '->', parse_flag(value))

except (TypeError, ValueError) as error:

print(repr(value), '->', type(error).__name__)

Получим:

' FALSE ' -> False

'true' -> True

'flase' -> ValueError

'' -> ValueError

None -> TypeError

Опечатка flase теперь видна сразу. Конструкция value == 'true' тоже выключила бы её, но тогда невозможно отличить неверное слово от корректного false.

Это не универсальный формат конфигурации. Если источник принимает yes/no или 1/0, добавлять нужно именно документированные варианты. Если он уже возвращает логический объект, повторно переводить его в строку ради этой функции не нужно.

Отсутствующее поле — ещё один отдельный случай. Для него можно заранее выбрать значение по умолчанию. Однако применять этот выбор к любому нераспознанному слову опасно для предсказуемости: опечатка начнёт управлять режимом так же, как сознательный выбор пользователя.

Проверку стоит делать на границе ввода. Дальше по программе передаётся уже True или False, а не строка, смысл которой каждый участок кода толкует заново.

Показать полностью
0

Два отчёта показывают среднее 100 и 0. Почему общий результат не 50⁠⁠

Представим две группы измерений задержки в миллисекундах. В первой одна запись: 100. Во второй девять записей: все по 0. Средние двух групп — 100 и 0. Их полусумма равна 50, а средняя задержка по десяти измерениям — 10.

Числа специально выбраны простыми. Это учебный пример, подготовленный с помощью ИИ, а не результаты работы сервиса или бизнеса.

Первый расчёт отвечает на вопрос «каково среднее из двух групповых показателей»: каждая группа получает половину веса. Во втором одинаковый вес получает каждое измерение. Подмена происходит в момент выбора того, что именно мы считаем.

from statistics import mean

a = [100]

b = [0] * 9

print("group averages:", mean([mean(a), mean(b)]))

print("all measurements:", mean(a + b))

print("from totals:", (sum(a) + sum(b)) / (len(a) + len(b)))

Вывод:

group averages: 50

all measurements: 10

from totals: 10.0

Если исходные строки передавать неудобно, в каждом отчёте можно сохранять две величины: сумму и количество. Здесь это (100, 1) и (0, 9). Общая сумма 100 делится на общее количество 10. Определение арифметического среднего приведено в документации statistics.mean: https://docs.python.org/3/library/statistics.html#statistics....

Точные средние вместе с размерами групп тоже позволяют вычислить взвешенный результат. Но уже округлённое среднее не всегда сохраняет нужную точность. Если первый отчёт отдаёт только «33», нельзя узнать, было до округления 32.6 или 33.4. Поэтому для дальнейшего объединения удобнее хранить суммы и количества, а округлять окончательное число на экране.

Есть три проверки, которые формула за нас не сделает:

- В обеих группах измеряется одна величина в одинаковых единицах. Миллисекунды и секунды сначала приводим к одному масштабу.

- Записи не пересекаются. Если один замер попал в два отчёта, сумма количеств посчитает его дважды.

- Пустая группа остаётся пустой. Она не превращается в одно измерение с нулём. Если измерений вообще нет, выводим отсутствие данных.

Полусумма групповых средних может быть именно тем, что нужно: например, когда группы по договорённости должны влиять одинаково независимо от размера. Ошибка — подписать её «среднее по всем измерениям».

Перед объединением готовых отчётов проверь, что у каждой строки со средним есть размер группы и понятная единица наблюдения. Эти два поля объясняют результат лучше, чем ещё один знак после запятой.

Показать полностью

SHA-256 совпал. Что это говорит о файле — и чего не доказывает⁠⁠

В отчёте помощника есть длинная строка SHA-256 и слово «проверено». Звучит убедительно, но важно уточнить: что именно сравнили? Контрольная сумма относится к байтам, а не к правдивости утверждений внутри документа.

Это учебный пример, подготовленный с помощью ИИ. Код выполнен локально, реальные файлы и конфиденциальные данные не использовались.

from hashlib import sha256

original = b"status=ready\n"

changed = b"status=ready"

print(sha256(original).hexdigest() == sha256(original).hexdigest())

print(sha256(original).hexdigest() == sha256(changed).hexdigest())

Результат:

True

False

Во второй строке сравниваются разные входы: в одном есть завершающий перенос строки, в другом его нет. Поэтому даже визуально похожий текст в этом примере даёт разные контрольные суммы. Как получить SHA-256 из байтов, показывает документация Python:

https://docs.python.org/3/library/hashlib.html

Практическая польза такой проверки — сверить текущий файл с ранее сохранённым эталоном. Но сначала нужно знать, откуда взялась эталонная сумма. Если кто-то может заменить и документ, и расположенную рядом контрольную сумму, простое совпадение не устанавливает надёжность источника.

Также совпадение с эталоном не отвечает на вопросы: верен ли расчёт, действительно ли автор согласовал текст и можно ли публиковать документ. Для них нужны отдельные сведения. Хеш заведомо неверной таблицы не превратит её в верную.

В журнале проверки полезно разделить поля: путь или ID файла, алгоритм, текущая сумма, эталонная сумма, источник эталона и результат сравнения. Отдельно записать, кто и когда проверял содержание. Так слово «проверено» перестаёт объединять разные действия.

Не стоит сначала незаметно менять текст — например, удалять все переносы — а потом утверждать, что сравнивались исходные файлы. Если проверяется нормализованное представление, правило нормализации должно быть явно записано. Это другая проверка.

В этой заметке нет обещания математически доказать равенство любых файлов по хешу. Пример показывает конкретное сравнение и предел его смысла. Какая формулировка полезнее в рабочем отчёте: «файл проверен» или «контрольная сумма совпала с таким-то эталоном»?

Показать полностью

Почему после сортировки 100 оказалось раньше 20: проверка кода от ИИ⁠⁠

Помощник написал сортировку выгрузки, но числа в ней хранятся текстом. Результат может выглядеть странно: 100 идёт раньше 20, а 3 оказывается в конце. Для строк это ожидаемый порядок.

Разберём маленький синтетический пример. Он подготовлен с помощью ИИ; приведённый код выполнен локально. Настоящих заказов или клиентских выгрузок здесь нет.

values = ["20", "3", "100"]

print(sorted(values))

print(sorted(values, key=int))

print(values)

Вывод:

['100', '20', '3']

['3', '20', '100']

['20', '3', '100']

Первый вызов сравнивает строки. Второй получает для сравнения целочисленный ключ. Сами элементы результата остаются строками, а исходный список не меняется: sorted возвращает новый список. Параметр key описан в официальном руководстве Python:

https://docs.python.org/3/howto/sorting.html

Прежде чем менять код, нужно решить, что означает колонка. Количество товаров обычно сравнивают как число. Номер документа, индекс или артикул может быть текстовым идентификатором. В таком поле ведущие нули бывают значимы. Преобразовать все поля выгрузки в int на всякий случай — плохая замена требованиям.

Для этого примера контракт простой: каждая строка содержит целое число. Если придёт «нет данных», преобразование завершится ошибкой. Такую запись нужно обработать по согласованному правилу: например, показать в списке отклонённых строк. Молча подставлять ноль нельзя — это будет уже другое значение.

Отдельно проверь форматы, которые действительно бывают в источнике. Строка с десятичной запятой, единицей измерения или разделителем тысяч не соответствует нашему простому контракту. Универсальную очистку всех символов легко написать, но вместе с символами можно потерять смысл.

Для короткой приёмки достаточно заранее задать ожидаемый порядок для 3, 20 и 100; затем добавить ведущие нули, пустую строку и недопустимое значение. Так проверяется не только удачный пример, но и граница возможностей функции.

Что у вас чаще приходит из выгрузок строкой: количество, цена или номер документа?

Показать полностью
Отличная работа, все прочитано!

Темы

Политика

Теги

Популярные авторы

Сообщества

18+

Теги

Популярные авторы

Сообщества

Игры

Теги

Популярные авторы

Сообщества

Юмор

Теги

Популярные авторы

Сообщества

Отношения

Теги

Популярные авторы

Сообщества

Здоровье

Теги

Популярные авторы

Сообщества

Путешествия

Теги

Популярные авторы

Сообщества

Спорт

Теги

Популярные авторы

Сообщества

Хобби

Теги

Популярные авторы

Сообщества

Сервис

Теги

Популярные авторы

Сообщества

Природа

Теги

Популярные авторы

Сообщества

Бизнес

Теги

Популярные авторы

Сообщества

Транспорт

Теги

Популярные авторы

Сообщества

Общение

Теги

Популярные авторы

Сообщества

Юриспруденция

Теги

Популярные авторы

Сообщества

Наука

Теги

Популярные авторы

Сообщества

IT

Теги

Популярные авторы

Сообщества

Животные

Теги

Популярные авторы

Сообщества

Кино и сериалы

Теги

Популярные авторы

Сообщества

Экономика

Теги

Популярные авторы

Сообщества

Кулинария

Теги

Популярные авторы

Сообщества

История

Теги

Популярные авторы

Сообщества

Недвижимость и ремонт

Теги

Популярные авторы

Сообщества