Python Materials For Beginners
447 subscribers
39 photos
1 video
9 files
150 links
Download Telegram
Що стосовно іноді моїх власних коротких заміток у цьому каналі? Моливо замітки будуть по якимось відео з ютюба.
Anonymous Poll
4%
Ні, тільки матеріали для початківців
86%
Так, можна замітки автора з прикладами коду
64%
Так, можна й замітки по відео з ютюба
🚫 Чому не варто збирати Python-проєкт у exe через PyInstaller

Багато початківців намагаються зібрати свій Python-проєкт у .exe-файл. Але є чимало причин, чому це не завжди гарна ідея.

Насправді цей процес не є компіляцією.
Фактично відбувається лише упаковка Python-інтерпретатора та бібліотек у саморозпакувальний архів [1].
Коли користувач запускає exe, спочатку виконується розпаковка цього архіву у тимчасову теку, і лише потім — запуск скрипта. Це має низку негативних наслідків:

1️⃣ Антивіруси часто сприймають такий .exe як загрозу.
Через використання стиснення файлів антивірусне ПЗ може активно скаржитися на ваш exe. Щоб переконатися в цьому, достатньо завантажити файл на VirusTotal. Якщо користувач зробить таку перевірку й побачить попередження, довіра до вас і вашого ПЗ може бути підірвана.

2️⃣ Проблеми з шляхами
exe запускається з однієї теки, але архів розпаковується та виконується в іншій. Це змушує вносити зміни до коду проєкту, які будуть враховувати зміну шляху.

3️⃣ Несумісність з окремими бібліотеками
Існує ймовірність, що через особливості певної бібліотеки проєкт не збиратиметься у exe. Причому ситуація може змінюватися від версії до версії бібліотеки і python. Найчастіше проблеми виникають із модулями, що використовують код на C/C++.

4️⃣ Складна діагностика помилок
Упаковка, особливо з опцією --windowed, може маскувати помилки: замість виведення повідомлення у консолі користувач просто отримає падіння програми. На одних пристроях програма може запускатися, а на інших — ні. Виявити причину, виправити її або оновити бібліотеки "на місці" просто неможливо.

5️⃣ Довгий старт програми
Очевидно що розпаковка з архіву буде сповільнювати запуск.

Хоча збірка у exe може здаватися зручною для поширення, на практиці вона часто створює більше проблем, ніж вирішує.

#PyInstaller
👍1
Приклад збірки PyQt6 проєкту PyInstaller-ом.

Якщо Ви все-таки наважилися зібрати свій проєкт pyinstaller у exe пропоную розглянути приклад.
Як приклад будемо використовувати мій шаблон для PyQt6
Шаблон дуже простий - показує просте вікно.

Він має файли які задані відносними шляхами:
1️⃣ Одна з переваг Qt - використання файлів "форм" формату *.ui в даному випадку це - main_window.ui
2️⃣ icon.png це іконка у головному вікні, вона задана main_window.ui через дизайнер "форм" QtDesiginer

Напишемо pyinstaller_build.bat скрипт який буде займатись збіркою у exe

SET SCRIPT_NAME=main.py
SET EXE_NAME=prog

.venv\Scripts\pyinstaller.exe ^
--add-data "main_window.ui;." ^
--add-data "icon.png;." ^
--icon "icon.ico" ^
--windowed ^
--onefile ^
--noupx ^
--name %EXE_NAME% ^
%SCRIPT_NAME%

pause


Як бачимо всі файли які використовуються програмою треба додавати опцією --add-data
Також сам exe файл може мати іконку для цього має бути використана опція --icon але треба зауважити що формат іконки має бути саме .ico (png не підходить).

Опція --windowed саме для GUI застосунків щоб сховати вікно консолі.
Опція --onefile саме збірка у exe
Опція --noupx збирати без upx стиснення.

Але цього недостатньо, щоб наш додаток знаходив файли main_window.ui та icon.png при запуску треба змінювати робочу директорію (це треба робити на самому початку скрипта).

try:
import os, sys

