19637
Фундаментальные знания по основам программирования Разместить рекламу: @tproger_sales_bot Правила общения: https://tprg.ru/rules Другие каналы: @tproger_channels Сайт: https://tprg.ru/site Регистрация в перечне РКН: https://tprg.ru/zrgj
Почему текст прыгает при загрузке картинки
На видео из статьи web.dev текст под серой плашкой уезжает вниз, когда загружается фотография. Браузер не знал заранее, какую высоту оставить под изображение. Такой сдвиг особенно мешает, если вы уже начали читать или нажимать ссылку.
Для картинки 800 × 400 задайте <img src="photo.jpg" width="800" height="400" alt="...">. Верное соотношение сторон помогает зарезервировать место до загрузки. Чтобы картинка занимала всю ширину, добавьте отдельно img { width: 100% } и img { height: auto }.
Проверьте страницу с замедленной загрузкой и сравните с вариантом без размеров. Соотношение сторон можно задавать и через aspect-ratio. Как задать размеры до загрузки, разобрано в статье web.dev.
Что покажет git diff после git add
Добавили файл через git add, а потом ещё раз его исправили? В индексе, или staging area, остаётся подготовленная версия. В рабочей папке уже могут быть новые правки.
В новом учебном репозитории создайте a.txt со строкой one и выполните git add a.txt. Затем замените строку на two. Команда git diff -- a.txt покажет замену one на two: это разница между рабочей папкой и индексом.
А git diff --staged -- a.txt сравнит индекс с последним коммитом. До первого коммита она покажет добавленный файл со строкой one. Обе команды только показывают различия. Сверить режимы можно в документации Git.
Рамка помогает найти кнопку без мыши
Откройте страницу с двумя кнопками и нажимайте Tab. По рамке должно быть понятно, какая кнопка сработает при нажатии Enter. Если убрать outline без другого заметного индикатора, будет трудно понять, где находится фокус.
Попробуйте button:focus-visible { outline: 4px solid #2463eb }. Селектор срабатывает, когда элемент в фокусе и браузер считает индикатор нужным. Для кнопки переход клавишей Tab обычно показывает рамку. :focus выбирает элемент в фокусе независимо от этого решения.
Сравните клик мышью и Tab в своём браузере. У других элементов поведение может отличаться: текстовое поле может показывать индикатор и после клика. Поведение и примеры разбираются на MDN.
Поле с подсказками разрешает свой вариант
В HTML-поле можно предложить варианты без JavaScript. Атрибут list="cities" у input связывает его с datalist id="cities". Значения option становятся подсказками при вводе.
Добавьте два варианта, Moscow и Kazan, как на карточке. Затем введите Perm. Поле примет и этот текст: список предлагает значения, но сам по себе не запрещает другие. Если нужен строго заданный набор, это уже другая задача.
Проверьте пример в нужных браузерах. Вид подсказок и поддержка различаются, есть ограничения для экранных дикторов и масштабирования. MDN описывает эти ограничения. Для реальной формы одной проверки внешнего вида недостаточно.
Символы + и & внутри поискового запроса
Хотите передать в адресе строку C++ & Python? Если просто приписать её к ?q=, специальные символы могут изменить смысл параметров. URLSearchParams собирает строку параметров с нужным кодированием.
Создайте const params = new URLSearchParams({ q: "C++ & Python" }) и выведите params.toString(). Получится q=C%2B%2B+%26+Python: исходный плюс записан как %2B, пробел как +.
Передавайте исходный текст, без предварительного кодирования. Вызов new URLSearchParams(params.toString()).get("q") вернёт исходную строку. Конструктору нужен набор параметров, а не полный адрес. Примеры — в документации MDN.
Один знак в Bash решает, дописать файл или стереть старый текст
Знак > отправляет обычный вывод команды в файл. Если файла нет, Bash создаст его, а существующий обнулит перед записью. У >> другое поведение: добавить в конец. Проверьте на новом файле notes.txt в отдельной учебной папке.
1️⃣ Запишите первую строку, затем добавьте вторую:
printf 'first\n' > notes.txt
printf 'second\n' >> notes.txt
cat notes.txt
cat покажет first и second на отдельных строках. Сами команды printf отправили текст в файл, поэтому в терминале он появился только после cat.printf 'third\n' > notes.txt
cat notes.txt
third. Это пример для Bash с выключенной защитой noclobber: при включённой защите перезапись существующего обычного файла через > завершится ошибкой. Обе операции описаны в руководстве Bash.
Читать полностью…
Три строки в таблице, но COUNT(email) возвращает два
У одного пользователя есть адрес, у второго — NULL, то есть отсутствие значения, у третьего — пустая строка. Запустите этот запрос в SQLite. WITH здесь задаёт учебный набор строк для одного запроса:
WITH users(email) AS (
VALUES ('a@example.test'), (NULL), ('')
)
SELECT COUNT(*) AS rows_total,
COUNT(email) AS emails_present
FROM users
3 и 2. По правилам COUNT в SQLite, COUNT(*) считает все строки, а COUNT(email) — только те, где значение не NULL. Пустая строка в SQLite считается значением, поэтому она тоже попала в счётчик. Это ещё не количество корректных почтовых адресов.('') на (NULL): строк по-прежнему три, но второй счётчик станет равен одному. А если добавить в конец запроса WHERE 1 = 0, выборка будет пустой и оба счётчика вернут ноль.
Читать полностью…
Раскрывающийся ответ на HTML без JavaScript
Для вопроса с ответом, который появляется по нажатию, достаточно двух HTML-элементов. Сохраните этот фрагмент в answer.html и откройте файл в браузере:
<details>
<summary>Где хранится ответ?</summary>
<p>Внутри details.</p>
</details>
details содержит весь блок, а первый дочерний summary — его видимый заголовок. Нажатие на заголовок раскрывает и сворачивает ответ. Обработчик клика для этого примера писать не нужно.<details open="false">. Вопреки названию значения, блок начнёт раскрытым. open — булев атрибут: важно само его наличие. Чтобы снова начать со свёрнутого ответа, уберите атрибут целиком. В учебнике web.dev показано, как связаны эти элементы и состояние блока.
Читать полностью…
Обычный метод получает объект, classmethod получает класс, а staticmethod обходится без них
Класс описывает устройство объектов, а экземпляр — конкретный объект этого класса. Например, класс Pizza и отдельная приготовленная пицца.
У методов есть три варианта:
class Pizza:
def describe(self):
return self.name
@classmethod
def create(cls, name):
pizza = cls()
pizza.name = name
return pizza
@staticmethod
def valid_name(name):
return bool(name.strip())
describe работает с конкретной пиццей через self. create получает класс через cls и создаёт его экземпляр. valid_name проверяет строку: ни объект, ни класс ей не передаются автоматически.
Запуск Python-файла и запуск модуля через -m по-разному находят код
Команда python app.py запускает файл по указанному пути. В команде python -m shop.app интерпретатор ищет модуль app внутри пакета shop и запускает его.
Модуль — это отдельная единица Python-кода, часто файл с расширением .py. Пакет объединяет модули.
В обоих случаях у запускаемого кода значение __name__ будет "__main__". При обычном импорте оно содержит имя модуля. На этом основан знакомый шаблон:
def main():
print("Программа запущена")
if __name__ == "__main__":
main()
Стек возвращает последний добавленный элемент, а очередь — первый
В стек положили A, затем B. Первым извлечётся B. Этот порядок называют LIFO: последним пришёл — первым вышел.
В очереди после тех же добавлений первым извлечётся A. Это FIFO: первым пришёл — первым вышел.
На маленьких массивах JavaScript разницу можно увидеть так:
const stack = ["A", "B"];
console.log(stack.pop()); // B
const queue = ["A", "B"];
console.log(queue.shift()); // A
Чистая функция вычисляет результат без изменения внешних данных
Функция получает сумму и скидку, возвращает новую сумму. Ей не нужно читать глобальную переменную или менять переданный объект:
def discounted(total, percent):
return total * (100 - percent) // 100
print(discounted(1000, 10)) # 900
Git не может обновить ветку простым сдвигом, если история разошлась
Вы сделали коммит локально, а коллега добавил другой коммит в общую ветку. Теперь у каждой стороны есть изменение, которого нет у другой:
A──B ваша ветка
└──C общая ветка
git pull --ff-only остановится: передвинуть указатель с B на C, сохранив обе линии как одну готовую историю, нельзя.git status, чтобы увидеть несохранённые в коммитах изменения. Затем git fetch: он скачает сведения об общей истории, но не объединит её с вашей веткой.
Почему цепочка Promise может заморозить страницу
Таймер с нулевой задержкой не запускается немедленно. Сначала завершается текущий код, затем обрабатываются ожидающие микрозадачи, включая обработчики Promise.
console.log("A");
setTimeout(() => console.log("B"), 0);
Promise.resolve()
.then(() => console.log("C"))
.then(() => console.log("D"));
console.log("E");
// A, E, C, D, B
Бинарный поиск: как одна граница зацикливает алгоритм
Половину массива можно отбросить, только если он отсортирован. Но даже тогда нужно уменьшать диапазон после каждого несовпадения.
def search(a, x):
left, right = 0, len(a) - 1
while left <= right:
mid = (left + right) // 2
if a[mid] == x:
return mid
if a[mid] < x:
left = mid + 1
else:
right = mid - 1
return -1
print(search([2, 5, 9], 8)) # -1
left = mid, диапазон из двух элементов может перестать сужаться.
Почему every возвращает true для пустого массива
Метод every проверяет, соответствует ли каждый элемент условию. [3, 7].every(n => n > 0) вернёт true, а массив с -1 — false. Проверка прекращается при первом неподходящем элементе.
Теперь выполните [].every(n => n > 0). Результат снова true: в пустом массиве нет элемента, который нарушил бы условие. Функция проверки при этом вообще не вызывается.
Если по задаче нужен непустой список положительных чисел, проверяйте оба условия: values.length > 0 && values.every(n => n > 0). Подставьте пустой массив и сравните результаты. На MDN есть и другие особенности метода, включая пропуск пустых мест в разреженном массиве.
Запятая внутри кавычек остаётся в CSV-поле
Строка Ada,"London, UK",3 содержит три CSV-поля. Город записан в кавычках, поэтому запятая внутри него не разделяет столбцы. Метод split(",") не учитывает кавычки и создаст четыре части.
Сравните оба способа на карточке. csv.reader([line]) из стандартной библиотеки Python вернёт ['Ada', 'London, UK', '3']. Последнее значение останется строкой: парсер не превращает число в int автоматически.
В учебном примере парсер читает список из одной строки. Если читаете CSV из файла, открывайте его с newline="" и задавайте разделитель под его формат. Попробуйте поле с ещё одной запятой внутри кавычек. Правила и диалекты — в документации csv.
Номер строки и порядок выдачи — разные вещи
В SQLite row_number() присваивает строкам номера. Порядок внутри OVER (...) определяет, какая строка получит номер 1. А внешний ORDER BY отдельно задаёт порядок, в котором вы увидите строки результата.
Возьмите пары (1, 'aaa'), (2, 'ccc'), (3, 'bbb'). При row_number() OVER (ORDER BY y) номера идут по буквам. Если вывести результат с ORDER BY x, увидите номера 1, 3, 2. Они не обязаны идти по возрастанию на экране.
Запрос с карточки сохраняет все три строки. Запустите его в SQLite 3.25.0 или новее, затем замените внешний порядок на ORDER BY y. При равных значениях добавляйте уникальный ключ в порядок нумерации. Подробнее — в документации SQLite.
Counter посчитает повторения в списке
В списке ["red", "blue", "red"] три элемента, но два разных цвета. Counter из стандартной библиотеки Python хранит, сколько раз встретился каждый элемент. Так не придётся вручную хранить и увеличивать счётчик для каждого цвета.
Выполните код с карточки. Для red получите 2, для отсутствующего green — 0. Вызов most_common(1) вернёт список с одной парой: [('red', 2)].
Добавьте ещё два blue и снова найдите самый частый цвет. Здесь мы считаем готовые элементы списка. Чтобы считать слова в тексте, сначала придётся решить, как его разбивать. Подробнее о счётчике — в документации Python.
Файл закроется, когда закончится блок with
Открытый файл нужно закрыть после работы. В Python это удобно поручить with: при выходе из его блока файл закроется, даже если внутри возникнет исключение. Саму ошибку эта конструкция не перехватывает.
В отдельной учебной папке запустите пример с карточки. Он создаст note.txt с текстом hello и напечатает True. Режим "w" перезаписывает существующий файл, поэтому не используйте имя важного документа.
Перенесите print(f.closed) внутрь блока: увидите False. А попытка чтения после закрытия даст ValueError. Подробнее о времени жизни файла — в учебнике Python.
Ноль может исчезнуть из-за значения по умолчанию
Вы задали количество повторов 0, чтобы ничего не запускать. Но выражение 0 || 10 подставит десять. Оператор || выбирает правую часть, когда левая при проверке условия считается ложной. К таким значениям относятся и ноль, и пустая строка, и false.
console.log(0 || 10) // 10
console.log(0 ?? 10) // 0
console.log(null ?? 10) // 10
?? подставляет правую часть только вместо null или undefined. Поэтому для настройки, где ноль допустим, он сохраняет смысл значения. Пустую строку и false он тоже оставит. При этом ?? не проверяет правильность числа и не исправляет NaN.false, затем на undefined. В первом случае результаты разойдутся, во втором оба будут 10. Выбор оператора зависит от того, что вы считаете отсутствующим значением. В примерах MDN можно сравнить остальные случаи.
Читать полностью…
lstrip может откусить часть нужного текста
Из строки нужно убрать имя автора в начале. Кажется, что lstrip("Arthur: ") подходит, но результат неожиданно короткий:
text = "Arthur: three!"
print(text.lstrip("Arthur: ")) # ee!
print(text.removeprefix("Arthur: ")) # three!
lstrip — набор отдельных символов для удаления слева. Метод идёт до первого символа вне набора, поэтому после имени съедает ещё t, h и r. removeprefix удаляет ровно один совпавший префикс, то есть заданное начало строки. Он доступен с Python 3.9. Оба метода возвращают результат, исходная строка text не меняется."three!". Первый снова оставит "ee!", второй вернёт "three!", потому что нужного префикса нет. А из "Arthur: Arthur: three!" второй удалит только первое имя. Это различие разобрано в документации строк Python.
Читать полностью…
Почему у backup.tar.gz расширение .gz, а основа — backup.tar
В имени файла может быть несколько точек. Модуль pathlib в Python помогает разобрать его без ручного разрезания строки. PurePosixPath работает с записью пути в стиле Unix и для этого не обращается к диску:
from pathlib import PurePosixPath
p = PurePosixPath("backup.tar.gz")
print(p.name) # backup.tar.gz
print(p.stem) # backup.tar
print(p.suffix) # .gz
print(p.suffixes) # ['.tar', '.gz']
name — полное имя последней части пути. stem отбрасывает только последний суффикс, а suffixes собирает их список. Эти свойства не переименовывают файл и не проверяют его содержимое: имя с .gz ещё не доказывает, что перед вами архив.README: основа останется README, суффикс будет пустой строкой, список суффиксов — []. Другие варианты есть в справочнике pathlib.
Читать полностью…
Связный список хранит порядок элементов через ссылки между узлами
Узел содержит значение и ссылку на следующий узел. Чтобы пройти список, программа следует по этим ссылкам:
const second = { value: "B", next: null };
const first = { value: "A", next: second };
for (let node = first; node !== null;
node = node.next) {
console.log(node.value); // A, затем B
}first указывает на начало. Каждый переход node.next ведёт к следующему элементу. Значение null обозначает конец.
WHERE отбирает отдельные строки, а HAVING — результаты группировки
Нужно найти классы, где средняя оценка не ниже восьми:
SELECT class_name, AVG(grade) AS avg_grade
FROM results
GROUP BY class_name
HAVING AVG(grade) >= 8;
GROUP BY собирает строки каждого класса в группу. AVG вычисляет среднее, а HAVING оставляет группы, которые прошли проверку.WHERE grade >= 8, получится другая задача: сначала исключить низкие оценки и только потом посчитать среднее по оставшимся.
Переменная внутри функции может скрывать одноимённую переменную снаружи
Посмотрите на два значения price:
price = 100
def show_price():
price = 80
print(price)
show_price() # 80
print(price) # 100
price, result или count, не перезаписывая значения друг друга.list может скрыть встроенный конструктор списка.
Условие в WHERE может убрать строки, сохранённые LEFT JOIN
Есть клиенты и их заказы. Нужно показать всех клиентов, а рядом — только оплаченные заказы.LEFT JOIN сохраняет клиента, даже если подходящего заказа нет. В колонках заказа тогда будет NULL — отсутствие значения.
Условие оплаты можно включить в правило соединения:
SELECT c.name, o.id
FROM customers AS c
LEFT JOIN orders AS o
ON o.customer_id = c.id
AND o.status = 'paid';
WHERE o.status = 'paid', она начнёт отбирать строки уже после соединения. У клиента без подходящего заказа сравнение с NULL не даст истину, и строка исчезнет.
Как проверить, что переписанная функция не потеряла поведение
Новая реализация короче и проходит обычный пример. Но сохранила ли она все свойства старой?
def old(items):
return sorted(items)
def new(items):
return sorted(set(items))
for case in ([], [2], [3, 1], [2, 2]):
print(case, old(case) == new(case))
# Последняя проверка: False
Почему второй проход по итератору ничего не возвращает
Массив хранит элементы. Итератор хранит состояние конкретного прохода по ним. Если он уже дошёл до конца, новый цикл не вернёт его к началу.
const values = [10, 20, 30];
const it = values[Symbol.iterator]();
console.log([...it]); // [10, 20, 30]
console.log([...it]); // []
console.log([...values]); // [10, 20, 30]
next() продвигает итератор и возвращает объект с полями value и done. Когда done становится true, этот проход завершён.
Почему NOT IN с NULL убирает все строки
В SQL условие бывает не только истинным или ложным, но и неизвестным. Обычное сравнение с NULL даёт именно неизвестный результат.
SELECT 3 AS id
WHERE 3 NOT IN (1, NULL);
-- Ни одной строки
3 <> 1 AND 3 <> NULL. Первая часть истинна, вторая неизвестна. Итог — неизвестно, а WHERE оставляет только строки с истинным условием.IS NULL, а не = NULL. При этом поведение пустой строки зависит от СУБД: Oracle считает её NULL, PostgreSQL — отдельным значением.