import __hello__
276 subscribers
16 photos
55 links
Канал про IT, здебільшого WEB та Python, але буває й у інші теми заносить
Download Telegram
Невже для Python нарешті зʼявилась бібліотека для побудови нативних UI.

Завдяки Toga можна робити нативні додатки для WIndows, Macos та Linux (нажаль лише GTK). В ідеалі написав один раз - працює одразу у всіх ОС (але це не точно). Також заявлено про підтримку Andoid та iOS, але я не перевіряв.

Toga - це частина проєкту BeeWare.
👍2🔥1
Останнім часом намагаюсь знайти більше україномовного IT контенту. Але звісно ж що один у полі не воїн, тому створив awesome list на github - можливо хтось наткнеться та захоче його доповнити, ну або просто буде комусь корисним, мене це теж влаштує.
👍7🔥3
Python 3.13 буде дуже цікавим. Зокрема через те що 3.12 був нудний. Так вийшло тому що у 3.12 увійшло багато підкапотних штук котрі знадобляться core-девам у наступних релізах для того щоб зробити CPython більш швидким.
Зокрема однією з таких фіч буде JIT, котрий Гвідо обіцяє включити у наступний реліз мови. Доречі ця ініціатива дуже цікава з точки зору підходу до неї, бо чому досі пайтон не мав JIT, хоча здається що більшість мов у яких є інтерпретатор його мають (JavaScript, Java, Ruby, та навіть PHP).
Відповідь на це питання досить прагматична і якщо розуміти ким розроблюється Python то досить логічна.
"Класична" реалізація JIT потребує суттєвого ускладнення реалізації інтерпретатора, тому що потребує написання коду на asm й до того ж, якщо у подальшому будуть виявлятись помилки у інтерпретаторі, то доведеться виправляти їх не лише у С-коді, але й у асемблері. Все вищесказане дуже ускладнює підтримку й розробку CPython, що досить критично для проєкту котрий підтримують практично лише волонтери й у який не вкладаються гроші як у V8 компанією Google.

Добре що буквально минулоріч зʼявилась дослідницька робота "Copy-and-patch compilation: a fast compilation algorithm for high-level languages and bytecode" завдяки котрій можна отримати досить швидкий JIT з мінімальним ускладненям реалізації інтерпретатора (більш зрозумілий пост).
Brandt Bucher вже, за два місяці, зумів зробити робочу імплементацію. Вона має такий вплив на кодову базу CPython:

Build time:
- ~700 строк комплексного Python коду
- ~100 строк комплексного С коду
- LLVM залежність

Run time:
- ~300 строк зрозумілого С коду (написанного програмістом)
- ~3000 строк зрозумілого С коду (згенерованого)
- жодних нових залежностей

Тобто основна складність у реалізації JIT міститься у Python коді, що дуже важливо для підтримки проєкту. Ще дуже важливо що хоча мейнтейнери й додали залежність на LLVM - це build-time залежність, тобто для тих хто встановлює Python нічого не зміниться.
А ще, на відміну від "класичної" реалізації - якщо треба буде виправити баг у інтерпретаторі - його треба буде виправити лише один раз, решту згенерує автоматично LLVM.

Таким чином нова версія Python не стане складнішою у підтримці, але всеодно отримає JIT. Щодо очікуваного пришвидшення поки ніякої інформації немає, бо це лише підготовчі роботи, по суті це baseline JIT котрий поки ніякого пришвидшення не приніс, але у розробників є ще майже рік, тому здається що в них все має вийти.

Тут продублюю лінку на відео з поста Гвідо
🔥21
Так ось, Python 3.13. У наступній версії ще мають завести API для сабінтерпретаторів. Чому я на це чекаю? Тому що здається це буде той тип мультипроцесінгу котрим вже можна буде користуватись. У кожного сабінтерпретатора свій GIL, тобто ми зможемо паралельно запускати CPU-bound обчислення на декількох ядрах одразу. Ми правда й зараз це можемо робити через модуль multiprocessing але сабінтерпретатори запускаються швидше ніж новий процес (але звісно повільніше ніж новий тред) і судячи з еспериментів Eric Snow мають менше накладних витрат.

Доречі у 3.12 цю фічу вже можна помацати якщо імпортувати модуль _xxsubinterpreters, але біжати писати продакшн код звісно не треба. Зараз виконувати у сабінтерпретаторі можна лише строки (ну тобто працює мийже як eval) і у 3.13 якраз мають доробити апі щоб можна було виконувати функції у окремому інтерпретаторі, а може додадуть й новий екзекутор до concurrent.futures. Взагалі схоже на те що сабінтерпретатори можуть розвиватись як горутини у Golang, бо shared пам'яті у них нема, а дані передавати можна будет через os.pipe, ну тобто напрошується якийсь механизм типу контекста.