os.chdir(sys._MEIPASS)
print(f"{sys._MEIPASS=}")

except AttributeError:
pass


Атрибут _MEIPASS додається динамічно pyinstaller-ом - це тимчасова тека куди розпаковується проєкт

Без цієї вставки коду буде помилка про те, що вказані файли відсутні.
Також я додам скріншот з збіркою без опції --windowed щоб можна було побачити як виглядає sys._MEIPASS

Додаткові файли будуть прикріплені у коментарях.

#PyInstaller #PyQt6
👍43
Схемка по основам git
яка дає уявлення які можуть бути стани репозиторію і які команди відповідають переходам між ними.

https://www.youtube.com/watch?v=G5nhfQ_lfNg
👍2
🔷 Використання SQLite разом з SqlAlchemy

⚠️ Цікавий нюанс: SQLite за замовчуванням не перевіряє зовнішні ключі.
У багатьох СУБД (PostgreSQL, MySQL) зовнішні ключі завжди перевіряються.

🔧 У SQLite за це відповідає прапорець foreign_keys.
За замовчуванням він OFF, і якщо його не виставити в ON, можна отримати неузгоджені дані у зв’язаних таблицях, і довго дивуватись чому так.

👉 Виставити цей прапорець можна таким чином:

async with engine.begin() as connection:
await connection.execute("PRAGMA foreign_keys=ON")


Або одразу оголосити обробник події:

from sqlalchemy import event
from sqlalchemy.engine import Engine

@event.listens_for(Engine, "connect")
def set_sqlite_pragma(dbapi_connection, _):
cursor = dbapi_connection.cursor()
cursor.execute("PRAGMA foreign_keys=ON")
cursor.close()


Так Ви уникнете "тихих" проблем з цілісністю даних.


#sqlite #sqlalchemy
👍2
🔶 Requests. Простий GET-запит.

У Python HTTP-запити можна робити вбудованою бібліотекою urllib.
Але значно зручніше використовувати сторонню бібліотеку requests. Варто також згадати інші бібліотеки зі схожим інтерфейсом — наприклад, httpx та curl_cffi. Вони можуть працювати асинхронно та мають більш наближені до браузерних TLS-відбитки, що зменшує ймовірність бану при скрапінгу.

import requests

response = requests.get("https://example.com/")
response.raise_for_status()

print(response.text)

with open("content.html", "wb") as file:
file.write(response.content)


Це простий приклад GET-запиту для отримання HTML-сторінки.
HTML-код виводиться в консоль як рядок і зберігається у файл у байтовому вигляді.

Для різних HTTP-методів є відповідні методи в requests. Додаткові дані можна передавати через аргументи методів (заголовки, параметри запиту, тіло, проксі тощо).

⚠️ Поширена помилка початківців — забути перевірити код відповіді сервера. Найпростіше це зробити викликом raise_for_status(), який підніме виняток, якщо код відповіді не в межах 200–399.
Але також можна перевіряти атрибут status_code.

#http #requests #GET
👍4
🔷 Requests. GET з параметрами та заголовками.

Для прикладу виконаємо запит до погодного API

https://api.open-meteo.com/v1/forecast?latitude=50.4501&longitude=30.5234&current_weather=True&timezone=Europe/Kyiv

import requests

url = "https://api.open-meteo.com/v1/forecast"

params = {
"latitude": 50.4501,
"longitude": 30.5234,
"current_weather": True,
"timezone": "Europe/Kyiv"
}

headers = {
"User-Agent": "my-script/1.0",
}

response = requests.get(url, params=params, headers=headers)
response.raise_for_status()

json_content = response.json()
current_weather = json_content["current_weather"]
print("Температура в Києві:", current_weather["temperature"])
print("Швидкість вітру:", current_weather["windspeed"])


Основні моменти:

1️⃣ Параметри запиту передаються у вигляді словника у метод, бібліотека кодує їх
2️⃣ Суто для демонстрації змінюємо заголовки запиту (User-Agent)
3️⃣ Об'єкт response має метод json який перетворює json-відповідь у python об'єкт (зазвичай словник)

