Точка входа в программирование
17.9K subscribers
1.82K photos
293 videos
4 files
3.41K links
Фундаментальные знания по основам программирования

Разместить рекламу: @tproger_sales_bot

Правила общения: https://tprg.ru/rules

Другие каналы: @tproger_channels

Сайт: https://tprg.ru/site

Регистрация в перечне РКН: https://tprg.ru/zrgj
Download Telegram
Как проверить, что переписанная функция не потеряла поведение

Новая реализация короче и проходит обычный пример. Но сохранила ли она все свойства старой?

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


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

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

Старая версия тоже может ошибаться. Найденное отличие нужно сверить с требованиями, прежде чем объявлять ошибкой новой реализации.

Как сравнивать две реализации
🆒2❤1
Git не может обновить ветку простым сдвигом, если история разошлась

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

A──B  ваша ветка
└──C общая ветка


Команда git pull --ff-only остановится: передвинуть указатель с B на C, сохранив обе линии как одну готовую историю, нельзя.

Сначала выполните git status, чтобы увидеть несохранённые в коммитах изменения. Затем git fetch: он скачает сведения об общей истории, но не объединит её с вашей веткой.

Дальше нужно выбрать способ объединения. Merge соединяет линии истории, сохраняя существующие коммиты. Rebase переносит ваши коммиты на новую основу и меняет их идентификаторы.

В разборе расхождения веток есть пример обоих подходов. Уже опубликованные коммиты не стоит переносить через rebase без согласования с командой.
👍3❤1
Условие в 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 не даст истину, и строка исчезнет.

В первом варианте мы выбираем, какие заказы присоединить. Во втором — какие строки оставить в результате. На маленьких таблицах эта разница хорошо видна в пошаговом разборе JOIN.
⚡1👍1🔥1
Чистая функция вычисляет результат без изменения внешних данных

Функция получает сумму и скидку, возвращает новую сумму. Ей не нужно читать глобальную переменную или менять переданный объект:

def discounted(total, percent):
return total * (100 - percent) // 100

print(discounted(1000, 10)) # 900


Здесь суммы заданы целыми единицами, а результат округляется вниз. Это правило конкретного примера, а не универсальный способ расчёта денег.

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

Такую функцию проще проверить: передали 1000 и 10 — ожидаем 900. Чтобы проверить вычисление, не приходится готовить базу или очищать глобальное состояние.

В руководстве по функциональному подходу разбираются и другие приёмы. Начать можно с отделения вычислений от ввода, вывода и изменения данных.
👍2🆒1
Переменная внутри функции может скрывать одноимённую переменную снаружи

Посмотрите на два значения price:

price = 100

def show_price():
price = 80
print(price)

show_price() # 80
print(price) # 100


Присваивание внутри функции создаёт локальное имя. Оно действует при выполнении этой функции и не меняет внешнюю переменную.

Пространство имён — это соответствие имён объектам. Благодаря отдельным пространствам разные функции могут использовать price, result или count, не перезаписывая значения друг друга.

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

В разборе пространств имён есть примеры порядка поиска. При чтении кода полезно каждый раз уточнять: к какому именно имени относится эта строка?
👍2⚡1
Стек возвращает последний добавленный элемент, а очередь — первый

В стек положили 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


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

Здесь массив помогает показать поведение. У большой очереди удаление первого элемента может требовать перемещения остальных, поэтому способ хранения тоже имеет значение.

В разборе стека и очереди можно проследить операции по шагам и попробовать кольцевую очередь.
❤3👍1
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, получится другая задача: сначала исключить низкие оценки и только потом посчитать среднее по оставшимся.

Например, у класса оценки 6 и 10. Среднее — 8. После такого WHERE останется только 10, и среднее станет 10.

Держите в голове логический порядок: сначала источник строк, затем WHERE, группировка и HAVING. Это помогает понять, на каком этапе исчезают данные.

В учебном разборе SQL-запросов есть небольшая база, запросы и таблицы с ответами.
✍1
Запуск Python-файла и запуск модуля через -m по-разному находят код

Команда python app.py запускает файл по указанному пути. В команде python -m shop.app интерпретатор ищет модуль app внутри пакета shop и запускает его.

Модуль — это отдельная единица Python-кода, часто файл с расширением .py. Пакет объединяет модули.

В обоих случаях у запускаемого кода значение __name__ будет "__main__". При обычном импорте оно содержит имя модуля. На этом основан знакомый шаблон:

def main():
print("Программа запущена")

if __name__ == "__main__":
main()


При запуске main() выполнится. При импорте файла функция определится, но этот блок её не вызовет. Остальной код верхнего уровня при импорте всё равно выполняется.

В руководстве по запуску Python-кода разобраны оба способа. Если в проекте есть пакеты, сначала уточните, что вы запускаете: путь к файлу или имя модуля.
✍2❤1🎉1
Связный список хранит порядок элементов через ссылки между узлами

Узел содержит значение и ссылку на следующий узел. Чтобы пройти список, программа следует по этим ссылкам:

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 обозначает конец.

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