Щодо очевидних проблем їх реалізації. Якщо розробники зроблять апі якогось SubInterpreterPool то їм треба буде очищати стейт сабінтерпретатора поміж викликами, бо якщо цього не робити, то те що буде виконуватись у окремому інтерпретаторі не буде детерміністичним, бо результат може залежати від порядку виконання функцій. Мене також турбує, як вирішать питання імпорту модулів. Тобто коли я хочу запустити функцію у сабінтерпретаторі, яка використовує імпортований модуль, як його автоматично імпортувати на старті? Я сподіваюся, що вони знайдуть розумне рішення, оскільки вимога робити імпорти прямо у функції була б зовсім не найкращим варіантом.

Контекст:
- Python 3.12 Preview: Subinterpreters
- Python 3.12 Subinterpreters: A New Era of Concurrency
🔥3
import __hello__
Так ось, Python 3.13. У наступній версії ще мають завести API для сабінтерпретаторів. Чому я на це чекаю? Тому що здається це буде той тип мультипроцесінгу котрим вже можна буде користуватись. У кожного сабінтерпретатора свій GIL, тобто ми зможемо паралельно…
Доречі ось дуже класний пост по темі. Там і про історію і про порівняння сабінтерпретаторів з тредами, процесами, корутинами, про PEP703, про механізми комунікації і тд.
Ну і вишенкою - запуск web фреймворків у сабінтерпретаторах, з no-gil Python (автор заюзав 3.13 альфа із спеціальним флагом). Із цікавого - Django поки так запустити не вийде, тому що Джанго використовує модуль zoneinfo, бо у модулі datetime у С коді використовується глобальний шейред стейт, але з fastapi та flask вже вийшло погратись.

https://tonybaloney.github.io/posts/sub-interpreter-web-workers.html
👍1🔥1
А ви ж вже бачили Textual? Це такий фреймворк для написання TUI програм. А я навіть спробував - мені інколи треба дивитись ревізії у CouchDB, але робити це через WEB-морду, чи через API не дуже зручно, а бачити діф між двома ревізіями там взагалі не можна.

Тому я згадав що десь чув про новий модний фреймворк для написання TUI і за пару днів склепав для себе апку. Спочатку щось не дуже добре виходило, але коли я збагнув що треба юзати такий самий підхід як при написанні Vuejs застосунків, то справа пішла значно швидше. Бо як виявилось фішка тут та сама - робиш "тупий компонент" котрий нічого не знає про бізнес-логіку, а шарить лише за відображення, а вже стейтом і логікою рулиш рівнем вище. Звісно якщо б я спочатку прочитав рідмі то одразу б побачив фразу "Textual adds interactivity to Rich with an API inspired by modern web development" яка б мене на ці думки б й наштовхнула, але хто ж ті рідмі читає, правда?

Тож у мене тепер є інструмент для швидкої розробки гарних TUI-апок, за допомогою яких можна трохи підвищити свою продуктивність. Доречі у екосистемі цього фреймворку є тулза для снапшот тестування pytest-textual-snapshot яка дуже непогано працює.

PS. а про те що у стдлібі пайтону є модуль для діфів знали? 🤓
🔥2
Історія йде по спіралі

До появи ангулярів, реактів та вью сайтики робили на шаблонах. У Джанзі був (і зараз є) свій движок шаблонів, була Jinja, у Ruby on Rails - ERB, у Laravel - Blade. "Фронтенд" писали або самі бекенд розробники, або верстальщики, котрі щось-там розуміли за шаблони. Коли треба був інтерактив, типу кнопочки "Додати в обране" без перезагрузки сторінки - писали скрипти на ванільному JS, але частіш на jquery - робиш запит на бекенд, бекенд там щось опрацьовує, а у відповідь відправляє шматок html, який ти через js вставляєш на сторінку. Але у такому сетапі багато красівостей, котрі хочуть дизайнери, не намалюєш, бо проєкт на jquery, де багацько логіки перетворюється на лапшу зі швидкістю світла. Хоча я на початку свого програмерського шляху працював у фірмі, де ідеєю-фікс було зробити свій продуктовий каталог з фільтрами і пошуком без оновлення сторінок на jquery - воно навіть якось працювало 😄

Потім зʼявились ангуляри з реактами, фронденд розробники стали вже справжніми програмістами, котрі писали свої окремі апки для клієнтів, а дані ганялись по ендпоінтам у json форматі. Осьо воно так працює і досі.