#http #requests #GET
👍3
Channel name was changed to «Python Materials For Beginners»
🔶 Requests. POST form-urlencoded

POST використовується для створення ресурсів або надсилання даних на сервер. Дані передаються у тілі запиту, а не у URL (як при GET).

Є різні типи POST-запитів:

🔺 application/x-www-form-urlencoded – дані форми кодуються у вигляді param1=value1&param2=value2
🔺 application/json – дані у вигляді json
🔺 multipart/form-data – форми з файлами.

Даний приклад демонструє передачу даних форми (form-urlencoded)
Бібліотека автоматично виставляє заголовок відповідно до типу:

Content-Type: application/x-www-form-urlencoded

Форми часто використовується для авторизації на сайтах: у даних передаються логін і пароль. Але у даному випадку ми передаємо текст і розмір картинки, а сайт повертає нам QR-код картинку.

import requests

url = "https://api.qrserver.com/v1/create-qr-code/"

data = {
"data": "Привіт світ!",
"size": "200x200"
}

response = requests.post(url, data=data)
response.raise_for_status()

with open("qr_code.png", "wb") as file:
file.write(response.content)

#http #requests #POST
👍2
🔷 Requests. POST application/json

POST/application-json використовується для надсилання json даних на сервер (REST API)

Замість параметру data треба використовувати параметр json
У цьому випадку бібліотека requests виставляє заголовок:

Content-Type: application/json

Реальне API мені не вдалось знайти тому використаємо для прикладу тестове API:
import requests

url = "https://jsonplaceholder.typicode.com/posts"

json_payload = {
"title": "foo",
"body": "bar",
"userId": 1
}

response = requests.post(url, json=json_payload)
response.raise_for_status()

print(response.status_code)
print(response.json())


201
{'title': 'foo', 'body': 'bar', 'userId': 1, 'id': 101}


⚠️ Зверніть увагу що статус кодом який повертається може бути 201, а не 200 що відповідає створенню ресурсу.

Основні статус коди

#http #requests #POST
👍1
🔶 Requests. POST multipart/form-data

multipart/form-data використовується у формах з надсилання файлів на сервер.
У цьому випадку бібліотека requests виставляє заголовок:

Content-Type: multipart/form-data; boundary=...

Для прикладу завантажимо наш qrcode з попереднього прикладу на файлообмінник.
Також будемо вдавати браузер Mozilla.

Для відправки файлів використовується словник files з вказанням:

1️⃣ назви файлу
2️⃣ самого файлу
3️⃣ типу файлу

import requests

with open("qr_code.png", "rb") as file:
url = "https://catbox.moe/user/api.php"

files = {
"fileToUpload": ("qr_code.png", file, "image/png"),
}
data = {
"reqtype": "fileupload",
"userhash": "",
}
headers = {
"User-Agent" : "Mozilla/5.0 "
"(Windows NT 10.0; Win64; x64; rv:142.0)"
"Gecko/20100101 Firefox/142.0"
}
r = requests.post(url, files=files, data=data, headers=headers)
r.raise_for_status()
print(r.text.strip()) # вивод url

https://files.catbox.moe/lmcc82.png


#http #requests #POST
👎1
🔷 Requests. Використання сесій.

У Python бібліотека requests дозволяє зручно працювати з HTTP-запитами. Якщо потрібно виконати кілька запитів до одного сайту — варто використовувати сесію.

Що дає використання сесії:

1️⃣ Автоматично підтримувати cookies між запитами.
2️⃣ Дозволяють повторно використовувати одні ті самі заголовки.
3️⃣ Використовують одне TCP-з’єднання для кількох запитів → запити виконуються швидше.

Приклад коду з використанням сесій:
import requests

url = "https://httpbin.org/headers"

with requests.Session() as session:
# Виставляємо заголовоки для всіх запитів у сесії
headers = {
"User-Agent" : "Mozilla/5.0 "
"(Windows NT 10.0; Win64; x64; rv:142.0)"
"Gecko/20100101 Firefox/142.0"
}
session.headers.update(headers)