Вставка рядом с уже найденным узлом требует изменить несколько ссылок. Но поиск нужного места может занять проход по всему списку. Поэтому обещание «любая вставка за O(1)» вводит в заблуждение.

В иллюстрированном руководстве по связным спискам есть упражнения на обход и изменение связей.
❤1
Обычный метод получает объект, 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 проверяет строку: ни объект, ни класс ей не передаются автоматически.

Запись с @ — декоратор, который здесь задаёт способ вызова метода.

В руководстве с примером Pizza эти различия показаны подробнее. Не каждой вспомогательной функции нужен staticmethod: иногда её удобнее оставить обычной функцией модуля.
🐳2❤1🔥1💯1🤝1
Почему у 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.
🐳2❤1✍1🎉1💯1
Раскрывающийся ответ на HTML без JavaScript

Для вопроса с ответом, который появляется по нажатию, достаточно двух HTML-элементов. Сохраните этот фрагмент в answer.html и откройте файл в браузере:

<details>
<summary>Где хранится ответ?</summary>
<p>Внутри details.</p>
</details>


details содержит весь блок, а первый дочерний summary — его видимый заголовок. Нажатие на заголовок раскрывает и сворачивает ответ. Обработчик клика для этого примера писать не нужно.

Теперь замените открывающий тег на <details open="false">. Вопреки названию значения, блок начнёт раскрытым. open — булев атрибут: важно само его наличие. Чтобы снова начать со свёрнутого ответа, уберите атрибут целиком. В учебнике web.dev показано, как связаны эти элементы и состояние блока.
❤3🐳2👏1👌1
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.
👍3❤1
Три строки в таблице, но COUNT(email) возвращает два

У одного пользователя есть адрес, у второго — NULL, то есть отсутствие значения, у третьего — пустая строка. Запустите этот запрос в SQLite. WITH здесь задаёт учебный набор строк для одного запроса:

WITH users(email) AS (
VALUES ('[email protected]'), (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, выборка будет пустой и оба счётчика вернут ноль.
❤4
Ноль может исчезнуть из-за значения по умолчанию

Вы задали количество повторов 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 можно сравнить остальные случаи.
👍2🆒2
Один знак в Bash решает, дописать файл или стереть старый текст

Знак > отправляет обычный вывод команды в файл. Если файла нет, Bash создаст его, а существующий обнулит перед записью. У >> другое поведение: добавить в конец. Проверьте на новом файле notes.txt в отдельной учебной папке.

1️⃣ Запишите первую строку, затем добавьте вторую:
printf 'first\n' > notes.txt
printf 'second\n' >> notes.txt
cat notes.txt

cat покажет first и second на отдельных строках. Сами команды printf отправили текст в файл, поэтому в терминале он появился только после cat.

2️⃣ Повторите запись с одним знаком:
printf 'third\n' > notes.txt
cat notes.txt

Останется только third. Это пример для Bash с выключенной защитой noclobber: при включённой защите перезапись существующего обычного файла через > завершится ошибкой. Обе операции описаны в руководстве Bash.
Please open Telegram to view this post
VIEW IN TELEGRAM
✍4
Файл закроется, когда закончится блок with

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

В отдельной учебной папке запустите пример с карточки. Он создаст note.txt с текстом hello и напечатает True. Режим "w" перезаписывает существующий файл, поэтому не используйте имя важного документа.

Перенесите print(f.closed) внутрь блока: увидите False. А попытка чтения после закрытия даст ValueError. Подробнее о времени жизни файла — в учебнике Python.
🔥3❤2🏆1
Символы + и & внутри поискового запроса

Хотите передать в адресе строку 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.
❤2🏆2🔥1
Counter посчитает повторения в списке

В списке ["red", "blue", "red"] три элемента, но два разных цвета. Counter из стандартной библиотеки Python хранит, сколько раз встретился каждый элемент. Так не придётся вручную хранить и увеличивать счётчик для каждого цвета.

Выполните код с карточки. Для red получите 2, для отсутствующего green — 0. Вызов most_common(1) вернёт список с одной парой: [('red', 2)].

Добавьте ещё два blue и снова найдите самый частый цвет. Здесь мы считаем готовые элементы списка. Чтобы считать слова в тексте, сначала придётся решить, как его разбивать. Подробнее о счётчике — в документации Python.
🆒3
Поле с подсказками разрешает свой вариант

В HTML-поле можно предложить варианты без JavaScript. Атрибут list="cities" у input связывает его с datalist id="cities". Значения option становятся подсказками при вводе.

Добавьте два варианта, Moscow и Kazan, как на карточке. Затем введите Perm. Поле примет и этот текст: список предлагает значения, но сам по себе не запрещает другие. Если нужен строго заданный набор, это уже другая задача.

Проверьте пример в нужных браузерах. Вид подсказок и поддержка различаются, есть ограничения для экранных дикторов и масштабирования. MDN описывает эти ограничения. Для реальной формы одной проверки внешнего вида недостаточно.
❤‍🔥2