Але зараз звідусіль можна почути, що те, як воно живе, наразі терпіти більш не можна і давайте все писати на htmx. Чули про таке? Ось я у своїй бульбашці про нього чую кожен день на ютубах, редітах, твітерах та подкастах. То що ж воно таке?

htmx - це така js-бібліотека, котра дозволяє викинути на мороз фронтендерів і писати інтерактивні сайтики на шаблонах, як у 2010 році. Ну тобто, тобі взагалі не треба думати про js - просто пишеш hx- атрибути у html, а воно все за тебе саме зробить - сходить на бекенд за шматочком html та вставить його на сторінку. Прикольна штука, фігачиш собі шаблончики, вони чудово кешуються, а дані запрошуються лише за необхідністю. Правда якщо стейкхолдери захочуть якихось космічних фіч на фронтенді, щоб все воно блимало і свистіло тоді доведеться щось вигадувати додаткове. А якщо воно справді масово стрельне? Куди всі фронтендери дінуться? 😅

Доречі, у нас у ua домені є маркетплейс, котрий це використовує, не htmx правда, а свою власну аналогічну лібу TwinSpark - це kasta.ua. Зайдіть, відкрийте консоль розробника та подивіться на дані які ганяються мережею. Між іншим, через такий мінімалістичний підхід PageSpeed показує ~66 поінтів на мобайлі, хоча, якщо викинути усю аналітичну мішуру, то буде мабуть усі 90 😅.

Який висновок? Та хз, просто приємно було згадати про часи коли jquery був кращим і єдиним js фреймворком 😂. Ось вам бонусом, якщо щось зацікавило:
- Як працює TwinSpark
- Twinspark.Morph — оновлення HTML без втрати стану
- інтервью з розробником htmx
👍4🔥1
Бажаєте прискорити свій Python скрипт до 1000Х не змінюючи жодного рядку коду? Щоправда не будь який, а лише той що використовує Pandas, ну і звісно лише якщо у вас є Nvidia GPU 😁

Matt Harrison твітнув що розробники у Nvidia зробили пакет которий дозволяє Pandas робити обчислення на GPU. Працює це як у Jupyter так і з консолі командою python -m cudf.pandas yourscript.py. Ось такі справи 🙂
🔥4🥱1
Ледь не забув - сьогодні ж перший день Advent of Code

Не впевнений що мого терпіння вистачить на весь місяць, але перший тиждень-два виконувати ці завдання доволі цікаво 🙂
👍1🔥1
Ліньки мені сьогодні щось писати, тому трохи поділюсь з вами каналами на ютубчику котрі дивлюсь. Але мої смаки специфічні, тому якщо шо - я попередив

anthonywritescode - це канал здебільшого про пайтон, який веде дуже крутий чувак, котрий зараз працює розробником в Sentry
CodeOpinion - тут про всяки архітектурні штучки у форматі коротких відосиків, досить пізнавально
Clear Code - тут мені подобається дивитись 8-годинні відоси про розробку ігор на pygame. Затягує, але не для кожного 😁
DaFluffyPotato - канал фултайм розробника ігор на pygame. Колись я таки перестану прокрастинувати і зроблю свою гру на цьому двіжку)
Coder Space - це скарб, чувак на пітоні витворяє усілякі штуки із графікою, особливо мені сподобалась серія відосів про відтворення DOOM на python 👍
Miziziziz - ще один канал про гейм-девелопмент, але про пайтон там нічого не має. Цікаво дивитись як автор робить різні ігри, бо ідеї для них у нього досить незвичайні
ThePrimeagen - навряд комусь відкрию Америку, але без цього каналу перелік був би не повний. Він працює у Netflix бтв
👍61🔥1
Хм, майже ніколи не мав необхідності використовувати collections.Counter, а сьогодні і в робочому проєкті заюзав бо воно прям лягало класно і у сьогоднішньому завданні Advent of Code також 🤔

Доречі маю звичку час від часу передивлятись доку стдліба пайтона, чи читати доки ліб котрими найчастіше користуюсь. Трапляється знаходити круті штуки про котрі не знав, чи забув.
Ось один з таких цікавих модулів - graphlib. Додали його нещодавно, у 3.9, я тоді реліз ноутс почитав і одразу ж про нього забув, допоки в черговий раз не поліз передивлятись доку.
Автори Piccolo-orm в захваті від такого доповнення 😁
👏3🔥2👍1
І - Іронія

Чувак граючи у https://oskaerik.github.io/theevalgame/ знайшов баг у CPython 😅
🔥3
Нещодавно мав невеличку суперечку з другом стосовно того як по замовчуванню пайтон округлює числа з плаваючою точкою.