r1 = session.get(url)
r1.raise_for_status()
print(r1.json())

r2 = session.get(url)
r2.raise_for_status()
print(r2.json())

#http #requests #session
👍1
🟣 Selectolax. Парсинг HTML

selectolax одна з найшвидших бібліотек для парсингу html. Для пошуку тегів використовується css selectors.
Вона швидша за BeautifulSoup4 і немає проблем з анотаціями типів.

Давайте розглянемо задачу коли треба з html спарсити всі фрукти з такої html сторінки
html_content = """
<html>
<body>
<section id="fruits">
<h2>Fruits</h2>
<ul>
<li class="item red-item">🍎 Apple</li>
<li class="item">🍌 Banana</li>
<li class="item"> 🍊 <span style="color: orange;">Orange</span> </li>
</ul>
</section>

<section id="vegetables">
<h2>Vegetables</h2>
<ul>
<li class="item">🥕 Carrot</li>
<li class="item" data-id="42">🥔 Potato</li>
<li class="item" data-id="69">🍆 Eggplant</li>
</ul>
</section>

<a href="https://t.me/Python_Materials_For_Beginners/170">Parsing</a>
</body>
</html>
"""


Для початку створимо об'єкт парсеру:
from selectolax.lexbor import LexborHTMLParser, LexborNode

parser = LexborHTMLParser(html_content)


Шукаєм один тег по id
container_el: LexborNode | None = parser.css_first("section#fruits")
assert container_el, "Fruits container not found!"


⚠️ Якщо тег не знайдено метод парсеру вертає None тому завжди варто перевіряти цей момент щоб отримати помилку зі зрозумілим описом. Для цього можна використовувати assert або використати if і кидати Exception.

В середині знайденого тегу шукаємо список по імені тегу:
list_el = container_el.css_first("ul")
assert list_el, "List of fruits not found!"


Використовуємо пошук по імені класу тега щоб знайти всі елементи списку
item_els: list[LexborNode] = list_el.css("li.item")
for item_el in item_els:
text = item_el.text(strip=True, separator="-").strip()
print(text)

🔺 strip=True - видаляє зайві пробіли всередині тексту дочірніх тегів
🔺 separator - задає розділювач між текстами з різних дочірніх тегів
🔺 .strip() - обрізає пробіли (та переводи рядків) з початку і кінця рядка

Вивід коду:
🍎 Apple
🍌 Banana
🍊-Orange-

#selectolax #parsing #scraping
🟠 Selectolax. Парсинг HTML. Частина 2.

🔺 Поєднанний пошук.
Пошук по CSS-селекторах дозволяє одразу поєднувати все в одному рядку запиту:
item_els = parser.css("section#fruits ul li.item")
for item_el in item_els:
text = item_el.text(strip=True, separator=" ").strip()
print(text)


❗️ Але я наполегливо не рекомендував би цим зловживати. Короткий запис — це добре, але у випадку помилки буде важко зрозуміти, з яким саме тегом проблема.

🔺 Пошук тега який має декілька класів
# <li class="item red-item"> ... </li> 

red_item_el = parser.css_first("li.item.red-item")
assert red_item_el

print(red_item_el.text()) # 🍎 Apple


🔺 Атрибути тегу
# <a href=" ... ">Parsing</a>

link_el = parser.css_first("a")
assert link_el

url = link_el.attributes["href"]

print(url) # https://t.me/Python_Materials_For_Beginners/170

Для отримання атрибуту тегу :
▪️attributes – це read-only словник. Ти можеш з нього читати, але не змінювати напряму.
▪️attrs – це mutable dict-like (звичайний словник), який можна змінювати.

🔺 Пошук по атрибуту:
data_els = parser.css("li.item[data-id]")
for data_el in data_els:
data_id = data_el.attributes["data-id"]
print(data_id) # 42 69


data_el = parser.css_first("li.item[data-id='42']")
assert data_el

print(data_el.html) # <li class="item" data-id="42"> ... Potato</li>