Контекст: якщо ви перший раз спробуєте попрацювати із вбудованою функцією round то вас може збентежити що вона працює не так як вас вчили округленню у школі (особливо якщо ви ніколи й на інших мовах не намагались цього зробити). У школі нас вчили що числа які закінчуються на .4 округлюються у меншу, а на .5 у більшу сторону, але пайтон робить щось інше round(1.5) == 2 але round(2.5) також 2.

Так що ж це коїться людоньки? Справа у стандартах. Доречі у 2 версії пайтон round робив це “математично”, але у 3 версії це змінили на метод Rounding half to even, цей метод також відомий як банківське округлення. Він описаний у стандарті IEEE 754 (Standard for Floating-Point Arithmetic) і працює так:

23.5 => 24, −23.5 => −24
24.5 => 24, -24.5 => -24

Цей спосіб згідно стандарту є дефолтним (принаймні у актуальній редакції на момент розробки 3 версії пайтон) 11 сторінка, параграф 4. Rounding:

if the two nearest representable values are near, the one with its least significant bit zero shall be delivered

Це значить, що коли число розташоване точно посередині між двома найближчими значеннями, обирається значення, у якого найменш значущий біт (LSB) дорівнює нулю. Іншими словами, воно округлює до найближчого парного числа. Тобто у випадку із 2.5:
1. Два найближчі цілі числа до 2.5 - це 2 і 3.
2. 2.5 знаходиться точно посередині між цими двома числами.
3. Згідно з правилом, ми перевіряємо LSB (найменш значущий біт) обох чисел 2 і 3.
3.1 Двійкове представлення 2 - це 10, де LSB дорівнює 0.
3.2 Двійкове представлення 3 - це 11, де LSB дорівнює 1.
4. Оскільки правило зазначає обирати число з LSB, який дорівнює нулю, 2.5 округлюється до 2.

Банківське округлення допомагає уникнути накопичення помилок, які можуть виникнути при систематичному округленні вгору або вниз і як наслідок допомагає досягти більшої точності в середньому по всіх значеннях. Його часто використовують у фінансах, статистиці та аналізі даних та у мовах програмування. Ось тут є гарне пояснення - Is "banker's rounding" really more numerically stable?
Цікавий факт: одним із перших його адаптував AppleScript, але після багатьох запитів "зробити округлення таким як я його вчив у школі" вони додали команду round 2.5 rounding as taught in school що є валідною командою у Apple Script 😁

Ще у мовах програмування також використовують Rounding half away from zero, тобто так як і вказано у назві - до першого числа дальшого від нуля. Це працює схоже на Rounding half to even але без винятку для парних чисел:

23.5 => 24, −23.5 => −24
24.5 => 25, -24.5 => -25


Rounding half up по іншому округлює числа зі знаком мінус:

23.5 => 24, −23.5 => −23
24.5 => 25, -24.5 => -24


Мені стало цікаво а як у інших, чи може пайтон тут один так вирішив виділитись. Ось що мені вдалось знайти - перелік мов по обраному дефолтному округленню:

Rounding half to even: Python3, .NET, C#, Haskell, R, Delphi
Rounding half away from zero: C, С++, Objective C, MATLAB, Swift, Perl, Go, Ruby, Rust, Python2
Rounding half up: PHP, JavaScript, Java

Тож що ми дізнались з цього тексту? По-перше це те що не всі слідують стандартам (і ще і дивуються). По-друге, що коли вас просять округлити число - завжди питайте яким методом. Ще мабуть розумним було б зберігати числа у тому виді у якому вони є, а округлювати лише тоді коли віддаєте ці дані у UI.

Ну і якщо вам раптом треба округлити число у пайтоні якимось іншим методом - ви завжди можете це зробити за допомогою стдліба

Лінки:
- Is "banker's rounding" really more numerically stable?
- How .NET's Math.Round has Nothing to do with Maths. And That's OK!
- Python 3.x rounding behavior
- IEEE Standard for Binary Floating-Point Arithmetic
🔥6👍3
import __hello__
Ледь не забув - сьогодні ж перший день Advent of Code Не впевнений що мого терпіння вистачить на весь місяць, але перший тиждень-два виконувати ці завдання доволі цікаво 🙂
Ну що, івент скінчився, а мене цьогоріч вистачило на вдвічі більший період ніж минулого року 😁
Якщо у минулому році я знудився вже на 12 день, то у цей раз я майже всі завдання зробив, лише з 21 дня не виконував 2 частину бо почались NP-Hard задачі, а я таке не дуже люблю вирішувати.

Але свою долю новорічного настрою я отримав, як і трохи нових знань (котрі ніколи на практиці не застосую звісно). Окреме задоволення продивлятись комуніті івенту на редіті.
Коротше мені сподобалось, треба буде у наступному році повторити. 🎄
👍4🔥1
Хочу поділитись невеличким переліком проблем які я найчастіше бачу у тестах

1. Робити мок того що тобі не належить, наприклад http-клієнта

Коли ви мокаєте зовнішній компонент, ви замінюєте його спрощеною версією, яка може не відображати його реальну поведінку. Це призведе до того, що коли у реальному середовищі виникнуть проблеми - ваші тести всеодно будуть зеленими.

2. Мокати через звичайний Mock() а не через create_autospec та альтернативи

Звичайний Mock() не відтворює інтерфейс того що ви мокаєте. Це означає, що ви можете викликати методи, які не існують у реальному об'єкті, або передати неправильну кількість аргументів у функцію чи метод. Тобто, якщо у замоканій функції зміняться аргументи, ваші тести продовжать бути зеленими, хоча реальний код буде падати з помилкою. Якщо мокаєте щось то робіть це зі спеціфікацією

3. Не використовувати фабрику для створення обʼєктів, сервісів, тощо

У кожному тесті створювати обʼєкт напряму через його конструктор, ще й кожного разу окремо мокати його залежності. Натомість краще зробити окрему функцію що буде інстанціювати обʼєкт, або скористатись паттерном Object Mother

4. Вказувати у сетапі неважливі для цього тесту аргументи

Це є наслідком попереднього пункту, коли ви інстанціюєте обʼєкт без фабрики у котрої вже задані дефолтні аргументи то не завжди зрозуміло які саме дані важливі для цього тесту. Ось наприклад:


def test_display_track_author_unknown_if_none():
sut = Track(
name="Icky Thump",
year=2007,
album="Icky Thump",
artist=None
)

result = sut.for_display() # Icky Thump - Icky Thump (2007) - Unknown Artist

assert result.endswith(" - Unknown Artist")


У Track усі аргументи є обовʼязковими, тож ми не можемо інстанціювати його лише із важлими для цього тесту аргументами. І це лише синтетичний приклад, у реальних проєктах аргументів може бути значно більше, до того ж частина з них може бути іншими сервісами котрі також треба інстанціювати. Було б краще якби ми таки мали фабріку для цього:


def make_track(
name: str = "Icky Thump",
year: int = 2007,
album: str = "Icky Thump",
artist: str | None = None,
) -> Track:
return Track(name=name, year=year, album=album, artist=artist)


def test_display_track_author_unknown_if_none():
sut = make_track(artist=None)

result = sut.for_display()

assert result.endswith(" - Unknown Artist")

У такому разі сетап тесту став значно простіше і ми інстанціюємо обʼєкт лише з важливими даними для нашого тесту

5. Не вказувати у сетапи важливі для цього тесту аргументи

Це якби ми у минулому прикладі замість sut = make_track(artist=None) написали sut = make_track(), адже всеодно у фабриці artist й так по-замовчуванню None. Так робити не треба бо знову ж таки не ясно які дані для цього тесту важливі, а які ні. Вказуючи artist=None явно ми показуємо від яких даних залежить результат

6. Відзеркалювати структуру проєкта у структурі тестів

Дуже часто бачу що струкруту проєкта відзеркалюють у тестах, тобто якщо те що ви тестуєте має шлях app.music_library.services.tracks.suggestions.SuggestionService то класти тест для цього тесту по такому самому шляху у модулі tests не треба. Це а) не зручно, б) якщо ви будете змінювати структуру діректорій проєкта, вам доведеться те саме зробити і з модулем тестів (але звісно ви про це забудете і все стане ще гірше)
Гарний приклад того як треба робити це Django - у них пласка структура модуля tests у якій зручно орієнтуватись

7. Назви тестів які не дають представлення про те що ми тестуємо

Ну тут все зрозуміло. Це якби у прикладі із автором треку тест би називався test_unknown_artist, або взагалі по назві методу test_for_display

Лінки:
- https://docs.python.org/3/library/unittest.mock.html#unittest.mock.create_autospec
- https://martinfowler.com/bliki/ObjectMother.html
- https://github.com/django/django/tree/main/tests
👍6
import __hello__
Хочу поділитись невеличким переліком проблем які я найчастіше бачу у тестах 1. Робити мок того що тобі не належить, наприклад http-клієнта Коли ви мокаєте зовнішній компонент, ви замінюєте його спрощеною версією, яка може не відображати його реальну поведінку.…
Ось ще вам дуже старий повчальний комікс про те що треба робити коли ви знаходите багу.