#selectolax #parsing #scraping
👍1
🟢 Selectolax. Парсинг HTML. Підсумки та рекомендації.

Для пошуку тегів ми можемо використовувати:
▪️ id тега
▪️ назву тега
▪️ клас тега
▪️ атрибути тега

❗️ У коректному HTML id завжди повинен бути унікальним, тому пошук по ньому — найнадійніший.

Якщо ж шукати по назві/класу/атрибутам, результатів може бути кілька, і серед них можуть траплятися елементи, які ви не очікували знайти.

У нашому прикладі, якщо ми хочемо тільки фрукти й напишемо:
item_els = parser.css("li.item") 


То отримаємо не лише фрукти, а й овочі — це видно одразу у нашому простому HTML-коду. Реальні html-сторінки по розміру значно більші і можна не помітити десь ще на сторінці використовуються схожі теги але у зовсім іншому контексті, як наслідок — незрозумілі помилки у коді.

👉 Тому рекомендую завжди звужувати пошук: спершу знайдіть найближчий батьківський елемент із логічним id, класом чи атрибутом, а вже потім отримуйте потрібні дочірні теги.

👉 Важливо, щоб назви класів, id чи атрибутів, до яких ви прив’язуєтеся, були логічними та «говорили самі за себе». Так менша ймовірність отримати помилкові результати, а код буде легше читати й підтримувати.

👉 Є певна термінологія в HTML: тег зазвичай називають елементом (DOM element) або нодою (Node). Я відповідно додаю суфікс _el або _els до змінних, які містять об’єкти тегів. Це не принципово, але, на мою думку, так код читається краще й спрощується іменування змінних.

#selectolax #parsing #scraping
👍1
💎 httpx+selectolax. Data scraping.

Розглянимо приклад коду який наводив у чаті . Код достатньо довгий тому Git-Gist

1️⃣ Часті запити на сайт можуть призвести до:
▪️ відповіді HTTP 429 Too Many Requests
▪️ обриву з'єднання сервером
▪️ появу капчі
▪️ блокуванню по IP

Тому варто обмежувати кількість запитів у секунду. Для цього існує бібліотека asynciolimiter яка дає можливість це зробити за допомогою Limiter: перед кожним запитом варто викликати його метод limiter.wait()

2️⃣ При скрапінгу даних використовуємо сесії. Це пришвидшує виконання запитів і дає підтримку cookie.

3️⃣ Щоб згрупувати дані разом варто використовувати класи, dataclasses, NamedTuple або як я моделі pydantic

4️⃣ Код варто розбивати на функції, класи, модулі і пакети уникаючи награмодження коду. В даному прикладі я показую розбиття на ф-ції але можна було всю роботу з сайтом винести у модуль або пакет. Також можна було зробити клас QuoteScraper з відповідними методами (це дозволило б не передавати постійно session та limiter у параметрах).

📦 quote_scraper
┣━━ 📜 __init__.py
┣━━ ⚙️ config.py - константи, наприклад BASE_URL
┣━━ 🧾 schemas.py - моделі даних
┣━━ 🔎 parsers.py - функції для парсингу html
┗━━ 🕷 scrapers.py - функції для запит+парсинг
📜 main.py

Структура має бути не обов'язково така сама, але вона має
бути. Коли весь проєкт у одному файлі — це не норм.

5️⃣ Особисто я використовую такий неймінг:
5.1. Функції які займаютья парсингом HTML назвиваю як
parse_ *
5.2. Функціїї які роблять запити: scrape_*
також можна назвивати get_* або fetch_*

6️⃣
Для перевірки наявності потрібних використовую assert але можна також кидати Exception/RuntimeError

7️⃣ Використання logging дає змогу налаштовувати кількість/рівень повідомлень у консоль як власного коду так і коду використаних бібліотек.

8️⃣ Використання генераторів та ітераторів дає можливість більш гнучко використовувати написаний код. (наприклад зупинити ітерацію по сторінкам коли знайдена потрібна цитата)

#httpx #selectolax #parsing #scraping
👍1