Перед тим як виправляти баг спитайте себе:
1. Чи допускав я цю помилку деінде?
2. Що станеться, коли я виправлю цей баг?
3. Що я можу зробити, щоб цей баг не виник знову?

Лінки:
- The day I started believing in Unit Tests
- Three Questions About Each Bug You Find
👍5
Тепер я цифровий археолог

Читав про історію пайтона, як він змінювався, як ставав популярним і зацікавився а які ж були його перші версії.

Погуглив сорці та збілдив усе у докері, ось тут можна подивитись. На диво, скомпілювати проєкт 30-річної давнини виявилось відносно легко.

У підсумку для тесту я зібрав 3 версії:
- 0.9.1 (1991), тому що цікаво якою була найперша публічна версія Python (звісно першою була 0.9.0, але різниця лише у один патч)
- 1.0.1 (1994), бо цікаво що змінилось і яким був перший мажорний реліз
- 1.6.1 (2000), щоб подивитись на останній реліз 1 версії

Отже що мені здалось цікавим:

Привітання різне у всіх версіях:

0.9.1

>>>

1.0.1

Python 1.0.1 (Jan 8 2024)
Copyright 1991-1994 Stichting Mathematisch Centrum, Amsterdam
>>>

1.6.1

Python 1.6.1 (#1, Jan 8 2024, 18:22:00) [GCC 4.7.4] on linux6
Copyright (c) 1995-2001 Corporation for National Research Initiatives.
All Rights Reserved.
Copyright (c) 1991-1995 Stichting Mathematisch Centrum, Amsterdam.
All Rights Reserved.
>>>


Юнікод у змінних не підтримується (принаймні у REPL):

>>> ї = 1
Parsing error: file <stdin>, line 1:
��ї = 1
^
Unhandled exception: run-time error: syntax error
>>>


help у REPL ще немає ніде, але exit у 1.6.1 у наявності

>>> exit
'Use Ctrl-D (i.e. EOF) to exit.'


getattr, setattr наявні вже у 1.0.1, але delattr відсутній (у 1.6.1 вже є)

Синтаксису import foo as bar ще не існує, проте вже є from foo import *

Немає bool, замість нього використовують 1 та 0

Особливості 0.9.1 (наявні лише у цій версії):
- не сприймає " лапки, видає syntax error
- при написанні класу треба обовʼзково вказувати () навіть якщо у нього немає суперкласу (class Foo():)
- __init__ ще не існує
- щось з наслідуванням, може його немає, бо методи суперкласу не доступні у його спадкоємцях
- ще немає and, or та is

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

Далі я вирішив зробити більш показовий тест - узяв код який написав на 3.11 коли вирішував AOC і спробував його портувати на старі версії поступово.

Про очевідні речі типу f-strings чи відсутніх модулів писати не буду, хто захоче - дивіться gist, там більш докладно.

З 3.11 на 1.6.1:
- Генератори та включення зʼявились лише у 2 😢
- Змінної __file__ ще не існує
- Синтаксис i += 1 ще не підтримується
- __eq__ та __slots__ також немає

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

З 1.6.1 на 1.0.1:
- Мультилайн стрінги ще не завезли
- Стрінги здається що взагалі не обʼєкти, у них немає жодних методів чи атрибутів, усі операції над ними треба робити через модуль string
- int("42") не працює, замість цього string.atoi("42")
- Розпаковка (a, b = func()) працює лише з таплами, а функції tuple() немає 😁
- Немає assert
- У "%s" % (val,) val не може бути числом, параметри не приводяться автоматично до типу string
- Виключення це звичайні строки, тож user-defined виключення це raise "My exception", ValueError (навідміну від Exception) існує, але це звичайна змінна яка зберігає строку

Портувати код на 1.0.1 вже було більшим викликом, довелось написати свої примітивні асерти, та підгледіти функцію tuple

Написання коду на 1.0 мені здалось схожим на написання коду на Go 😁. У тому сенсі що стдліб малий, сахару мало, багато дуже простих речей доводиться писати самому. Але як для релізу якому 30 років, ну прям дуже непогано.

Цікаво що перший реліз мови не так вже й кардинально відрізняється від актуального сьогодні. Багато концепцій існувало від самого початку і її розвиток відчувається як поступова еволюція. І я тепер розумію звідки у стдлібі зʼявився модуль string (він доступний досі) і нащо він треба був 😁

Якщо хтось хоче сам потикати у раритет - можете скористатись імейджами що я створив

Про історію Python:
- A brief history of Python
- How Python Became the Popular Choice
🔥8👍3🤓3🥰1
Clean Architecture isn't understood by article authors

Один із улюблених постів на реддіті про помилки які найчастіше допускають автори статей про Clean Architecture. У коментарях зазначили що це мабуть через те що Роберт Мартін поганий письменник (і поганий комунікатор, і погана людина, і поганий гравець у гольф 😄)

Тож перелік того що найчастіше люди неправильно розуміють у CA:

- Контролери (або Презентери або ViewModels) зовсім не теж саме що і Use-Case Інтерактори. Контролери не мають містити логіки, а мають просто викликати методи Інтеракторів. Єдина логіка що вони можуть містити - мапити дата обʼєкти контролера у доменні ентіті.
- Юніт-тести майже виключно повинні писатися для Інтеракторів (а не для кожного маленького класу).
- Юніт-тести мають заміняти вам Acceptance та Функціональні тести. Ви не повинні писати E2E UI тести якщо використовуєте Clean Architecture. Юніт-тест повинен безпосередньо описувати користувацьку історію. Кожен крок у цій історії це "unit".
- Єдині моки що ви використовуєте у своїх юніт-тестах мають бути для output портів, що зазвичай означає DAO інтерфейси.
- Юніт-тести ніколи не повинні запускати код адаптерів. Дивись попередній пункт (здається що більшість це розуміють).
- Інтеграційні тести потрібні лише для перевірки ваших адаптерів, а не тестування бізнес-логіки.
- Автоматизовані тести GUI (наприклад, selenium) повинні писатись лише як smoke-тести. Вони просто перевіряють, що все працює разом, як супер-інтеграційний тест. Таких тестів повинно бути дуже мало.
- Дата об'єкти специфічні для Адаптерів не повинні перетинати межі портів (port boundaries). Наприклад, модель ORM обʼєкту не повинна бути доступна для об'єкта сервісу або Інтерактора. Інтерфейс DAO (тобто порт) повинен повертати доменні сутності замаплені з сутностей БД (здається що більшість це розуміють).
- Домен, сутності та інтерактори не повинні бути побудовані фреймворками. Вони повинні бути простими об'єктами.
- Звичайно, можуть бути прагматичні винятки з усього вищезазначеного, але мінімально.

Усе вищесказане також валідне й для Гексагональної Архітектури і архітектури Портів та Адаптерів, так як вони майже ідентичні CA.
👍7
Усі ж чули про ruff? Це такий лінтер all-in-one для пайтону написаний на Rust. Головна його конкурентна фішка - це швидкість, він дійсно значно швидший ніж будь який інший лінтер для пайтону, вміє автоматично фіксити код, а ще у нього можливо навіть інтегрують black та mypy.

Останній рік він був на хайпі - новини, твіттер, різні проєкти переводили на нього свої CI (один із таких проєктів FastAPI). А нещодавно автори цього лінтера заснували свою компанію Astral яка одразу ж підняла 4 мільйони доларів.

Мені уся ця метушня навкого ruff не подобається з таких причин:

1. Автори беруть вже існуючі лінтери та переписують їх на Rust (не хочу вчити раст щоб писати собі тулінг).
2. Плагіни для flake8 продовжують розвиватись і оновлюватись і ruff не завжди встигає за цим стежити. Та й взагалі імплементація у ruff може відрізнятись від оригіналу чи не містити частини функцій
3. Навідміну від flake8 ця штука моноліт і не має системи плагінів. Тобто якщо для flake8 я можу написати свій плагін, опублікувати його у pypi, використовувати й росповсюджувати, то для ruff мені треба робити МР у їх проєкт, чекати апрувів (якщо вони будуть) і чекати поки то потрапить у нову версію
4. Найбільша моя претензія - Python розробники не можуть вносити вклад у лінтер для пайтону, бо він написаний на зовсім іншій мові (дивись попередній пункт, навіть плагіни писати не можуть на расті). А дивлячись на зростаючу популярність ruff у мене це викликає побоювання що через нього flake8 та інші лінтери написані на пайтоні перестануть розвиватись, як наслідок - уся екосистема лінтерів буде залежати від проєкту на расті у котрий контрибьютить 2.5 розробника.

А ось сьогодні Anthony Sottile, мейнтейнер flake8, багатьох плагинів для flake8 і інших різних лінтерів висказав свою думку на рахунок ruff у відео. Якщо коротко:

1. Ruff це дуже вражаюче ПО яке робить багато крутих штук.
2. Автори Ruff скопіювали дуже багато результатів роботи пайтон комьюніті (специфічно частину яка про лінтери) за останні 15-20 років та переписали їх на Rust
3. Попередній пункт звісно не порушує жодних ліцензій (ліцензії коду який за основу взяв ruff це дозволяють)
4. Претензія Anthony у тому що ruff узяв результати роботи і нічого не віддав взамін
5. Більше того, була заснована компанія навколо ruff, яка зібрала 4 мільйони доларів і жодна частина цих коштів не була віддана на спонсорство flake8 чи інших проєктів без яких ruff просто би не існувало
6. Anthony засмучений через те що ruff "вбив" багато проєктів над якими він працював і він більше не бачить стимулів далі працювати над цими проєктами
7. Ruff це дуже вражаюче ПО, але їх підхід брати результати чиєїсь роботи і не віддавати нічого взамін "kinda sucks"

Звісно автори ruff мали повне право зробити те що вони зробили, але з етичної точки зору це не зовсім ок, і я розумію автора відео.

Я на ruff не спішив переходити з причин описаних у першій частині цього тексту і ще через те що у нього немає деяких плагінів котрими я користуюсь, а швидкість flake8 достатня, особливо якщо використовувати pre-commit котрий вміє запускати лінтери лише для змінених файлів. Тому я на ruff світчитись не планую і сподіваюсь що у flake8 та взагалі у лінтерів для пайтону написаних на пайтоні все буде добре.
👍8
Моноліт, мікросервіси, поліліт?

У сучасному світі розробки поширена думка, що моноліт застарів, а мікросервіси — єдиний правильний шлях побудови систем. Звісно, я трохи перебільшую, тим паче що час, коли кожна поважаюча себе компанія, мала у своєму блозі статтю про перехід від моноліту до 300 і більше мікросервісів, минув. Проте й досі часто розробники при старті нового проєкту одразу починають з мікросервісної архітектури. Але скоріш за все то не дуже гарна ідея і почати варто б було з побудови моноліту.

Бо починаючи новий проєкт, ви ще не знаєте, як правильно вибудувати bounded context і не розумієте меж доменних областей. Тому, у процесі розробки, вони будуть змінюватися: деякі сутності переходитимуть з одного контексту в інший, контексти дробитимуться та об'єднуватимуться, бізнес вимоги будуть постійно змінюватись. Таким чином, ваша система буде інтенсивно еволюціонувати, поки ви шляхом проб та помилок не визначите правильні межі. Такі великі зміни робити коли у тебе все побудовано на мікросервісах досить боляче. Більш доцільно почати з моноліту, а в процесі розробки та експлуатації, коли будете впевнені в правильності визначених bounded contexts, поступово "від'єднувати" частини системи в мікросервіси. Звичайно, не варто створювати мікросервіси лише тому, що так роблять усі. Це має бути обгрунтовано: можливо, частина вашої системи має вище навантаження і, виносячи її в окремий сервіс, ви зможете ефективніше масштабуватись; або в різних частинах системи можуть бути різні SLA та вимоги до безпеки; чи ви хочете передати частину системи в управління окремій команді, тощо. Гарний допис про те чому не варто стартувати з мікросервісів є у Мартіна Фаулера

Проте, ті, хто хоч раз спробував розділити вже існуючий, можливо застарілий, моноліт, скажуть, що це не просто. Сильна сторона моноліта "легкість змін" має зворотній бік — дуже легко побудувати систему із високою зв'язністю (high coupling), через що розбиття моноліту на окремі мікросервіси може бути настільки складним, що простіше переписати систему з нуля. Тому, щоб вирішити цю проблему, розумні люди придумали таку штуку як "модульний моноліт".

Модульний моноліт — це архітектурний прийом, що поєднує елементи монолітної архітектури з модульним підходом. Код розділений на модулі з чіткими межами в рамках одного додатку. Кожен модуль відповідає за певну функціональність і характеризується високою когезією та слабкою зв'язністю з іншими модулями. Слабка зв'язаність досягається завдяки комунікації між модулями через добре пропрацьований публічний API. Модулі можуть взаємодіяти як через прямі виклики методів, так і асинхронно через черги повідомлень, що ускладнює проектування, але спрощує перехід до мікросервісів (у разі необхідності). Кожен модуль представляє окремий bounded context, тож, якщо потрібно розділити моноліт на мікросервіси, це буде легко зробити. Ще один плюс такого підходу - ви можете ділити модулі між командами, тобто одна команда може ексклюзивно овнити один чи декілька модулів.

Більш детальний приклад модульного моноліту можно подивитись тут

Можливо, наступного разу розповім про наявні інструменти для Python, що сприяють побудові модульних монолітів.

Лінки:
- Дуже багато матеріалів по темі
- Monolith First
- Microservices Killer: Modular Monolithic Architecture
- microservices.io
👍131