[{"data":1,"prerenderedAt":15130},["ShallowReactive",2],{"navigation_docs":3,"-python-abstraction-protocols":3379,"-python-abstraction-protocols-surround":15125},[4,1707,1912,2366,2547,2649,2856,2978,3028,3085,3119,3245,3322,3375],{"title":5,"icon":6,"path":7,"stem":8,"children":9},"C#","i-devicon-csharp","\u002Fcsharp","01.csharp",[10,13,60,90,120,202,219,253,379,404,457,650,1364,1654,1703],{"title":11,"path":7,"stem":12},"C# та .NET","01.csharp\u002Findex",{"title":14,"icon":15,"path":16,"stem":17,"children":18,"page":59},"Fundamentals","i-lucide-book-open","\u002Fcsharp\u002Ffundamentals","01.csharp\u002F01.fundamentals",[19,23,27,31,35,39,43,47,51,55],{"title":20,"path":21,"stem":22},"Вступ до екосистеми .NET","\u002Fcsharp\u002Ffundamentals\u002Fintroduction-to-ecosystem","01.csharp\u002F01.fundamentals\u002F01.introduction-to-ecosystem",{"title":24,"path":25,"stem":26},"Структура програми на C#","\u002Fcsharp\u002Ffundamentals\u002Fprogram-structure","01.csharp\u002F01.fundamentals\u002F02.program-structure",{"title":28,"path":29,"stem":30},"Змінні та Типи Даних","\u002Fcsharp\u002Ffundamentals\u002Fvariables-data-types","01.csharp\u002F01.fundamentals\u002F03.variables-data-types",{"title":32,"path":33,"stem":34},"Масиви","\u002Fcsharp\u002Ffundamentals\u002Farrays","01.csharp\u002F01.fundamentals\u002F04.arrays",{"title":36,"path":37,"stem":38},"Strings & Text Handling","\u002Fcsharp\u002Ffundamentals\u002Fstrings-text-handling","01.csharp\u002F01.fundamentals\u002F05.strings-text-handling",{"title":40,"path":41,"stem":42},"Дати і Час","\u002Fcsharp\u002Ffundamentals\u002Fdates-time-handling","01.csharp\u002F01.fundamentals\u002F06.dates-time-handling",{"title":44,"path":45,"stem":46},"Потік Керування","\u002Fcsharp\u002Ffundamentals\u002Fcontrol-flow","01.csharp\u002F01.fundamentals\u002F07.control-flow",{"title":48,"path":49,"stem":50},"Методи","\u002Fcsharp\u002Ffundamentals\u002Fmethods","01.csharp\u002F01.fundamentals\u002F08.methods",{"title":52,"path":53,"stem":54},"Основи Відлагодження","\u002Fcsharp\u002Ffundamentals\u002Fdebugging-basics","01.csharp\u002F01.fundamentals\u002F09.debugging-basics",{"title":56,"path":57,"stem":58},"Інтерактивна Консоль (Classic)","\u002Fcsharp\u002Ffundamentals\u002Finteractive-console","01.csharp\u002F01.fundamentals\u002F10.interactive-console",false,{"title":61,"icon":62,"path":63,"stem":64,"children":65,"page":59},"OOP","i-lucide-box","\u002Fcsharp\u002Foop","01.csharp\u002F02.oop",[66,70,74,78,82,86],{"title":67,"path":68,"stem":69},"Package Management (Управління Пакетами)","\u002Fcsharp\u002Foop\u002Fpackage-management","01.csharp\u002F02.oop\u002F01.package-management",{"title":71,"path":72,"stem":73},"Класи та Об'єкти","\u002Fcsharp\u002Foop\u002Fclasses-objects","01.csharp\u002F02.oop\u002F02.classes-objects",{"title":75,"path":76,"stem":77},"Властивості та Поля","\u002Fcsharp\u002Foop\u002Fproperties-fields","01.csharp\u002F02.oop\u002F03.properties-fields",{"title":79,"path":80,"stem":81},"Стовпи ООП","\u002Fcsharp\u002Foop\u002Foop-pillars","01.csharp\u002F02.oop\u002F04.oop-pillars",{"title":83,"path":84,"stem":85},"Advanced Types","\u002Fcsharp\u002Foop\u002Fadvanced-types","01.csharp\u002F02.oop\u002F05.advanced-types",{"title":87,"path":88,"stem":89},"Namespaces (Простори Імен)","\u002Fcsharp\u002Foop\u002Fnamespaces","01.csharp\u002F02.oop\u002F06.namespaces",{"title":91,"icon":92,"path":93,"stem":94,"children":95,"page":59},"Advanced Core","i-lucide-zap","\u002Fcsharp\u002Fadvanced-core","01.csharp\u002F03.advanced-core",[96,100,104,108,112,116],{"title":97,"path":98,"stem":99},"Generics (Узагальнення)","\u002Fcsharp\u002Fadvanced-core\u002Fgenerics","01.csharp\u002F03.advanced-core\u002F01.generics",{"title":101,"path":102,"stem":103},"Делегати, Події та Лямбда-вирази","\u002Fcsharp\u002Fadvanced-core\u002Fdelegates-events-lambdas","01.csharp\u002F03.advanced-core\u002F02.delegates-events-lambdas",{"title":105,"path":106,"stem":107},"Interfaces Deep Dive (Інтерфейси: Поглиблений Розгляд)","\u002Fcsharp\u002Fadvanced-core\u002Finterfaces-deep-dive","01.csharp\u002F03.advanced-core\u002F03.interfaces-deep-dive",{"title":109,"path":110,"stem":111},"Обробка Винятків","\u002Fcsharp\u002Fadvanced-core\u002Fexception-handling","01.csharp\u002F03.advanced-core\u002F04.exception-handling",{"title":113,"path":114,"stem":115},"Pattern Matching","\u002Fcsharp\u002Fadvanced-core\u002Fpattern-matching","01.csharp\u002F03.advanced-core\u002F05.pattern-matching",{"title":117,"path":118,"stem":119},"Додаткові Можливості C#","\u002Fcsharp\u002Fadvanced-core\u002Fadditional-features","01.csharp\u002F03.advanced-core\u002F06.additional-features",{"title":121,"icon":122,"path":123,"stem":124,"children":125,"page":59},"Architecture Best Practices","i-lucide-building-2","\u002Fcsharp\u002Farchitecture-best-practices","01.csharp\u002F04.architecture-best-practices",[126,130,149,153,157,161,165,169],{"title":127,"path":128,"stem":129},"Software Design Principles (Частина 1)","\u002Fcsharp\u002Farchitecture-best-practices\u002Fsoftware-design-principles","01.csharp\u002F04.architecture-best-practices\u002F01.software-design-principles",{"title":131,"icon":132,"path":133,"stem":134,"children":135,"page":59},"Design Patterns","i-lucide-folder","\u002Fcsharp\u002Farchitecture-best-practices\u002Fdesign-patterns","01.csharp\u002F04.architecture-best-practices\u002F02.design-patterns",[136],{"title":137,"icon":132,"path":138,"stem":139,"children":140,"page":59},"Creational","\u002Fcsharp\u002Farchitecture-best-practices\u002Fdesign-patterns\u002Fcreational","01.csharp\u002F04.architecture-best-practices\u002F02.design-patterns\u002Fcreational",[141,145],{"title":142,"path":143,"stem":144},"Singleton (Одинак)","\u002Fcsharp\u002Farchitecture-best-practices\u002Fdesign-patterns\u002Fcreational\u002Fsingleton","01.csharp\u002F04.architecture-best-practices\u002F02.design-patterns\u002Fcreational\u002F01.singleton",{"title":146,"path":147,"stem":148},"Builder (Будівельник)","\u002Fcsharp\u002Farchitecture-best-practices\u002Fdesign-patterns\u002Fcreational\u002Fbuilder","01.csharp\u002F04.architecture-best-practices\u002F02.design-patterns\u002Fcreational\u002F02.builder",{"title":150,"path":151,"stem":152},"Building Professional CLIs","\u002Fcsharp\u002Farchitecture-best-practices\u002Fbuilding-professional-clis","01.csharp\u002F04.architecture-best-practices\u002F03.building-professional-clis",{"title":154,"path":155,"stem":156},"Validation & Flow Control","\u002Fcsharp\u002Farchitecture-best-practices\u002Fvalidation-flow-control","01.csharp\u002F04.architecture-best-practices\u002F04.validation-flow-control",{"title":158,"path":159,"stem":160},"The Modern .NET Host (Microsoft.Extensions)","\u002Fcsharp\u002Farchitecture-best-practices\u002Fmodern-dotnet-host","01.csharp\u002F04.architecture-best-practices\u002F05.modern-dotnet-host",{"title":162,"path":163,"stem":164},"Data Mapper: Repository та DAO патерни (Частина 1)","\u002Fcsharp\u002Farchitecture-best-practices\u002Fdata-mapper-part1","01.csharp\u002F04.architecture-best-practices\u002F06.data-mapper-part1",{"title":166,"path":167,"stem":168},"Data Mapper: Repository та DAO патерни (Частина 2)","\u002Fcsharp\u002Farchitecture-best-practices\u002Fdata-mapper-part2","01.csharp\u002F04.architecture-best-practices\u002F07.data-mapper-part2",{"title":170,"icon":132,"path":171,"stem":172,"children":173,"page":59},"Di Ioc","\u002Fcsharp\u002Farchitecture-best-practices\u002Fdi-ioc","01.csharp\u002F04.architecture-best-practices\u002F08.di-ioc",[174,178,182,186,190,194,198],{"title":175,"path":176,"stem":177},"Проблема залежностей та Інверсія Контролю","\u002Fcsharp\u002Farchitecture-best-practices\u002Fdi-ioc\u002Fthe-dependency-problem","01.csharp\u002F04.architecture-best-practices\u002F08.di-ioc\u002F01.the-dependency-problem",{"title":179,"path":180,"stem":181},"Будуємо власний Service Container","\u002Fcsharp\u002Farchitecture-best-practices\u002Fdi-ioc\u002Fbuild-your-own-container","01.csharp\u002F04.architecture-best-practices\u002F08.di-ioc\u002F02.build-your-own-container",{"title":183,"path":184,"stem":185},"Service Locator: Паттерн та Анти-паттерн","\u002Fcsharp\u002Farchitecture-best-practices\u002Fdi-ioc\u002Fservice-locator-pattern","01.csharp\u002F04.architecture-best-practices\u002F08.di-ioc\u002F03.service-locator-pattern",{"title":187,"path":188,"stem":189},"Паттерни Dependency Injection","\u002Fcsharp\u002Farchitecture-best-practices\u002Fdi-ioc\u002Fdependency-injection-patterns","01.csharp\u002F04.architecture-best-practices\u002F08.di-ioc\u002F04.dependency-injection-patterns",{"title":191,"path":192,"stem":193},"Microsoft DI: IServiceCollection та IServiceProvider","\u002Fcsharp\u002Farchitecture-best-practices\u002Fdi-ioc\u002Fmicrosoft-di-deep-dive","01.csharp\u002F04.architecture-best-practices\u002F08.di-ioc\u002F05.microsoft-di-deep-dive",{"title":195,"path":196,"stem":197},"Service Lifetimes та Scopes","\u002Fcsharp\u002Farchitecture-best-practices\u002Fdi-ioc\u002Fservice-lifetimes-and-scopes","01.csharp\u002F04.architecture-best-practices\u002F08.di-ioc\u002F06.service-lifetimes-and-scopes",{"title":199,"path":200,"stem":201},"DI Анти-паттерни та Найкращі Практики","\u002Fcsharp\u002Farchitecture-best-practices\u002Fdi-ioc\u002Fdi-anti-patterns-and-best-practices","01.csharp\u002F04.architecture-best-practices\u002F08.di-ioc\u002F07.di-anti-patterns-and-best-practices",{"title":203,"icon":132,"path":204,"stem":205,"children":206,"page":59},"Standard Library","\u002Fcsharp\u002Fstandard-library","01.csharp\u002F05.standard-library",[207,211,215],{"title":208,"path":209,"stem":210},"Collections (Колекції)","\u002Fcsharp\u002Fstandard-library\u002Fcollections","01.csharp\u002F05.standard-library\u002F01.collections",{"title":212,"path":213,"stem":214},"High Performance Types (Високопродуктивні Типи)","\u002Fcsharp\u002Fstandard-library\u002Fhigh-performance-types","01.csharp\u002F05.standard-library\u002F02.high-performance-types",{"title":216,"path":217,"stem":218},"LINQ (Language Integrated Query)","\u002Fcsharp\u002Fstandard-library\u002Flinq","01.csharp\u002F05.standard-library\u002F03.linq",{"title":220,"icon":221,"path":222,"stem":223,"children":224,"page":59},"System Internals Concurrency","i-lucide-server","\u002Fcsharp\u002Fsystem-internals-concurrency","01.csharp\u002F06.system-internals-concurrency",[225,229,233,237,241,245,249],{"title":226,"path":227,"stem":228},"Memory Management","\u002Fcsharp\u002Fsystem-internals-concurrency\u002Fmemory-management","01.csharp\u002F06.system-internals-concurrency\u002F01.memory-management",{"title":230,"path":231,"stem":232},"Reflection API: System.Type та Метадані","\u002Fcsharp\u002Fsystem-internals-concurrency\u002Freflection-fundamentals","01.csharp\u002F06.system-internals-concurrency\u002F02.reflection-fundamentals",{"title":234,"path":235,"stem":236},"Attributes та Dynamic Language Runtime","\u002Fcsharp\u002Fsystem-internals-concurrency\u002Fattributes-dynamic","01.csharp\u002F06.system-internals-concurrency\u002F03.attributes-dynamic",{"title":238,"path":239,"stem":240},"Expression Trees: Швидка Альтернатива Рефлексії","\u002Fcsharp\u002Fsystem-internals-concurrency\u002Fexpression-trees-compiled","01.csharp\u002F06.system-internals-concurrency\u002F04.expression-trees-compiled",{"title":242,"path":243,"stem":244},"Source Generators: Compile-Time Code Generation","\u002Fcsharp\u002Fsystem-internals-concurrency\u002Fsource-generators","01.csharp\u002F06.system-internals-concurrency\u002F05.source-generators",{"title":246,"path":247,"stem":248},"Multithreading Fundamentals","\u002Fcsharp\u002Fsystem-internals-concurrency\u002Fmultithreading-fundamentals","01.csharp\u002F06.system-internals-concurrency\u002F06.multithreading-fundamentals",{"title":250,"path":251,"stem":252},"Synchronization Primitives","\u002Fcsharp\u002Fsystem-internals-concurrency\u002Fsynchronization-primitives","01.csharp\u002F06.system-internals-concurrency\u002F07.synchronization-primitives",{"title":254,"icon":255,"path":256,"stem":257,"children":258,"page":59},"System Programming Windows","i-lucide-cpu","\u002Fcsharp\u002Fsystem-programming-windows","01.csharp\u002F07.system-programming-windows",[259,263,267,271,275,279,283,287,291,295,299,303,307,311,315,319,323,327,331,335,339,343,347,351,355,359,363,367,371,375],{"title":260,"path":261,"stem":262},"Як Працює Операційна Система","\u002Fcsharp\u002Fsystem-programming-windows\u002Fhow-os-works","01.csharp\u002F07.system-programming-windows\u002F01.how-os-works",{"title":264,"path":265,"stem":266},"Процеси в .NET — API та Запуск","\u002Fcsharp\u002Fsystem-programming-windows\u002Fprocesses-in-dotnet","01.csharp\u002F07.system-programming-windows\u002F02.processes-in-dotnet",{"title":268,"path":269,"stem":270},"Процеси в .NET — IPC та Міжпроцесна Комунікація","\u002Fcsharp\u002Fsystem-programming-windows\u002F02a.processes-ipc","01.csharp\u002F07.system-programming-windows\u002F02a.processes-ipc",{"title":272,"path":273,"stem":274},"Application Domains та Збірки — AppDomain і AssemblyLoadContext","\u002Fcsharp\u002Fsystem-programming-windows\u002Fappdomains-assemblies","01.csharp\u002F07.system-programming-windows\u002F03.appdomains-assemblies",{"title":276,"path":277,"stem":278},"Application Domains та Збірки — Plug-in Система з Hot-Reload","\u002Fcsharp\u002Fsystem-programming-windows\u002F03a.appdomains-plugin-system","01.csharp\u002F07.system-programming-windows\u002F03a.appdomains-plugin-system",{"title":280,"path":281,"stem":282},"Потоки — Основи та API Thread","\u002Fcsharp\u002Fsystem-programming-windows\u002Fthread-fundamentals","01.csharp\u002F07.system-programming-windows\u002F04.thread-fundamentals",{"title":284,"path":285,"stem":286},"Потоки — Lifecycle, Пріоритети та Безпечне Завершення","\u002Fcsharp\u002Fsystem-programming-windows\u002F04a.thread-lifecycle-priorities","01.csharp\u002F07.system-programming-windows\u002F04a.thread-lifecycle-priorities",{"title":288,"path":289,"stem":290},"Проблеми Спільного Стану — Race Condition та Data Race","\u002Fcsharp\u002Fsystem-programming-windows\u002Fshared-state-problems","01.csharp\u002F07.system-programming-windows\u002F05.shared-state-problems",{"title":292,"path":293,"stem":294},"Проблеми Спільного Стану — Memory Model та volatile","\u002Fcsharp\u002Fsystem-programming-windows\u002F05a.shared-state-memory-model","01.csharp\u002F07.system-programming-windows\u002F05a.shared-state-memory-model",{"title":296,"path":297,"stem":298},"Синхронізація — Monitor, lock та еволюція примітивів","\u002Fcsharp\u002Fsystem-programming-windows\u002Fsynchronization-fundamentals","01.csharp\u002F07.system-programming-windows\u002F06.synchronization-fundamentals",{"title":300,"path":301,"stem":302},"Синхронізація — Наскрізний Приклад та Deadlock Detection","\u002Fcsharp\u002Fsystem-programming-windows\u002F06a.synchronization-walkthrough","01.csharp\u002F07.system-programming-windows\u002F06a.synchronization-walkthrough",{"title":304,"path":305,"stem":306},"Синхронізація — Mutex, Semaphore та Event-Based Primitives","\u002Fcsharp\u002Fsystem-programming-windows\u002Fsynchronization-advanced","01.csharp\u002F07.system-programming-windows\u002F07.synchronization-advanced",{"title":308,"path":309,"stem":310},"Синхронізація — Interlocked, Volatile та Lock-Free Структури","\u002Fcsharp\u002Fsystem-programming-windows\u002F07a.synchronization-advanced-walkthrough","01.csharp\u002F07.system-programming-windows\u002F07a.synchronization-advanced-walkthrough",{"title":312,"path":313,"stem":314},"Interlocked, CAS та Lock-Free Структури","\u002Fcsharp\u002Fsystem-programming-windows\u002Finterlocked-cas-lockfree","01.csharp\u002F07.system-programming-windows\u002F08.interlocked-cas-lockfree",{"title":316,"path":317,"stem":318},"Volatile, Memory Model та Spinning","\u002Fcsharp\u002Fsystem-programming-windows\u002F08a.volatile-memory-model","01.csharp\u002F07.system-programming-windows\u002F08a.volatile-memory-model",{"title":320,"path":321,"stem":322},"ThreadPool — Пул Потоків для Ефективного Виконання","\u002Fcsharp\u002Fsystem-programming-windows\u002Fthread-pool","01.csharp\u002F07.system-programming-windows\u002F09.thread-pool",{"title":324,"path":325,"stem":326},"ThreadPool — Просунуті Сценарії та Внутрішня Будова","\u002Fcsharp\u002Fsystem-programming-windows\u002F09a.thread-pool-advanced","01.csharp\u002F07.system-programming-windows\u002F09a.thread-pool-advanced",{"title":328,"path":329,"stem":330},"Concurrent та Immutable Collections","\u002Fcsharp\u002Fsystem-programming-windows\u002Fconcurrent-collections","01.csharp\u002F07.system-programming-windows\u002F10.concurrent-collections",{"title":332,"path":333,"stem":334},"TPL, Task та Композиція — Від Thread до Task","\u002Fcsharp\u002Fsystem-programming-windows\u002Ftpl-parallel-plinq","01.csharp\u002F07.system-programming-windows\u002F11.tpl-parallel-plinq",{"title":336,"path":337,"stem":338},"Parallel Class та PLINQ — Data Parallelism","\u002Fcsharp\u002Fsystem-programming-windows\u002F11a.tpl-parallel-plinq-advanced","01.csharp\u002F07.system-programming-windows\u002F11a.tpl-parallel-plinq-advanced",{"title":340,"path":341,"stem":342},"Async\u002FAwait — Фундамент Асинхронного Програмування","\u002Fcsharp\u002Fsystem-programming-windows\u002Fasync-fundamentals","01.csharp\u002F07.system-programming-windows\u002F12.async-fundamentals",{"title":344,"path":345,"stem":346},"SynchronizationContext та ConfigureAwait — Контекст Виконання","\u002Fcsharp\u002Fsystem-programming-windows\u002Fasync-context-configureawait","01.csharp\u002F07.system-programming-windows\u002F13.async-context-configureawait",{"title":348,"path":349,"stem":350},"Async — Просунуті Паттерни","\u002Fcsharp\u002Fsystem-programming-windows\u002Fasync-advanced","01.csharp\u002F07.system-programming-windows\u002F14.async-advanced",{"title":352,"path":353,"stem":354},"System.Threading.Channels — Async Producer-Consumer","\u002Fcsharp\u002Fsystem-programming-windows\u002Fchannels","01.csharp\u002F07.system-programming-windows\u002F15.channels",{"title":356,"path":357,"stem":358},"Асинхронна Синхронізація","\u002Fcsharp\u002Fsystem-programming-windows\u002Fasync-synchronization","01.csharp\u002F07.system-programming-windows\u002F16.async-synchronization",{"title":360,"path":361,"stem":362},"Unsafe Code та Вказівники","\u002Fcsharp\u002Fsystem-programming-windows\u002Funsafe-code","01.csharp\u002F07.system-programming-windows\u002F17.unsafe-code",{"title":364,"path":365,"stem":366},"P\u002FInvoke та Windows API — Міст між .NET та Native Code","\u002Fcsharp\u002Fsystem-programming-windows\u002Fpinvoke-winapi","01.csharp\u002F07.system-programming-windows\u002F18.pinvoke-winapi",{"title":368,"path":369,"stem":370},"Реєстр Windows — Центральна База Конфігурації Системи","\u002Fcsharp\u002Fsystem-programming-windows\u002Fwindows-registry","01.csharp\u002F07.system-programming-windows\u002F19.windows-registry",{"title":372,"path":373,"stem":374},"Windows Hooks, Hotkeys та Services — Глибока Інтеграція з ОС","\u002Fcsharp\u002Fsystem-programming-windows\u002Fwindows-hooks-services","01.csharp\u002F07.system-programming-windows\u002F20.windows-hooks-services",{"title":376,"path":377,"stem":378},"Системне Програмування C# (Windows) — 07.system-programming-windows","\u002Fcsharp\u002Fsystem-programming-windows\u002Fimplementation_plan","01.csharp\u002F07.system-programming-windows\u002Fimplementation_plan",{"title":380,"icon":132,"path":381,"stem":382,"children":383,"page":59},"Io","\u002Fcsharp\u002Fio","01.csharp\u002F08.io",[384,388,392,396,400],{"title":385,"path":386,"stem":387},"8.1.1. Основи роботи з файловою системою","\u002Fcsharp\u002Fio\u002Ffile-system-basics","01.csharp\u002F08.io\u002F01.file-system-basics",{"title":389,"path":390,"stem":391},"8.1.2. Потоки (Streams) та Серіалізація Даних","\u002Fcsharp\u002Fio\u002Fstreams-serialization","01.csharp\u002F08.io\u002F02.streams-serialization",{"title":393,"path":394,"stem":395},"8.2.1. JSON Serialization з System.Text.Json","\u002Fcsharp\u002Fio\u002Fjson-serialization","01.csharp\u002F08.io\u002F03.json-serialization",{"title":397,"path":398,"stem":399},"8.2.2. XML Serialization та LINQ to XML","\u002Fcsharp\u002Fio\u002Fxml-serialization","01.csharp\u002F08.io\u002F04.xml-serialization",{"title":401,"path":402,"stem":403},"8.2.3. Binary Serialization: MessagePack та Protocol Buffers","\u002Fcsharp\u002Fio\u002Fbinary-serialization","01.csharp\u002F08.io\u002F05.binary-serialization",{"title":405,"icon":132,"path":406,"stem":407,"children":408,"page":59},"Ado Net","\u002Fcsharp\u002Fado-net","01.csharp\u002F09.ado-net",[409,413,417,421,425,429,433,437,441,445,449,453],{"title":410,"path":411,"stem":412},"9.1. Введення в ADO.NET","\u002Fcsharp\u002Fado-net\u002Fintroduction-to-adonet","01.csharp\u002F09.ado-net\u002F01.introduction-to-adonet",{"title":414,"path":415,"stem":416},"9.2. Клас DbConnection — з'єднання з базою даних","\u002Fcsharp\u002Fado-net\u002Fconnection","01.csharp\u002F09.ado-net\u002F02.connection",{"title":418,"path":419,"stem":420},"9.3. Клас DbCommand — виконання SQL-запитів","\u002Fcsharp\u002Fado-net\u002Fcommand-and-queries","01.csharp\u002F09.ado-net\u002F03.command-and-queries",{"title":422,"path":423,"stem":424},"9.4. Клас DbDataReader — ефективне читання даних","\u002Fcsharp\u002Fado-net\u002Fdatareader","01.csharp\u002F09.ado-net\u002F04.datareader",{"title":426,"path":427,"stem":428},"9.5. Параметризовані запити та захист від SQL Injection","\u002Fcsharp\u002Fado-net\u002Fparameters-and-sql-injection","01.csharp\u002F09.ado-net\u002F05.parameters-and-sql-injection",{"title":430,"path":431,"stem":432},"9.6. Транзакції в ADO.NET","\u002Fcsharp\u002Fado-net\u002Ftransactions","01.csharp\u002F09.ado-net\u002F06.transactions",{"title":434,"path":435,"stem":436},"9.7. DbProviderFactory — провайдер-незалежний код","\u002Fcsharp\u002Fado-net\u002Fprovider-factory","01.csharp\u002F09.ado-net\u002F07.provider-factory",{"title":438,"path":439,"stem":440},"9.8. Асинхронний доступ до даних","\u002Fcsharp\u002Fado-net\u002Fasync-data-access","01.csharp\u002F09.ado-net\u002F08.async-data-access",{"title":442,"path":443,"stem":444},"9.9. Від'єднаний режим: DataSet, DataTable, DataRow","\u002Fcsharp\u002Fado-net\u002Fdisconnected-mode-dataset","01.csharp\u002F09.ado-net\u002F09.disconnected-mode-dataset",{"title":446,"path":447,"stem":448},"9.10. DataAdapter — міст між DataSet та базою даних","\u002Fcsharp\u002Fado-net\u002Fdata-adapter","01.csharp\u002F09.ado-net\u002F10.data-adapter",{"title":450,"path":451,"stem":452},"9.11. Data Mapper та Repository: Архітектура доступу до даних","\u002Fcsharp\u002Fado-net\u002Fdata-mapper-repository","01.csharp\u002F09.ado-net\u002F11.data-mapper-repository",{"title":454,"path":455,"stem":456},"9.12. Identity Map, Unit of Work та Specification Pattern","\u002Fcsharp\u002Fado-net\u002Fadvanced-patterns","01.csharp\u002F09.ado-net\u002F12.advanced-patterns",{"title":458,"icon":255,"path":459,"stem":460,"children":461,"page":59},"Ef Core","\u002Fcsharp\u002Fef-core","01.csharp\u002F10.ef-core",[462,466,470,474,478,482,486,490,494,498,502,506,510,514,518,522,526,532,538,542,546,550,554,558,562,566,570,574,578,582,586,590,594,598,602,606,610,614,618,622,626,630,634,638,642,646],{"title":463,"path":464,"stem":465},"Що таке ORM? Від SQL до об'єктів","\u002Fcsharp\u002Fef-core\u002Fwhat-is-orm","01.csharp\u002F10.ef-core\u002F01.what-is-orm",{"title":467,"path":468,"stem":469},"Перший проєкт — від нуля до CRUD","\u002Fcsharp\u002Fef-core\u002Ffirst-project","01.csharp\u002F10.ef-core\u002F02.first-project",{"title":471,"path":472,"stem":473},"DbContext — Серце EF Core","\u002Fcsharp\u002Fef-core\u002Fdbcontext-deep-dive","01.csharp\u002F10.ef-core\u002F03.dbcontext-deep-dive",{"title":475,"path":476,"stem":477},"Провайдери баз даних — Архітектура та Вибір СУБД","\u002Fcsharp\u002Fef-core\u002Fdatabase-providers","01.csharp\u002F10.ef-core\u002F04.database-providers",{"title":479,"path":480,"stem":481},"Конвенції EF Core — Магія без конфігурації","\u002Fcsharp\u002Fef-core\u002Fconventions","01.csharp\u002F10.ef-core\u002F05.conventions",{"title":483,"path":484,"stem":485},"Fluent API та Data Annotations — Явна конфігурація моделі","\u002Fcsharp\u002Fef-core\u002Ffluent-api-vs-annotations","01.csharp\u002F10.ef-core\u002F06.fluent-api-vs-annotations",{"title":487,"path":488,"stem":489},"Зв'язки — One-to-One та One-to-Many","\u002Fcsharp\u002Fef-core\u002Frelationships-basics","01.csharp\u002F10.ef-core\u002F07.relationships-basics",{"title":491,"path":492,"stem":493},"Зв'язки Advanced — Many-to-Many та Складні Сценарії","\u002Fcsharp\u002Fef-core\u002Frelationships-advanced","01.csharp\u002F10.ef-core\u002F08.relationships-advanced",{"title":495,"path":496,"stem":497},"Властивості — Типи, Конвертери, Компаратори (Частина 1)","\u002Fcsharp\u002Fef-core\u002Fproperty-configuration-part1","01.csharp\u002F10.ef-core\u002F09.property-configuration-part1",{"title":499,"path":500,"stem":501},"Властивості — Value Comparers, Generators, Shadow Properties (Частина 2)","\u002Fcsharp\u002Fef-core\u002Fproperty-configuration-part2","01.csharp\u002F10.ef-core\u002F09.property-configuration-part2",{"title":503,"path":504,"stem":505},"Складні типи — Owned Types та Complex Types (Частина 1)","\u002Fcsharp\u002Fef-core\u002Fcomplex-types-owned-part1","01.csharp\u002F10.ef-core\u002F10.complex-types-owned-part1",{"title":507,"path":508,"stem":509},"Складні типи — Complex Types, Keyless Entities, Порівняння (Частина 2)","\u002Fcsharp\u002Fef-core\u002Fcomplex-types-owned-part2","01.csharp\u002F10.ef-core\u002F10.complex-types-owned-part2",{"title":511,"path":512,"stem":513},"JSON Columns — Складні дані у JSON (Частина 1)","\u002Fcsharp\u002Fef-core\u002Fjson-columns-part1","01.csharp\u002F10.ef-core\u002F11.json-columns-part1",{"title":515,"path":516,"stem":517},"JSON Columns — Value Comparers, Індекси, Провайдери (Частина 2)","\u002Fcsharp\u002Fef-core\u002Fjson-columns-part2","01.csharp\u002F10.ef-core\u002F11.json-columns-part2",{"title":519,"path":520,"stem":521},"Успадкування — Абстрактні класи та TPH (Частина 1)","\u002Fcsharp\u002Fef-core\u002Finheritance-part1","01.csharp\u002F10.ef-core\u002F12.inheritance-part1",{"title":523,"path":524,"stem":525},"Успадкування — TPT, TPC та Порівняння Стратегій (Частина 2)","\u002Fcsharp\u002Fef-core\u002Finheritance-part2","01.csharp\u002F10.ef-core\u002F12.inheritance-part2",{"title":527,"path":528,"stem":529,"children":530},"Індекси, Обмеження та Схема (Частина 1)","\u002Fcsharp\u002Fef-core\u002Findexes-constraints-part1","01.csharp\u002F10.ef-core\u002F13.indexes-constraints-part1",[531],{"title":527,"path":528,"stem":529},{"title":533,"path":534,"stem":535,"children":536},"Індекси, Обмеження та Схема (Частина 2)","\u002Fcsharp\u002Fef-core\u002Findexes-constraints-part2","01.csharp\u002F10.ef-core\u002F13.indexes-constraints-part2",[537],{"title":533,"path":534,"stem":535},{"title":539,"path":540,"stem":541},"Seed Data — Початкові Дані (Частина 1)","\u002Fcsharp\u002Fef-core\u002Fseeding-part1","01.csharp\u002F10.ef-core\u002F14.seeding-part1",{"title":543,"path":544,"stem":545},"Seed Data — SQL-скрипти, Bogus та Стратегії (Частина 2)","\u002Fcsharp\u002Fef-core\u002Fseeding-part2","01.csharp\u002F10.ef-core\u002F14.seeding-part2",{"title":547,"path":548,"stem":549},"Global Query Filters — Глобальні Фільтри (Частина 1)","\u002Fcsharp\u002Fef-core\u002Fglobal-query-filters-part1","01.csharp\u002F10.ef-core\u002F15.global-query-filters-part1",{"title":551,"path":552,"stem":553},"Global Query Filters — Підводні камені та Інтеграція (Частина 2)","\u002Fcsharp\u002Fef-core\u002Fglobal-query-filters-part2","01.csharp\u002F10.ef-core\u002F15.global-query-filters-part2",{"title":555,"path":556,"stem":557},"LINQ-запити в EF Core (Частина 1)","\u002Fcsharp\u002Fef-core\u002Flinq-queries-part1","01.csharp\u002F10.ef-core\u002F16.linq-queries-part1",{"title":559,"path":560,"stem":561},"LINQ-запити в EF Core (Частина 2)","\u002Fcsharp\u002Fef-core\u002Flinq-queries-part2","01.csharp\u002F10.ef-core\u002F16.linq-queries-part2",{"title":563,"path":564,"stem":565},"Завантаження Пов'язаних Даних (Частина 1)","\u002Fcsharp\u002Fef-core\u002Floading-related-data-part1","01.csharp\u002F10.ef-core\u002F17.loading-related-data-part1",{"title":567,"path":568,"stem":569},"Завантаження Пов'язаних Даних (Частина 2)","\u002Fcsharp\u002Fef-core\u002Floading-related-data-part2","01.csharp\u002F10.ef-core\u002F17.loading-related-data-part2",{"title":571,"path":572,"stem":573},"Raw SQL, Views та Stored Procedures (Частина 1)","\u002Fcsharp\u002Fef-core\u002Fraw-sql-part1","01.csharp\u002F10.ef-core\u002F18.raw-sql-part1",{"title":575,"path":576,"stem":577},"Raw SQL — Stored Procedures, DbFunction та Bulk Operations (Частина 2)","\u002Fcsharp\u002Fef-core\u002Fraw-sql-part2","01.csharp\u002F10.ef-core\u002F18.raw-sql-part2",{"title":579,"path":580,"stem":581},"Продвинуті Запити — Compiled Queries, Bulk та Оптимізація (Частина 1)","\u002Fcsharp\u002Fef-core\u002Fadvanced-queries-part1","01.csharp\u002F10.ef-core\u002F19.advanced-queries-part1",{"title":583,"path":584,"stem":585},"Продвинуті Запити — Query Tags, Bulk та Interceptors (Частина 2)","\u002Fcsharp\u002Fef-core\u002Fadvanced-queries-part2","01.csharp\u002F10.ef-core\u002F19.advanced-queries-part2",{"title":587,"path":588,"stem":589},"Change Tracker — Відстеження Змін (Частина 1)","\u002Fcsharp\u002Fef-core\u002Fchange-tracking-part1","01.csharp\u002F10.ef-core\u002F20.change-tracking-part1",{"title":591,"path":592,"stem":593},"Change Tracker — Графи Об'єктів та Disconnected (Частина 2)","\u002Fcsharp\u002Fef-core\u002Fchange-tracking-part2","01.csharp\u002F10.ef-core\u002F20.change-tracking-part2",{"title":595,"path":596,"stem":597},"Збереження Даних та Транзакції (Частина 1)","\u002Fcsharp\u002Fef-core\u002Fsaving-data-part1","01.csharp\u002F10.ef-core\u002F21.saving-data-part1",{"title":599,"path":600,"stem":601},"Збереження Даних — Concurrency та Outbox (Частина 2)","\u002Fcsharp\u002Fef-core\u002Fsaving-data-part2","01.csharp\u002F10.ef-core\u002F21.saving-data-part2",{"title":603,"path":604,"stem":605},"Конкурентність та Блокування (Частина 1)","\u002Fcsharp\u002Fef-core\u002Fconcurrency-part1","01.csharp\u002F10.ef-core\u002F22.concurrency-part1",{"title":607,"path":608,"stem":609},"Конкурентність — Дедлоки та Queue Processing (Частина 2)","\u002Fcsharp\u002Fef-core\u002Fconcurrency-part2","01.csharp\u002F10.ef-core\u002F22.concurrency-part2",{"title":611,"path":612,"stem":613},"Міграції в EF Core — Основи (Частина 1)","\u002Fcsharp\u002Fef-core\u002Fmigrations-basics-part1","01.csharp\u002F10.ef-core\u002F23.migrations-basics-part1",{"title":615,"path":616,"stem":617},"Міграції в EF Core — Основи (Частина 2)","\u002Fcsharp\u002Fef-core\u002Fmigrations-basics-part2","01.csharp\u002F10.ef-core\u002F23.migrations-basics-part2",{"title":619,"path":620,"stem":621},"Міграції — Просунуті Сценарії (Частина 1)","\u002Fcsharp\u002Fef-core\u002Fmigrations-advanced-part1","01.csharp\u002F10.ef-core\u002F24.migrations-advanced-part1",{"title":623,"path":624,"stem":625},"Міграції — Просунуті Сценарії (Частина 2)","\u002Fcsharp\u002Fef-core\u002Fmigrations-advanced-part2","01.csharp\u002F10.ef-core\u002F24.migrations-advanced-part2",{"title":627,"path":628,"stem":629},"Управління Схемою та Database-First (Частина 1)","\u002Fcsharp\u002Fef-core\u002Fschema-management-part1","01.csharp\u002F10.ef-core\u002F25.schema-management-part1",{"title":631,"path":632,"stem":633},"Управління Схемою та Database-First (Частина 2)","\u002Fcsharp\u002Fef-core\u002Fschema-management-part2","01.csharp\u002F10.ef-core\u002F25.schema-management-part2",{"title":635,"path":636,"stem":637},"Продуктивність EF Core — Основи (Частина 1)","\u002Fcsharp\u002Fef-core\u002Fperformance-fundamentals-part1","01.csharp\u002F10.ef-core\u002F26.performance-fundamentals-part1",{"title":639,"path":640,"stem":641},"Interceptors в EF Core (Частина 1)","\u002Fcsharp\u002Fef-core\u002Finterceptors-part1","01.csharp\u002F10.ef-core\u002F29.interceptors-part1",{"title":643,"path":644,"stem":645},"Interceptors в EF Core — Connection, Transaction та Materialization (Частина 2)","\u002Fcsharp\u002Fef-core\u002Finterceptors-part2","01.csharp\u002F10.ef-core\u002F29.interceptors-part2",{"title":647,"path":648,"stem":649},"План вивчення Entity Framework Core — Повний курс","\u002Fcsharp\u002Fef-core\u002Fimplementation_plan","01.csharp\u002F10.ef-core\u002Fimplementation_plan",{"title":651,"icon":652,"path":653,"stem":654,"children":655,"page":59},"ASP.NET","i-devicon-dotnetcore","\u002Fcsharp\u002Faspnet","01.csharp\u002F11.aspnet",[656,730,791,869,927,941,967,1057,1111,1182,1212,1289,1346],{"title":657,"icon":658,"path":659,"stem":660,"children":661,"page":59},"Minimal API","i-lucide-network","\u002Fcsharp\u002Faspnet\u002Fminimal-api","01.csharp\u002F11.aspnet\u002F01.minimal-api",[662,666,670,674,678,682,686,690,694,698,702,706,710,714,718,722,726],{"title":663,"path":664,"stem":665},"Вступ до ASP.NET та еволюція фреймворку","\u002Fcsharp\u002Faspnet\u002Fminimal-api\u002Fintroduction","01.csharp\u002F11.aspnet\u002F01.minimal-api\u002F01.introduction",{"title":667,"path":668,"stem":669},"Перший додаток на ASP.NET Core","\u002Fcsharp\u002Faspnet\u002Fminimal-api\u002Ffirst-application","01.csharp\u002F11.aspnet\u002F01.minimal-api\u002F02.first-application",{"title":671,"path":672,"stem":673},"WebApplication, Builder та Dependency Injection","\u002Fcsharp\u002Faspnet\u002Fminimal-api\u002Fwebapplication-builder","01.csharp\u002F11.aspnet\u002F01.minimal-api\u002F03.webapplication-builder",{"title":675,"path":676,"stem":677},"Конвеєр запитів та Middleware","\u002Fcsharp\u002Faspnet\u002Fminimal-api\u002Frequest-pipeline-middleware","01.csharp\u002F11.aspnet\u002F01.minimal-api\u002F04.request-pipeline-middleware",{"title":679,"path":680,"stem":681},"Маршрутизація в ASP.NET Core: Основи","\u002Fcsharp\u002Faspnet\u002Fminimal-api\u002Frouting-basics","01.csharp\u002F11.aspnet\u002F01.minimal-api\u002F05.routing-basics",{"title":683,"path":684,"stem":685},"Маршрутизація в ASP.NET Core: Розширені можливості","\u002Fcsharp\u002Faspnet\u002Fminimal-api\u002Frouting-advanced","01.csharp\u002F11.aspnet\u002F01.minimal-api\u002F06.routing-advanced",{"title":687,"path":688,"stem":689},"Статичні файли в ASP.NET Core","\u002Fcsharp\u002Faspnet\u002Fminimal-api\u002Fstatic-files","01.csharp\u002F11.aspnet\u002F01.minimal-api\u002F07.static-files",{"title":691,"path":692,"stem":693},"Статичні Активи: MapStaticAssets (ASP.NET Core 9.0)","\u002Fcsharp\u002Faspnet\u002Fminimal-api\u002Fstatic-assets","01.csharp\u002F11.aspnet\u002F01.minimal-api\u002F08.static-assets",{"title":695,"path":696,"stem":697},"Конфігурація в ASP.NET Core: Основи","\u002Fcsharp\u002Faspnet\u002Fminimal-api\u002Fconfiguration-fundamentals","01.csharp\u002F11.aspnet\u002F01.minimal-api\u002F09.configuration-fundamentals",{"title":699,"path":700,"stem":701},"Конфігурація: Паттерн Options","\u002Fcsharp\u002Faspnet\u002Fminimal-api\u002Fconfiguration-options","01.csharp\u002F11.aspnet\u002F01.minimal-api\u002F10.configuration-options",{"title":703,"path":704,"stem":705},"Логування в ASP.NET Core: Основи","\u002Fcsharp\u002Faspnet\u002Fminimal-api\u002Flogging-basics","01.csharp\u002F11.aspnet\u002F01.minimal-api\u002F11.logging-basics",{"title":707,"path":708,"stem":709},"Логування: Serilog та Middleware","\u002Fcsharp\u002Faspnet\u002Fminimal-api\u002Flogging-advanced","01.csharp\u002F11.aspnet\u002F01.minimal-api\u002F12.logging-advanced",{"title":711,"path":712,"stem":713},"Управління станом: HttpContext.Items та Cookies","\u002Fcsharp\u002Faspnet\u002Fminimal-api\u002Fstate-management","01.csharp\u002F11.aspnet\u002F01.minimal-api\u002F13.state-management",{"title":715,"path":716,"stem":717},"Стан сесії: Sessions","\u002Fcsharp\u002Faspnet\u002Fminimal-api\u002Fsession-state","01.csharp\u002F11.aspnet\u002F01.minimal-api\u002F14.session-state",{"title":719,"path":720,"stem":721},"Структура проєкту: від хаосу до архітектури","\u002Fcsharp\u002Faspnet\u002Fminimal-api\u002Fproject-structure","01.csharp\u002F11.aspnet\u002F01.minimal-api\u002F15.project-structure",{"title":723,"path":724,"stem":725},"Scalar у Minimal API: повний проєкт і Fluent OpenAPI","\u002Fcsharp\u002Faspnet\u002Fminimal-api\u002Fscalar-openapi-fluent","01.csharp\u002F11.aspnet\u002F01.minimal-api\u002F16.scalar-openapi-fluent",{"title":727,"path":728,"stem":729},"Swagger \u002F Swashbuckle у Minimal API: окремий класичний шлях","\u002Fcsharp\u002Faspnet\u002Fminimal-api\u002Fswagger-swashbuckle","01.csharp\u002F11.aspnet\u002F01.minimal-api\u002F17.swagger-swashbuckle",{"title":731,"icon":658,"path":732,"stem":733,"children":734,"page":59},"API","\u002Fcsharp\u002Faspnet\u002Fapi","01.csharp\u002F11.aspnet\u002F02.api",[735,739,743,747,751,755,759,763,767,771,775,779,783,787],{"title":736,"path":737,"stem":738},"Що таке API. Клієнт-серверна архітектура","\u002Fcsharp\u002Faspnet\u002Fapi\u002Fwhat-is-api","01.csharp\u002F11.aspnet\u002F02.api\u002F01.what-is-api",{"title":740,"path":741,"stem":742},"Формати даних: JSON, XML, TOML та бінарні формати","\u002Fcsharp\u002Faspnet\u002Fapi\u002Fdata-formats","01.csharp\u002F11.aspnet\u002F02.api\u002F02.data-formats",{"title":744,"path":745,"stem":746},"Парадигми API та концепція REST","\u002Fcsharp\u002Faspnet\u002Fapi\u002Fapi-paradigms-rest","01.csharp\u002F11.aspnet\u002F02.api\u002F03.api-paradigms-rest",{"title":748,"path":749,"stem":750},"HTTP-методи, статус-коди та заголовки","\u002Fcsharp\u002Faspnet\u002Fapi\u002Fhttp-methods-status-codes","01.csharp\u002F11.aspnet\u002F02.api\u002F04.http-methods-status-codes",{"title":752,"path":753,"stem":754},"Організація HTTP API за принципами REST","\u002Fcsharp\u002Faspnet\u002Fapi\u002Frest-organizing","01.csharp\u002F11.aspnet\u002F02.api\u002F05.rest-organizing",{"title":756,"path":757,"stem":758},"Номенклатура URL та CRUD-операції","\u002Fcsharp\u002Faspnet\u002Fapi\u002Furl-nomenclature-crud","01.csharp\u002F11.aspnet\u002F02.api\u002F06.url-nomenclature-crud",{"title":760,"path":761,"stem":762},"Правила дизайну: іменування та стандарти","\u002Fcsharp\u002Faspnet\u002Fapi\u002Fapi-design-naming","01.csharp\u002F11.aspnet\u002F02.api\u002F07.api-design-naming",{"title":764,"path":765,"stem":766},"Валідація, ліміти та обробка помилок","\u002Fcsharp\u002Faspnet\u002Fapi\u002Fapi-design-validation","01.csharp\u002F11.aspnet\u002F02.api\u002F08.api-design-validation",{"title":768,"path":769,"stem":770},"Обробка помилок у Minimal API","\u002Fcsharp\u002Faspnet\u002Fapi\u002Ferror-handling-http","01.csharp\u002F11.aspnet\u002F02.api\u002F09.error-handling-http",{"title":772,"path":773,"stem":774},"Ідемпотентність та синхронізація стану","\u002Fcsharp\u002Faspnet\u002Fapi\u002Fidempotency-sync","01.csharp\u002F11.aspnet\u002F02.api\u002F10.idempotency-sync",{"title":776,"path":777,"stem":778},"Пагінація та організація списків","\u002Fcsharp\u002Faspnet\u002Fapi\u002Fpagination-lists","01.csharp\u002F11.aspnet\u002F02.api\u002F11.pagination-lists",{"title":780,"path":781,"stem":782},"Безпека API, кешування та інтернаціоналізація","\u002Fcsharp\u002Faspnet\u002Fapi\u002Fsecurity-auth","01.csharp\u002F11.aspnet\u002F02.api\u002F12.security-auth",{"title":784,"path":785,"stem":786},"Процес проєктування API та документування","\u002Fcsharp\u002Faspnet\u002Fapi\u002Fapi-design-process","01.csharp\u002F11.aspnet\u002F02.api\u002F13.api-design-process",{"title":788,"path":789,"stem":790},"OpenAPI: контракт, специфікація та документація API","\u002Fcsharp\u002Faspnet\u002Fapi\u002Fopenapi","01.csharp\u002F11.aspnet\u002F02.api\u002F14.openapi",{"title":792,"icon":793,"path":794,"stem":795,"children":796,"page":59},"Auth","i-lucide-shield-check","\u002Fcsharp\u002Faspnet\u002Fauth","01.csharp\u002F11.aspnet\u002F03.auth",[797,801,805,809,813,817,821,825,829,833,837,841,845,849,853,857,861,865],{"title":798,"path":799,"stem":800},"Основи аутентифікації та авторизації","\u002Fcsharp\u002Faspnet\u002Fauth\u002Fauth-fundamentals","01.csharp\u002F11.aspnet\u002F03.auth\u002F01.auth-fundamentals",{"title":802,"path":803,"stem":804},"JWT-аутентифікація","\u002Fcsharp\u002Faspnet\u002Fauth\u002Fjwt-authentication","01.csharp\u002F11.aspnet\u002F03.auth\u002F02.jwt-authentication",{"title":806,"path":807,"stem":808},"Авторизація: ролі, політики та resource-based доступ","\u002Fcsharp\u002Faspnet\u002Fauth\u002Fauthorization-policies","01.csharp\u002F11.aspnet\u002F03.auth\u002F03.authorization-policies",{"title":810,"path":811,"stem":812},"Cookie-аутентифікація та ASP.NET Core Identity","\u002Fcsharp\u002Faspnet\u002Fauth\u002Fcookie-auth-identity","01.csharp\u002F11.aspnet\u002F03.auth\u002F04.cookie-auth-identity",{"title":814,"path":815,"stem":816},"JWT + Refresh Tokens (HttpOnly Cookie)","\u002Fcsharp\u002Faspnet\u002Fauth\u002F04b.identity-auth-jwt","01.csharp\u002F11.aspnet\u002F03.auth\u002F04b.identity-auth-jwt",{"title":818,"path":819,"stem":820},"Identity: Підтвердження Email та Скидання Пароля","\u002Fcsharp\u002Faspnet\u002Fauth\u002Fidentity-email-confirmation","01.csharp\u002F11.aspnet\u002F03.auth\u002F05.identity-email-confirmation",{"title":822,"path":823,"stem":824},"Identity: Двофакторна Аутентифікація (2FA)","\u002Fcsharp\u002Faspnet\u002Fauth\u002Fidentity-two-factor","01.csharp\u002F11.aspnet\u002F03.auth\u002F06.identity-two-factor",{"title":826,"path":827,"stem":828},"Identity: Внутрішня Архітектура та Кастомізація","\u002Fcsharp\u002Faspnet\u002Fauth\u002Fidentity-internals","01.csharp\u002F11.aspnet\u002F03.auth\u002F07.identity-internals",{"title":830,"path":831,"stem":832},"OAuth 2.0 та зовнішні провайдери","\u002Fcsharp\u002Faspnet\u002Fauth\u002Foauth-external-providers","01.csharp\u002F11.aspnet\u002F03.auth\u002F08.oauth-external-providers",{"title":834,"path":835,"stem":836},"Безпека на практиці: CORS, HTTPS та захист від атак","\u002Fcsharp\u002Faspnet\u002Fauth\u002Fsecurity-hardening","01.csharp\u002F11.aspnet\u002F03.auth\u002F09.security-hardening",{"title":838,"path":839,"stem":840},"Теорія OAuth 2.0: Поняття, Аналогії та Флоу","\u002Fcsharp\u002Faspnet\u002Fauth\u002Foauth-theory","01.csharp\u002F11.aspnet\u002F03.auth\u002F10.oauth-theory",{"title":842,"path":843,"stem":844},"OIDC, OAuth 2.0 та Keycloak в ASP.NET Core","\u002Fcsharp\u002Faspnet\u002Fauth\u002Foidc-keycloak","01.csharp\u002F11.aspnet\u002F03.auth\u002F10.oidc-keycloak",{"title":846,"path":847,"stem":848},"API Keys аутентифікація в ASP.NET Core","\u002Fcsharp\u002Faspnet\u002Fauth\u002Fapi-keys","01.csharp\u002F11.aspnet\u002F03.auth\u002F11.api-keys",{"title":850,"path":851,"stem":852},"Rate Limiting та Throttling в ASP.NET Core","\u002Fcsharp\u002Faspnet\u002Fauth\u002Frate-limiting","01.csharp\u002F11.aspnet\u002F03.auth\u002F12.rate-limiting",{"title":854,"path":855,"stem":856},"Refresh Token Rotation в ASP.NET Core","\u002Fcsharp\u002Faspnet\u002Fauth\u002Frefresh-token-rotation","01.csharp\u002F11.aspnet\u002F03.auth\u002F13.refresh-token-rotation",{"title":858,"path":859,"stem":860},"Certificate Authentication та mTLS в ASP.NET Core","\u002Fcsharp\u002Faspnet\u002Fauth\u002Fcertificate-auth","01.csharp\u002F11.aspnet\u002F03.auth\u002F14.certificate-auth",{"title":862,"path":863,"stem":864},"RBAC, ABAC та ReBAC в ASP.NET Core","\u002Fcsharp\u002Faspnet\u002Fauth\u002Frbac-abac-rebac","01.csharp\u002F11.aspnet\u002F03.auth\u002F15.rbac-abac-rebac",{"title":866,"path":867,"stem":868},"Multi-tenancy та ізоляція даних в ASP.NET Core","\u002Fcsharp\u002Faspnet\u002Fauth\u002Fmulti-tenancy","01.csharp\u002F11.aspnet\u002F03.auth\u002F16.multi-tenancy",{"title":870,"icon":871,"path":872,"stem":873,"children":874,"page":59},"Нотифікації","i-lucide-bell","\u002Fcsharp\u002Faspnet\u002Fnotifications","01.csharp\u002F11.aspnet\u002F04.notifications",[875,879,883,887,891,895,899,903,907,911,915,919,923],{"title":876,"path":877,"stem":878},"In-App нотифікації через базу даних","\u002Fcsharp\u002Faspnet\u002Fnotifications\u002Fin-app-database-notifications","01.csharp\u002F11.aspnet\u002F04.notifications\u002F01.in-app-database-notifications",{"title":880,"path":881,"stem":882},"Polling: Регулярний запит оновлень","\u002Fcsharp\u002Faspnet\u002Fnotifications\u002Fpolling","01.csharp\u002F11.aspnet\u002F04.notifications\u002F02.polling",{"title":884,"path":885,"stem":886},"Server-Sent Events: Однострімовий push від сервера","\u002Fcsharp\u002Faspnet\u002Fnotifications\u002Fserver-sent-events","01.csharp\u002F11.aspnet\u002F04.notifications\u002F03.server-sent-events",{"title":888,"path":889,"stem":890},"WebSockets: Двостороннє з'єднання в реальному часі","\u002Fcsharp\u002Faspnet\u002Fnotifications\u002Fwebsockets","01.csharp\u002F11.aspnet\u002F04.notifications\u002F04.websockets",{"title":892,"path":893,"stem":894},"SignalR: Абстракція над транспортами реального часу","\u002Fcsharp\u002Faspnet\u002Fnotifications\u002Fsignalr","01.csharp\u002F11.aspnet\u002F04.notifications\u002F05.signalr",{"title":896,"path":897,"stem":898},"Background Services: Фонові задачі в ASP.NET Core","\u002Fcsharp\u002Faspnet\u002Fnotifications\u002Fbackground-services","01.csharp\u002F11.aspnet\u002F04.notifications\u002F06.background-services",{"title":900,"path":901,"stem":902},"Web Push нотифікації","\u002Fcsharp\u002Faspnet\u002Fnotifications\u002Fweb-push","01.csharp\u002F11.aspnet\u002F04.notifications\u002F07.web-push",{"title":904,"path":905,"stem":906},"Email нотифікації","\u002Fcsharp\u002Faspnet\u002Fnotifications\u002Femail-notifications","01.csharp\u002F11.aspnet\u002F04.notifications\u002F08.email-notifications",{"title":908,"path":909,"stem":910},"Порівняння підходів: Як вибрати правильну технологію нотифікацій","\u002Fcsharp\u002Faspnet\u002Fnotifications\u002Fchoosing-the-right-approach","01.csharp\u002F11.aspnet\u002F04.notifications\u002F09.choosing-the-right-approach",{"title":912,"path":913,"stem":914},"Hangfire: Надійне планування фонових задач","\u002Fcsharp\u002Faspnet\u002Fnotifications\u002Fhangfire","01.csharp\u002F11.aspnet\u002F04.notifications\u002F10.hangfire",{"title":916,"path":917,"stem":918},"Практика: Конвертація зображень у WebP через Hangfire","\u002Fcsharp\u002Faspnet\u002Fnotifications\u002Fhangfire-image-webp","01.csharp\u002F11.aspnet\u002F04.notifications\u002F11.hangfire-image-webp",{"title":920,"path":921,"stem":922},"Практика: Підготовка відео до HLS-стрімінгу через Hangfire","\u002Fcsharp\u002Faspnet\u002Fnotifications\u002Fhangfire-video-hls","01.csharp\u002F11.aspnet\u002F04.notifications\u002F12.hangfire-video-hls",{"title":924,"path":925,"stem":926},"Telegram-нотифікації: від одного повідомлення до масових розсилок і мульти-канального підходу","\u002Fcsharp\u002Faspnet\u002Fnotifications\u002Ftelegram-notifications","01.csharp\u002F11.aspnet\u002F04.notifications\u002F13.telegram-notifications",{"title":928,"icon":929,"path":930,"stem":931,"children":932,"page":59},"Інтернаціоналізація","i-lucide-languages","\u002Fcsharp\u002Faspnet\u002Fi18n","01.csharp\u002F11.aspnet\u002F05.i18n",[933,937],{"title":934,"path":935,"stem":936},"Інтернаціоналізація (i18n) у Minimal API: від A до Я","\u002Fcsharp\u002Faspnet\u002Fi18n\u002Finternationalization","01.csharp\u002F11.aspnet\u002F05.i18n\u002F01.internationalization",{"title":938,"path":939,"stem":940},"Humanizer: людиномовні рядки у .NET","\u002Fcsharp\u002Faspnet\u002Fi18n\u002Fhumanizer","01.csharp\u002F11.aspnet\u002F05.i18n\u002F02.humanizer",{"title":942,"icon":943,"path":944,"stem":945,"children":946,"page":59},"Кешування","i-lucide-layers","\u002Fcsharp\u002Faspnet\u002Fcaching","01.csharp\u002F11.aspnet\u002F06.caching",[947,951,955,959,963],{"title":948,"path":949,"stem":950},"Огляд кешування: чотири рівні і коли що обирати","\u002Fcsharp\u002Faspnet\u002Fcaching\u002Fcaching","01.csharp\u002F11.aspnet\u002F06.caching\u002F01.caching",{"title":952,"path":953,"stem":954},"IMemoryCache: кеш в оперативній пам'яті","\u002Fcsharp\u002Faspnet\u002Fcaching\u002Fmemory-cache","01.csharp\u002F11.aspnet\u002F06.caching\u002F02.memory-cache",{"title":956,"path":957,"stem":958},"IDistributedCache і Redis: розподілений кеш","\u002Fcsharp\u002Faspnet\u002Fcaching\u002Fdistributed-cache","01.csharp\u002F11.aspnet\u002F06.caching\u002F03.distributed-cache",{"title":960,"path":961,"stem":962},"Response Cache: HTTP-кешування через Cache-Control","\u002Fcsharp\u002Faspnet\u002Fcaching\u002Fresponse-cache","01.csharp\u002F11.aspnet\u002F06.caching\u002F04.response-cache",{"title":964,"path":965,"stem":966},"Output Cache: серверний кеш HTTP-відповідей (.NET 7+)","\u002Fcsharp\u002Faspnet\u002Fcaching\u002Foutput-cache","01.csharp\u002F11.aspnet\u002F06.caching\u002F05.output-cache",{"title":968,"icon":969,"path":970,"stem":971,"children":972,"page":59},"Тестування","i-lucide-test-tube","\u002Fcsharp\u002Faspnet\u002Ftesting","01.csharp\u002F11.aspnet\u002F07.testing",[973,977,981,985,989,993,997,1001,1005,1009,1013,1017,1021,1025,1029,1033,1037,1041,1045,1049,1053],{"title":974,"path":975,"stem":976},"Що таке тестування? Від інтуїції до науки","\u002Fcsharp\u002Faspnet\u002Ftesting\u002Fwhat-is-testing","01.csharp\u002F11.aspnet\u002F07.testing\u002F01.what-is-testing",{"title":978,"path":979,"stem":980},"Піраміда тестування — Стратегія, а не Догма","\u002Fcsharp\u002Faspnet\u002Ftesting\u002Ftesting-pyramid","01.csharp\u002F11.aspnet\u002F07.testing\u002F02.testing-pyramid",{"title":982,"path":983,"stem":984},"Дві Школи Тестування — Лондон проти Детройту","\u002Fcsharp\u002Faspnet\u002Ftesting\u002Ftesting-schools","01.csharp\u002F11.aspnet\u002F07.testing\u002F03.testing-schools",{"title":986,"path":987,"stem":988},"TDD та BDD — Тести як Дизайн-інструмент","\u002Fcsharp\u002Faspnet\u002Ftesting\u002Ftdd-and-bdd","01.csharp\u002F11.aspnet\u002F07.testing\u002F04.tdd-and-bdd",{"title":990,"path":991,"stem":992},"Що саме тестувати — Техніки аналізу та Циклomatична складність","\u002Fcsharp\u002Faspnet\u002Ftesting\u002Fwhat-to-test","01.csharp\u002F11.aspnet\u002F07.testing\u002F05.what-to-test",{"title":994,"path":995,"stem":996},"Тестові Фреймворки — Навіщо вони і що всередині","\u002Fcsharp\u002Faspnet\u002Ftesting\u002Ftest-frameworks","01.csharp\u002F11.aspnet\u002F07.testing\u002F06.test-frameworks",{"title":998,"path":999,"stem":1000},"xUnit — Факти, Теорії та Lifecycle тестів","\u002Fcsharp\u002Faspnet\u002Ftesting\u002Fxunit-basics","01.csharp\u002F11.aspnet\u002F07.testing\u002F07.xunit-basics",{"title":1002,"path":1003,"stem":1004},"xUnit Advanced — Fixtures, Кастомізація та Розширення","\u002Fcsharp\u002Faspnet\u002Ftesting\u002Fxunit-advanced","01.csharp\u002F11.aspnet\u002F07.testing\u002F08.xunit-advanced",{"title":1006,"path":1007,"stem":1008},"Moq — Глибоке занурення в мокування","\u002Fcsharp\u002Faspnet\u002Ftesting\u002Fmocking-with-moq","01.csharp\u002F11.aspnet\u002F07.testing\u002F09.mocking-with-moq",{"title":1010,"path":1011,"stem":1012},"Тестування Баз Даних — EF Core, SQLite та Testcontainers","\u002Fcsharp\u002Faspnet\u002Ftesting\u002Fdatabase-testing","01.csharp\u002F11.aspnet\u002F07.testing\u002F10.database-testing",{"title":1014,"path":1015,"stem":1016},"Integration Testing — Частина 1 [Теорія та WebApplicationFactory]","\u002Fcsharp\u002Faspnet\u002Ftesting\u002Fintegration-testing","01.csharp\u002F11.aspnet\u002F07.testing\u002F11.integration-testing",{"title":1018,"path":1019,"stem":1020},"Інтеграційне тестування — Практика","\u002Fcsharp\u002Faspnet\u002Ftesting\u002F11a.integration-testing-practice","01.csharp\u002F11.aspnet\u002F07.testing\u002F11a.integration-testing-practice",{"title":1022,"path":1023,"stem":1024},"Integration Testing — Частина 2 [Просунуті Сценарії та Testcontainers]","\u002Fcsharp\u002Faspnet\u002Ftesting\u002Fintegration-testing-advanced","01.csharp\u002F11.aspnet\u002F07.testing\u002F12.integration-testing-advanced",{"title":1026,"path":1027,"stem":1028},"Професійний Postman: Колекції, Змінні та GitHub Інтеграція","\u002Fcsharp\u002Faspnet\u002Ftesting\u002Fpostman-professional","01.csharp\u002F11.aspnet\u002F07.testing\u002F13.postman-professional",{"title":1030,"path":1031,"stem":1032},"HttpClient у Тестах Частина 1: Архітектура та MockHttpMessageHandler","\u002Fcsharp\u002Faspnet\u002Ftesting\u002Fhttpclient-testing","01.csharp\u002F11.aspnet\u002F07.testing\u002F14.httpclient-testing",{"title":1034,"path":1035,"stem":1036},"HttpClient у Тестах Частина 2: WireMock.Net та Resilience","\u002Fcsharp\u002Faspnet\u002Ftesting\u002Fwiremock-net","01.csharp\u002F11.aspnet\u002F07.testing\u002F15.wiremock-net",{"title":1038,"path":1039,"stem":1040},"Патерни та Анти-патерни Тестування: Test Smells","\u002Fcsharp\u002Faspnet\u002Ftesting\u002Ftesting-patterns","01.csharp\u002F11.aspnet\u002F07.testing\u002F16.testing-patterns",{"title":1042,"path":1043,"stem":1044},"Просунуті інструменти: Time, Snapshots та Властивості","\u002Fcsharp\u002Faspnet\u002Ftesting\u002Fadvanced-testing-tools","01.csharp\u002F11.aspnet\u002F07.testing\u002F17.advanced-testing-tools",{"title":1046,"path":1047,"stem":1048},"Тестування Архітектури з NetArchTest","\u002Fcsharp\u002Faspnet\u002Ftesting\u002Farchitecture-testing","01.csharp\u002F11.aspnet\u002F07.testing\u002F18.architecture-testing",{"title":1050,"path":1051,"stem":1052},"Тестування Продуктивності: BenchmarkDotNet, NBomber та k6","\u002Fcsharp\u002Faspnet\u002Ftesting\u002Fperformance-testing","01.csharp\u002F11.aspnet\u002F07.testing\u002F19.performance-testing",{"title":1054,"path":1055,"stem":1056},"Залишок плану для курсу \"Тестування ASP.NET Minimal API\"","\u002Fcsharp\u002Faspnet\u002Ftesting\u002Fremaining_plan","01.csharp\u002F11.aspnet\u002F07.testing\u002Fremaining_plan",{"title":1058,"icon":1059,"path":1060,"stem":1061,"children":1062,"page":59},"Платежі","i-lucide-credit-card","\u002Fcsharp\u002Faspnet\u002Fpayments","01.csharp\u002F11.aspnet\u002F08.payments",[1063,1067,1071,1075,1079,1083,1087,1091,1095,1099,1103,1107],{"title":1064,"path":1065,"stem":1066},"Основи платіжної інфраструктури","\u002Fcsharp\u002Faspnet\u002Fpayments\u002Fpayment-fundamentals","01.csharp\u002F11.aspnet\u002F08.payments\u002F01.payment-fundamentals",{"title":1068,"path":1069,"stem":1070},"Методи оплати в Україні","\u002Fcsharp\u002Faspnet\u002Fpayments\u002Fpayment-methods-ukraine","01.csharp\u002F11.aspnet\u002F08.payments\u002F02.payment-methods-ukraine",{"title":1072,"path":1073,"stem":1074},"PCI DSS та безпека платежів","\u002Fcsharp\u002Faspnet\u002Fpayments\u002Fpci-dss-security","01.csharp\u002F11.aspnet\u002F08.payments\u002F03.pci-dss-security",{"title":1076,"path":1077,"stem":1078},"Архітектура платіжної підсистеми","\u002Fcsharp\u002Faspnet\u002Fpayments\u002Fpayment-architecture","01.csharp\u002F11.aspnet\u002F08.payments\u002F04.payment-architecture",{"title":1080,"path":1081,"stem":1082},"Інтеграція LiqPay (ПриватБанк)","\u002Fcsharp\u002Faspnet\u002Fpayments\u002Fliqpay-integration","01.csharp\u002F11.aspnet\u002F08.payments\u002F05.liqpay-integration",{"title":1084,"path":1085,"stem":1086},"Інтеграція Monobank Acquiring API","\u002Fcsharp\u002Faspnet\u002Fpayments\u002Fmonobank-acquiring","01.csharp\u002F11.aspnet\u002F08.payments\u002F06.monobank-acquiring",{"title":1088,"path":1089,"stem":1090},"Інтеграція Stripe","\u002Fcsharp\u002Faspnet\u002Fpayments\u002Fstripe-integration","01.csharp\u002F11.aspnet\u002F08.payments\u002F07.stripe-integration",{"title":1092,"path":1093,"stem":1094},"Webhooks — глибоке занурення","\u002Fcsharp\u002Faspnet\u002Fpayments\u002Fwebhooks-deep-dive","01.csharp\u002F11.aspnet\u002F08.payments\u002F08.webhooks-deep-dive",{"title":1096,"path":1097,"stem":1098},"Підписки та рекурентні платежі","\u002Fcsharp\u002Faspnet\u002Fpayments\u002Fsubscriptions-recurring","01.csharp\u002F11.aspnet\u002F08.payments\u002F09.subscriptions-recurring",{"title":1100,"path":1101,"stem":1102},"Повернення коштів та диспути","\u002Fcsharp\u002Faspnet\u002Fpayments\u002Frefunds-disputes","01.csharp\u002F11.aspnet\u002F08.payments\u002F10.refunds-disputes",{"title":1104,"path":1105,"stem":1106},"Тестування платіжних інтеграцій","\u002Fcsharp\u002Faspnet\u002Fpayments\u002Ftesting-payments","01.csharp\u002F11.aspnet\u002F08.payments\u002F11.testing-payments",{"title":1108,"path":1109,"stem":1110},"Чекліст виходу в Production","\u002Fcsharp\u002Faspnet\u002Fpayments\u002Fproduction-checklist","01.csharp\u002F11.aspnet\u002F08.payments\u002F12.production-checklist",{"title":1112,"icon":1113,"items":1114,"path":1127,"stem":1128,"children":1129,"page":59},"Популярні бібліотеки","lucide:box",[1115,1116,1117,1118,1119,1120,1121,1122,1123,1124,1125,1126],"01.fluent-validation","02.mapster","03.erroror-result-pattern","04.serilog","05.mediatr","06.polly","07.health-checks","08.feature-management","09.fluent-email","10.quest-pdf","11.bogus","12.humanizer-guard","\u002Fcsharp\u002Faspnet\u002Flibraries","01.csharp\u002F11.aspnet\u002F09.libraries",[1130,1134,1138,1142,1146,1150,1154,1158,1162,1166,1170,1174,1178],{"title":1131,"path":1132,"stem":1133},"Валідація з FluentValidation в ASP.NET Core","\u002Fcsharp\u002Faspnet\u002Flibraries\u002Ffluent-validation","01.csharp\u002F11.aspnet\u002F09.libraries\u002F01.fluent-validation",{"title":1135,"path":1136,"stem":1137},"Маппінг об","\u002Fcsharp\u002Faspnet\u002Flibraries\u002Fmapster","01.csharp\u002F11.aspnet\u002F09.libraries\u002F02.mapster",{"title":1139,"path":1140,"stem":1141},"Обробка помилок з ErrorOr та Result Pattern в ASP.NET Core","\u002Fcsharp\u002Faspnet\u002Flibraries\u002Ferroror-result-pattern","01.csharp\u002F11.aspnet\u002F09.libraries\u002F03.erroror-result-pattern",{"title":1143,"path":1144,"stem":1145},"Структуроване логування з Serilog в ASP.NET Core","\u002Fcsharp\u002Faspnet\u002Flibraries\u002Fserilog","01.csharp\u002F11.aspnet\u002F09.libraries\u002F04.serilog",{"title":1147,"path":1148,"stem":1149},"CQRS та Mediator з MediatR в ASP.NET Core","\u002Fcsharp\u002Faspnet\u002Flibraries\u002Fmediatr","01.csharp\u002F11.aspnet\u002F09.libraries\u002F05.mediatr",{"title":1151,"path":1152,"stem":1153},"Відмовостійкість з Polly в ASP.NET Core","\u002Fcsharp\u002Faspnet\u002Flibraries\u002Fpolly","01.csharp\u002F11.aspnet\u002F09.libraries\u002F06.polly",{"title":1155,"path":1156,"stem":1157},"Health Checks в ASP.NET Core","\u002Fcsharp\u002Faspnet\u002Flibraries\u002Fhealth-checks","01.csharp\u002F11.aspnet\u002F09.libraries\u002F07.health-checks",{"title":1159,"path":1160,"stem":1161},"Feature Management та Feature Flags в ASP.NET Core","\u002Fcsharp\u002Faspnet\u002Flibraries\u002Ffeature-management","01.csharp\u002F11.aspnet\u002F09.libraries\u002F08.feature-management",{"title":1163,"path":1164,"stem":1165},"Відправка Email з FluentEmail в ASP.NET Core","\u002Fcsharp\u002Faspnet\u002Flibraries\u002Ffluent-email","01.csharp\u002F11.aspnet\u002F09.libraries\u002F09.fluent-email",{"title":1167,"path":1168,"stem":1169},"Генерація PDF з QuestPDF в ASP.NET Core","\u002Fcsharp\u002Faspnet\u002Flibraries\u002Fquest-pdf","01.csharp\u002F11.aspnet\u002F09.libraries\u002F10.quest-pdf",{"title":1171,"path":1172,"stem":1173},"Генерація тестових даних з Bogus в ASP.NET Core","\u002Fcsharp\u002Faspnet\u002Flibraries\u002Fbogus","01.csharp\u002F11.aspnet\u002F09.libraries\u002F11.bogus",{"title":1175,"path":1176,"stem":1177},"Humanizer та Guard Clauses в ASP.NET Core","\u002Fcsharp\u002Faspnet\u002Flibraries\u002Fhumanizer-guard","01.csharp\u002F11.aspnet\u002F09.libraries\u002F12.humanizer-guard",{"title":1179,"path":1180,"stem":1181},"План модуля 10.libraries — Популярні бібліотеки ASP.NET","\u002Fcsharp\u002Faspnet\u002Flibraries\u002Fplan","01.csharp\u002F11.aspnet\u002F09.libraries\u002Fplan",{"title":1183,"icon":1184,"path":1185,"stem":1186,"children":1187,"page":59},"Razor Pages","i-lucide-layout-template","\u002Fcsharp\u002Faspnet\u002Frazor-pages","01.csharp\u002F11.aspnet\u002F10.razor-pages",[1188,1192,1196,1200,1204,1208],{"title":1189,"path":1190,"stem":1191},"Від Minimal API до Razor Pages: концептуальний перехід","\u002Fcsharp\u002Faspnet\u002Frazor-pages\u002Ffrom-minimal-api","01.csharp\u002F11.aspnet\u002F10.razor-pages\u002F01.from-minimal-api",{"title":1193,"path":1194,"stem":1195},"PageModel: логіка сторінки Razor Pages","\u002Fcsharp\u002Faspnet\u002Frazor-pages\u002Fpage-model","01.csharp\u002F11.aspnet\u002F10.razor-pages\u002F02.page-model",{"title":1197,"path":1198,"stem":1199},"Razor синтаксис: шаблонізатор у .cshtml","\u002Fcsharp\u002Faspnet\u002Frazor-pages\u002Frazor-syntax","01.csharp\u002F11.aspnet\u002F10.razor-pages\u002F03.razor-syntax",{"title":1201,"path":1202,"stem":1203},"Tag Helpers: типізований HTML","\u002Fcsharp\u002Faspnet\u002Frazor-pages\u002Ftag-helpers","01.csharp\u002F11.aspnet\u002F10.razor-pages\u002F04.tag-helpers",{"title":1205,"path":1206,"stem":1207},"Форми і валідація: повний цикл обробки даних","\u002Fcsharp\u002Faspnet\u002Frazor-pages\u002Fforms-validation","01.csharp\u002F11.aspnet\u002F10.razor-pages\u002F05.forms-validation",{"title":1209,"path":1210,"stem":1211},"Практичний проєкт: TaskManager на Razor Pages","\u002Fcsharp\u002Faspnet\u002Frazor-pages\u002Fproject-task-manager","01.csharp\u002F11.aspnet\u002F10.razor-pages\u002F06.project-task-manager",{"title":1213,"path":1214,"stem":1215,"children":1216,"page":59},"ASP.NET Core MVC","\u002Fcsharp\u002Faspnet\u002Fmvc","01.csharp\u002F11.aspnet\u002F11.mvc",[1217,1221,1225,1229,1233,1237,1241,1245,1249,1253,1257,1261,1265,1269,1273,1277,1281,1285],{"title":1218,"path":1219,"stem":1220},"Патерн MVC: архітектура, що змінила веб","\u002Fcsharp\u002Faspnet\u002Fmvc\u002Fmvc-pattern","01.csharp\u002F11.aspnet\u002F11.mvc\u002F01.mvc-pattern",{"title":1222,"path":1223,"stem":1224},"Від Razor Pages до MVC: концептуальний перехід","\u002Fcsharp\u002Faspnet\u002Fmvc\u002Ffrom-razor-pages","01.csharp\u002F11.aspnet\u002F11.mvc\u002F02.from-razor-pages",{"title":1226,"path":1227,"stem":1228},"Controllers та Actions: серце MVC","\u002Fcsharp\u002Faspnet\u002Fmvc\u002Fcontrollers-actions","01.csharp\u002F11.aspnet\u002F11.mvc\u002F03.controllers-actions",{"title":1230,"path":1231,"stem":1232},"Маршрутизація в MVC: Convention vs Attribute Routing","\u002Fcsharp\u002Faspnet\u002Fmvc\u002Frouting-mvc","01.csharp\u002F11.aspnet\u002F11.mvc\u002F04.routing-mvc",{"title":1234,"path":1235,"stem":1236},"Model Binding: від HTTP до C#","\u002Fcsharp\u002Faspnet\u002Fmvc\u002Fmodel-binding","01.csharp\u002F11.aspnet\u002F11.mvc\u002F05.model-binding",{"title":1238,"path":1239,"stem":1240},"Views, ViewData, ViewBag, TempData і ViewModel","\u002Fcsharp\u002Faspnet\u002Fmvc\u002Fviews-viewdata-tempdata","01.csharp\u002F11.aspnet\u002F11.mvc\u002F06.views-viewdata-tempdata",{"title":1242,"path":1243,"stem":1244},"Filters: аспектно-орієнтоване програмування в MVC","\u002Fcsharp\u002Faspnet\u002Fmvc\u002Ffilters","01.csharp\u002F11.aspnet\u002F11.mvc\u002F07.filters",{"title":1246,"path":1247,"stem":1248},"Areas: структурування великих застосунків","\u002Fcsharp\u002Faspnet\u002Fmvc\u002Fareas","01.csharp\u002F11.aspnet\u002F11.mvc\u002F08.areas",{"title":1250,"path":1251,"stem":1252},"View Components: повторювані незалежні блоки UI","\u002Fcsharp\u002Faspnet\u002Fmvc\u002Fview-components","01.csharp\u002F11.aspnet\u002F11.mvc\u002F09.view-components",{"title":1254,"path":1255,"stem":1256},"Display та Editor Templates","\u002Fcsharp\u002Faspnet\u002Fmvc\u002Fdisplay-editor-templates","01.csharp\u002F11.aspnet\u002F11.mvc\u002F10.display-editor-templates",{"title":1258,"path":1259,"stem":1260},"Валідація: IValidatableObject та FluentValidation","\u002Fcsharp\u002Faspnet\u002Fmvc\u002Fvalidation-advanced","01.csharp\u002F11.aspnet\u002F11.mvc\u002F11.validation-advanced",{"title":1262,"path":1263,"stem":1264},"HTMX: інтерактивність через HTML-атрибути","\u002Fcsharp\u002Faspnet\u002Fmvc\u002Fhtmx","01.csharp\u002F11.aspnet\u002F11.mvc\u002F12.htmx",{"title":1266,"path":1267,"stem":1268},"HTMX у ASP.NET Core MVC: серверна інтеграція","\u002Fcsharp\u002Faspnet\u002Fmvc\u002Fajax-htmx-mvc","01.csharp\u002F11.aspnet\u002F11.mvc\u002F13.ajax-htmx-mvc",{"title":1270,"path":1271,"stem":1272},"Практичний проєкт: Каталог товарів з HTMX","\u002Fcsharp\u002Faspnet\u002Fmvc\u002Fhtmx-project","01.csharp\u002F11.aspnet\u002F11.mvc\u002F14.htmx-project",{"title":1274,"path":1275,"stem":1276},"Завантаження та обробка файлів","\u002Fcsharp\u002Faspnet\u002Fmvc\u002Ffile-upload","01.csharp\u002F11.aspnet\u002F11.mvc\u002F15.file-upload",{"title":1278,"path":1279,"stem":1280},"Глобалізація та Локалізація MVC","\u002Fcsharp\u002Faspnet\u002Fmvc\u002Fglobalization-localization","01.csharp\u002F11.aspnet\u002F11.mvc\u002F16.globalization-localization",{"title":1282,"path":1283,"stem":1284},"Підсумковий проєкт: Блог-платформа","\u002Fcsharp\u002Faspnet\u002Fmvc\u002Fmvc-project","01.csharp\u002F11.aspnet\u002F11.mvc\u002F17.mvc-project",{"title":1286,"path":1287,"stem":1288},"План курсу: ASP.NET Core MVC","\u002Fcsharp\u002Faspnet\u002Fmvc\u002Fplan","01.csharp\u002F11.aspnet\u002F11.mvc\u002Fplan",{"title":1290,"path":1291,"stem":1292,"children":1293,"page":59},"Web Api","\u002Fcsharp\u002Faspnet\u002Fweb-api","01.csharp\u002F11.aspnet\u002F12.web-api",[1294,1298,1302,1306,1310,1314,1318,1322,1326,1330,1334,1338,1342],{"title":1295,"path":1296,"stem":1297},"Від Minimal API до Controller-based API","\u002Fcsharp\u002Faspnet\u002Fweb-api\u002Ffrom-minimal-api-to-controllers","01.csharp\u002F11.aspnet\u002F12.web-api\u002F01.from-minimal-api-to-controllers",{"title":1299,"path":1300,"stem":1301},"ControllerBase, ActionResult\u003CT> та Response Types","\u002Fcsharp\u002Faspnet\u002Fweb-api\u002Fcontroller-base-actionresult","01.csharp\u002F11.aspnet\u002F12.web-api\u002F02.controller-base-actionresult",{"title":1303,"path":1304,"stem":1305},"Content Negotiation - JSON, XML та власні форматери","\u002Fcsharp\u002Faspnet\u002Fweb-api\u002Fcontent-negotiation","01.csharp\u002F11.aspnet\u002F12.web-api\u002F03.content-negotiation",{"title":1307,"path":1308,"stem":1309},"Версіонування API","\u002Fcsharp\u002Faspnet\u002Fweb-api\u002Fapi-versioning","01.csharp\u002F11.aspnet\u002F12.web-api\u002F04.api-versioning",{"title":1311,"path":1312,"stem":1313},"ProblemDetails та структурована обробка помилок","\u002Fcsharp\u002Faspnet\u002Fweb-api\u002Fproblemdetails-error-handling","01.csharp\u002F11.aspnet\u002F12.web-api\u002F05.problemdetails-error-handling",{"title":1315,"path":1316,"stem":1317},"Фільтри у Web API контексті","\u002Fcsharp\u002Faspnet\u002Fweb-api\u002Ffilters-for-api","01.csharp\u002F11.aspnet\u002F12.web-api\u002F06.filters-for-api",{"title":1319,"path":1320,"stem":1321},"Пагінація, фільтрація та сортування","\u002Fcsharp\u002Faspnet\u002Fweb-api\u002Fpagination-filtering-sorting","01.csharp\u002F11.aspnet\u002F12.web-api\u002F07.pagination-filtering-sorting",{"title":1323,"path":1324,"stem":1325},"HATEOAS та Resource Expansion","\u002Fcsharp\u002Faspnet\u002Fweb-api\u002Fhateoas-resource-expansion","01.csharp\u002F11.aspnet\u002F12.web-api\u002F08.hateoas-resource-expansion",{"title":1327,"path":1328,"stem":1329},"Гібридна архітектура - Minimal API + Controllers","\u002Fcsharp\u002Faspnet\u002Fweb-api\u002Fminimal-api-vs-controllers-hybrid","01.csharp\u002F11.aspnet\u002F12.web-api\u002F09.minimal-api-vs-controllers-hybrid",{"title":1331,"path":1332,"stem":1333},"Документація API - Swashbuckle, NSwag та генерація клієнтів","\u002Fcsharp\u002Faspnet\u002Fweb-api\u002Fapi-documentation-generation","01.csharp\u002F11.aspnet\u002F12.web-api\u002F10.api-documentation-generation",{"title":1335,"path":1336,"stem":1337},"Health Checks та моніторинг API","\u002Fcsharp\u002Faspnet\u002Fweb-api\u002Fhealth-checks-monitoring","01.csharp\u002F11.aspnet\u002F12.web-api\u002F11.health-checks-monitoring",{"title":1339,"path":1340,"stem":1341},"Підсумковий проєкт - Production-Ready REST API","\u002Fcsharp\u002Faspnet\u002Fweb-api\u002Fweb-api-project","01.csharp\u002F11.aspnet\u002F12.web-api\u002F12.web-api-project",{"title":1343,"path":1344,"stem":1345},"План курсу: ASP.NET Core Web API (Controllers)","\u002Fcsharp\u002Faspnet\u002Fweb-api\u002Fplan","01.csharp\u002F11.aspnet\u002F12.web-api\u002Fplan",{"title":1347,"icon":1348,"path":1349,"stem":1350,"children":1351,"page":59},"Моніторинг","i-lucide-activity","\u002Fcsharp\u002Faspnet\u002Fmonitoring","01.csharp\u002F11.aspnet\u002F13.monitoring",[1352,1356,1360],{"title":1353,"path":1354,"stem":1355},"Спостережуваність: від console.log до production-систем","\u002Fcsharp\u002Faspnet\u002Fmonitoring\u002Fobservability-intro","01.csharp\u002F11.aspnet\u002F13.monitoring\u002F01.observability-intro",{"title":1357,"path":1358,"stem":1359},"Health Checks: перший рівень observability","\u002Fcsharp\u002Faspnet\u002Fmonitoring\u002Fhealth-checks","01.csharp\u002F11.aspnet\u002F13.monitoring\u002F02.health-checks",{"title":1361,"path":1362,"stem":1363},"Вбудовані метрики .NET 10 та System.Diagnostics.Metrics","\u002Fcsharp\u002Faspnet\u002Fmonitoring\u002Fdotnet-metrics","01.csharp\u002F11.aspnet\u002F13.monitoring\u002F03.dotnet-metrics",{"title":1365,"icon":1366,"path":1367,"stem":1368,"children":1369,"page":59},"Desktop UI","i-lucide-app-window","\u002Fcsharp\u002Fdesktop-ui","01.csharp\u002F12.desktop-ui",[1370,1374,1378,1382,1386,1390,1394,1398,1402,1406,1410,1414,1418,1422,1426,1430,1434,1438,1442,1446,1450,1454,1458,1462,1466,1470,1474,1478,1482,1486,1490,1494,1498,1502,1506,1510,1514,1518,1522,1526,1530,1534,1538,1542,1546,1550,1554,1558,1562,1566,1570,1574,1578,1582,1586,1590,1594,1598,1602,1606,1610,1614,1618,1622,1626,1630,1634,1638,1642,1646,1650],{"title":1371,"path":1372,"stem":1373},"Що таке десктопна розробка?","\u002Fcsharp\u002Fdesktop-ui\u002Fwhat-is-desktop-dev","01.csharp\u002F12.desktop-ui\u002F01.what-is-desktop-dev",{"title":1375,"path":1376,"stem":1377},"Архітектура WPF — як влаштований графічний інтерфейс","\u002Fcsharp\u002Fdesktop-ui\u002Fwpf-architecture","01.csharp\u002F12.desktop-ui\u002F02.wpf-architecture",{"title":1379,"path":1380,"stem":1381},"Перший WPF-проєкт — від нуля до вікна","\u002Fcsharp\u002Fdesktop-ui\u002Ffirst-wpf-app","01.csharp\u002F12.desktop-ui\u002F03.first-wpf-app",{"title":1383,"path":1384,"stem":1385},"Перший Avalonia-проєкт: WPF для всіх платформ","\u002Fcsharp\u002Fdesktop-ui\u002F03a.first-avalonia-app","01.csharp\u002F12.desktop-ui\u002F03a.first-avalonia-app",{"title":1387,"path":1388,"stem":1389},"XAML: декларативний інтерфейс","\u002Fcsharp\u002Fdesktop-ui\u002Fxaml-basics","01.csharp\u002F12.desktop-ui\u002F04.xaml-basics",{"title":1391,"path":1392,"stem":1393},"Fluent UI у WPF — сучасний дизайн Windows 11","\u002Fcsharp\u002Fdesktop-ui\u002F04a.wpf-fluent-ui","01.csharp\u002F12.desktop-ui\u002F04a.wpf-fluent-ui",{"title":1395,"path":1396,"stem":1397},"WPF UI — сучасна бібліотека Fluent контролів","\u002Fcsharp\u002Fdesktop-ui\u002F04b.wpf-ui-library","01.csharp\u002F12.desktop-ui\u002F04b.wpf-ui-library",{"title":1399,"path":1400,"stem":1401},"HandyControl — велика бібліотека UI контролів для WPF","\u002Fcsharp\u002Fdesktop-ui\u002F04c.handycontrol-library","01.csharp\u002F12.desktop-ui\u002F04c.handycontrol-library",{"title":1403,"path":1404,"stem":1405},"Простори імен та ресурси XAML","\u002Fcsharp\u002Fdesktop-ui\u002Fxaml-namespaces-resources","01.csharp\u002F12.desktop-ui\u002F05.xaml-namespaces-resources",{"title":1407,"path":1408,"stem":1409},"XAML в Avalonia: ключові відмінності від WPF","\u002Fcsharp\u002Fdesktop-ui\u002F05a.avalonia-xaml-differences","01.csharp\u002F12.desktop-ui\u002F05a.avalonia-xaml-differences",{"title":1411,"path":1412,"stem":1413},"Розширення розмітки XAML (Markup Extensions)","\u002Fcsharp\u002Fdesktop-ui\u002Fxaml-markup-extensions","01.csharp\u002F12.desktop-ui\u002F06.xaml-markup-extensions",{"title":1415,"path":1416,"stem":1417},"Панелі Layout: StackPanel, WrapPanel, DockPanel","\u002Fcsharp\u002Fdesktop-ui\u002Flayout-panels-part1","01.csharp\u002F12.desktop-ui\u002F07.layout-panels-part1",{"title":1419,"path":1420,"stem":1421},"Grid, Canvas, UniformGrid","\u002Fcsharp\u002Fdesktop-ui\u002Flayout-panels-part2","01.csharp\u002F12.desktop-ui\u002F07.layout-panels-part2",{"title":1423,"path":1424,"stem":1425},"Просунуті техніки Layout","\u002Fcsharp\u002Fdesktop-ui\u002Flayout-advanced","01.csharp\u002F12.desktop-ui\u002F08.layout-advanced",{"title":1427,"path":1428,"stem":1429},"Адаптивний Layout та найкращі практики","\u002Fcsharp\u002Fdesktop-ui\u002Flayout-responsive","01.csharp\u002F12.desktop-ui\u002F09.layout-responsive",{"title":1431,"path":1432,"stem":1433},"Layout в Avalonia: відмінності та нові можливості","\u002Fcsharp\u002Fdesktop-ui\u002F09a.layout-avalonia","01.csharp\u002F12.desktop-ui\u002F09a.layout-avalonia",{"title":1435,"path":1436,"stem":1437},"Button, Image, ProgressBar та інші базові контроли","\u002Fcsharp\u002Fdesktop-ui\u002Fbasic-controls","01.csharp\u002F12.desktop-ui\u002F10.basic-controls",{"title":1439,"path":1440,"stem":1441},"Контроли в Avalonia: відмінності від WPF","\u002Fcsharp\u002Fdesktop-ui\u002F10a.controls-avalonia","01.csharp\u002F12.desktop-ui\u002F10a.controls-avalonia",{"title":1443,"path":1444,"stem":1445},"Текстові контроли — TextBlock, TextBox, RichTextBox","\u002Fcsharp\u002Fdesktop-ui\u002Ftext-controls","01.csharp\u002F12.desktop-ui\u002F11.text-controls",{"title":1447,"path":1448,"stem":1449},"Контроли вибору — CheckBox, RadioButton, ComboBox, ListBox, DatePicker","\u002Fcsharp\u002Fdesktop-ui\u002Fselection-controls","01.csharp\u002F12.desktop-ui\u002F12.selection-controls",{"title":1451,"path":1452,"stem":1453},"Content Model — GroupBox, Expander, TabControl, StatusBar","\u002Fcsharp\u002Fdesktop-ui\u002Fcontent-controls","01.csharp\u002F12.desktop-ui\u002F13.content-controls",{"title":1455,"path":1456,"stem":1457},"UI\u002FUX принципи десктопних застосунків","\u002Fcsharp\u002Fdesktop-ui\u002F13a.ui-ux-principles","01.csharp\u002F12.desktop-ui\u002F13a.ui-ux-principles",{"title":1459,"path":1460,"stem":1461},"Dependency Properties — Концепція та Value Resolution","\u002Fcsharp\u002Fdesktop-ui\u002Fdependency-properties-part1","01.csharp\u002F12.desktop-ui\u002F14.dependency-properties-part1",{"title":1463,"path":1464,"stem":1465},"Avalonia Property System — StyledProperty та DirectProperty","\u002Fcsharp\u002Fdesktop-ui\u002F14a.avalonia-property-system","01.csharp\u002F12.desktop-ui\u002F14a.avalonia-property-system",{"title":1467,"path":1468,"stem":1469},"Attached Properties — Властивості без меж","\u002Fcsharp\u002Fdesktop-ui\u002Fattached-properties","01.csharp\u002F12.desktop-ui\u002F15.attached-properties",{"title":1471,"path":1472,"stem":1473},"Routed Events — Маршрутизація подій у WPF","\u002Fcsharp\u002Fdesktop-ui\u002Frouted-events","01.csharp\u002F12.desktop-ui\u002F16.routed-events",{"title":1475,"path":1476,"stem":1477},"Data Binding — Від Code-Behind до Декларативності","\u002Fcsharp\u002Fdesktop-ui\u002Fdata-binding-basics-part1","01.csharp\u002F12.desktop-ui\u002F17.data-binding-basics-part1",{"title":1479,"path":1480,"stem":1481},"INotifyPropertyChanged — Живе оновлення UI","\u002Fcsharp\u002Fdesktop-ui\u002Fdata-binding-basics-part2","01.csharp\u002F12.desktop-ui\u002F17.data-binding-basics-part2",{"title":1483,"path":1484,"stem":1485},"Compiled Bindings в Avalonia — Безпека на етапі компіляції","\u002Fcsharp\u002Fdesktop-ui\u002F17a.avalonia-compiled-bindings","01.csharp\u002F12.desktop-ui\u002F17a.avalonia-compiled-bindings",{"title":1487,"path":1488,"stem":1489},"Просунутий Data Binding — ElementName, RelativeSource, MultiBinding","\u002Fcsharp\u002Fdesktop-ui\u002Fdata-binding-advanced","01.csharp\u002F12.desktop-ui\u002F18.data-binding-advanced",{"title":1491,"path":1492,"stem":1493},"Value Converters — Перетворення типів даних у Data Binding","\u002Fcsharp\u002Fdesktop-ui\u002Fvalue-converters","01.csharp\u002F12.desktop-ui\u002F19.value-converters",{"title":1495,"path":1496,"stem":1497},"Data Templates — Візуалізація об'єктів у WPF","\u002Fcsharp\u002Fdesktop-ui\u002Fdata-templates","01.csharp\u002F12.desktop-ui\u002F20.data-templates",{"title":1499,"path":1500,"stem":1501},"Collections Binding Part 1 — ObservableCollection та ItemsControl","\u002Fcsharp\u002Fdesktop-ui\u002Fcollections-binding-part1","01.csharp\u002F12.desktop-ui\u002F21.collections-binding-part1",{"title":1503,"path":1504,"stem":1505},"Collections Binding Part 2 — ICollectionView, Filtering, Sorting та Virtualization","\u002Fcsharp\u002Fdesktop-ui\u002Fcollections-binding-part2","01.csharp\u002F12.desktop-ui\u002F21.collections-binding-part2",{"title":1507,"path":1508,"stem":1509},"MVVM Pattern — Від Spaghetti Code до архітектури","\u002Fcsharp\u002Fdesktop-ui\u002Fmvvm-pattern","01.csharp\u002F12.desktop-ui\u002F22.mvvm-pattern",{"title":1511,"path":1512,"stem":1513},"ViewModel Implementation — Від BaseViewModel до валідації","\u002Fcsharp\u002Fdesktop-ui\u002Fviewmodel-implementation","01.csharp\u002F12.desktop-ui\u002F23.viewmodel-implementation",{"title":1515,"path":1516,"stem":1517},"Commands — Від event handlers до декларативних команд","\u002Fcsharp\u002Fdesktop-ui\u002Fcommands","01.csharp\u002F12.desktop-ui\u002F24.commands",{"title":1519,"path":1520,"stem":1521},"MVVM Toolkit — MVVM без boilerplate через Source Generators","\u002Fcsharp\u002Fdesktop-ui\u002Fmvvm-toolkit","01.csharp\u002F12.desktop-ui\u002F25.mvvm-toolkit",{"title":1523,"path":1524,"stem":1525},"Messenger Pattern — Комунікація між ViewModel без прямих посилань","\u002Fcsharp\u002Fdesktop-ui\u002Fmessenger-pattern","01.csharp\u002F12.desktop-ui\u002F26.messenger-pattern",{"title":1527,"path":1528,"stem":1529},"Стилі WPF — CSS для десктопу","\u002Fcsharp\u002Fdesktop-ui\u002Fstyles-basics","01.csharp\u002F12.desktop-ui\u002F27.styles-basics",{"title":1531,"path":1532,"stem":1533},"CSS-like стилі Avalonia","\u002Fcsharp\u002Fdesktop-ui\u002F27a.avalonia-css-styling","01.csharp\u002F12.desktop-ui\u002F27a.avalonia-css-styling",{"title":1535,"path":1536,"stem":1537},"Control Templates — Частина 1. Концепція та TemplateBinding","\u002Fcsharp\u002Fdesktop-ui\u002Fcontrol-templates-part1","01.csharp\u002F12.desktop-ui\u002F28.control-templates-part1",{"title":1539,"path":1540,"stem":1541},"Control Templates — Частина 2. Named Parts та ContentPresenter","\u002Fcsharp\u002Fdesktop-ui\u002Fcontrol-templates-part2","01.csharp\u002F12.desktop-ui\u002F28.control-templates-part2",{"title":1543,"path":1544,"stem":1545},"Control Themes в Avalonia — нова ера стилізації","\u002Fcsharp\u002Fdesktop-ui\u002F28a.avalonia-control-themes","01.csharp\u002F12.desktop-ui\u002F28a.avalonia-control-themes",{"title":1547,"path":1548,"stem":1549},"Triggers та Visual State Manager у WPF","\u002Fcsharp\u002Fdesktop-ui\u002Ftriggers-visual-states","01.csharp\u002F12.desktop-ui\u002F29.triggers-visual-states",{"title":1551,"path":1552,"stem":1553},"Pseudo-classes в Avalonia — замість WPF Triggers","\u002Fcsharp\u002Fdesktop-ui\u002F29a.avalonia-pseudo-classes","01.csharp\u002F12.desktop-ui\u002F29a.avalonia-pseudo-classes",{"title":1555,"path":1556,"stem":1557},"Теми та ресурсні словники у WPF","\u002Fcsharp\u002Fdesktop-ui\u002Fresources-themes","01.csharp\u002F12.desktop-ui\u002F30.resources-themes",{"title":1559,"path":1560,"stem":1561},"Avalonia Themes — Fluent Design та система тематизації","\u002Fcsharp\u002Fdesktop-ui\u002F30a.avalonia-themes-fluent","01.csharp\u002F12.desktop-ui\u002F30a.avalonia-themes-fluent",{"title":1563,"path":1564,"stem":1565},"Контроли колекцій — глибоке занурення","\u002Fcsharp\u002Fdesktop-ui\u002Fcollection-controls","01.csharp\u002F12.desktop-ui\u002F31.collection-controls",{"title":1567,"path":1568,"stem":1569},"DataGrid — колонки та базове відображення","\u002Fcsharp\u002Fdesktop-ui\u002Fdatagrid-part1","01.csharp\u002F12.desktop-ui\u002F32.datagrid-part1",{"title":1571,"path":1572,"stem":1573},"DataGrid — сортування, фільтрація, редагування","\u002Fcsharp\u002Fdesktop-ui\u002Fdatagrid-part2","01.csharp\u002F12.desktop-ui\u002F32.datagrid-part2",{"title":1575,"path":1576,"stem":1577},"TreeView та GridView","\u002Fcsharp\u002Fdesktop-ui\u002Ftreeview-listview","01.csharp\u002F12.desktop-ui\u002F33.treeview-listview",{"title":1579,"path":1580,"stem":1581},"Меню, Toolbar, ContextMenu, StatusBar","\u002Fcsharp\u002Fdesktop-ui\u002Fmenus-toolbars","01.csharp\u002F12.desktop-ui\u002F34.menus-toolbars",{"title":1583,"path":1584,"stem":1585},"Навігація та керування вікнами. Частина 1: вікна та сторінки","\u002Fcsharp\u002Fdesktop-ui\u002Fnavigation-windows-part1","01.csharp\u002F12.desktop-ui\u002F35.navigation-windows-part1",{"title":1587,"path":1588,"stem":1589},"Навігація та керування вікнами. Частина 2: MVVM-навігація","\u002Fcsharp\u002Fdesktop-ui\u002Fnavigation-windows-part2","01.csharp\u002F12.desktop-ui\u002F35.navigation-windows-part2",{"title":1591,"path":1592,"stem":1593},"Avalonia — Навігація та діалоги","\u002Fcsharp\u002Fdesktop-ui\u002F35a.avalonia-navigation-dialogs","01.csharp\u002F12.desktop-ui\u002F35a.avalonia-navigation-dialogs",{"title":1595,"path":1596,"stem":1597},"Діалоги та File Pickers у WPF","\u002Fcsharp\u002Fdesktop-ui\u002Fdialogs-file-pickers","01.csharp\u002F12.desktop-ui\u002F36.dialogs-file-pickers",{"title":1599,"path":1600,"stem":1601},"UserControl: компонентний підхід у WPF","\u002Fcsharp\u002Fdesktop-ui\u002Fuser-controls","01.csharp\u002F12.desktop-ui\u002F37.user-controls",{"title":1603,"path":1604,"stem":1605},"Custom Controls: Lookless Controls у WPF","\u002Fcsharp\u002Fdesktop-ui\u002Fcustom-controls","01.csharp\u002F12.desktop-ui\u002F38.custom-controls",{"title":1607,"path":1608,"stem":1609},"Avalonia TemplatedControl — Lookless Controls","\u002Fcsharp\u002Fdesktop-ui\u002F38a.avalonia-templated-controls","01.csharp\u002F12.desktop-ui\u002F38a.avalonia-templated-controls",{"title":1611,"path":1612,"stem":1613},"Анімації у WPF: Storyboard та Easing Functions","\u002Fcsharp\u002Fdesktop-ui\u002Fanimations-transitions","01.csharp\u002F12.desktop-ui\u002F39.animations-transitions",{"title":1615,"path":1616,"stem":1617},"Анімації в Avalonia","\u002Fcsharp\u002Fdesktop-ui\u002F39a.avalonia-animations","01.csharp\u002F12.desktop-ui\u002F39a.avalonia-animations",{"title":1619,"path":1620,"stem":1621},"2D Графіка та Мультимедіа у WPF","\u002Fcsharp\u002Fdesktop-ui\u002Fmedia-graphics","01.csharp\u002F12.desktop-ui\u002F40.media-graphics",{"title":1623,"path":1624,"stem":1625},"Dependency Injection у WPF та Avalonia","\u002Fcsharp\u002Fdesktop-ui\u002Fdi-integration","01.csharp\u002F12.desktop-ui\u002F41.di-integration",{"title":1627,"path":1628,"stem":1629},"SQLite та EF Core у десктопних додатках","\u002Fcsharp\u002Fdesktop-ui\u002Fdata-persistence-part1","01.csharp\u002F12.desktop-ui\u002F42.data-persistence-part1",{"title":1631,"path":1632,"stem":1633},"Repository Pattern та Unit of Work","\u002Fcsharp\u002Fdesktop-ui\u002Fdata-persistence-part2","01.csharp\u002F12.desktop-ui\u002F43.data-persistence-part2",{"title":1635,"path":1636,"stem":1637},"Тестування ViewModels","\u002Fcsharp\u002Fdesktop-ui\u002Fviewmodel-testing","01.csharp\u002F12.desktop-ui\u002F44.viewmodel-testing",{"title":1639,"path":1640,"stem":1641},"Avalonia Headless Testing — тестування UI без вікон","\u002Fcsharp\u002Fdesktop-ui\u002F44a.avalonia-headless-testing","01.csharp\u002F12.desktop-ui\u002F44a.avalonia-headless-testing",{"title":1643,"path":1644,"stem":1645},"Кросплатформна розробка з Avalonia","\u002Fcsharp\u002Fdesktop-ui\u002Favalonia-cross-platform","01.csharp\u002F12.desktop-ui\u002F45.avalonia-cross-platform",{"title":1647,"path":1648,"stem":1649},"Пакування та розгортання Avalonia додатків","\u002Fcsharp\u002Fdesktop-ui\u002Favalonia-packaging-deployment","01.csharp\u002F12.desktop-ui\u002F46.avalonia-packaging-deployment",{"title":1651,"path":1652,"stem":1653},"Розгортання WPF застосунків","\u002Fcsharp\u002Fdesktop-ui\u002Fwpf-packaging-deployment","01.csharp\u002F12.desktop-ui\u002F47.wpf-packaging-deployment",{"title":1655,"icon":658,"path":1656,"stem":1657,"children":1658,"page":59},"Network Programming","\u002Fcsharp\u002Fnetwork-programming","01.csharp\u002F13.network-programming",[1659,1663,1667,1671,1675,1679,1683,1687,1691,1695,1699],{"title":1660,"path":1661,"stem":1662},"Основи комп'ютерних мереж","\u002Fcsharp\u002Fnetwork-programming\u002Ffoundations","01.csharp\u002F13.network-programming\u002F01.foundations",{"title":1664,"path":1665,"stem":1666},"Модель OSI та стек TCP\u002FIP","\u002Fcsharp\u002Fnetwork-programming\u002Fosi-model","01.csharp\u002F13.network-programming\u002F02.osi-model",{"title":1668,"path":1669,"stem":1670},"IP-протокол та адресація","\u002Fcsharp\u002Fnetwork-programming\u002Fip-addressing","01.csharp\u002F13.network-programming\u002F03.ip-addressing",{"title":1672,"path":1673,"stem":1674},"UDP — протокол без з'єднання","\u002Fcsharp\u002Fnetwork-programming\u002Fudp","01.csharp\u002F13.network-programming\u002F05.udp",{"title":1676,"path":1677,"stem":1678},"UDP Broadcast та Multicast","\u002Fcsharp\u002Fnetwork-programming\u002Fudp-broadcast-multicast","01.csharp\u002F13.network-programming\u002F06.udp-broadcast-multicast",{"title":1680,"path":1681,"stem":1682},"HTTP — протокол вебу","\u002Fcsharp\u002Fnetwork-programming\u002Fhttp-fundamentals","01.csharp\u002F13.network-programming\u002F07.http-fundamentals",{"title":1684,"path":1685,"stem":1686},"HttpListener — вбудований HTTP-сервер .NET","\u002Fcsharp\u002Fnetwork-programming\u002F07a.http-listener","01.csharp\u002F13.network-programming\u002F07a.http-listener",{"title":1688,"path":1689,"stem":1690},"HTTP Advanced — cookies, аутентифікація та HTTPS","\u002Fcsharp\u002Fnetwork-programming\u002Fhttp-advanced","01.csharp\u002F13.network-programming\u002F08.http-advanced",{"title":1692,"path":1693,"stem":1694},"SMTP та протоколи електронної пошти","\u002Fcsharp\u002Fnetwork-programming\u002Fsmtp","01.csharp\u002F13.network-programming\u002F09.smtp",{"title":1696,"path":1697,"stem":1698},"WebSocket — повнодуплексний протокол реального часу","\u002Fcsharp\u002Fnetwork-programming\u002Fwebsockets","01.csharp\u002F13.network-programming\u002F10.websockets",{"title":1700,"path":1701,"stem":1702},"TLS\u002FSSL — криптографічний захист мережевих з'єднань","\u002Fcsharp\u002Fnetwork-programming\u002Ftls-ssl","01.csharp\u002F13.network-programming\u002F11.tls-ssl",{"title":1704,"path":1705,"stem":1706},"C# & .NET: The Ultimate Roadmap","\u002Fcsharp\u002Froadmap","01.csharp\u002Froadmap",{"title":1708,"icon":1709,"path":1710,"stem":1711,"children":1712,"page":59},"C++","i-devicon-cplusplus","\u002Fcpp","02.cpp",[1713,1717,1721,1725,1729,1733,1737,1741,1745,1748,1752,1756,1760,1764,1768,1772,1776,1780,1784,1788,1792,1796,1800,1804,1808,1812,1816,1820,1824,1828,1832,1836,1840,1844,1848,1852,1856,1860,1864,1868,1872,1876,1880,1884,1888,1892,1896,1900,1904,1908],{"title":1714,"path":1715,"stem":1716},"Вступ у програмування та алгоритми","\u002Fcpp\u002Fintro-algorithms","02.cpp\u002F01.intro-algorithms",{"title":1718,"path":1719,"stem":1720},"Code Style: угоди про оформлення коду","\u002Fcpp\u002Fcode-style","02.cpp\u002F02.code-style",{"title":1722,"path":1723,"stem":1724},"Середовище розробки та перший проєкт","\u002Fcpp\u002Fide-setup","02.cpp\u002F03.ide-setup",{"title":1726,"path":1727,"stem":1728},"Вивід даних на екран","\u002Fcpp\u002Fdata-output","02.cpp\u002F04.data-output",{"title":1730,"path":1731,"stem":1732},"Типи даних, змінні та константи","\u002Fcpp\u002Fdata-types-variables","02.cpp\u002F05.data-types-variables",{"title":1734,"path":1735,"stem":1736},"Ввід даних з клавіатури","\u002Fcpp\u002Fdata-input","02.cpp\u002F06.data-input",{"title":1738,"path":1739,"stem":1740},"Оператори, перетворення типів та логічні операції","\u002Fcpp\u002Foperators-type-conversion","02.cpp\u002F07.operators-type-conversion",{"title":1742,"path":1743,"stem":1744},"Цикли","\u002Fcpp\u002Floops","02.cpp\u002F08.loops",{"title":32,"path":1746,"stem":1747},"\u002Fcpp\u002Farrays","02.cpp\u002F09.arrays",{"title":1749,"path":1750,"stem":1751},"Алгоритми сортування та аналіз складності","\u002Fcpp\u002Fsorting","02.cpp\u002F10.sorting",{"title":1753,"path":1754,"stem":1755},"Алгоритми пошуку","\u002Fcpp\u002Fsearching","02.cpp\u002F11.searching",{"title":1757,"path":1758,"stem":1759},"Функції: основи","\u002Fcpp\u002Ffunctions-basics","02.cpp\u002F12.functions-basics",{"title":1761,"path":1762,"stem":1763},"Функції: прототипи, область видимості та додаткові можливості","\u002Fcpp\u002Ffunctions-scope","02.cpp\u002F13.functions-scope",{"title":1765,"path":1766,"stem":1767},"Функції: перевантаження та шаблони","\u002Fcpp\u002Ffunctions-overloading-templates","02.cpp\u002F14.functions-overloading-templates",{"title":1769,"path":1770,"stem":1771},"Вказівники: основи","\u002Fcpp\u002Fpointers-basics","02.cpp\u002F15.pointers-basics",{"title":1773,"path":1774,"stem":1775},"Посилання (References)","\u002Fcpp\u002Freferences","02.cpp\u002F16.references",{"title":1777,"path":1778,"stem":1779},"Вказівники, const і масиви","\u002Fcpp\u002Fpointers-const-arrays","02.cpp\u002F17.pointers-const-arrays",{"title":1781,"path":1782,"stem":1783},"Адресна арифметика","\u002Fcpp\u002Fpointer-arithmetic","02.cpp\u002F18.pointer-arithmetic",{"title":1785,"path":1786,"stem":1787},"Динамічна пам'ять","\u002Fcpp\u002Fdynamic-memory","02.cpp\u002F19.dynamic-memory",{"title":1789,"path":1790,"stem":1791},"Вказівники типу void","\u002Fcpp\u002Fvoid-pointers","02.cpp\u002F20.void-pointers",{"title":1793,"path":1794,"stem":1795},"Вказівники на вказівники","\u002Fcpp\u002Fpointers-to-pointers","02.cpp\u002F21.pointers-to-pointers",{"title":1797,"path":1798,"stem":1799},"Оператор доступу до членів через вказівник (->)","\u002Fcpp\u002Fmember-access-operator","02.cpp\u002F22.member-access-operator",{"title":1801,"path":1802,"stem":1803},"Цикл for-each (Range-based for)","\u002Fcpp\u002Fforeach-loop","02.cpp\u002F23.foreach-loop",{"title":1805,"path":1806,"stem":1807},"Вказівники на функції","\u002Fcpp\u002Ffunction-pointers","02.cpp\u002F24.function-pointers",{"title":1809,"path":1810,"stem":1811},"Лямбда-вирази","\u002Fcpp\u002Flambdas","02.cpp\u002F25.lambdas",{"title":1813,"path":1814,"stem":1815},"Лямбда-захоплення","\u002Fcpp\u002Flambda-captures","02.cpp\u002F26.lambda-captures",{"title":1817,"path":1818,"stem":1819},"Еліпсис","\u002Fcpp\u002Fellipsis","02.cpp\u002F27.ellipsis",{"title":1821,"path":1822,"stem":1823},"Безпечні альтернативи еліпсису","\u002Fcpp\u002F27a.ellipsis","02.cpp\u002F27a.ellipsis",{"title":1825,"path":1826,"stem":1827},"Аргументи командного рядка","\u002Fcpp\u002Fcommand-line-arguments","02.cpp\u002F28.command-line-arguments",{"title":1829,"path":1830,"stem":1831},"Перерахування (enum)","\u002Fcpp\u002Fenum","02.cpp\u002F29.enum",{"title":1833,"path":1834,"stem":1835},"Класи-перерахування (enum class)","\u002Fcpp\u002Fenum-class","02.cpp\u002F30.enum-class",{"title":1837,"path":1838,"stem":1839},"Псевдоніми типів (typedef і using)","\u002Fcpp\u002Ftype-aliases","02.cpp\u002F31.type-aliases",{"title":1841,"path":1842,"stem":1843},"Системи числення та двійкова арифметика","\u002Fcpp\u002Fnumber-systems","02.cpp\u002F32.number-systems",{"title":1845,"path":1846,"stem":1847},"Структури (struct): агрегування даних","\u002Fcpp\u002Fstruct","02.cpp\u002F33.struct",{"title":1849,"path":1850,"stem":1851},"Структури у функціях","\u002Fcpp\u002Fstruct-functions","02.cpp\u002F34.struct-functions",{"title":1853,"path":1854,"stem":1855},"Масиви структур і вкладені структури","\u002Fcpp\u002Fstruct-arrays","02.cpp\u002F35.struct-arrays",{"title":1857,"path":1858,"stem":1859},"Патерни struct та межі застосування","\u002Fcpp\u002Fstruct-patterns","02.cpp\u002F36.struct-patterns",{"title":1861,"path":1862,"stem":1863},"Символи та таблиця ASCII","\u002Fcpp\u002Fascii-characters","02.cpp\u002F37.ascii-characters",{"title":1865,"path":1866,"stem":1867},"Unicode та кодування UTF","\u002Fcpp\u002Funicode-utf","02.cpp\u002F38.unicode-utf",{"title":1869,"path":1870,"stem":1871},"C-style рядки","\u002Fcpp\u002Fc-strings","02.cpp\u002F39.c-strings",{"title":1873,"path":1874,"stem":1875},"Вступ до std::string","\u002Fcpp\u002Fstd-string-intro","02.cpp\u002F40.std-string-intro",{"title":1877,"path":1878,"stem":1879},"Довжина, ємність та доступ до символів std::string","\u002Fcpp\u002Fstd-string-capacity-access","02.cpp\u002F41.std-string-capacity-access",{"title":1881,"path":1882,"stem":1883},"Модифікація std::string: присвоювання, додавання, вставка, видалення та заміна","\u002Fcpp\u002Fstd-string-modification","02.cpp\u002F42.std-string-modification",{"title":1885,"path":1886,"stem":1887},"Пошук у std::string: find, npos та практичні патерни","\u002Fcpp\u002Fstd-string-search","02.cpp\u002F43.std-string-search",{"title":1889,"path":1890,"stem":1891},"std::string_view: невласницький погляд на рядок без копіювання","\u002Fcpp\u002Fstd-string-view","02.cpp\u002F44.std-string-view",{"title":1893,"path":1894,"stem":1895},"Об'єднання (union): один блок пам'яті, кілька інтерпретацій","\u002Fcpp\u002Funion","02.cpp\u002F45.union",{"title":1897,"path":1898,"stem":1899},"Організація коду: файли, препроцесор, простори імен","\u002Fcpp\u002Fmultifile-programs","02.cpp\u002F46.multifile-programs",{"title":1901,"path":1902,"stem":1903},"Робота з файлами: C-стиль (stdio.h)","\u002Fcpp\u002Fc-style-files","02.cpp\u002F47.c-style-files",{"title":1905,"path":1906,"stem":1907},"Робота з файлами: C++-стиль (fstream)","\u002Fcpp\u002Fcpp-style-files","02.cpp\u002F48.cpp-style-files",{"title":1909,"path":1910,"stem":1911},"План навчання: Курс C++ — Продовження (Статті 29–60+)","\u002Fcpp\u002Fcurriculum-plan","02.cpp\u002Fcurriculum-plan",{"title":1913,"icon":1914,"path":1915,"stem":1916,"children":1917,"page":59},"JavaScript","i-devicon-javascript","\u002Fjavascript","03.javascript",[1918,1944,1998,2020,2324,2362],{"title":1919,"icon":1920,"path":1921,"stem":1922,"children":1923,"page":59},"Events","i-lucide-mouse-pointer-click","\u002Fjavascript\u002Fevents","03.javascript\u002F01.events",[1924,1928,1932,1936,1940],{"title":1925,"path":1926,"stem":1927},"Вступ до подій браузера","\u002Fjavascript\u002Fevents\u002Fintro","03.javascript\u002F01.events\u002F01.intro",{"title":1929,"path":1930,"stem":1931},"Бульбашковий механізм (Bubbling) та занурення (Capturing)","\u002Fjavascript\u002Fevents\u002Fbubbling-capturing","03.javascript\u002F01.events\u002F02.bubbling-capturing",{"title":1933,"path":1934,"stem":1935},"Делегування подій (Event Delegation)","\u002Fjavascript\u002Fevents\u002Fdelegate-events","03.javascript\u002F01.events\u002F03.delegate-events",{"title":1937,"path":1938,"stem":1939},"Типові дії браузера та preventDefault()","\u002Fjavascript\u002Fevents\u002Fprevent-default","03.javascript\u002F01.events\u002F04.prevent-default",{"title":1941,"path":1942,"stem":1943},"Запуск користувацьких подій (Custom Events)","\u002Fjavascript\u002Fevents\u002Fcustom-events","03.javascript\u002F01.events\u002F05.custom-events",{"title":1945,"icon":1946,"path":1947,"stem":1948,"children":1949,"page":59},"Network","i-lucide-globe","\u002Fjavascript\u002Fnetwork","03.javascript\u002F02.network",[1950,1954,1958,1962,1966,1970,1974,1978,1982,1986,1990,1994],{"title":1951,"path":1952,"stem":1953},"Fetch API - Сучасний підхід до HTTP-запитів","\u002Fjavascript\u002Fnetwork\u002F01-fetch-api","03.javascript\u002F02.network\u002F01-fetch-api",{"title":1955,"path":1956,"stem":1957},"FormData - Робота з формами та файлами","\u002Fjavascript\u002Fnetwork\u002F02-formdata","03.javascript\u002F02.network\u002F02-formdata",{"title":1959,"path":1960,"stem":1961},"Відстеження прогресу завантаження","\u002Fjavascript\u002Fnetwork\u002F03-download-progress","03.javascript\u002F02.network\u002F03-download-progress",{"title":1963,"path":1964,"stem":1965},"Переривання fetch-запитів","\u002Fjavascript\u002Fnetwork\u002F04-abort-requests","03.javascript\u002F02.network\u002F04-abort-requests",{"title":1967,"path":1968,"stem":1969},"CORS - Запити між різними джерелами","\u002Fjavascript\u002Fnetwork\u002F05-cors","03.javascript\u002F02.network\u002F05-cors",{"title":1971,"path":1972,"stem":1973},"Fetch API - Повний довідник опцій","\u002Fjavascript\u002Fnetwork\u002F06-fetch-options","03.javascript\u002F02.network\u002F06-fetch-options",{"title":1975,"path":1976,"stem":1977},"URL Objects - Робота з посиланнями","\u002Fjavascript\u002Fnetwork\u002F07-url-objects","03.javascript\u002F02.network\u002F07-url-objects",{"title":1979,"path":1980,"stem":1981},"XMLHttpRequest - AJAX та низькорівневі запити","\u002Fjavascript\u002Fnetwork\u002F08-xmlhttprequest","03.javascript\u002F02.network\u002F08-xmlhttprequest",{"title":1983,"path":1984,"stem":1985},"Відновлюване завантаження файлів","\u002Fjavascript\u002Fnetwork\u002F09-resumable-upload","03.javascript\u002F02.network\u002F09-resumable-upload",{"title":1987,"path":1988,"stem":1989},"Cookies, document.cookie та світ після \"Cookiepocalypse\"","\u002Fjavascript\u002Fnetwork\u002F10-cookies","03.javascript\u002F02.network\u002F10-cookies",{"title":1991,"path":1992,"stem":1993},"js-cookie: Керування Cookies без Болю","\u002Fjavascript\u002Fnetwork\u002F11-js-cookie","03.javascript\u002F02.network\u002F11-js-cookie",{"title":1995,"path":1996,"stem":1997},"Axios: Потужний HTTP-клієнт для JavaScript","\u002Fjavascript\u002Fnetwork\u002F12-axios","03.javascript\u002F02.network\u002F12-axios",{"title":1999,"icon":2000,"path":2001,"stem":2002,"children":2003,"page":59},"Bom","i-lucide-monitor","\u002Fjavascript\u002Fbom","03.javascript\u002F03.bom",[2004,2008,2012,2016],{"title":2005,"path":2006,"stem":2007},"LocalStorage, SessionStorage та patterns збереження даних","\u002Fjavascript\u002Fbom\u002F01-localstorage","03.javascript\u002F03.bom\u002F01-localstorage",{"title":2009,"path":2010,"stem":2011},"Location Object - Керування адресою сторінки","\u002Fjavascript\u002Fbom\u002F02-location-object","03.javascript\u002F03.bom\u002F02-location-object",{"title":2013,"path":2014,"stem":2015},"History API - Керування історією браузера","\u002Fjavascript\u002Fbom\u002F03-history-api","03.javascript\u002F03.bom\u002F03-history-api",{"title":2017,"path":2018,"stem":2019},"Navigator Object - Ідентифікація та Можливості Пристрою","\u002Fjavascript\u002Fbom\u002F04-navigator-object","03.javascript\u002F03.bom\u002F04-navigator-object",{"title":2021,"icon":2022,"path":2023,"stem":2024,"children":2025},"React","i-devicon-react","\u002Fjavascript\u002Freact","03.javascript\u002F04.react\u002Findex",[2026,2027,2031,2035,2039,2043,2106,2141,2293],{"title":2021,"path":2023,"stem":2024},{"title":2028,"path":2029,"stem":2030},"Робота з Формами в React","\u002Fjavascript\u002Freact\u002Freact-forms","03.javascript\u002F04.react\u002F01.react-forms",{"title":2032,"path":2033,"stem":2034},"React Hook Form: Професійна Робота з Формами","\u002Fjavascript\u002Freact\u002Freact-hook-form","03.javascript\u002F04.react\u002F02.react-hook-form",{"title":2036,"path":2037,"stem":2038},"React Hook Form: Глибоке Розуміння Архітектури та Оптимізації","\u002Fjavascript\u002Freact\u002Freact-hook-form-new","03.javascript\u002F04.react\u002F02.react-hook-form-new",{"title":2040,"path":2041,"stem":2042},"Axios та React: Професійна Архітектура Запитів","\u002Fjavascript\u002Freact\u002Fdata-fetching-axios","03.javascript\u002F04.react\u002F03.data-fetching-axios",{"title":2044,"icon":132,"path":2045,"stem":2046,"children":2047},"Tanstack Query","\u002Fjavascript\u002Freact\u002Ftanstack-query","03.javascript\u002F04.react\u002F04.tanstack-query\u002Findex",[2048,2050,2054,2058,2062,2066,2070,2074,2078,2082,2086,2090,2094,2098,2102],{"title":2049,"path":2045,"stem":2046},"TanStack Query: Майстерність Керування Станом Сервера",{"title":2051,"path":2052,"stem":2053},"Парадигма Server State: Чому useEffect недостатньо","\u002Fjavascript\u002Freact\u002Ftanstack-query\u002Fserver-state-paradigm","03.javascript\u002F04.react\u002F04.tanstack-query\u002F01.server-state-paradigm",{"title":2055,"path":2056,"stem":2057},"Встановлення та Налаштування: Фундамент","\u002Fjavascript\u002Freact\u002Ftanstack-query\u002Finstallation-and-devtools","03.javascript\u002F04.react\u002F04.tanstack-query\u002F02.installation-and-devtools",{"title":2059,"path":2060,"stem":2061},"Основи Запитів та Магія Ключів","\u002Fjavascript\u002Freact\u002Ftanstack-query\u002Fquery-basics-and-keys","03.javascript\u002F04.react\u002F04.tanstack-query\u002F03.query-basics-and-keys",{"title":2063,"path":2064,"stem":2065},"Синхронізація Даних: Життєвий Цикл Запиту","\u002Fjavascript\u002Freact\u002Ftanstack-query\u002Fdata-synchronization","03.javascript\u002F04.react\u002F04.tanstack-query\u002F04.data-synchronization",{"title":2067,"path":2068,"stem":2069},"Мутації та Інвалідація: Зміна Даних","\u002Fjavascript\u002Freact\u002Ftanstack-query\u002Fmutations-and-invalidation","03.javascript\u002F04.react\u002F04.tanstack-query\u002F05.mutations-and-invalidation",{"title":2071,"path":2072,"stem":2073},"Оптимістичні Оновлення: Швидше за Світло","\u002Fjavascript\u002Freact\u002Ftanstack-query\u002Foptimistic-updates","03.javascript\u002F04.react\u002F04.tanstack-query\u002F06.optimistic-updates",{"title":2075,"path":2076,"stem":2077},"Пагінація та Infinite Scroll","\u002Fjavascript\u002Freact\u002Ftanstack-query\u002Fpagination-and-load-more","03.javascript\u002F04.react\u002F04.tanstack-query\u002F07.pagination-and-load-more",{"title":2079,"path":2080,"stem":2081},"Просунуті Патерни та Оптимізація","\u002Fjavascript\u002Freact\u002Ftanstack-query\u002Fadvanced-patterns","03.javascript\u002F04.react\u002F04.tanstack-query\u002F08.advanced-patterns",{"title":2083,"path":2084,"stem":2085},"Архітектура та Best Practices","\u002Fjavascript\u002Freact\u002Ftanstack-query\u002Farchitecture-and-best-practices","03.javascript\u002F04.react\u002F04.tanstack-query\u002F09.architecture-and-best-practices",{"title":2087,"path":2088,"stem":2089},"Server-Side Rendering (SSR) та Гідратація","\u002Fjavascript\u002Freact\u002Ftanstack-query\u002Fserver-side-rendering","03.javascript\u002F04.react\u002F04.tanstack-query\u002F10.server-side-rendering",{"title":2091,"path":2092,"stem":2093},"Стратегії Тестування","\u002Fjavascript\u002Freact\u002Ftanstack-query\u002Ftesting-strategies","03.javascript\u002F04.react\u002F04.tanstack-query\u002F11.testing-strategies",{"title":2095,"path":2096,"stem":2097},"Аутентифікація та Обробка Помилок","\u002Fjavascript\u002Freact\u002Ftanstack-query\u002Fauthentication-and-errors","03.javascript\u002F04.react\u002F04.tanstack-query\u002F12.authentication-and-errors",{"title":2099,"path":2100,"stem":2101},"React Suspense та Майбутнє","\u002Fjavascript\u002Freact\u002Ftanstack-query\u002Freact-suspense","03.javascript\u002F04.react\u002F04.tanstack-query\u002F13.react-suspense",{"title":2103,"path":2104,"stem":2105},"Глибоке Занурення в Продуктивність","\u002Fjavascript\u002Freact\u002Ftanstack-query\u002Fperformance-deep-dive","03.javascript\u002F04.react\u002F04.tanstack-query\u002F14.performance-deep-dive",{"title":2107,"icon":2022,"path":2108,"stem":2109,"children":2110},"React Router","\u002Fjavascript\u002Freact\u002Freact-router","03.javascript\u002F04.react\u002F05.react-router\u002Findex",[2111,2113,2117,2121,2125,2129,2133,2137],{"title":2112,"path":2108,"stem":2109},"React Router: Навігаційна система сучасного вебу",{"title":2114,"path":2115,"stem":2116},"Налаштування та Базовий Роутинг","\u002Fjavascript\u002Freact\u002Freact-router\u002Fsetup-and-basic-routing","03.javascript\u002F04.react\u002F05.react-router\u002F01.setup-and-basic-routing",{"title":2118,"path":2119,"stem":2120},"Динамічна Навігація","\u002Fjavascript\u002Freact\u002Freact-router\u002Fnavigation-and-links","03.javascript\u002F04.react\u002F05.react-router\u002F02.navigation-and-links",{"title":2122,"path":2123,"stem":2124},"Вкладені Маршрути та Макети","\u002Fjavascript\u002Freact\u002Freact-router\u002Fnested-routes-and-layouts","03.javascript\u002F04.react\u002F05.react-router\u002F03.nested-routes-and-layouts",{"title":2126,"path":2127,"stem":2128},"Динамічні Маршрути та Параметри","\u002Fjavascript\u002Freact\u002Freact-router\u002Fdynamic-routing","03.javascript\u002F04.react\u002F05.react-router\u002F04.dynamic-routing",{"title":2130,"path":2131,"stem":2132},"Data APIs: Loaders та Actions","\u002Fjavascript\u002Freact\u002Freact-router\u002Fdata-loading","03.javascript\u002F04.react\u002F05.react-router\u002F05.data-loading",{"title":2134,"path":2135,"stem":2136},"Просунуті Патерни","\u002Fjavascript\u002Freact\u002Freact-router\u002Fadvanced-patterns","03.javascript\u002F04.react\u002F05.react-router\u002F06.advanced-patterns",{"title":2138,"path":2139,"stem":2140},"Legacy Routing: Компонентний підхід","\u002Fjavascript\u002Freact\u002Freact-router\u002Flegacy-routing","03.javascript\u002F04.react\u002F05.react-router\u002F07.legacy-routing",{"title":2142,"icon":132,"path":2143,"stem":2144,"children":2145},"Redux","\u002Fjavascript\u002Freact\u002Fredux","03.javascript\u002F04.react\u002F06.redux\u002Findex",[2146,2148,2164,2193,2202,2223,2239,2268],{"title":2147,"path":2143,"stem":2144},"Redux: Еволюція управління станом",{"title":14,"icon":15,"path":2149,"stem":2150,"children":2151,"page":59},"\u002Fjavascript\u002Freact\u002Fredux\u002Ffundamentals","03.javascript\u002F04.react\u002F06.redux\u002F01.fundamentals",[2152,2156,2160],{"title":2153,"path":2154,"stem":2155},"Вступ до State Management","\u002Fjavascript\u002Freact\u002Fredux\u002Ffundamentals\u002Fintro-state-management","03.javascript\u002F04.react\u002F06.redux\u002F01.fundamentals\u002F01.intro-state-management",{"title":2157,"path":2158,"stem":2159},"Філософія Redux та Три Принципи","\u002Fjavascript\u002Freact\u002Fredux\u002Ffundamentals\u002Fredux-philosophy","03.javascript\u002F04.react\u002F06.redux\u002F01.fundamentals\u002F02.redux-philosophy",{"title":2161,"path":2162,"stem":2163},"Чисті функції та Іммутабельність","\u002Fjavascript\u002Freact\u002Fredux\u002Ffundamentals\u002Fpure-functions-immutability","03.javascript\u002F04.react\u002F06.redux\u002F01.fundamentals\u002F03.pure-functions-immutability",{"title":2165,"icon":132,"path":2166,"stem":2167,"children":2168,"page":59},"Classic Redux","\u002Fjavascript\u002Freact\u002Fredux\u002Fclassic-redux","03.javascript\u002F04.react\u002F06.redux\u002F02.classic-redux",[2169,2173,2177,2181,2185,2189],{"title":2170,"path":2171,"stem":2172},"Створення Store (Classic Redux)","\u002Fjavascript\u002Freact\u002Fredux\u002Fclassic-redux\u002Fstore-setup","03.javascript\u002F04.react\u002F06.redux\u002F02.classic-redux\u002F01.store-setup",{"title":2174,"path":2175,"stem":2176},"Actions, Constants та Action Creators","\u002Fjavascript\u002Freact\u002Fredux\u002Fclassic-redux\u002Factions-constants","03.javascript\u002F04.react\u002F06.redux\u002F02.classic-redux\u002F02.actions-constants",{"title":2178,"path":2179,"stem":2180},"Логіка Reducers","\u002Fjavascript\u002Freact\u002Fredux\u002Fclassic-redux\u002Freducers","03.javascript\u002F04.react\u002F06.redux\u002F02.classic-redux\u002F03.reducers",{"title":2182,"path":2183,"stem":2184},"Комбінування Reducers (Root Reducer)","\u002Fjavascript\u002Freact\u002Fredux\u002Fclassic-redux\u002Fdata-flow","03.javascript\u002F04.react\u002F06.redux\u002F02.classic-redux\u002F04.data-flow",{"title":2186,"path":2187,"stem":2188},"Підключення до React (React-Redux)","\u002Fjavascript\u002Freact\u002Fredux\u002Fclassic-redux\u002Freact-redux-connection","03.javascript\u002F04.react\u002F06.redux\u002F02.classic-redux\u002F05.react-redux-connection",{"title":2190,"path":2191,"stem":2192},"Middleware та Асинхронність (Redux Thunk)","\u002Fjavascript\u002Freact\u002Fredux\u002Fclassic-redux\u002Fmiddleware-thunk","03.javascript\u002F04.react\u002F06.redux\u002F02.classic-redux\u002F06.middleware-thunk",{"title":2194,"icon":132,"path":2195,"stem":2196,"children":2197,"page":59},"Transition To Rtk","\u002Fjavascript\u002Freact\u002Fredux\u002Ftransition-to-rtk","03.javascript\u002F04.react\u002F06.redux\u002F03.transition-to-rtk",[2198],{"title":2199,"path":2200,"stem":2201},"Проблеми класичного Redux","\u002Fjavascript\u002Freact\u002Fredux\u002Ftransition-to-rtk\u002Fproblems-with-classic","03.javascript\u002F04.react\u002F06.redux\u002F03.transition-to-rtk\u002F01.problems-with-classic",{"title":2203,"icon":132,"path":2204,"stem":2205,"children":2206,"page":59},"Redux Toolkit","\u002Fjavascript\u002Freact\u002Fredux\u002Fredux-toolkit","03.javascript\u002F04.react\u002F06.redux\u002F04.redux-toolkit",[2207,2211,2215,2219],{"title":2208,"path":2209,"stem":2210},"Налаштування Store з configureStore","\u002Fjavascript\u002Freact\u002Fredux\u002Fredux-toolkit\u002Fconfigure-store","03.javascript\u002F04.react\u002F06.redux\u002F04.redux-toolkit\u002F01.configure-store",{"title":2212,"path":2213,"stem":2214},"createSlice: Революція в Redux","\u002Fjavascript\u002Freact\u002Fredux\u002Fredux-toolkit\u002Fcreate-slice","03.javascript\u002F04.react\u002F06.redux\u002F04.redux-toolkit\u002F02.create-slice",{"title":2216,"path":2217,"stem":2218},"Асинхронність з createAsyncThunk","\u002Fjavascript\u002Freact\u002Fredux\u002Fredux-toolkit\u002Fasync-thunks","03.javascript\u002F04.react\u002F06.redux\u002F04.redux-toolkit\u002F03.async-thunks",{"title":2220,"path":2221,"stem":2222},"04. Entity Adapter: Керування нормалізованим станом","\u002Fjavascript\u002Freact\u002Fredux\u002Fredux-toolkit\u002Fentity-adapter","03.javascript\u002F04.react\u002F06.redux\u002F04.redux-toolkit\u002F04.entity-adapter",{"title":2224,"icon":92,"path":2225,"stem":2226,"children":2227,"page":59},"Advanced","\u002Fjavascript\u002Freact\u002Fredux\u002Fadvanced","03.javascript\u002F04.react\u002F06.redux\u002F05.advanced",[2228,2232,2236],{"title":2229,"path":2230,"stem":2231},"Мемоізація та Селектори: Повний Гайд по Reselect","\u002Fjavascript\u002Freact\u002Fredux\u002Fadvanced\u002Fselectors-reselect","03.javascript\u002F04.react\u002F06.redux\u002F05.advanced\u002F01.selectors-reselect",{"title":2233,"path":2234,"stem":2235},"RTK Query: Архітектура Серверного Кешу","\u002Fjavascript\u002Freact\u002Fredux\u002Fadvanced\u002Frtk-query-intro","03.javascript\u002F04.react\u002F06.redux\u002F05.advanced\u002F02.rtk-query-intro",{"title":2083,"path":2237,"stem":2238},"\u002Fjavascript\u002Freact\u002Fredux\u002Fadvanced\u002Farchitecture-best-practices","03.javascript\u002F04.react\u002F06.redux\u002F05.advanced\u002F03.architecture-best-practices",{"title":2240,"icon":132,"path":2241,"stem":2242,"children":2243,"page":59},"Project Kanban","\u002Fjavascript\u002Freact\u002Fredux\u002Fproject-kanban","03.javascript\u002F04.react\u002F06.redux\u002F06.project-kanban",[2244,2248,2252,2256,2260,2264],{"title":2245,"path":2246,"stem":2247},"Проєкт: Kanban Board (Trello Clone)","\u002Fjavascript\u002Freact\u002Fredux\u002Fproject-kanban\u002Fproject-overview","03.javascript\u002F04.react\u002F06.redux\u002F06.project-kanban\u002F01.project-overview",{"title":2249,"path":2250,"stem":2251},"Налаштування та Типізація","\u002Fjavascript\u002Freact\u002Fredux\u002Fproject-kanban\u002Fsetup-and-types","03.javascript\u002F04.react\u002F06.redux\u002F06.project-kanban\u002F02.setup-and-types",{"title":2253,"path":2254,"stem":2255},"Board Slice: Серце Дошки","\u002Fjavascript\u002Freact\u002Fredux\u002Fproject-kanban\u002Fboard-slice","03.javascript\u002F04.react\u002F06.redux\u002F06.project-kanban\u002F03.board-slice",{"title":2257,"path":2258,"stem":2259},"Логіка Drag & Drop","\u002Fjavascript\u002Freact\u002Fredux\u002Fproject-kanban\u002Fdrag-and-drop-logic","03.javascript\u002F04.react\u002F06.redux\u002F06.project-kanban\u002F04.drag-and-drop-logic",{"title":2261,"path":2262,"stem":2263},"Інтеграція з RTK Query","\u002Fjavascript\u002Freact\u002Fredux\u002Fproject-kanban\u002Frtk-query-integration","03.javascript\u002F04.react\u002F06.redux\u002F06.project-kanban\u002F05.rtk-query-integration",{"title":2265,"path":2266,"stem":2267},"Optimistic Updates","\u002Fjavascript\u002Freact\u002Fredux\u002Fproject-kanban\u002Foptimistic-updates","03.javascript\u002F04.react\u002F06.redux\u002F06.project-kanban\u002F06.optimistic-updates",{"title":2269,"icon":132,"path":2270,"stem":2271,"children":2272,"page":59},"Testing","\u002Fjavascript\u002Freact\u002Fredux\u002Ftesting","03.javascript\u002F04.react\u002F06.redux\u002F07.testing",[2273,2277,2281,2285,2289],{"title":2274,"path":2275,"stem":2276},"Тестування Redux","\u002Fjavascript\u002Freact\u002Fredux\u002Ftesting\u002Fintro-testing","03.javascript\u002F04.react\u002F06.redux\u002F07.testing\u002F01.intro-testing",{"title":2278,"path":2279,"stem":2280},"Тестування Reducers","\u002Fjavascript\u002Freact\u002Fredux\u002Ftesting\u002Ftesting-reducers","03.javascript\u002F04.react\u002F06.redux\u002F07.testing\u002F02.testing-reducers",{"title":2282,"path":2283,"stem":2284},"Тестування Селекторів","\u002Fjavascript\u002Freact\u002Fredux\u002Ftesting\u002Ftesting-selectors","03.javascript\u002F04.react\u002F06.redux\u002F07.testing\u002F03.testing-selectors",{"title":2286,"path":2287,"stem":2288},"Тестування Компонентів (Integration)","\u002Fjavascript\u002Freact\u002Fredux\u002Ftesting\u002Ftesting-components","03.javascript\u002F04.react\u002F06.redux\u002F07.testing\u002F04.testing-components",{"title":2290,"path":2291,"stem":2292},"Тестування Async Thunks","\u002Fjavascript\u002Freact\u002Fredux\u002Ftesting\u002Ftesting-thunks","03.javascript\u002F04.react\u002F06.redux\u002F07.testing\u002F05.testing-thunks",{"title":2294,"icon":132,"path":2295,"stem":2296,"children":2297},"Ui Libraries","\u002Fjavascript\u002Freact\u002Fui-libraries","03.javascript\u002F04.react\u002F07.ui-libraries\u002Findex",[2298,2300,2304,2308,2312,2316,2320],{"title":2299,"path":2295,"stem":2296},"UI Бібліотеки в React",{"title":2301,"path":2302,"stem":2303},"Вступ до UI Бібліотек: Навіщо Винаходити Велосипед Двічі?","\u002Fjavascript\u002Freact\u002Fui-libraries\u002Fintroduction-to-ui-libraries","03.javascript\u002F04.react\u002F07.ui-libraries\u002F01.introduction-to-ui-libraries",{"title":2305,"path":2306,"stem":2307},"Філософія shadcn\u002Fui: \"Not a Component Library\"","\u002Fjavascript\u002Freact\u002Fui-libraries\u002Fshadcn-philosophy","03.javascript\u002F04.react\u002F07.ui-libraries\u002F02.shadcn-philosophy",{"title":2309,"path":2310,"stem":2311},"Установка та Налаштування shadcn\u002Fui","\u002Fjavascript\u002Freact\u002Fui-libraries\u002Fshadcn-installation","03.javascript\u002F04.react\u002F07.ui-libraries\u002F03.shadcn-installation",{"title":2313,"path":2314,"stem":2315},"Базові Компоненти shadcn\u002Fui: Фундамент Інтерфейсу","\u002Fjavascript\u002Freact\u002Fui-libraries\u002Fshadcn-components-basics","03.javascript\u002F04.react\u002F07.ui-libraries\u002F04.shadcn-components-basics",{"title":2317,"path":2318,"stem":2319},"Компоненти Форм: Побудова Інтерактивних Form","\u002Fjavascript\u002Freact\u002Fui-libraries\u002Fshadcn-components-forms","03.javascript\u002F04.react\u002F07.ui-libraries\u002F05.shadcn-components-forms",{"title":2321,"path":2322,"stem":2323},"Складні Компоненти: Dialog, Dropdown, Table та Command","\u002Fjavascript\u002Freact\u002Fui-libraries\u002Fshadcn-components-advanced","03.javascript\u002F04.react\u002F07.ui-libraries\u002F06.shadcn-components-advanced",{"title":2325,"icon":2326,"path":2327,"stem":2328,"children":2329,"page":59},"TypeScript","i-devicon-typescript","\u002Fjavascript\u002Ftypescript","03.javascript\u002F05.typescript",[2330,2334,2338,2342,2346,2350,2354,2358],{"title":2331,"path":2332,"stem":2333},"TypeScript: Броня для вашого коду","\u002Fjavascript\u002Ftypescript\u002Fintro-and-basic-types","03.javascript\u002F05.typescript\u002F01.intro-and-basic-types",{"title":2335,"path":2336,"stem":2337},"Майстерність Моделювання Даних: Інтерфейси та Просунуті Типи","\u002Fjavascript\u002Ftypescript\u002Finterfaces-and-advanced-types","03.javascript\u002F05.typescript\u002F02.interfaces-and-advanced-types",{"title":2339,"path":2340,"stem":2341},"Алхімія Типів: Generics та Utility Types","\u002Fjavascript\u002Ftypescript\u002Fgenerics-and-utilities","03.javascript\u002F05.typescript\u002F03.generics-and-utilities",{"title":2343,"path":2344,"stem":2345},"Архітектура та Шаблони: Класи в TypeScript","\u002Fjavascript\u002Ftypescript\u002Fclasses-and-oop","03.javascript\u002F05.typescript\u002F04.classes-and-oop",{"title":2347,"path":2348,"stem":2349},"Продакшн та Екосистема: Advanced Config & Workflow","\u002Fjavascript\u002Ftypescript\u002Fadvanced-patterns-and-config","03.javascript\u002F05.typescript\u002F05.advanced-patterns-and-config",{"title":2351,"path":2352,"stem":2353},"TypeScript у світі React","\u002Fjavascript\u002Ftypescript\u002Freact-basics","03.javascript\u002F05.typescript\u002F06.react-basics",{"title":2355,"path":2356,"stem":2357},"React + TypeScript: Продвинуті патерни","\u002Fjavascript\u002Ftypescript\u002Freact-advanced","03.javascript\u002F05.typescript\u002F07.react-advanced",{"title":2359,"path":2360,"stem":2361},"React + TypeScript: Екосистема та бібліотеки","\u002Fjavascript\u002Ftypescript\u002Freact-ecosystem","03.javascript\u002F05.typescript\u002F08.react-ecosystem",{"title":2363,"path":2364,"stem":2365},"Atomic Design","\u002Fjavascript\u002Fatomic-design","03.javascript\u002F2.atomic-design",{"title":2367,"icon":2368,"path":2369,"stem":2370,"children":2371,"page":59},"Java","i-devicon-java","\u002Fjava","04.java",[2372,2375,2378,2382,2386,2390,2394],{"title":162,"path":2373,"stem":2374},"\u002Fjava\u002Fdata-mapper-part1","04.java\u002F01.data-mapper-part1",{"title":166,"path":2376,"stem":2377},"\u002Fjava\u002Fdata-mapper-part2","04.java\u002F02.data-mapper-part2",{"title":2379,"path":2380,"stem":2381},"Service Layer: Організація бізнес-логіки","\u002Fjava\u002Fservice-layer","04.java\u002F03.service-layer",{"title":2383,"path":2384,"stem":2385},"Rich Domain Model та State Pattern","\u002Fjava\u002Frich-domain-model","04.java\u002F04.rich-domain-model",{"title":2387,"path":2388,"stem":2389},"Патерни для складної бізнес-логіки","\u002Fjava\u002Fbusiness-logic-patterns","04.java\u002F05.business-logic-patterns",{"title":2391,"path":2392,"stem":2393},"Обробка помилок та валідація","\u002Fjava\u002Ferror-handling-validation","04.java\u002F06.error-handling-validation",{"title":2395,"path":2396,"stem":2397,"children":2398,"page":59},"Проектування баз даних","\u002Fjava\u002Fpr2","04.java\u002Fpr2",[2399,2403,2407,2411,2415,2419,2423,2427,2431,2435,2439,2443,2447,2451,2455,2459,2463,2467,2471,2475,2479,2483,2487,2491,2495,2499,2503,2507,2511,2515,2519,2523,2527,2531,2535,2539,2543],{"title":2400,"path":2401,"stem":2402},"Концептуальне моделювання: Мистецтво розуміння предметної області","\u002Fjava\u002Fpr2\u002Fconceptual-modeling","04.java\u002Fpr2\u002F01.conceptual-modeling",{"title":2404,"path":2405,"stem":2406},"Логічне моделювання: Від бізнес-ідей до структур даних","\u002Fjava\u002Fpr2\u002Flogical-modeling","04.java\u002Fpr2\u002F02.logical-modeling",{"title":2408,"path":2409,"stem":2410},"Нормалізація: Гігієна даних та боротьба з аномаліями","\u002Fjava\u002Fpr2\u002Fnormalization","04.java\u002Fpr2\u002F03.normalization",{"title":2412,"path":2413,"stem":2414},"Фізична схема: Від абстракції до DDL","\u002Fjava\u002Fpr2\u002Fphysical-schema","04.java\u002Fpr2\u002F04.physical-schema",{"title":2416,"path":2417,"stem":2418},"Архітектурна класифікація таблиць","\u002Fjava\u002Fpr2\u002Ftable-classification","04.java\u002Fpr2\u002F05.table-classification",{"title":2420,"path":2421,"stem":2422},"Database Migrations: Версіонування схеми з Flyway","\u002Fjava\u002Fpr2\u002Fdatabase-migrations","04.java\u002Fpr2\u002F06.database-migrations",{"title":2424,"path":2425,"stem":2426},"А що, якби це була не реляційна БД?","\u002Fjava\u002Fpr2\u002Fbeyond-relational","04.java\u002Fpr2\u002F07.beyond-relational",{"title":2428,"path":2429,"stem":2430},"Object-Relational Impedance Mismatch: Два світи, що не хочуть дружити","\u002Fjava\u002Fpr2\u002Fimpedance-mismatch","04.java\u002Fpr2\u002F09.impedance-mismatch",{"title":2432,"path":2433,"stem":2434},"JDBC: Перший контакт із базою даних","\u002Fjava\u002Fpr2\u002Fjdbc-fundamentals","04.java\u002Fpr2\u002F10.jdbc-fundamentals",{"title":2436,"path":2437,"stem":2438},"Якість коду: Spotless, SpotBugs та SonarQube","\u002Fjava\u002Fpr2\u002F10a.code-quality","04.java\u002Fpr2\u002F10a.code-quality",{"title":2440,"path":2441,"stem":2442},"Connection Pool: Патерн Object Pool для JDBC-з'єднань","\u002Fjava\u002Fpr2\u002Fconnection-pool","04.java\u002Fpr2\u002F11.connection-pool",{"title":2444,"path":2445,"stem":2446},"Row Data Gateway: Об'єкт як обгортка рядка таблиці","\u002Fjava\u002Fpr2\u002Frow-data-gateway","04.java\u002Fpr2\u002F12.row-data-gateway",{"title":2448,"path":2449,"stem":2450},"Table Data Gateway: Фасад таблиці як архітектурний відступ","\u002Fjava\u002Fpr2\u002Ftable-data-gateway","04.java\u002Fpr2\u002F13.table-data-gateway",{"title":2452,"path":2453,"stem":2454},"Repository + Data Mapper: Правильна шарова архітектура з JDBC","\u002Fjava\u002Fpr2\u002Frepository-data-mapper","04.java\u002Fpr2\u002F14.repository-data-mapper",{"title":2456,"path":2457,"stem":2458},"Identity Map: Кешування сутностей у рамках сесії","\u002Fjava\u002Fpr2\u002Fidentity-map","04.java\u002Fpr2\u002F15.identity-map",{"title":2460,"path":2461,"stem":2462},"Unit of Work: Відстеження змін і координація JDBC-транзакцій","\u002Fjava\u002Fpr2\u002Funit-of-work","04.java\u002Fpr2\u002F16.unit-of-work",{"title":2464,"path":2465,"stem":2466},"Strategy: Замінювані SQL-стратегії для підтримки різних СУБД","\u002Fjava\u002Fpr2\u002Fstrategy-sql","04.java\u002Fpr2\u002F17.strategy-sql",{"title":2468,"path":2469,"stem":2470},"Proxy: Lazy Loading для One-To-Many колекцій","\u002Fjava\u002Fpr2\u002Fproxy-lazy-loading","04.java\u002Fpr2\u002F18.proxy-lazy-loading",{"title":2472,"path":2473,"stem":2474},"Generic Repository через Java Reflection: анотації та динамічний SQL","\u002Fjava\u002Fpr2\u002Fgeneric-repository-reflection","04.java\u002Fpr2\u002F19.generic-repository-reflection",{"title":2476,"path":2477,"stem":2478},"Specification Pattern: Композиція бізнес-правил для складних запитів","\u002Fjava\u002Fpr2\u002Fspecification-pattern","04.java\u002Fpr2\u002F20.specification-pattern",{"title":2480,"path":2481,"stem":2482},"Розширені можливості Specification Pattern: підзапити, агрегації та гібридний підхід","\u002Fjava\u002Fpr2\u002F20a.advanced-specifications","04.java\u002Fpr2\u002F20a.advanced-specifications",{"title":2484,"path":2485,"stem":2486},"Асинхронність у JDBC: Від блокуючих викликів до CompletableFuture","\u002Fjava\u002Fpr2\u002Fasynchronous-jdbc","04.java\u002Fpr2\u002F21.asynchronous-jdbc",{"title":2488,"path":2489,"stem":2490},"Інтеграційне тестування JDBC-репозиторіїв: Embedded H2 та патерн AAA","\u002Fjava\u002Fpr2\u002Fintegration-testing-h2","04.java\u002Fpr2\u002F22.integration-testing-h2",{"title":2492,"path":2493,"stem":2494},"Testcontainers: Тестування з реальною PostgreSQL у Docker-контейнерах","\u002Fjava\u002Fpr2\u002Fintegration-testing-testcontainers","04.java\u002Fpr2\u002F23.integration-testing-testcontainers",{"title":2496,"path":2497,"stem":2498},"Google Guice: Впровадження залежностей у JavaFX-проєкті","\u002Fjava\u002Fpr2\u002Fdependency-injection-guice","04.java\u002Fpr2\u002F24.dependency-injection-guice",{"title":2500,"path":2501,"stem":2502},"JavaFX: Основи побудови графічних інтерфейсів","\u002Fjava\u002Fpr2\u002Fjavafx-fundamentals","04.java\u002Fpr2\u002F25.javafx-fundamentals",{"title":2504,"path":2505,"stem":2506},"Properties та Bindings: Реактивність у JavaFX","\u002Fjava\u002Fpr2\u002Fjavafx-properties-bindings","04.java\u002Fpr2\u002F26.javafx-properties-bindings",{"title":2508,"path":2509,"stem":2510},"MVC vs MVP vs MVVM: Еволюція архітектурних патернів UI","\u002Fjava\u002Fpr2\u002Fui-architecture-patterns","04.java\u002Fpr2\u002F27.ui-architecture-patterns",{"title":2512,"path":2513,"stem":2514},"MVVM на практиці: Побудова ViewModel","\u002Fjava\u002Fpr2\u002Fmvvm-viewmodel-implementation","04.java\u002Fpr2\u002F28.mvvm-viewmodel-implementation",{"title":2516,"path":2517,"stem":2518},"View та Controller: Зв'язування з ViewModel через FXML","\u002Fjava\u002Fpr2\u002Fmvvm-view-controller","04.java\u002Fpr2\u002F29.mvvm-view-controller",{"title":2520,"path":2521,"stem":2522},"Інтеграція MVVM з Guice: Автоматична ін'єкція залежностей","\u002Fjava\u002Fpr2\u002Fmvvm-guice-integration","04.java\u002Fpr2\u002F30.mvvm-guice-integration",{"title":2524,"path":2525,"stem":2526},"Валідація та обробка помилок у MVVM","\u002Fjava\u002Fpr2\u002Fmvvm-validation-error-handling","04.java\u002Fpr2\u002F31.mvvm-validation-error-handling",{"title":2528,"path":2529,"stem":2530},"Навігація та управління екранами у JavaFX MVVM","\u002Fjava\u002Fpr2\u002Fmvvm-navigation-screen-management","04.java\u002Fpr2\u002F32.mvvm-navigation-screen-management",{"title":2532,"path":2533,"stem":2534},"Тестування JavaFX MVVM-додатків","\u002Fjava\u002Fpr2\u002Fmvvm-testing","04.java\u002Fpr2\u002F33.mvvm-testing",{"title":2536,"path":2537,"stem":2538},"Стилізація та теми у JavaFX: CSS та User Experience","\u002Fjava\u002Fpr2\u002Fjavafx-styling-themes","04.java\u002Fpr2\u002F34.javafx-styling-themes",{"title":2540,"path":2541,"stem":2542},"AtlantaFX: Сучасні теми для JavaFX додатків","\u002Fjava\u002Fpr2\u002Fatlantafx-modern-themes","04.java\u002Fpr2\u002F35.atlantafx-modern-themes",{"title":2544,"path":2545,"stem":2546},"Пакування та розповсюдження JavaFX-додатків","\u002Fjava\u002Fpr2\u002Fjar-packaging-distribution","04.java\u002Fpr2\u002F36.jar-packaging-distribution",{"title":2548,"icon":2549,"path":2550,"stem":2551,"children":2552,"page":59},"Python","i-devicon-python","\u002Fpython","05.python",[2553,2557,2560,2564,2568,2572,2576,2580,2584,2588,2592,2596,2600,2604,2608,2645],{"title":2554,"path":2555,"stem":2556},"Модулі, Пакети та Віртуальні Середовища","\u002Fpython\u002Fmodules-packages-venv","05.python\u002F00.modules-packages-venv",{"title":71,"path":2558,"stem":2559},"\u002Fpython\u002Fclasses-objects","05.python\u002F01.classes-objects",{"title":2561,"path":2562,"stem":2563},"Інкапсуляція, Керування Доступом та Властивості","\u002Fpython\u002Fencapsulation","05.python\u002F02.encapsulation",{"title":2565,"path":2566,"stem":2567},"Наслідування, MRO та суперсила super()","\u002Fpython\u002Finheritance-mro","05.python\u002F03.inheritance-mro",{"title":2569,"path":2570,"stem":2571},"Абстракція — ABC проти Статичних Протоколів (PEP 544)","\u002Fpython\u002Fabstraction-protocols","05.python\u002F04.abstraction-protocols",{"title":2573,"path":2574,"stem":2575},"Магічні методи (Dunder) та Емуляція протоколів","\u002Fpython\u002Fdunder-methods","05.python\u002F05.dunder-methods",{"title":2577,"path":2578,"stem":2579},"Декоратори та Керування життєвим циклом методів","\u002Fpython\u002Fdecorators-static-class","05.python\u002F06.decorators-static-class",{"title":2581,"path":2582,"stem":2583},"Дескриптори — Магія доступу до атрибутів","\u002Fpython\u002Fdescriptors","05.python\u002F07.descriptors",{"title":2585,"path":2586,"stem":2587},"Метакласи — Динамічне створення класів під капотом CPython","\u002Fpython\u002Fmetaclasses","05.python\u002F08.metaclasses",{"title":2589,"path":2590,"stem":2591},"Dataclasses, NamedTuple та сучасні контейнери Python","\u002Fpython\u002Fmodern-containers","05.python\u002F09.modern-containers",{"title":2593,"path":2594,"stem":2595},"GIL та модель конкурентності CPython — фундамент перед потоками і процесами","\u002Fpython\u002Fgil-concurrency-intro","05.python\u002F11.gil-concurrency-intro",{"title":2597,"path":2598,"stem":2599},"Threading — конкурентність для I\u002FO-bound задач","\u002Fpython\u002Fthreading","05.python\u002F12.threading",{"title":2601,"path":2602,"stem":2603},"Multiprocessing — справжній паралелізм для CPU-bound задач","\u002Fpython\u002Fmultiprocessing","05.python\u002F13.multiprocessing",{"title":2605,"path":2606,"stem":2607},"asyncio — кооперативна конкурентність та event loop","\u002Fpython\u002Fasyncio","05.python\u002F14.asyncio",{"title":2609,"icon":92,"path":2610,"stem":2611,"children":2612,"page":59},"FastAPI","\u002Fpython\u002Ffastapi","05.python\u002Ffastapi",[2613,2617,2621,2625,2629,2633,2637,2641],{"title":2614,"path":2615,"stem":2616},"Глибокий Typing та Pydantic v2 — від анотацій до валідації","\u002Fpython\u002Ffastapi\u002Ftyping-pydantic","05.python\u002Ffastapi\u002F15.typing-pydantic",{"title":2618,"path":2619,"stem":2620},"WSGI, ASGI та Python Web-екосистема","\u002Fpython\u002Ffastapi\u002Fwsgi-asgi-ecosystem","05.python\u002Ffastapi\u002F16.wsgi-asgi-ecosystem",{"title":2622,"path":2623,"stem":2624},"FastAPI: Перший додаток, Uvicorn та OpenAPI","\u002Fpython\u002Ffastapi\u002Ffastapi-intro","05.python\u002Ffastapi\u002F17.fastapi-intro",{"title":2626,"path":2627,"stem":2628},"Маршрутизація, параметри запитів та APIRouter","\u002Fpython\u002Ffastapi\u002Ffastapi-routing-params","05.python\u002Ffastapi\u002F18.fastapi-routing-params",{"title":2630,"path":2631,"stem":2632},"Pydantic v2 у FastAPI — схеми, валідація та серіалізація","\u002Fpython\u002Ffastapi\u002Ffastapi-pydantic-schemas","05.python\u002Ffastapi\u002F19.fastapi-pydantic-schemas",{"title":2634,"path":2635,"stem":2636},"Dependency Injection — серце архітектури FastAPI","\u002Fpython\u002Ffastapi\u002Ffastapi-dependency-injection","05.python\u002Ffastapi\u002F20.fastapi-dependency-injection",{"title":2638,"path":2639,"stem":2640},"Обробка помилок, Middleware та CORS у FastAPI","\u002Fpython\u002Ffastapi\u002Ffastapi-errors-middleware","05.python\u002Ffastapi\u002F21.fastapi-errors-middleware",{"title":2642,"path":2643,"stem":2644},"SQLAlchemy 2.0 — ORM, Core та Async Engine","\u002Fpython\u002Ffastapi\u002Fsqlalchemy-orm","05.python\u002Ffastapi\u002F22.sqlalchemy-orm",{"title":2646,"path":2647,"stem":2648},"[object Object]","\u002Fpython\u002Foop-plan","05.python\u002Foop-plan",{"title":2650,"icon":2651,"path":2652,"stem":2653,"children":2654,"page":59},"Бази даних","i-lucide-database","\u002Fdatabases","06.databases",[2655,2685,2708,2745,2774,2792,2826,2838,2847],{"title":2656,"icon":2657,"path":2658,"stem":2659,"children":2660,"page":59},"Intro","i-lucide-play","\u002Fdatabases\u002Fintro","06.databases\u002F01.intro",[2661,2665,2669,2673,2677,2681],{"title":2662,"path":2663,"stem":2664},"Введення в теорію баз даних","\u002Fdatabases\u002Fintro\u002Fintroduction-to-databases","06.databases\u002F01.intro\u002F01.introduction-to-databases",{"title":2666,"path":2667,"stem":2668},"Реляційна модель даних","\u002Fdatabases\u002Fintro\u002Frelational-model-theory","06.databases\u002F01.intro\u002F02.relational-model-theory",{"title":2670,"path":2671,"stem":2672},"ER-моделювання","\u002Fdatabases\u002Fintro\u002Fer-modeling","06.databases\u002F01.intro\u002F03.er-modeling",{"title":2674,"path":2675,"stem":2676},"Логічне проектування БД","\u002Fdatabases\u002Fintro\u002Flogical-schema","06.databases\u002F01.intro\u002F04.logical-schema",{"title":2678,"path":2679,"stem":2680},"Класифікація таблиць","\u002Fdatabases\u002Fintro\u002Ftable-classification","06.databases\u002F01.intro\u002F05.table-classification",{"title":2682,"path":2683,"stem":2684},"PlantUML для баз даних","\u002Fdatabases\u002Fintro\u002Fplantuml-diagrams","06.databases\u002F01.intro\u002F06.plantuml-diagrams",{"title":2686,"icon":2651,"path":2687,"stem":2688,"children":2689,"page":59},"MS SQL Server Start","\u002Fdatabases\u002Fms-sql-server-start","06.databases\u002F02.ms-sql-server-start",[2690,2694,2700,2704],{"title":2691,"path":2692,"stem":2693},"Типи даних у MS SQL Server","\u002Fdatabases\u002Fms-sql-server-start\u002Fdata-types","06.databases\u002F02.ms-sql-server-start\u002F01.data-types",{"title":2695,"path":2696,"stem":2697,"children":2698},"Індекси у MS SQL Server","\u002Fdatabases\u002Fms-sql-server-start\u002Fsql-indexes","06.databases\u002F02.ms-sql-server-start\u002F02.sql-indexes",[2699],{"title":2695,"path":2696,"stem":2697},{"title":2701,"path":2702,"stem":2703},"Системні бази даних MS SQL Server","\u002Fdatabases\u002Fms-sql-server-start\u002Fsystem-databases","06.databases\u002F02.ms-sql-server-start\u002F03.system-databases",{"title":2705,"path":2706,"stem":2707},"Огляд мови SQL та запитів","\u002Fdatabases\u002Fms-sql-server-start\u002Fsql-queries-overview","06.databases\u002F02.ms-sql-server-start\u002F04.sql-queries-overview",{"title":2709,"icon":2651,"path":2710,"stem":2711,"children":2712,"page":59},"SQL","\u002Fdatabases\u002Fsql","06.databases\u002F03.sql",[2713,2717,2721,2725,2729,2733,2737,2741],{"title":2714,"path":2715,"stem":2716},"Налаштування демонстраційної бази даних","\u002Fdatabases\u002Fsql\u002Fsample-database-setup","06.databases\u002F03.sql\u002F00.sample-database-setup",{"title":2718,"path":2719,"stem":2720},"DDL - Створення таблиць (CREATE TABLE)","\u002Fdatabases\u002Fsql\u002Fddl-create-table","06.databases\u002F03.sql\u002F01.ddl-create-table",{"title":2722,"path":2723,"stem":2724},"DDL - Зміна та видалення таблиць (ALTER, DROP)","\u002Fdatabases\u002Fsql\u002Fddl-alter-drop-table","06.databases\u002F03.sql\u002F02.ddl-alter-drop-table",{"title":2726,"path":2727,"stem":2728},"SELECT запити - Основи","\u002Fdatabases\u002Fsql\u002Fselect-queries-fundamentals","06.databases\u002F03.sql\u002F03.select-queries-fundamentals",{"title":2730,"path":2731,"stem":2732},"SELECT запити - Розширені можливості","\u002Fdatabases\u002Fsql\u002Fselect-queries-advanced","06.databases\u002F03.sql\u002F04.select-queries-advanced",{"title":2734,"path":2735,"stem":2736},"INSERT запити - Додавання даних","\u002Fdatabases\u002Fsql\u002Finsert-queries","06.databases\u002F03.sql\u002F05.insert-queries",{"title":2738,"path":2739,"stem":2740},"UPDATE та DELETE запити","\u002Fdatabases\u002Fsql\u002Fupdate-delete-queries","06.databases\u002F03.sql\u002F06.update-delete-queries",{"title":2742,"path":2743,"stem":2744},"Транзакції в SQL","\u002Fdatabases\u002Fsql\u002Ftransactions","06.databases\u002F03.sql\u002F07.transactions",{"title":2746,"icon":2651,"path":2747,"stem":2748,"children":2749,"page":59},"Multi Table Databases","\u002Fdatabases\u002Fmulti-table-databases","06.databases\u002F04.multi-table-databases",[2750,2754,2758,2762,2766,2770],{"title":2751,"path":2752,"stem":2753},"Зв'язки та нормалізація БД","\u002Fdatabases\u002Fmulti-table-databases\u002Frelationships-and-normalization","06.databases\u002F04.multi-table-databases\u002F00.relationships-and-normalization",{"title":2755,"path":2756,"stem":2757},"INNER JOIN - З'єднання таблиць","\u002Fdatabases\u002Fmulti-table-databases\u002Finner-join","06.databases\u002F04.multi-table-databases\u002F01.inner-join",{"title":2759,"path":2760,"stem":2761},"OUTER JOINs - LEFT, RIGHT, FULL","\u002Fdatabases\u002Fmulti-table-databases\u002Fouter-joins","06.databases\u002F04.multi-table-databases\u002F02.outer-joins",{"title":2763,"path":2764,"stem":2765},"CROSS та SELF JOINs","\u002Fdatabases\u002Fmulti-table-databases\u002Fcross-self-joins","06.databases\u002F04.multi-table-databases\u002F03.cross-self-joins",{"title":2767,"path":2768,"stem":2769},"Підзапити (Subqueries)","\u002Fdatabases\u002Fmulti-table-databases\u002Fsubqueries","06.databases\u002F04.multi-table-databases\u002F04.subqueries",{"title":2771,"path":2772,"stem":2773},"Агрегації з JOIN","\u002Fdatabases\u002Fmulti-table-databases\u002Faggregations-with-joins","06.databases\u002F04.multi-table-databases\u002F05.aggregations-with-joins",{"title":2775,"icon":2776,"path":2777,"stem":2778,"children":2779,"page":59},"Aggregate Functions","i-lucide-calculator","\u002Fdatabases\u002Faggregate-functions","06.databases\u002F05.aggregate-functions",[2780,2784,2788],{"title":2781,"path":2782,"stem":2783},"Функції агрегування в MS SQL Server","\u002Fdatabases\u002Faggregate-functions\u002Fintroduction-aggregate-functions","06.databases\u002F05.aggregate-functions\u002F01.introduction-aggregate-functions",{"title":2785,"path":2786,"stem":2787},"Групування даних в MS SQL Server","\u002Fdatabases\u002Faggregate-functions\u002Fgrouping-data","06.databases\u002F05.aggregate-functions\u002F02.grouping-data",{"title":2789,"path":2790,"stem":2791},"Підзапити з агрегатними функціями","\u002Fdatabases\u002Faggregate-functions\u002Fsubqueries-aggregates","06.databases\u002F05.aggregate-functions\u002F03.subqueries-aggregates",{"title":2793,"icon":2794,"path":2795,"stem":2796,"children":2797,"page":59},"Тригери та зберігаємі процедури","i-lucide-database-zap","\u002Fdatabases\u002Ftriggers-stored-procedures","06.databases\u002F07.triggers-stored-procedures",[2798,2802,2806,2810,2814,2818,2822],{"title":2799,"path":2800,"stem":2801},"DML-тригери","\u002Fdatabases\u002Ftriggers-stored-procedures\u002Fdml-triggers","06.databases\u002F07.triggers-stored-procedures\u002F01.dml-triggers",{"title":2803,"path":2804,"stem":2805},"DDL-тригери","\u002Fdatabases\u002Ftriggers-stored-procedures\u002Fddl-triggers","06.databases\u002F07.triggers-stored-procedures\u002F02.ddl-triggers",{"title":2807,"path":2808,"stem":2809},"Transact-SQL розширення","\u002Fdatabases\u002Ftriggers-stored-procedures\u002Ftransact-sql-extensions","06.databases\u002F07.triggers-stored-procedures\u002F03.transact-sql-extensions",{"title":2811,"path":2812,"stem":2813},"Транзакції","\u002Fdatabases\u002Ftriggers-stored-procedures\u002Ftransactions","06.databases\u002F07.triggers-stored-procedures\u002F04.transactions",{"title":2815,"path":2816,"stem":2817},"Зберігаємі процедури","\u002Fdatabases\u002Ftriggers-stored-procedures\u002Fstored-procedures","06.databases\u002F07.triggers-stored-procedures\u002F05.stored-procedures",{"title":2819,"path":2820,"stem":2821},"Користувацькі функції","\u002Fdatabases\u002Ftriggers-stored-procedures\u002Fuser-defined-functions","06.databases\u002F07.triggers-stored-procedures\u002F06.user-defined-functions",{"title":2823,"path":2824,"stem":2825},"Безпека баз даних","\u002Fdatabases\u002Ftriggers-stored-procedures\u002Fsecurity","06.databases\u002F07.triggers-stored-procedures\u002F08.security",{"title":2823,"icon":793,"path":2827,"stem":2828,"children":2829,"page":59},"\u002Fdatabases\u002Fsecurity","06.databases\u002F08.security",[2830,2834],{"title":2831,"path":2832,"stem":2833},"Вступ до безпеки баз даних","\u002Fdatabases\u002Fsecurity\u002Fintroduction","06.databases\u002F08.security\u002F01.introduction",{"title":2835,"path":2836,"stem":2837},"Системні представлення та метадані","\u002Fdatabases\u002Fsecurity\u002Fsystem-views","06.databases\u002F08.security\u002F02.system-views",{"title":2839,"icon":2840,"path":2841,"stem":2842,"children":2843,"page":59},"Резервне копіювання та відновлення","i-lucide-database-backup","\u002Fdatabases\u002Fbackup-recovery","06.databases\u002F09.backup-recovery",[2844],{"title":2839,"path":2845,"stem":2846},"\u002Fdatabases\u002Fbackup-recovery\u002Fbackup-restore","06.databases\u002F09.backup-recovery\u002F01.backup-restore",{"title":2848,"icon":2849,"path":2850,"stem":2851,"children":2852,"page":59},"Повнотекстовий пошук","i-lucide-search","\u002Fdatabases\u002Ffull-text-search","06.databases\u002F10.full-text-search",[2853],{"title":2848,"path":2854,"stem":2855},"\u002Fdatabases\u002Ffull-text-search\u002Ffull-text-search","06.databases\u002F10.full-text-search\u002F01.full-text-search",{"title":2857,"icon":2858,"path":2859,"stem":2860,"children":2861,"page":59},"Tools","i-lucide-wrench","\u002Ftools","07.tools",[2862,2938],{"title":2863,"icon":2864,"path":2865,"stem":2866,"children":2867},"Docker","i-simple-icons-docker","\u002Ftools\u002Fdocker","07.tools\u002F01.docker\u002Findex",[2868,2870,2874,2878,2882,2886,2890,2894,2898,2902,2906,2910,2914,2918,2922,2926,2930,2934],{"title":2869,"path":2865,"stem":2866},"Docker: від нуля до production",{"title":2871,"path":2872,"stem":2873},"Контейнеризація — від проблеми до рішення","\u002Ftools\u002Fdocker\u002Fcontainerization-concept","07.tools\u002F01.docker\u002F01.containerization-concept",{"title":2875,"path":2876,"stem":2877},"Docker — що це і навіщо?","\u002Ftools\u002Fdocker\u002Fdocker-what-and-why","07.tools\u002F01.docker\u002F02.docker-what-and-why",{"title":2879,"path":2880,"stem":2881},"Архітектура Docker Engine","\u002Ftools\u002Fdocker\u002Fdocker-architecture","07.tools\u002F01.docker\u002F03.docker-architecture",{"title":2883,"path":2884,"stem":2885},"Встановлення Docker","\u002Ftools\u002Fdocker\u002Finstallation","07.tools\u002F01.docker\u002F04.installation",{"title":2887,"path":2888,"stem":2889},"Перший контейнер — docker run","\u002Ftools\u002Fdocker\u002Ffirst-container","07.tools\u002F01.docker\u002F05.first-container",{"title":2891,"path":2892,"stem":2893},"Життєвий цикл контейнера","\u002Ftools\u002Fdocker\u002Fcontainer-lifecycle","07.tools\u002F01.docker\u002F06.container-lifecycle",{"title":2895,"path":2896,"stem":2897},"Docker Images — фундаментальні концепції","\u002Ftools\u002Fdocker\u002Fdocker-images-fundamentals","07.tools\u002F01.docker\u002F07.docker-images-fundamentals",{"title":2899,"path":2900,"stem":2901},"Dockerfile — основи","\u002Ftools\u002Fdocker\u002Fdockerfile-basics","07.tools\u002F01.docker\u002F08.dockerfile-basics",{"title":2903,"path":2904,"stem":2905},"Dockerfile — просунуті техніки","\u002Ftools\u002Fdocker\u002Fdockerfile-advanced","07.tools\u002F01.docker\u002F09.dockerfile-advanced",{"title":2907,"path":2908,"stem":2909},"Build Context та кешування шарів","\u002Ftools\u002Fdocker\u002Fbuild-context-and-cache","07.tools\u002F01.docker\u002F10.build-context-and-cache",{"title":2911,"path":2912,"stem":2913},"Реєстри Docker-образів","\u002Ftools\u002Fdocker\u002Fimage-registries","07.tools\u002F01.docker\u002F11.image-registries",{"title":2915,"path":2916,"stem":2917},"Контейнеризація .NET додатків","\u002Ftools\u002Fdocker\u002Fdotnet-containerization","07.tools\u002F01.docker\u002F12.dotnet-containerization",{"title":2919,"path":2920,"stem":2921},"Томи та збереження даних","\u002Ftools\u002Fdocker\u002Fvolumes-and-data","07.tools\u002F01.docker\u002F13.volumes-and-data",{"title":2923,"path":2924,"stem":2925},"Основи мережі в Docker","\u002Ftools\u002Fdocker\u002Fnetworking-basics","07.tools\u002F01.docker\u002F14.networking-basics",{"title":2927,"path":2928,"stem":2929},"Змінні оточення та конфігурація","\u002Ftools\u002Fdocker\u002Fenvironment-and-configuration","07.tools\u002F01.docker\u002F15.environment-and-configuration",{"title":2931,"path":2932,"stem":2933},"Docker Compose — оркестрація контейнерів","\u002Ftools\u002Fdocker\u002Fdocker-compose-basics","07.tools\u002F01.docker\u002F16.docker-compose-basics",{"title":2935,"path":2936,"stem":2937},"Docker Compose — Multi-Service застосунки","\u002Ftools\u002Fdocker\u002Fcompose-multi-service","07.tools\u002F01.docker\u002F17.compose-multi-service",{"title":2939,"icon":2940,"path":2941,"stem":2942,"children":2943},"Kubernetes","simple-icons:kubernetes","\u002Ftools\u002Fkubernetes","07.tools\u002F02.kubernetes\u002Findex",[2944,2946,2950,2954,2958,2962,2966,2970,2974],{"title":2945,"path":2941,"stem":2942},"Kubernetes: від розробки до production",{"title":2947,"path":2948,"stem":2949},"Kubernetes — коли Docker Compose більше не вистачає","\u002Ftools\u002Fkubernetes\u002Fwhy-kubernetes","07.tools\u002F02.kubernetes\u002F01.why-kubernetes",{"title":2951,"path":2952,"stem":2953},"Архітектура Kubernetes — анатомія кластера","\u002Ftools\u002Fkubernetes\u002Fkubernetes-architecture","07.tools\u002F02.kubernetes\u002F02.kubernetes-architecture",{"title":2955,"path":2956,"stem":2957},"Локальне середовище — minikube, kind та k3s","\u002Ftools\u002Fkubernetes\u002Flocal-environment","07.tools\u002F02.kubernetes\u002F03.local-environment",{"title":2959,"path":2960,"stem":2961},"Pod — атомарна одиниця Kubernetes","\u002Ftools\u002Fkubernetes\u002Fpods-and-containers","07.tools\u002F02.kubernetes\u002F04.pods-and-containers",{"title":2963,"path":2964,"stem":2965},"Патерни використання Pod","\u002Ftools\u002Fkubernetes\u002Fpod-patterns","07.tools\u002F02.kubernetes\u002F05.pod-patterns",{"title":2967,"path":2968,"stem":2969},"Deployment — декларативне управління Pod","\u002Ftools\u002Fkubernetes\u002Fdeployment-basics","07.tools\u002F02.kubernetes\u002F06.deployment-basics",{"title":2971,"path":2972,"stem":2973},"Rolling Updates та управління життєвим циклом Deployment","\u002Ftools\u002Fkubernetes\u002Fdeployment-rolling-updates","07.tools\u002F02.kubernetes\u002F07.deployment-rolling-updates",{"title":2975,"path":2976,"stem":2977},"Service — мережева абстракція для Pod","\u002Ftools\u002Fkubernetes\u002Fservices-networking","07.tools\u002F02.kubernetes\u002F08.services-networking",{"title":2979,"icon":2980,"path":2981,"stem":2982,"children":2983,"page":59},"Software Engineering","i-lucide-code-2","\u002Fsoftware-engineering","09.software-engineering",[2984,2988,2992,2996,3000,3004,3008,3012,3016,3020,3024],{"title":2985,"path":2986,"stem":2987},"1. Аналіз предметної області. Експертні знання та складність","\u002Fsoftware-engineering\u002Fintro-subdomains","09.software-engineering\u002F01.intro-subdomains",{"title":2989,"path":2990,"stem":2991},"2. Обмежені контексти. Інтеграція обмежених контекстів","\u002Fsoftware-engineering\u002Fintegrating-limited-contexts","09.software-engineering\u002F02.integrating-limited-contexts",{"title":2993,"path":2994,"stem":2995},"3. Реалізація простої бізнес-логіки","\u002Fsoftware-engineering\u002Fsimple","09.software-engineering\u002F03.simple",{"title":2997,"path":2998,"stem":2999},"4. Опрацювання складної бізнес-логіки","\u002Fsoftware-engineering\u002Fcomplex-business-logic","09.software-engineering\u002F04.complex-business-logic",{"title":3001,"path":3002,"stem":3003},"5. Моделювання фактора часу. Подієво-орієнтована архітектура.","\u002Fsoftware-engineering\u002Fmodelling-the-time-factor","09.software-engineering\u002F05.modelling-the-time-factor",{"title":3005,"path":3006,"stem":3007},"6. Архітектурні патерни","\u002Fsoftware-engineering\u002Farchitectural-patterns","09.software-engineering\u002F06.architectural-patterns",{"title":3009,"path":3010,"stem":3011},"Паттерни взаємодії","\u002Fsoftware-engineering\u002Fpatterns-of-interaction","09.software-engineering\u002F07.patterns-of-interaction",{"title":3013,"path":3014,"stem":3015},"Евристика проєктування","\u002Fsoftware-engineering\u002Fdesign-heuristics","09.software-engineering\u002F08.design-heuristics",{"title":3017,"path":3018,"stem":3019},"Еволюція проєктних рішень","\u002Fsoftware-engineering\u002Fevolution-of-design-solutions","09.software-engineering\u002F09.evolution-of-design-solutions",{"title":3021,"path":3022,"stem":3023},"EventStorming","\u002Fsoftware-engineering\u002Feventstorming","09.software-engineering\u002F10.eventstorming",{"title":3025,"path":3026,"stem":3027},"DDD на практиці","\u002Fsoftware-engineering\u002Fddd-in-practice","09.software-engineering\u002F11.ddd-in-practice",{"title":3029,"icon":943,"path":3030,"stem":3031,"children":3032,"page":59},"DDD","\u002Fddd","10.ddd",[3033,3037,3041,3045,3049,3053,3057,3061,3065,3069,3073,3077,3081],{"title":3034,"path":3035,"stem":3036},"Аналіз предметної області","\u002Fddd\u002Fdomain-analysis","10.ddd\u002F01.domain-analysis",{"title":3038,"path":3039,"stem":3040},"Експертні знання про предметну область","\u002Fddd\u002Fdomain-expert-knowledge","10.ddd\u002F02.domain-expert-knowledge",{"title":3042,"path":3043,"stem":3044},"Як осмислити складність предметної області","\u002Fddd\u002Fmanaging-domain-complexity","10.ddd\u002F03.managing-domain-complexity",{"title":3046,"path":3047,"stem":3048},"Інтеграція обмежених контекстів","\u002Fddd\u002Fbounded-context-integration","10.ddd\u002F04.bounded-context-integration",{"title":3050,"path":3051,"stem":3052},"Реалізація простої бізнес-логіки","\u002Fddd\u002Fsimple-business-logic","10.ddd\u002F05.simple-business-logic",{"title":3054,"path":3055,"stem":3056},"Обробка складної бізнес-логіки","\u002Fddd\u002Fcomplex-business-logic","10.ddd\u002F06.complex-business-logic",{"title":3058,"path":3059,"stem":3060},"Моделювання фактора часу","\u002Fddd\u002Ftime-modeling","10.ddd\u002F07.time-modeling",{"title":3062,"path":3063,"stem":3064},"Глава 8. Архітектурні Патерни","\u002Fddd\u002Farchitectural-patterns","10.ddd\u002F08.architectural-patterns",{"title":3066,"path":3067,"stem":3068},"Глава 9. Патерни Взаємодії","\u002Fddd\u002Finteraction-patterns","10.ddd\u002F09.interaction-patterns",{"title":3070,"path":3071,"stem":3072},"Глава 10. Проектні Евристики","\u002Fddd\u002Fdesign-heuristics","10.ddd\u002F10.design-heuristics",{"title":3074,"path":3075,"stem":3076},"Глава 11. Еволюція Проектних Рішень","\u002Fddd\u002Fevolution-of-design-decisions","10.ddd\u002F11.evolution-of-design-decisions",{"title":3078,"path":3079,"stem":3080},"Глава 12. EventStorming","\u002Fddd\u002Fevent-storming","10.ddd\u002F12.event-storming",{"title":3082,"path":3083,"stem":3084},"Глава 13. DDD на Практиці","\u002Fddd\u002Fddd-in-practice","10.ddd\u002F13.ddd-in-practice",{"title":3086,"icon":3087,"path":3088,"stem":3089,"children":3090,"page":59},"Media Streaming","i-lucide-video","\u002Fmedia-streaming","11.media-streaming",[3091,3095,3099,3103,3107,3111,3115],{"title":3092,"path":3093,"stem":3094},"01. Магія Стрімінгу: Що відбувається, коли ви натискаєте \"Play\"","\u002Fmedia-streaming\u002Fintroduction","11.media-streaming\u002F01.introduction",{"title":3096,"path":3097,"stem":3098},"02. Анатомія Медіа: Кодеки, Контейнери та Стиснення","\u002Fmedia-streaming\u002Faudio-video-anatomy","11.media-streaming\u002F02.audio-video-anatomy",{"title":3100,"path":3101,"stem":3102},"03. The Gym: FFmpeg Deep Dive","\u002Fmedia-streaming\u002Fffmpeg-gym","11.media-streaming\u002F03.ffmpeg-gym",{"title":3104,"path":3105,"stem":3106},"04. HLS Protocol: HTTP Live Streaming у Деталях","\u002Fmedia-streaming\u002Fhls-protocol","11.media-streaming\u002F04.hls-protocol",{"title":3108,"path":3109,"stem":3110},"05. DASH Protocol: Відкритий Стандарт","\u002Fmedia-streaming\u002Fdash-protocol","11.media-streaming\u002F05.dash-protocol",{"title":3112,"path":3113,"stem":3114},"06. Масштабування: CDN та Adaptive Bitrate","\u002Fmedia-streaming\u002Fcdn-and-adaptive-bitrate","11.media-streaming\u002F06.cdn-and-adaptive-bitrate",{"title":3116,"path":3117,"stem":3118},"07. Війна із Затримкою (Latency)","\u002Fmedia-streaming\u002Frealtime-latency","11.media-streaming\u002F07.realtime-latency",{"title":3120,"icon":3121,"path":3122,"stem":3123,"children":3124,"page":59},"HTML & CSS","i-devicon-html5","\u002Fhtml-css","12.html-css",[3125,3129,3133,3137,3141,3145,3149,3153,3157,3161,3165,3169,3173,3177,3181,3185,3189,3193,3197,3201,3205,3209,3213,3217,3221,3225,3229,3233,3237,3241],{"title":3126,"path":3127,"stem":3128},"Вступ до HTML. Структура документа","\u002Fhtml-css\u002Fintro-html-structure","12.html-css\u002F01.intro-html-structure",{"title":3130,"path":3131,"stem":3132},"Форматування тексту в HTML","\u002Fhtml-css\u002Fhtml-text-formatting","12.html-css\u002F02.html-text-formatting",{"title":3134,"path":3135,"stem":3136},"Посилання та зображення в HTML","\u002Fhtml-css\u002Fhtml-links-images","12.html-css\u002F03.html-links-images",{"title":3138,"path":3139,"stem":3140},"Списки та таблиці в HTML","\u002Fhtml-css\u002Fhtml-lists-tables","12.html-css\u002F04.html-lists-tables",{"title":3142,"path":3143,"stem":3144},"Форми в HTML","\u002Fhtml-css\u002Fhtml-forms","12.html-css\u002F05.html-forms",{"title":3146,"path":3147,"stem":3148},"Семантичні елементи HTML5","\u002Fhtml-css\u002Fhtml-semantic-elements","12.html-css\u002F06.html-semantic-elements",{"title":3150,"path":3151,"stem":3152},"Мультимедіа та розширені елементи HTML","\u002Fhtml-css\u002Fhtml-multimedia-advanced","12.html-css\u002F07.html-multimedia-advanced",{"title":3154,"path":3155,"stem":3156},"Мікророзмітка та SEO в HTML","\u002Fhtml-css\u002Fhtml-microdata-seo","12.html-css\u002F08.html-microdata-seo",{"title":3158,"path":3159,"stem":3160},"Вступ до CSS. Селектори та специфічність","\u002Fhtml-css\u002Fcss-intro-selectors","12.html-css\u002F09.css-intro-selectors",{"title":3162,"path":3163,"stem":3164},"Блокова модель CSS. Відступи. Box Sizing","\u002Fhtml-css\u002Fcss-box-model","12.html-css\u002F10.css-box-model",{"title":3166,"path":3167,"stem":3168},"Розміри у CSS: повний довідник одиниць і ключових слів","\u002Fhtml-css\u002F10a.css-sizing","12.html-css\u002F10a.css-sizing",{"title":3170,"path":3171,"stem":3172},"Типографіка в CSS. Шрифти та текст","\u002Fhtml-css\u002Fcss-typography","12.html-css\u002F11.css-typography",{"title":3174,"path":3175,"stem":3176},"Кольори та фони в CSS","\u002Fhtml-css\u002Fcss-colors-backgrounds","12.html-css\u002F12.css-colors-backgrounds",{"title":3178,"path":3179,"stem":3180},"Тіні та фільтри в CSS","\u002Fhtml-css\u002F12b.css-shadows-filters","12.html-css\u002F12b.css-shadows-filters",{"title":3182,"path":3183,"stem":3184},"CSS Flexbox: Фундамент гнучких макетів","\u002Fhtml-css\u002Fcss-flexbox-fundamentals","12.html-css\u002F13.css-flexbox-fundamentals",{"title":3186,"path":3187,"stem":3188},"CSS Flexbox: Вирівнювання та Позиціонування","\u002Fhtml-css\u002Fcss-flexbox-alignment-sizing-and-patterns","12.html-css\u002F14.css-flexbox-alignment-sizing-and-patterns",{"title":3190,"path":3191,"stem":3192},"CSS Grid. Двовимірний макет. Частина 1","\u002Fhtml-css\u002Fcss-layout-grid","12.html-css\u002F15.css-layout-grid",{"title":3194,"path":3195,"stem":3196},"CSS Grid. Двовимірний макет. Частина 2","\u002Fhtml-css\u002Fcss-layout-grid-advanced","12.html-css\u002F16.css-layout-grid-advanced",{"title":3198,"path":3199,"stem":3200},"Позиціонування в CSS. Z-index. Stacking Context","\u002Fhtml-css\u002Fcss-positioning","12.html-css\u002F17.css-positioning",{"title":3202,"path":3203,"stem":3204},"CSS Анімації та Переходи","\u002Fhtml-css\u002Fcss-animations-transitions","12.html-css\u002F18.css-animations-transitions",{"title":3206,"path":3207,"stem":3208},"Адаптивний дизайн. Media Queries. Частина 1","\u002Fhtml-css\u002Fcss-responsive-media-queries","12.html-css\u002F19.css-responsive-media-queries",{"title":3210,"path":3211,"stem":3212},"Адаптивний дизайн. Частина 2: clamp(), Container Queries, @layer","\u002Fhtml-css\u002Fcss-responsive-advanced","12.html-css\u002F20.css-responsive-advanced",{"title":3214,"path":3215,"stem":3216},"CSS Custom Properties. Методології. Сучасний CSS","\u002Fhtml-css\u002Fcss-variables-methodologies","12.html-css\u002F21.css-variables-methodologies",{"title":3218,"path":3219,"stem":3220},"Сучасний CSS 2023–2025: Нові можливості","\u002Fhtml-css\u002Fcss-modern-features","12.html-css\u002F22.css-modern-features",{"title":3222,"path":3223,"stem":3224},"CSS Nesting, @layer, @scope та @property: нативний препроцесор","\u002Fhtml-css\u002F22a.css-nesting-modern-syntax","12.html-css\u002F22a.css-nesting-modern-syntax",{"title":3226,"path":3227,"stem":3228},"CSS для форм та інтерактивних станів","\u002Fhtml-css\u002Fcss-forms-interactive-states","12.html-css\u002F23.css-forms-interactive-states",{"title":3230,"path":3231,"stem":3232},"Доступність у CSS (CSS Accessibility)","\u002Fhtml-css\u002Fcss-accessibility","12.html-css\u002F24.css-accessibility",{"title":3234,"path":3235,"stem":3236},"CSS-функції та сучасні sizing primitives","\u002Fhtml-css\u002Fcss-functions-sizing","12.html-css\u002F25.css-functions-sizing",{"title":3238,"path":3239,"stem":3240},"Rendering Pipeline і CSS Performance","\u002Fhtml-css\u002Fcss-rendering-performance","12.html-css\u002F26.css-rendering-performance",{"title":3242,"path":3243,"stem":3244},"CSS Best Practices: типові ситуації та правильні рішення","\u002Fhtml-css\u002Fcss-best-practices","12.html-css\u002F27.css-best-practices",{"title":3246,"path":3247,"stem":3248,"children":3249,"page":59},"AWS","\u002Faws","13.aws",[3250,3254,3258,3262,3266,3270,3274,3278,3282,3286,3290,3294,3298,3302,3306,3310,3314,3318],{"title":3251,"path":3252,"stem":3253},"Реєстрація AWS акаунту та студентські програми","\u002Faws\u002Faccount-registration","13.aws\u002F00.account-registration",{"title":3255,"path":3256,"stem":3257},"Вступ до хмарних обчислень та AWS","\u002Faws\u002Fintroduction-to-cloud","13.aws\u002F01.introduction-to-cloud",{"title":3259,"path":3260,"stem":3261},"AWS IAM — Identity and Access Management","\u002Faws\u002Fiam","13.aws\u002F02.iam",{"title":3263,"path":3264,"stem":3265},"AWS IAM CLI — Довідник команд","\u002Faws\u002F02a.iam-doc","13.aws\u002F02a.iam-doc",{"title":3267,"path":3268,"stem":3269},"Docker та контейнеризація в AWS — ECR, ECS та Fargate","\u002Faws\u002Fdocker-ecs","13.aws\u002F03.docker-ecs",{"title":3271,"path":3272,"stem":3273},"AWS ECR \u002F ECS CLI — Довідник команд","\u002Faws\u002F03a.docker-ecs-doc","13.aws\u002F03a.docker-ecs-doc",{"title":3275,"path":3276,"stem":3277},"Amazon EC2 — Elastic Compute Cloud","\u002Faws\u002Fec2","13.aws\u002F04.ec2",{"title":3279,"path":3280,"stem":3281},"AWS EC2 CLI — Довідник команд","\u002Faws\u002F04a.ec2-doc","13.aws\u002F04a.ec2-doc",{"title":3283,"path":3284,"stem":3285},"Elastic Load Balancing та Auto Scaling","\u002Faws\u002Falb-asg","13.aws\u002F05.alb-asg",{"title":3287,"path":3288,"stem":3289},"Amazon S3 — Simple Storage Service","\u002Faws\u002Fs3","13.aws\u002F06.s3",{"title":3291,"path":3292,"stem":3293},"Amazon CloudFront — Content Delivery Network","\u002Faws\u002Fcloudfront","13.aws\u002F07.cloudfront",{"title":3295,"path":3296,"stem":3297},"Amazon RDS — Relational Database Service","\u002Faws\u002Frds","13.aws\u002F08.rds",{"title":3299,"path":3300,"stem":3301},"Amazon DynamoDB — NoSQL Database","\u002Faws\u002Fdynamodb","13.aws\u002F09.dynamodb",{"title":3303,"path":3304,"stem":3305},"AWS Lambda та Serverless Compute","\u002Faws\u002Flambda","13.aws\u002F10.lambda",{"title":3307,"path":3308,"stem":3309},"Amazon Bedrock - Foundation Models, RAG та Agents","\u002Faws\u002Fbedrock","13.aws\u002F22.bedrock",{"title":3311,"path":3312,"stem":3313},"Amazon Rekognition - Комп'ютерний зір","\u002Faws\u002Frekognition","13.aws\u002F23.rekognition",{"title":3315,"path":3316,"stem":3317},"Amazon Textract - Інтелектуальний аналіз документів","\u002Faws\u002Ftextract","13.aws\u002F24.textract",{"title":3319,"path":3320,"stem":3321},"Amazon Polly, Transcribe, Comprehend та Translate","\u002Faws\u002Faudio-nlp-services","13.aws\u002F25.audio-nlp-services",{"title":3323,"path":3324,"stem":3325,"children":3326,"page":59},"Tailwind","\u002Ftailwind","21.tailwind",[3327,3331,3335,3339,3343,3347,3351,3355,3359,3363,3367,3371],{"title":3328,"path":3329,"stem":3330},"Що таке Tailwind CSS і навіщо він потрібен","\u002Ftailwind\u002Ftailwind-intro-philosophy","21.tailwind\u002F01.tailwind-intro-philosophy",{"title":3332,"path":3333,"stem":3334},"Встановлення та налаштування Tailwind CSS v4","\u002Ftailwind\u002Ftailwind-installation-setup","21.tailwind\u002F02.tailwind-installation-setup",{"title":3336,"path":3337,"stem":3338},"Utility-класи: основи та система Tailwind","\u002Ftailwind\u002Ftailwind-utility-classes-core","21.tailwind\u002F03.tailwind-utility-classes-core",{"title":3340,"path":3341,"stem":3342},"Layout: Flexbox та Grid через Tailwind","\u002Ftailwind\u002Ftailwind-flexbox-grid","21.tailwind\u002F04.tailwind-flexbox-grid",{"title":3344,"path":3345,"stem":3346},"Кастомізація теми через @theme у Tailwind v4","\u002Ftailwind\u002Ftailwind-theme-customization","21.tailwind\u002F05.tailwind-theme-customization",{"title":3348,"path":3349,"stem":3350},"Варіанти: hover, focus, responsive, dark mode та нові v4","\u002Ftailwind\u002Ftailwind-variants-states","21.tailwind\u002F06.tailwind-variants-states",{"title":3352,"path":3353,"stem":3354},"Типографіка та система кольорів у Tailwind v4","\u002Ftailwind\u002Ftailwind-typography-colors","21.tailwind\u002F07.tailwind-typography-colors",{"title":3356,"path":3357,"stem":3358},"Компоненти та повторюваність: @apply, @utility та патерни","\u002Ftailwind\u002Ftailwind-components-patterns","21.tailwind\u002F08.tailwind-components-patterns",{"title":3360,"path":3361,"stem":3362},"Темна тема та система дизайн-токенів у Tailwind v4","\u002Ftailwind\u002Ftailwind-dark-mode-theming","21.tailwind\u002F09.tailwind-dark-mode-theming",{"title":3364,"path":3365,"stem":3366},"Довільні значення та контейнерні запити у Tailwind v4","\u002Ftailwind\u002Ftailwind-arbitrary-container-queries","21.tailwind\u002F10.tailwind-arbitrary-container-queries",{"title":3368,"path":3369,"stem":3370},"Анімації, трансформації та 3D у Tailwind v4","\u002Ftailwind\u002Ftailwind-animations-transforms","21.tailwind\u002F11.tailwind-animations-transforms",{"title":3372,"path":3373,"stem":3374},"Tailwind CLI, PostCSS та інтеграція з фреймворками","\u002Ftailwind\u002Ftailwind-cli-tooling","21.tailwind\u002F12.tailwind-cli-tooling",{"title":3376,"path":3377,"stem":3378},"Тестування компонентів діаграм","\u002Ftest-components","98.test-components",{"id":3380,"title":2569,"body":3381,"description":15119,"extension":15120,"links":15121,"meta":15122,"navigation":3474,"path":2570,"seo":15123,"stem":2571,"__hash__":15124},"docs\u002F05.python\u002F04.abstraction-protocols.md",{"type":3382,"value":3383,"toc":15081},"minimark",[3384,3388,3393,3397,3400,3537,3544,3551,3568,3583,3586,3590,3595,3618,3730,3733,3747,3751,3765,3867,3879,3893,4060,4062,4066,4070,4080,4098,4102,4105,4129,4421,4441,4445,4458,4951,4999,5003,5009,5056,5717,5725,5732,5931,5955,5959,5965,6091,6124,6405,6451,6453,6457,6461,6471,6478,6533,6555,6559,6673,6681,7021,7037,7041,7048,7273,7343,7358,7365,7383,7714,7750,7864,7907,7911,7919,8203,8207,8210,8472,8474,8478,8482,8655,8659,8821,8823,8827,8831,8834,10938,11025,11027,11031,11035,11038,11066,11874,11939,11941,11945,11953,13354,13423,13425,13429,13437,14892,15023,15025,15029,15032,15044,15056,15063,15077],[3385,3386,2569],"h1",{"id":3387},"абстракція-abc-проти-статичних-протоколів-pep-544",[3389,3390,3392],"h2",{"id":3391},"проблема-як-описати-контракт-не-привязуючись-до-реалізації","Проблема: Як описати контракт, не прив'язуючись до реалізації",[3394,3395,3396],"p",{},"Уявіть, що ви проектуєте систему сповіщень для великого SaaS-сервісу. У вас є кілька транспортів: email, SMS, push-нотифікації, Slack-повідомлення. Логіка надсилання різна, але кожен транспорт виконує одне завдання — відправляє повідомлення.",[3394,3398,3399],{},"Ваша перша спроба виглядає так:",[3401,3402,3407],"pre",{"className":3403,"code":3404,"language":3405,"meta":3406,"style":3406},"language-python shiki shiki-themes light-plus dark-plus dark-plus","# ❌ Погана архітектура: залежність від конкретних класів\nclass NotificationService:\n    def __init__(self, transport):\n        self.transport = transport\n\n    def send(self, user_id: int, message: str) -> None:\n        # А що якщо transport — це email? А може SMS?\n        # Як IDE зрозуміє, які методи є у transport?\n        self.transport.send(user_id, message)\n","python","",[3408,3409,3410,3419,3434,3460,3469,3476,3517,3523,3529],"code",{"__ignoreMap":3406},[3411,3412,3415],"span",{"class":3413,"line":3414},"line",1,[3411,3416,3418],{"class":3417},"spJ8K","# ❌ Погана архітектура: залежність від конкретних класів\n",[3411,3420,3422,3426,3430],{"class":3413,"line":3421},2,[3411,3423,3425],{"class":3424},"su1O8","class",[3411,3427,3429],{"class":3428},"sN1BT"," NotificationService",[3411,3431,3433],{"class":3432},"sHH4Y",":\n",[3411,3435,3437,3440,3444,3447,3451,3454,3457],{"class":3413,"line":3436},3,[3411,3438,3439],{"class":3424},"    def",[3411,3441,3443],{"class":3442},"s8Opu"," __init__",[3411,3445,3446],{"class":3432},"(",[3411,3448,3450],{"class":3449},"siwwj","self",[3411,3452,3453],{"class":3432},", ",[3411,3455,3456],{"class":3449},"transport",[3411,3458,3459],{"class":3432},"):\n",[3411,3461,3463,3466],{"class":3413,"line":3462},4,[3411,3464,3465],{"class":3424},"        self",[3411,3467,3468],{"class":3432},".transport = transport\n",[3411,3470,3472],{"class":3413,"line":3471},5,[3411,3473,3475],{"emptyLinePlaceholder":3474},true,"\n",[3411,3477,3479,3481,3484,3486,3488,3490,3493,3496,3499,3501,3504,3506,3509,3512,3515],{"class":3413,"line":3478},6,[3411,3480,3439],{"class":3424},[3411,3482,3483],{"class":3442}," send",[3411,3485,3446],{"class":3432},[3411,3487,3450],{"class":3449},[3411,3489,3453],{"class":3432},[3411,3491,3492],{"class":3449},"user_id",[3411,3494,3495],{"class":3432},": ",[3411,3497,3498],{"class":3428},"int",[3411,3500,3453],{"class":3432},[3411,3502,3503],{"class":3449},"message",[3411,3505,3495],{"class":3432},[3411,3507,3508],{"class":3428},"str",[3411,3510,3511],{"class":3432},") -> ",[3411,3513,3514],{"class":3424},"None",[3411,3516,3433],{"class":3432},[3411,3518,3520],{"class":3413,"line":3519},7,[3411,3521,3522],{"class":3417},"        # А що якщо transport — це email? А може SMS?\n",[3411,3524,3526],{"class":3413,"line":3525},8,[3411,3527,3528],{"class":3417},"        # Як IDE зрозуміє, які методи є у transport?\n",[3411,3530,3532,3534],{"class":3413,"line":3531},9,[3411,3533,3465],{"class":3424},[3411,3535,3536],{"class":3432},".transport.send(user_id, message)\n",[3394,3538,3539,3540,3543],{},"Код працює, але він крихкий і непрозорий. IDE не підказує автодоповнення. Новий розробник не знає, що саме повинен реалізувати його ",[3408,3541,3542],{},"CustomTransport",". Тест потребує реального транспорту або складного mock-об'єкта.",[3394,3545,3546,3550],{},[3547,3548,3549],"strong",{},"Питання архітектора:"," Як описати «контракт» (інтерфейс) об'єкта-транспорту так, щоб:",[3552,3553,3554,3562,3565],"ul",{},[3555,3556,3557,3558,3561],"li",{},"IDE та статичний аналізатор ",[3408,3559,3560],{},"mypy"," «розуміли» структуру об'єкта",[3555,3563,3564],{},"Розробник, що пише новий транспорт, чітко знав, що потрібно реалізувати",[3555,3566,3567],{},"Система могла перевірити відповідність контракту хоча б під час тестування",[3394,3569,3570,3571,3574,3575,3578,3579,3582],{},"Python надає ",[3547,3572,3573],{},"два принципово різних"," підходи до вирішення цієї проблеми: ",[3547,3576,3577],{},"Abstract Base Classes (ABC)"," та ",[3547,3580,3581],{},"Protocols (PEP 544)",". Ця стаття — глибокий розбір обох механізмів, їхніх внутрішніх реалізацій і точних сценаріїв застосування.",[3584,3585],"hr",{},[3389,3587,3589],{"id":3588},"частина-i-теоретичний-фундамент-два-підходи-до-типізації","Частина I: Теоретичний фундамент — два підходи до типізації",[3591,3592,3594],"h3",{"id":3593},"номінативна-типізація-ти-є-те-чим-себе-називаєш","Номінативна типізація: «Ти є те, чим себе називаєш»",[3394,3596,3597,3598,3601,3602,3605,3606,3609,3610,3613,3614,3617],{},"У мовах зі ",[3547,3599,3600],{},"статичною номінативною типізацією"," (Java, C#, Go \u003C 1.18) клас вважається відповідним певному типу лише якщо він явно ",[3547,3603,3604],{},"оголошує"," цю відповідність. Наприклад, у Java клас ",[3408,3607,3608],{},"EmailSender"," повинен явно написати ",[3408,3611,3612],{},"implements INotifier",", щоб вважатися типом ",[3408,3615,3616],{},"INotifier",".",[3401,3619,3623],{"className":3620,"code":3621,"language":3622,"meta":3406,"style":3406},"language-java shiki shiki-themes light-plus dark-plus dark-plus","\u002F\u002F Java: явна декларація відповідності\ninterface INotifier {\n    void send(int userId, String message);\n}\n\nclass EmailSender implements INotifier { \u002F\u002F ← явне оголошення\n    @Override\n    public void send(int userId, String message) { ... }\n}\n","java",[3408,3624,3625,3630,3641,3666,3671,3675,3693,3701,3726],{"__ignoreMap":3406},[3411,3626,3627],{"class":3413,"line":3414},[3411,3628,3629],{"class":3417},"\u002F\u002F Java: явна декларація відповідності\n",[3411,3631,3632,3635,3638],{"class":3413,"line":3421},[3411,3633,3634],{"class":3424},"interface",[3411,3636,3637],{"class":3428}," INotifier",[3411,3639,3640],{"class":3432}," {\n",[3411,3642,3643,3646,3648,3650,3652,3655,3657,3660,3663],{"class":3413,"line":3436},[3411,3644,3645],{"class":3428},"    void",[3411,3647,3483],{"class":3442},[3411,3649,3446],{"class":3432},[3411,3651,3498],{"class":3428},[3411,3653,3654],{"class":3449}," userId",[3411,3656,3453],{"class":3432},[3411,3658,3659],{"class":3428},"String",[3411,3661,3662],{"class":3449}," message",[3411,3664,3665],{"class":3432},");\n",[3411,3667,3668],{"class":3413,"line":3462},[3411,3669,3670],{"class":3432},"}\n",[3411,3672,3673],{"class":3413,"line":3471},[3411,3674,3475],{"emptyLinePlaceholder":3474},[3411,3676,3677,3679,3682,3685,3687,3690],{"class":3413,"line":3478},[3411,3678,3425],{"class":3424},[3411,3680,3681],{"class":3428}," EmailSender",[3411,3683,3684],{"class":3424}," implements",[3411,3686,3637],{"class":3428},[3411,3688,3689],{"class":3432}," { ",[3411,3691,3692],{"class":3417},"\u002F\u002F ← явне оголошення\n",[3411,3694,3695,3698],{"class":3413,"line":3519},[3411,3696,3697],{"class":3432},"    @",[3411,3699,3700],{"class":3428},"Override\n",[3411,3702,3703,3706,3709,3711,3713,3715,3717,3719,3721,3723],{"class":3413,"line":3525},[3411,3704,3705],{"class":3424},"    public",[3411,3707,3708],{"class":3428}," void",[3411,3710,3483],{"class":3442},[3411,3712,3446],{"class":3432},[3411,3714,3498],{"class":3428},[3411,3716,3654],{"class":3449},[3411,3718,3453],{"class":3432},[3411,3720,3659],{"class":3428},[3411,3722,3662],{"class":3449},[3411,3724,3725],{"class":3432},") { ... }\n",[3411,3727,3728],{"class":3413,"line":3531},[3411,3729,3670],{"class":3432},[3394,3731,3732],{},"Перевага: тип-система є строгою і передбачуваною. Вада: клас у сторонній бібліотеці (яку ви не контролюєте) ніколи не зможе задовольнити ваш інтерфейс, навіть якщо він структурно ідентичний.",[3394,3734,3735,3736,3739,3740,3743,3744,3746],{},"Саме цей підхід реалізується через ",[3547,3737,3738],{},"ABC"," у Python: щоб клас вважався ",[3408,3741,3742],{},"Notifier",", він повинен явно успадкувати від ",[3408,3745,3742],{}," або зареєструватися через спеціальний метод.",[3591,3748,3750],{"id":3749},"структурна-типізація-ти-є-те-що-ти-можеш-робити","Структурна типізація: «Ти є те, що ти можеш робити»",[3394,3752,3753,3756,3757,3760,3761,3764],{},[3547,3754,3755],{},"Структурна типізація"," (Duck Typing, Structural Subtyping) — підхід, за яким відповідність типу визначається ",[3547,3758,3759],{},"структурою"," об'єкта, а не явним оголошенням. Якщо об'єкт має метод ",[3408,3762,3763],{},"send(user_id, message)",", він задовольняє контракту «нотифікатора» — незалежно від того, чи він успадковує від певного базового класу.",[3401,3766,3768],{"className":3403,"code":3767,"language":3405,"meta":3406,"style":3406},"# Python: качині типи (Duck Typing)\nclass SlackSender:\n    def send(self, user_id: int, message: str) -> None:\n        print(f\"Slack: {message} → user {user_id}\")\n\n# SlackSender нічого не успадковує — але його можна передати\n# туди, де очікується об'єкт з методом send()\n",[3408,3769,3770,3775,3784,3816,3853,3857,3862],{"__ignoreMap":3406},[3411,3771,3772],{"class":3413,"line":3414},[3411,3773,3774],{"class":3417},"# Python: качині типи (Duck Typing)\n",[3411,3776,3777,3779,3782],{"class":3413,"line":3421},[3411,3778,3425],{"class":3424},[3411,3780,3781],{"class":3428}," SlackSender",[3411,3783,3433],{"class":3432},[3411,3785,3786,3788,3790,3792,3794,3796,3798,3800,3802,3804,3806,3808,3810,3812,3814],{"class":3413,"line":3436},[3411,3787,3439],{"class":3424},[3411,3789,3483],{"class":3442},[3411,3791,3446],{"class":3432},[3411,3793,3450],{"class":3449},[3411,3795,3453],{"class":3432},[3411,3797,3492],{"class":3449},[3411,3799,3495],{"class":3432},[3411,3801,3498],{"class":3428},[3411,3803,3453],{"class":3432},[3411,3805,3503],{"class":3449},[3411,3807,3495],{"class":3432},[3411,3809,3508],{"class":3428},[3411,3811,3511],{"class":3432},[3411,3813,3514],{"class":3424},[3411,3815,3433],{"class":3432},[3411,3817,3818,3821,3823,3826,3830,3833,3835,3838,3841,3843,3845,3847,3850],{"class":3413,"line":3462},[3411,3819,3820],{"class":3442},"        print",[3411,3822,3446],{"class":3432},[3411,3824,3825],{"class":3424},"f",[3411,3827,3829],{"class":3828},"sbdoH","\"Slack: ",[3411,3831,3832],{"class":3424},"{",[3411,3834,3503],{"class":3432},[3411,3836,3837],{"class":3424},"}",[3411,3839,3840],{"class":3828}," → user ",[3411,3842,3832],{"class":3424},[3411,3844,3492],{"class":3432},[3411,3846,3837],{"class":3424},[3411,3848,3849],{"class":3828},"\"",[3411,3851,3852],{"class":3432},")\n",[3411,3854,3855],{"class":3413,"line":3471},[3411,3856,3475],{"emptyLinePlaceholder":3474},[3411,3858,3859],{"class":3413,"line":3478},[3411,3860,3861],{"class":3417},"# SlackSender нічого не успадковує — але його можна передати\n",[3411,3863,3864],{"class":3413,"line":3519},[3411,3865,3866],{"class":3417},"# туди, де очікується об'єкт з методом send()\n",[3868,3869,3870,3873,3874,3878],"tip",{},[3547,3871,3872],{},"Походження терміну «Duck Typing»"," — від англійського вислову: ",[3875,3876,3877],"em",{},"«If it walks like a duck and quacks like a duck, it's a duck»"," («Якщо воно ходить як качка і крякає як качка — це качка»). Тобто, якщо об'єкт веде себе як певний тип, Python вважатиме його цим типом.",[3394,3880,3881,3882,3885,3886,3889,3890,3617],{},"Python традиційно є мовою з динамічною структурною типізацією. Але до Python 3.8 ця типізація була ",[3547,3883,3884],{},"неявною"," — статичні аналізатори не могли перевірити її коректність. ",[3408,3887,3888],{},"Protocol"," (PEP 544) з'явився саме для того, щоб зробити структурну типізацію ",[3547,3891,3892],{},"явною та статично перевірюваною",[3894,3895,3896],"plant-uml",{},[3401,3897,3901],{"className":3898,"code":3899,"language":3900,"meta":3406,"style":3406},"language-plantuml shiki shiki-themes light-plus dark-plus dark-plus","@startuml\nskinparam style plain\nskinparam backgroundColor #ffffff\nskinparam ArrowColor #6366f1\nskinparam node {\n    BackgroundColor #f3f4f6\n    BorderColor #d1d5db\n    FontName \"Outfit\"\n}\n\npackage \"Підходи до типізації\" {\n    node \"Номінативна типізація\\n(ABC)\\n\\nВідповідність через IS-A\\nявне наслідування\" as Nominal #dbeafe\n    node \"Структурна типізація\\n(Protocol)\\n\\nВідповідність через HAS-A\\nнаявність методів\u002Fатрибутів\" as Structural #d1fae5\n}\n\nnode \"Статична перевірка\\n(mypy, pyright)\" as Static #e0e7ff\nnode \"Runtime-перевірка\\n(isinstance, issubclass)\" as Runtime #fef3c7\nnode \"Традиційний Duck Typing\\n(неявна, без анотацій)\" as Duck #fee2e2\n\nNominal --> Static : повна підтримка\nNominal --> Runtime : isinstance() повертає True\n\nStructural --> Static : повна підтримка\\n(з typing.Protocol)\nStructural --> Runtime : лише з\\nruntime_checkable\n\nDuck --> Runtime : лише атрибут-check\\n(hasattr)\nDuck -[dashed]-> Static : ❌ не перевіряється\n\n@enduml\n","plantuml",[3408,3902,3903,3908,3913,3918,3923,3928,3933,3938,3943,3947,3952,3958,3964,3970,3975,3980,3986,3992,3998,4003,4009,4015,4020,4026,4032,4037,4043,4049,4054],{"__ignoreMap":3406},[3411,3904,3905],{"class":3413,"line":3414},[3411,3906,3907],{},"@startuml\n",[3411,3909,3910],{"class":3413,"line":3421},[3411,3911,3912],{},"skinparam style plain\n",[3411,3914,3915],{"class":3413,"line":3436},[3411,3916,3917],{},"skinparam backgroundColor #ffffff\n",[3411,3919,3920],{"class":3413,"line":3462},[3411,3921,3922],{},"skinparam ArrowColor #6366f1\n",[3411,3924,3925],{"class":3413,"line":3471},[3411,3926,3927],{},"skinparam node {\n",[3411,3929,3930],{"class":3413,"line":3478},[3411,3931,3932],{},"    BackgroundColor #f3f4f6\n",[3411,3934,3935],{"class":3413,"line":3519},[3411,3936,3937],{},"    BorderColor #d1d5db\n",[3411,3939,3940],{"class":3413,"line":3525},[3411,3941,3942],{},"    FontName \"Outfit\"\n",[3411,3944,3945],{"class":3413,"line":3531},[3411,3946,3670],{},[3411,3948,3950],{"class":3413,"line":3949},10,[3411,3951,3475],{"emptyLinePlaceholder":3474},[3411,3953,3955],{"class":3413,"line":3954},11,[3411,3956,3957],{},"package \"Підходи до типізації\" {\n",[3411,3959,3961],{"class":3413,"line":3960},12,[3411,3962,3963],{},"    node \"Номінативна типізація\\n(ABC)\\n\\nВідповідність через IS-A\\nявне наслідування\" as Nominal #dbeafe\n",[3411,3965,3967],{"class":3413,"line":3966},13,[3411,3968,3969],{},"    node \"Структурна типізація\\n(Protocol)\\n\\nВідповідність через HAS-A\\nнаявність методів\u002Fатрибутів\" as Structural #d1fae5\n",[3411,3971,3973],{"class":3413,"line":3972},14,[3411,3974,3670],{},[3411,3976,3978],{"class":3413,"line":3977},15,[3411,3979,3475],{"emptyLinePlaceholder":3474},[3411,3981,3983],{"class":3413,"line":3982},16,[3411,3984,3985],{},"node \"Статична перевірка\\n(mypy, pyright)\" as Static #e0e7ff\n",[3411,3987,3989],{"class":3413,"line":3988},17,[3411,3990,3991],{},"node \"Runtime-перевірка\\n(isinstance, issubclass)\" as Runtime #fef3c7\n",[3411,3993,3995],{"class":3413,"line":3994},18,[3411,3996,3997],{},"node \"Традиційний Duck Typing\\n(неявна, без анотацій)\" as Duck #fee2e2\n",[3411,3999,4001],{"class":3413,"line":4000},19,[3411,4002,3475],{"emptyLinePlaceholder":3474},[3411,4004,4006],{"class":3413,"line":4005},20,[3411,4007,4008],{},"Nominal --> Static : повна підтримка\n",[3411,4010,4012],{"class":3413,"line":4011},21,[3411,4013,4014],{},"Nominal --> Runtime : isinstance() повертає True\n",[3411,4016,4018],{"class":3413,"line":4017},22,[3411,4019,3475],{"emptyLinePlaceholder":3474},[3411,4021,4023],{"class":3413,"line":4022},23,[3411,4024,4025],{},"Structural --> Static : повна підтримка\\n(з typing.Protocol)\n",[3411,4027,4029],{"class":3413,"line":4028},24,[3411,4030,4031],{},"Structural --> Runtime : лише з\\nruntime_checkable\n",[3411,4033,4035],{"class":3413,"line":4034},25,[3411,4036,3475],{"emptyLinePlaceholder":3474},[3411,4038,4040],{"class":3413,"line":4039},26,[3411,4041,4042],{},"Duck --> Runtime : лише атрибут-check\\n(hasattr)\n",[3411,4044,4046],{"class":3413,"line":4045},27,[3411,4047,4048],{},"Duck -[dashed]-> Static : ❌ не перевіряється\n",[3411,4050,4052],{"class":3413,"line":4051},28,[3411,4053,3475],{"emptyLinePlaceholder":3474},[3411,4055,4057],{"class":3413,"line":4056},29,[3411,4058,4059],{},"@enduml\n",[3584,4061],{},[3389,4063,4065],{"id":4064},"частина-ii-abstract-base-classes-abc","Частина II: Abstract Base Classes (ABC)",[3591,4067,4069],{"id":4068},"що-таке-abc-і-навіщо-він-існує","Що таке ABC і навіщо він існує",[3394,4071,4072,4073,4076,4077,3617],{},"Модуль ",[3408,4074,4075],{},"abc"," (Abstract Base Classes) з'явився у Python 2.6 (PEP 3119) і вирішував конкретну проблему: перевірку відповідності об'єктів інтерфейсам без потреби перевіряти наявність кожного методу вручну через ",[3408,4078,4079],{},"hasattr",[3394,4081,4082,4083,4085,4086,4089,4090,4093,4094,4097],{},"До появи ",[3408,4084,4075],{}," розробники перевіряли «тип» об'єкта або через ",[3408,4087,4088],{},"isinstance(obj, SomeClass)"," (жорстка номінативна прив'язка), або через ",[3408,4091,4092],{},"hasattr(obj, 'send')"," (крихко, не масштабується). ABC запропонував третій шлях: ",[3547,4095,4096],{},"реєстрацію"," об'єктів як таких, що реалізують інтерфейс.",[3591,4099,4101],{"id":4100},"механіка-створення-абстрактного-класу","Механіка створення абстрактного класу",[3394,4103,4104],{},"Для створення абстрактного класу потрібні два компоненти:",[4106,4107,4108,4120],"ol",{},[3555,4109,4110,4115,4116,4119],{},[3547,4111,4112,4113],{},"Успадкування від ",[3408,4114,3738],{}," (або використання ",[3408,4117,4118],{},"ABCMeta"," як метакласу)",[3555,4121,4122,4128],{},[3547,4123,4124,4125],{},"Декоратор ",[3408,4126,4127],{},"@abstractmethod"," для позначення методів, що обов'язково мають бути перевизначені",[3401,4130,4132],{"className":3403,"code":4131,"language":3405,"meta":3406,"style":3406},"# notifiers.py\nfrom abc import ABC, abstractmethod\n\nclass BaseNotifier(ABC):\n    \"\"\"\n    Абстрактний базовий клас для всіх транспортів сповіщень.\n    Оголошує контракт: будь-який нотифікатор повинен реалізувати send() та close().\n    \"\"\"\n\n    @abstractmethod\n    def send(self, user_id: int, message: str) -> bool:\n        \"\"\"\n        Надсилає повідомлення користувачу.\n\n        Args:\n            user_id: Ідентифікатор отримувача.\n            message: Текст повідомлення.\n\n        Returns:\n            True — якщо доставлено, False — якщо виникла помилка.\n        \"\"\"\n        ...\n\n    @abstractmethod\n    def close(self) -> None:\n        \"\"\"Звільняє ресурси транспорту (з'єднання, сесії).\"\"\"\n        ...\n\n    # Конкретний метод у абстрактному класі — цілком допустимо!\n    def send_bulk(self, user_ids: list[int], message: str) -> dict[int, bool]:\n        \"\"\"\n        Надсилає повідомлення кільком користувачам.\n        Реалізація за замовчуванням: послідовне надсилання.\n        Може бути перевизначена для ефективних batch-операцій.\n        \"\"\"\n        return {uid: self.send(uid, message) for uid in user_ids}\n",[3408,4133,4134,4139,4154,4158,4168,4173,4178,4183,4187,4191,4196,4229,4234,4239,4243,4248,4253,4258,4262,4267,4272,4276,4281,4285,4289,4306,4311,4315,4319,4324,4367,4372,4378,4384,4390,4395],{"__ignoreMap":3406},[3411,4135,4136],{"class":3413,"line":3414},[3411,4137,4138],{"class":3417},"# notifiers.py\n",[3411,4140,4141,4145,4148,4151],{"class":3413,"line":3421},[3411,4142,4144],{"class":4143},"s8xlr","from",[3411,4146,4147],{"class":3432}," abc ",[3411,4149,4150],{"class":4143},"import",[3411,4152,4153],{"class":3432}," ABC, abstractmethod\n",[3411,4155,4156],{"class":3413,"line":3436},[3411,4157,3475],{"emptyLinePlaceholder":3474},[3411,4159,4160,4162,4165],{"class":3413,"line":3462},[3411,4161,3425],{"class":3424},[3411,4163,4164],{"class":3428}," BaseNotifier",[3411,4166,4167],{"class":3432},"(ABC):\n",[3411,4169,4170],{"class":3413,"line":3471},[3411,4171,4172],{"class":3828},"    \"\"\"\n",[3411,4174,4175],{"class":3413,"line":3478},[3411,4176,4177],{"class":3828},"    Абстрактний базовий клас для всіх транспортів сповіщень.\n",[3411,4179,4180],{"class":3413,"line":3519},[3411,4181,4182],{"class":3828},"    Оголошує контракт: будь-який нотифікатор повинен реалізувати send() та close().\n",[3411,4184,4185],{"class":3413,"line":3525},[3411,4186,4172],{"class":3828},[3411,4188,4189],{"class":3413,"line":3531},[3411,4190,3475],{"emptyLinePlaceholder":3474},[3411,4192,4193],{"class":3413,"line":3949},[3411,4194,4195],{"class":3442},"    @abstractmethod\n",[3411,4197,4198,4200,4202,4204,4206,4208,4210,4212,4214,4216,4218,4220,4222,4224,4227],{"class":3413,"line":3954},[3411,4199,3439],{"class":3424},[3411,4201,3483],{"class":3442},[3411,4203,3446],{"class":3432},[3411,4205,3450],{"class":3449},[3411,4207,3453],{"class":3432},[3411,4209,3492],{"class":3449},[3411,4211,3495],{"class":3432},[3411,4213,3498],{"class":3428},[3411,4215,3453],{"class":3432},[3411,4217,3503],{"class":3449},[3411,4219,3495],{"class":3432},[3411,4221,3508],{"class":3428},[3411,4223,3511],{"class":3432},[3411,4225,4226],{"class":3428},"bool",[3411,4228,3433],{"class":3432},[3411,4230,4231],{"class":3413,"line":3960},[3411,4232,4233],{"class":3828},"        \"\"\"\n",[3411,4235,4236],{"class":3413,"line":3966},[3411,4237,4238],{"class":3828},"        Надсилає повідомлення користувачу.\n",[3411,4240,4241],{"class":3413,"line":3972},[3411,4242,3475],{"emptyLinePlaceholder":3474},[3411,4244,4245],{"class":3413,"line":3977},[3411,4246,4247],{"class":3828},"        Args:\n",[3411,4249,4250],{"class":3413,"line":3982},[3411,4251,4252],{"class":3828},"            user_id: Ідентифікатор отримувача.\n",[3411,4254,4255],{"class":3413,"line":3988},[3411,4256,4257],{"class":3828},"            message: Текст повідомлення.\n",[3411,4259,4260],{"class":3413,"line":3994},[3411,4261,3475],{"emptyLinePlaceholder":3474},[3411,4263,4264],{"class":3413,"line":4000},[3411,4265,4266],{"class":3828},"        Returns:\n",[3411,4268,4269],{"class":3413,"line":4005},[3411,4270,4271],{"class":3828},"            True — якщо доставлено, False — якщо виникла помилка.\n",[3411,4273,4274],{"class":3413,"line":4011},[3411,4275,4233],{"class":3828},[3411,4277,4278],{"class":3413,"line":4017},[3411,4279,4280],{"class":3432},"        ...\n",[3411,4282,4283],{"class":3413,"line":4022},[3411,4284,3475],{"emptyLinePlaceholder":3474},[3411,4286,4287],{"class":3413,"line":4028},[3411,4288,4195],{"class":3442},[3411,4290,4291,4293,4296,4298,4300,4302,4304],{"class":3413,"line":4034},[3411,4292,3439],{"class":3424},[3411,4294,4295],{"class":3442}," close",[3411,4297,3446],{"class":3432},[3411,4299,3450],{"class":3449},[3411,4301,3511],{"class":3432},[3411,4303,3514],{"class":3424},[3411,4305,3433],{"class":3432},[3411,4307,4308],{"class":3413,"line":4039},[3411,4309,4310],{"class":3828},"        \"\"\"Звільняє ресурси транспорту (з'єднання, сесії).\"\"\"\n",[3411,4312,4313],{"class":3413,"line":4045},[3411,4314,4280],{"class":3432},[3411,4316,4317],{"class":3413,"line":4051},[3411,4318,3475],{"emptyLinePlaceholder":3474},[3411,4320,4321],{"class":3413,"line":4056},[3411,4322,4323],{"class":3417},"    # Конкретний метод у абстрактному класі — цілком допустимо!\n",[3411,4325,4327,4329,4332,4334,4336,4338,4341,4344,4346,4349,4351,4353,4355,4358,4360,4362,4364],{"class":3413,"line":4326},30,[3411,4328,3439],{"class":3424},[3411,4330,4331],{"class":3442}," send_bulk",[3411,4333,3446],{"class":3432},[3411,4335,3450],{"class":3449},[3411,4337,3453],{"class":3432},[3411,4339,4340],{"class":3449},"user_ids",[3411,4342,4343],{"class":3432},": list[",[3411,4345,3498],{"class":3428},[3411,4347,4348],{"class":3432},"], ",[3411,4350,3503],{"class":3449},[3411,4352,3495],{"class":3432},[3411,4354,3508],{"class":3428},[3411,4356,4357],{"class":3432},") -> dict[",[3411,4359,3498],{"class":3428},[3411,4361,3453],{"class":3432},[3411,4363,4226],{"class":3428},[3411,4365,4366],{"class":3432},"]:\n",[3411,4368,4370],{"class":3413,"line":4369},31,[3411,4371,4233],{"class":3828},[3411,4373,4375],{"class":3413,"line":4374},32,[3411,4376,4377],{"class":3828},"        Надсилає повідомлення кільком користувачам.\n",[3411,4379,4381],{"class":3413,"line":4380},33,[3411,4382,4383],{"class":3828},"        Реалізація за замовчуванням: послідовне надсилання.\n",[3411,4385,4387],{"class":3413,"line":4386},34,[3411,4388,4389],{"class":3828},"        Може бути перевизначена для ефективних batch-операцій.\n",[3411,4391,4393],{"class":3413,"line":4392},35,[3411,4394,4233],{"class":3828},[3411,4396,4398,4401,4404,4406,4409,4412,4415,4418],{"class":3413,"line":4397},36,[3411,4399,4400],{"class":4143},"        return",[3411,4402,4403],{"class":3432}," {uid: ",[3411,4405,3450],{"class":3424},[3411,4407,4408],{"class":3432},".send(uid, message) ",[3411,4410,4411],{"class":4143},"for",[3411,4413,4414],{"class":3432}," uid ",[3411,4416,4417],{"class":4143},"in",[3411,4419,4420],{"class":3432}," user_ids}\n",[4422,4423,4424,4427,4428,4430,4431,4434,4435,4437,4438,4440],"warning",{},[3547,4425,4426],{},"Важливо:"," Клас, що наслідує ",[3408,4429,3738],{},", стає ",[3547,4432,4433],{},"абстрактним"," лише якщо у ньому є хоча б один ",[3408,4436,4127],{},". Якщо всі методи конкретні — клас можна інстанціювати, навіть якщо він наслідує від ",[3408,4439,3738],{},". ABC — це інструмент, а не чарівна позначка.",[3591,4442,4444],{"id":4443},"заборона-інстанціювання-абстрактних-класів","Заборона інстанціювання абстрактних класів",[3394,4446,4447,4448,4451,4452,4455,4456,3617],{},"Ключова поведінка ABC: ",[3547,4449,4450],{},"неможливість створити екземпляр класу, який не реалізував усі абстрактні методи",". Python перевіряє це під час виклику ",[3408,4453,4454],{},"__init__"," метакласу ",[3408,4457,4118],{},[3401,4459,4461],{"className":3403,"code":4460,"language":3405,"meta":3406,"style":3406},"from abc import ABC, abstractmethod\n\nclass BaseNotifier(ABC):\n    @abstractmethod\n    def send(self, user_id: int, message: str) -> bool: ...\n\n    @abstractmethod\n    def close(self) -> None: ...\n\n# ❌ Спроба 1: Інстанціювати абстрактний клас напряму\ntry:\n    notifier = BaseNotifier()\nexcept TypeError as e:\n    print(f\"TypeError: {e}\")\n    # TypeError: Can't instantiate abstract class BaseNotifier\n    # with abstract methods close, send\n\n# ❌ Спроба 2: Підклас реалізував лише один із двох методів\nclass IncompleteNotifier(BaseNotifier):\n    def send(self, user_id: int, message: str) -> bool:\n        return True\n    # close() не реалізований!\n\ntry:\n    incomplete = IncompleteNotifier()\nexcept TypeError as e:\n    print(f\"TypeError: {e}\")\n    # TypeError: Can't instantiate abstract class IncompleteNotifier\n    # with abstract method close\n\n# ✅ Спроба 3: Повна реалізація\nclass EmailNotifier(BaseNotifier):\n    def __init__(self, smtp_host: str):\n        self._host = smtp_host\n        self._connected = True\n\n    def send(self, user_id: int, message: str) -> bool:\n        print(f\"[EMAIL] Надсилання до user {user_id}: {message}\")\n        return True\n\n    def close(self) -> None:\n        self._connected = False\n        print(\"[EMAIL] З'єднання закрито\")\n\nemail = EmailNotifier(\"smtp.gmail.com\")  # ✅ Працює\nprint(isinstance(email, BaseNotifier))   # True\n",[3408,4462,4463,4473,4477,4485,4489,4522,4526,4530,4546,4550,4555,4562,4567,4581,4604,4609,4614,4618,4623,4637,4669,4676,4681,4685,4691,4696,4706,4726,4731,4736,4740,4745,4758,4779,4786,4796,4800,4833,4863,4870,4875,4892,4902,4914,4919,4934],{"__ignoreMap":3406},[3411,4464,4465,4467,4469,4471],{"class":3413,"line":3414},[3411,4466,4144],{"class":4143},[3411,4468,4147],{"class":3432},[3411,4470,4150],{"class":4143},[3411,4472,4153],{"class":3432},[3411,4474,4475],{"class":3413,"line":3421},[3411,4476,3475],{"emptyLinePlaceholder":3474},[3411,4478,4479,4481,4483],{"class":3413,"line":3436},[3411,4480,3425],{"class":3424},[3411,4482,4164],{"class":3428},[3411,4484,4167],{"class":3432},[3411,4486,4487],{"class":3413,"line":3462},[3411,4488,4195],{"class":3442},[3411,4490,4491,4493,4495,4497,4499,4501,4503,4505,4507,4509,4511,4513,4515,4517,4519],{"class":3413,"line":3471},[3411,4492,3439],{"class":3424},[3411,4494,3483],{"class":3442},[3411,4496,3446],{"class":3432},[3411,4498,3450],{"class":3449},[3411,4500,3453],{"class":3432},[3411,4502,3492],{"class":3449},[3411,4504,3495],{"class":3432},[3411,4506,3498],{"class":3428},[3411,4508,3453],{"class":3432},[3411,4510,3503],{"class":3449},[3411,4512,3495],{"class":3432},[3411,4514,3508],{"class":3428},[3411,4516,3511],{"class":3432},[3411,4518,4226],{"class":3428},[3411,4520,4521],{"class":3432},": ...\n",[3411,4523,4524],{"class":3413,"line":3478},[3411,4525,3475],{"emptyLinePlaceholder":3474},[3411,4527,4528],{"class":3413,"line":3519},[3411,4529,4195],{"class":3442},[3411,4531,4532,4534,4536,4538,4540,4542,4544],{"class":3413,"line":3525},[3411,4533,3439],{"class":3424},[3411,4535,4295],{"class":3442},[3411,4537,3446],{"class":3432},[3411,4539,3450],{"class":3449},[3411,4541,3511],{"class":3432},[3411,4543,3514],{"class":3424},[3411,4545,4521],{"class":3432},[3411,4547,4548],{"class":3413,"line":3531},[3411,4549,3475],{"emptyLinePlaceholder":3474},[3411,4551,4552],{"class":3413,"line":3949},[3411,4553,4554],{"class":3417},"# ❌ Спроба 1: Інстанціювати абстрактний клас напряму\n",[3411,4556,4557,4560],{"class":3413,"line":3954},[3411,4558,4559],{"class":4143},"try",[3411,4561,3433],{"class":3432},[3411,4563,4564],{"class":3413,"line":3960},[3411,4565,4566],{"class":3432},"    notifier = BaseNotifier()\n",[3411,4568,4569,4572,4575,4578],{"class":3413,"line":3966},[3411,4570,4571],{"class":4143},"except",[3411,4573,4574],{"class":3428}," TypeError",[3411,4576,4577],{"class":4143}," as",[3411,4579,4580],{"class":3432}," e:\n",[3411,4582,4583,4586,4588,4590,4593,4595,4598,4600,4602],{"class":3413,"line":3972},[3411,4584,4585],{"class":3442},"    print",[3411,4587,3446],{"class":3432},[3411,4589,3825],{"class":3424},[3411,4591,4592],{"class":3828},"\"TypeError: ",[3411,4594,3832],{"class":3424},[3411,4596,4597],{"class":3432},"e",[3411,4599,3837],{"class":3424},[3411,4601,3849],{"class":3828},[3411,4603,3852],{"class":3432},[3411,4605,4606],{"class":3413,"line":3977},[3411,4607,4608],{"class":3417},"    # TypeError: Can't instantiate abstract class BaseNotifier\n",[3411,4610,4611],{"class":3413,"line":3982},[3411,4612,4613],{"class":3417},"    # with abstract methods close, send\n",[3411,4615,4616],{"class":3413,"line":3988},[3411,4617,3475],{"emptyLinePlaceholder":3474},[3411,4619,4620],{"class":3413,"line":3994},[3411,4621,4622],{"class":3417},"# ❌ Спроба 2: Підклас реалізував лише один із двох методів\n",[3411,4624,4625,4627,4630,4632,4635],{"class":3413,"line":4000},[3411,4626,3425],{"class":3424},[3411,4628,4629],{"class":3428}," IncompleteNotifier",[3411,4631,3446],{"class":3432},[3411,4633,4634],{"class":3428},"BaseNotifier",[3411,4636,3459],{"class":3432},[3411,4638,4639,4641,4643,4645,4647,4649,4651,4653,4655,4657,4659,4661,4663,4665,4667],{"class":3413,"line":4005},[3411,4640,3439],{"class":3424},[3411,4642,3483],{"class":3442},[3411,4644,3446],{"class":3432},[3411,4646,3450],{"class":3449},[3411,4648,3453],{"class":3432},[3411,4650,3492],{"class":3449},[3411,4652,3495],{"class":3432},[3411,4654,3498],{"class":3428},[3411,4656,3453],{"class":3432},[3411,4658,3503],{"class":3449},[3411,4660,3495],{"class":3432},[3411,4662,3508],{"class":3428},[3411,4664,3511],{"class":3432},[3411,4666,4226],{"class":3428},[3411,4668,3433],{"class":3432},[3411,4670,4671,4673],{"class":3413,"line":4011},[3411,4672,4400],{"class":4143},[3411,4674,4675],{"class":3424}," True\n",[3411,4677,4678],{"class":3413,"line":4017},[3411,4679,4680],{"class":3417},"    # close() не реалізований!\n",[3411,4682,4683],{"class":3413,"line":4022},[3411,4684,3475],{"emptyLinePlaceholder":3474},[3411,4686,4687,4689],{"class":3413,"line":4028},[3411,4688,4559],{"class":4143},[3411,4690,3433],{"class":3432},[3411,4692,4693],{"class":3413,"line":4034},[3411,4694,4695],{"class":3432},"    incomplete = IncompleteNotifier()\n",[3411,4697,4698,4700,4702,4704],{"class":3413,"line":4039},[3411,4699,4571],{"class":4143},[3411,4701,4574],{"class":3428},[3411,4703,4577],{"class":4143},[3411,4705,4580],{"class":3432},[3411,4707,4708,4710,4712,4714,4716,4718,4720,4722,4724],{"class":3413,"line":4045},[3411,4709,4585],{"class":3442},[3411,4711,3446],{"class":3432},[3411,4713,3825],{"class":3424},[3411,4715,4592],{"class":3828},[3411,4717,3832],{"class":3424},[3411,4719,4597],{"class":3432},[3411,4721,3837],{"class":3424},[3411,4723,3849],{"class":3828},[3411,4725,3852],{"class":3432},[3411,4727,4728],{"class":3413,"line":4051},[3411,4729,4730],{"class":3417},"    # TypeError: Can't instantiate abstract class IncompleteNotifier\n",[3411,4732,4733],{"class":3413,"line":4056},[3411,4734,4735],{"class":3417},"    # with abstract method close\n",[3411,4737,4738],{"class":3413,"line":4326},[3411,4739,3475],{"emptyLinePlaceholder":3474},[3411,4741,4742],{"class":3413,"line":4369},[3411,4743,4744],{"class":3417},"# ✅ Спроба 3: Повна реалізація\n",[3411,4746,4747,4749,4752,4754,4756],{"class":3413,"line":4374},[3411,4748,3425],{"class":3424},[3411,4750,4751],{"class":3428}," EmailNotifier",[3411,4753,3446],{"class":3432},[3411,4755,4634],{"class":3428},[3411,4757,3459],{"class":3432},[3411,4759,4760,4762,4764,4766,4768,4770,4773,4775,4777],{"class":3413,"line":4380},[3411,4761,3439],{"class":3424},[3411,4763,3443],{"class":3442},[3411,4765,3446],{"class":3432},[3411,4767,3450],{"class":3449},[3411,4769,3453],{"class":3432},[3411,4771,4772],{"class":3449},"smtp_host",[3411,4774,3495],{"class":3432},[3411,4776,3508],{"class":3428},[3411,4778,3459],{"class":3432},[3411,4780,4781,4783],{"class":3413,"line":4386},[3411,4782,3465],{"class":3424},[3411,4784,4785],{"class":3432},"._host = smtp_host\n",[3411,4787,4788,4790,4793],{"class":3413,"line":4392},[3411,4789,3465],{"class":3424},[3411,4791,4792],{"class":3432},"._connected = ",[3411,4794,4795],{"class":3424},"True\n",[3411,4797,4798],{"class":3413,"line":4397},[3411,4799,3475],{"emptyLinePlaceholder":3474},[3411,4801,4803,4805,4807,4809,4811,4813,4815,4817,4819,4821,4823,4825,4827,4829,4831],{"class":3413,"line":4802},37,[3411,4804,3439],{"class":3424},[3411,4806,3483],{"class":3442},[3411,4808,3446],{"class":3432},[3411,4810,3450],{"class":3449},[3411,4812,3453],{"class":3432},[3411,4814,3492],{"class":3449},[3411,4816,3495],{"class":3432},[3411,4818,3498],{"class":3428},[3411,4820,3453],{"class":3432},[3411,4822,3503],{"class":3449},[3411,4824,3495],{"class":3432},[3411,4826,3508],{"class":3428},[3411,4828,3511],{"class":3432},[3411,4830,4226],{"class":3428},[3411,4832,3433],{"class":3432},[3411,4834,4836,4838,4840,4842,4845,4847,4849,4851,4853,4855,4857,4859,4861],{"class":3413,"line":4835},38,[3411,4837,3820],{"class":3442},[3411,4839,3446],{"class":3432},[3411,4841,3825],{"class":3424},[3411,4843,4844],{"class":3828},"\"[EMAIL] Надсилання до user ",[3411,4846,3832],{"class":3424},[3411,4848,3492],{"class":3432},[3411,4850,3837],{"class":3424},[3411,4852,3495],{"class":3828},[3411,4854,3832],{"class":3424},[3411,4856,3503],{"class":3432},[3411,4858,3837],{"class":3424},[3411,4860,3849],{"class":3828},[3411,4862,3852],{"class":3432},[3411,4864,4866,4868],{"class":3413,"line":4865},39,[3411,4867,4400],{"class":4143},[3411,4869,4675],{"class":3424},[3411,4871,4873],{"class":3413,"line":4872},40,[3411,4874,3475],{"emptyLinePlaceholder":3474},[3411,4876,4878,4880,4882,4884,4886,4888,4890],{"class":3413,"line":4877},41,[3411,4879,3439],{"class":3424},[3411,4881,4295],{"class":3442},[3411,4883,3446],{"class":3432},[3411,4885,3450],{"class":3449},[3411,4887,3511],{"class":3432},[3411,4889,3514],{"class":3424},[3411,4891,3433],{"class":3432},[3411,4893,4895,4897,4899],{"class":3413,"line":4894},42,[3411,4896,3465],{"class":3424},[3411,4898,4792],{"class":3432},[3411,4900,4901],{"class":3424},"False\n",[3411,4903,4905,4907,4909,4912],{"class":3413,"line":4904},43,[3411,4906,3820],{"class":3442},[3411,4908,3446],{"class":3432},[3411,4910,4911],{"class":3828},"\"[EMAIL] З'єднання закрито\"",[3411,4913,3852],{"class":3432},[3411,4915,4917],{"class":3413,"line":4916},44,[3411,4918,3475],{"emptyLinePlaceholder":3474},[3411,4920,4922,4925,4928,4931],{"class":3413,"line":4921},45,[3411,4923,4924],{"class":3432},"email = EmailNotifier(",[3411,4926,4927],{"class":3828},"\"smtp.gmail.com\"",[3411,4929,4930],{"class":3432},")  ",[3411,4932,4933],{"class":3417},"# ✅ Працює\n",[3411,4935,4937,4940,4942,4945,4948],{"class":3413,"line":4936},46,[3411,4938,4939],{"class":3442},"print",[3411,4941,3446],{"class":3432},[3411,4943,4944],{"class":3442},"isinstance",[3411,4946,4947],{"class":3432},"(email, BaseNotifier))   ",[3411,4949,4950],{"class":3417},"# True\n",[4952,4953,4955,4968,4977,4984,4992],"terminal-preview",{"title":4954},"Демонстрація захисту ABC",[4956,4957,4959,4964,4965],"div",{"className":4958},[3413],[3411,4960,4963],{"className":4961},[4962],"opacity-40","$"," ",[3547,4966,4967],{},"python notifiers.py",[4956,4969,4971,4972],{"className":4970},[3413],"TypeError: ",[3411,4973,4976],{"className":4974},[4975],"text-rose-400","Can't instantiate abstract class BaseNotifier with abstract methods close, send",[4956,4978,4971,4980],{"className":4979},[3413],[3411,4981,4983],{"className":4982},[4975],"Can't instantiate abstract class IncompleteNotifier with abstract method close",[4956,4985,4987],{"className":4986},[3413],[3411,4988,4991],{"className":4989},[4990],"text-green-400","[EMAIL] Надсилання до user 42: Привіт!",[4956,4993,4995],{"className":4994},[3413],[3411,4996,4998],{"className":4997},[4990],"True",[3591,5000,5002],{"id":5001},"розширені-форми-абстрактних-оголошень","Розширені форми абстрактних оголошень",[3394,5004,5005,5006,5008],{},"ABC дозволяє поєднувати ",[3408,5007,4127],{}," з іншими декораторами для опису більш складних контрактів.",[5010,5011,5012,5020,5034,5049],"field-group",{},[5013,5014,5016,5017,3617],"field",{"name":4127,"type":5015},"decorator","Базовий декоратор. Позначає метод як такий, що обов'язково має бути перевизначений у конкретному підкласі. При спробі інстанціювати клас з нереалізованими абстрактними методами — викидається ",[3408,5018,5019],{},"TypeError",[5013,5021,5024,5025,5028,5029,5031,5032,3617],{"name":5022,"type":5023},"@property + @abstractmethod","decorator combo","Абстрактна властивість. Підклас зобов'язаний реалізувати ",[3408,5026,5027],{},"@property"," з відповідним ім'ям. Порядок декораторів суворий: спочатку ",[3408,5030,5027],{},", потім ",[3408,5033,4127],{},[5013,5035,5037,5038,5041,5042,3453,5045,5048],{"name":5036,"type":5023},"@classmethod + @abstractmethod","Абстрактний метод класу. Підклас повинен реалізувати метод через ",[3408,5039,5040],{},"@classmethod",". Ідеальний для абстрактних фабричних методів (",[3408,5043,5044],{},"from_config",[3408,5046,5047],{},"from_dict",").",[5013,5050,5052,5053,3617],{"name":5051,"type":5023},"@staticmethod + @abstractmethod","Абстрактний статичний метод. Рідко використовується, але дозволяє описати контракт для ",[3408,5054,5055],{},"@staticmethod",[3401,5057,5059],{"className":3403,"code":5058,"language":3405,"meta":3406,"style":3406},"from abc import ABC, abstractmethod\n\nclass DataStore(ABC):\n    \"\"\"Абстракція сховища даних.\"\"\"\n\n    # ── Абстрактна властивість ────────────────────────────────────────\n    @property\n    @abstractmethod\n    def connection_string(self) -> str:\n        \"\"\"Рядок підключення (лише читання).\"\"\"\n        ...\n\n    # ── Абстрактний classmethod ───────────────────────────────────────\n    @classmethod\n    @abstractmethod\n    def from_config(cls, config: dict) -> \"DataStore\":\n        \"\"\"Альтернативний конструктор — з конфігураційного словника.\"\"\"\n        ...\n\n    # ── Абстрактний staticmethod ──────────────────────────────────────\n    @staticmethod\n    @abstractmethod\n    def validate_config(config: dict) -> bool:\n        \"\"\"Валідує конфігурацію перед підключенням.\"\"\"\n        ...\n\n    # ── Абстрактний метод ─────────────────────────────────────────────\n    @abstractmethod\n    def execute(self, query: str) -> list:\n        \"\"\"Виконує запит і повертає результати.\"\"\"\n        ...\n\n\nclass PostgreSQLStore(DataStore):\n    \"\"\"Конкретна реалізація для PostgreSQL.\"\"\"\n\n    def __init__(self, host: str, port: int, db: str):\n        self._conn_str = f\"postgresql:\u002F\u002F{host}:{port}\u002F{db}\"\n\n    @property\n    def connection_string(self) -> str:\n        return self._conn_str\n\n    @classmethod\n    def from_config(cls, config: dict) -> \"PostgreSQLStore\":\n        return cls(config[\"host\"], config.get(\"port\", 5432), config[\"db\"])\n\n    @staticmethod\n    def validate_config(config: dict) -> bool:\n        return \"host\" in config and \"db\" in config\n\n    def execute(self, query: str) -> list:\n        print(f\"[PG] Виконання: {query}\")\n        return []\n\n\n# Тестуємо\nif PostgreSQLStore.validate_config({\"host\": \"localhost\", \"db\": \"mydb\"}):\n    store = PostgreSQLStore.from_config({\"host\": \"localhost\", \"db\": \"mydb\"})\n    print(store.connection_string)   # postgresql:\u002F\u002Flocalhost:5432\u002Fmydb\n    store.execute(\"SELECT 1\")        # [PG] Виконання: SELECT 1\n",[3408,5060,5061,5071,5075,5084,5089,5093,5098,5105,5109,5126,5131,5135,5139,5144,5151,5155,5184,5189,5193,5197,5202,5209,5213,5234,5239,5243,5247,5252,5256,5283,5288,5292,5296,5300,5314,5319,5323,5362,5401,5405,5411,5427,5437,5441,5447,5472,5506,5511,5518,5539,5564,5569,5594,5616,5624,5629,5634,5640,5668,5691,5702],{"__ignoreMap":3406},[3411,5062,5063,5065,5067,5069],{"class":3413,"line":3414},[3411,5064,4144],{"class":4143},[3411,5066,4147],{"class":3432},[3411,5068,4150],{"class":4143},[3411,5070,4153],{"class":3432},[3411,5072,5073],{"class":3413,"line":3421},[3411,5074,3475],{"emptyLinePlaceholder":3474},[3411,5076,5077,5079,5082],{"class":3413,"line":3436},[3411,5078,3425],{"class":3424},[3411,5080,5081],{"class":3428}," DataStore",[3411,5083,4167],{"class":3432},[3411,5085,5086],{"class":3413,"line":3462},[3411,5087,5088],{"class":3828},"    \"\"\"Абстракція сховища даних.\"\"\"\n",[3411,5090,5091],{"class":3413,"line":3471},[3411,5092,3475],{"emptyLinePlaceholder":3474},[3411,5094,5095],{"class":3413,"line":3478},[3411,5096,5097],{"class":3417},"    # ── Абстрактна властивість ────────────────────────────────────────\n",[3411,5099,5100,5102],{"class":3413,"line":3519},[3411,5101,3697],{"class":3442},[3411,5103,5104],{"class":3428},"property\n",[3411,5106,5107],{"class":3413,"line":3525},[3411,5108,4195],{"class":3442},[3411,5110,5111,5113,5116,5118,5120,5122,5124],{"class":3413,"line":3531},[3411,5112,3439],{"class":3424},[3411,5114,5115],{"class":3442}," connection_string",[3411,5117,3446],{"class":3432},[3411,5119,3450],{"class":3449},[3411,5121,3511],{"class":3432},[3411,5123,3508],{"class":3428},[3411,5125,3433],{"class":3432},[3411,5127,5128],{"class":3413,"line":3949},[3411,5129,5130],{"class":3828},"        \"\"\"Рядок підключення (лише читання).\"\"\"\n",[3411,5132,5133],{"class":3413,"line":3954},[3411,5134,4280],{"class":3432},[3411,5136,5137],{"class":3413,"line":3960},[3411,5138,3475],{"emptyLinePlaceholder":3474},[3411,5140,5141],{"class":3413,"line":3966},[3411,5142,5143],{"class":3417},"    # ── Абстрактний classmethod ───────────────────────────────────────\n",[3411,5145,5146,5148],{"class":3413,"line":3972},[3411,5147,3697],{"class":3442},[3411,5149,5150],{"class":3428},"classmethod\n",[3411,5152,5153],{"class":3413,"line":3977},[3411,5154,4195],{"class":3442},[3411,5156,5157,5159,5162,5164,5167,5169,5172,5174,5177,5179,5182],{"class":3413,"line":3982},[3411,5158,3439],{"class":3424},[3411,5160,5161],{"class":3442}," from_config",[3411,5163,3446],{"class":3432},[3411,5165,5166],{"class":3449},"cls",[3411,5168,3453],{"class":3432},[3411,5170,5171],{"class":3449},"config",[3411,5173,3495],{"class":3432},[3411,5175,5176],{"class":3428},"dict",[3411,5178,3511],{"class":3432},[3411,5180,5181],{"class":3828},"\"DataStore\"",[3411,5183,3433],{"class":3432},[3411,5185,5186],{"class":3413,"line":3988},[3411,5187,5188],{"class":3828},"        \"\"\"Альтернативний конструктор — з конфігураційного словника.\"\"\"\n",[3411,5190,5191],{"class":3413,"line":3994},[3411,5192,4280],{"class":3432},[3411,5194,5195],{"class":3413,"line":4000},[3411,5196,3475],{"emptyLinePlaceholder":3474},[3411,5198,5199],{"class":3413,"line":4005},[3411,5200,5201],{"class":3417},"    # ── Абстрактний staticmethod ──────────────────────────────────────\n",[3411,5203,5204,5206],{"class":3413,"line":4011},[3411,5205,3697],{"class":3442},[3411,5207,5208],{"class":3428},"staticmethod\n",[3411,5210,5211],{"class":3413,"line":4017},[3411,5212,4195],{"class":3442},[3411,5214,5215,5217,5220,5222,5224,5226,5228,5230,5232],{"class":3413,"line":4022},[3411,5216,3439],{"class":3424},[3411,5218,5219],{"class":3442}," validate_config",[3411,5221,3446],{"class":3432},[3411,5223,5171],{"class":3449},[3411,5225,3495],{"class":3432},[3411,5227,5176],{"class":3428},[3411,5229,3511],{"class":3432},[3411,5231,4226],{"class":3428},[3411,5233,3433],{"class":3432},[3411,5235,5236],{"class":3413,"line":4028},[3411,5237,5238],{"class":3828},"        \"\"\"Валідує конфігурацію перед підключенням.\"\"\"\n",[3411,5240,5241],{"class":3413,"line":4034},[3411,5242,4280],{"class":3432},[3411,5244,5245],{"class":3413,"line":4039},[3411,5246,3475],{"emptyLinePlaceholder":3474},[3411,5248,5249],{"class":3413,"line":4045},[3411,5250,5251],{"class":3417},"    # ── Абстрактний метод ─────────────────────────────────────────────\n",[3411,5253,5254],{"class":3413,"line":4051},[3411,5255,4195],{"class":3442},[3411,5257,5258,5260,5263,5265,5267,5269,5272,5274,5276,5278,5281],{"class":3413,"line":4056},[3411,5259,3439],{"class":3424},[3411,5261,5262],{"class":3442}," execute",[3411,5264,3446],{"class":3432},[3411,5266,3450],{"class":3449},[3411,5268,3453],{"class":3432},[3411,5270,5271],{"class":3449},"query",[3411,5273,3495],{"class":3432},[3411,5275,3508],{"class":3428},[3411,5277,3511],{"class":3432},[3411,5279,5280],{"class":3428},"list",[3411,5282,3433],{"class":3432},[3411,5284,5285],{"class":3413,"line":4326},[3411,5286,5287],{"class":3828},"        \"\"\"Виконує запит і повертає результати.\"\"\"\n",[3411,5289,5290],{"class":3413,"line":4369},[3411,5291,4280],{"class":3432},[3411,5293,5294],{"class":3413,"line":4374},[3411,5295,3475],{"emptyLinePlaceholder":3474},[3411,5297,5298],{"class":3413,"line":4380},[3411,5299,3475],{"emptyLinePlaceholder":3474},[3411,5301,5302,5304,5307,5309,5312],{"class":3413,"line":4386},[3411,5303,3425],{"class":3424},[3411,5305,5306],{"class":3428}," PostgreSQLStore",[3411,5308,3446],{"class":3432},[3411,5310,5311],{"class":3428},"DataStore",[3411,5313,3459],{"class":3432},[3411,5315,5316],{"class":3413,"line":4392},[3411,5317,5318],{"class":3828},"    \"\"\"Конкретна реалізація для PostgreSQL.\"\"\"\n",[3411,5320,5321],{"class":3413,"line":4397},[3411,5322,3475],{"emptyLinePlaceholder":3474},[3411,5324,5325,5327,5329,5331,5333,5335,5338,5340,5342,5344,5347,5349,5351,5353,5356,5358,5360],{"class":3413,"line":4802},[3411,5326,3439],{"class":3424},[3411,5328,3443],{"class":3442},[3411,5330,3446],{"class":3432},[3411,5332,3450],{"class":3449},[3411,5334,3453],{"class":3432},[3411,5336,5337],{"class":3449},"host",[3411,5339,3495],{"class":3432},[3411,5341,3508],{"class":3428},[3411,5343,3453],{"class":3432},[3411,5345,5346],{"class":3449},"port",[3411,5348,3495],{"class":3432},[3411,5350,3498],{"class":3428},[3411,5352,3453],{"class":3432},[3411,5354,5355],{"class":3449},"db",[3411,5357,3495],{"class":3432},[3411,5359,3508],{"class":3428},[3411,5361,3459],{"class":3432},[3411,5363,5364,5366,5369,5371,5374,5376,5378,5380,5383,5385,5387,5389,5392,5394,5396,5398],{"class":3413,"line":4835},[3411,5365,3465],{"class":3424},[3411,5367,5368],{"class":3432},"._conn_str = ",[3411,5370,3825],{"class":3424},[3411,5372,5373],{"class":3828},"\"postgresql:\u002F\u002F",[3411,5375,3832],{"class":3424},[3411,5377,5337],{"class":3432},[3411,5379,3837],{"class":3424},[3411,5381,5382],{"class":3828},":",[3411,5384,3832],{"class":3424},[3411,5386,5346],{"class":3432},[3411,5388,3837],{"class":3424},[3411,5390,5391],{"class":3828},"\u002F",[3411,5393,3832],{"class":3424},[3411,5395,5355],{"class":3432},[3411,5397,3837],{"class":3424},[3411,5399,5400],{"class":3828},"\"\n",[3411,5402,5403],{"class":3413,"line":4865},[3411,5404,3475],{"emptyLinePlaceholder":3474},[3411,5406,5407,5409],{"class":3413,"line":4872},[3411,5408,3697],{"class":3442},[3411,5410,5104],{"class":3428},[3411,5412,5413,5415,5417,5419,5421,5423,5425],{"class":3413,"line":4877},[3411,5414,3439],{"class":3424},[3411,5416,5115],{"class":3442},[3411,5418,3446],{"class":3432},[3411,5420,3450],{"class":3449},[3411,5422,3511],{"class":3432},[3411,5424,3508],{"class":3428},[3411,5426,3433],{"class":3432},[3411,5428,5429,5431,5434],{"class":3413,"line":4894},[3411,5430,4400],{"class":4143},[3411,5432,5433],{"class":3424}," self",[3411,5435,5436],{"class":3432},"._conn_str\n",[3411,5438,5439],{"class":3413,"line":4904},[3411,5440,3475],{"emptyLinePlaceholder":3474},[3411,5442,5443,5445],{"class":3413,"line":4916},[3411,5444,3697],{"class":3442},[3411,5446,5150],{"class":3428},[3411,5448,5449,5451,5453,5455,5457,5459,5461,5463,5465,5467,5470],{"class":3413,"line":4921},[3411,5450,3439],{"class":3424},[3411,5452,5161],{"class":3442},[3411,5454,3446],{"class":3432},[3411,5456,5166],{"class":3449},[3411,5458,3453],{"class":3432},[3411,5460,5171],{"class":3449},[3411,5462,3495],{"class":3432},[3411,5464,5176],{"class":3428},[3411,5466,3511],{"class":3432},[3411,5468,5469],{"class":3828},"\"PostgreSQLStore\"",[3411,5471,3433],{"class":3432},[3411,5473,5474,5476,5479,5482,5485,5488,5491,5493,5497,5500,5503],{"class":3413,"line":4936},[3411,5475,4400],{"class":4143},[3411,5477,5478],{"class":3424}," cls",[3411,5480,5481],{"class":3432},"(config[",[3411,5483,5484],{"class":3828},"\"host\"",[3411,5486,5487],{"class":3432},"], config.get(",[3411,5489,5490],{"class":3828},"\"port\"",[3411,5492,3453],{"class":3432},[3411,5494,5496],{"class":5495},"sJj4R","5432",[3411,5498,5499],{"class":3432},"), config[",[3411,5501,5502],{"class":3828},"\"db\"",[3411,5504,5505],{"class":3432},"])\n",[3411,5507,5509],{"class":3413,"line":5508},47,[3411,5510,3475],{"emptyLinePlaceholder":3474},[3411,5512,5514,5516],{"class":3413,"line":5513},48,[3411,5515,3697],{"class":3442},[3411,5517,5208],{"class":3428},[3411,5519,5521,5523,5525,5527,5529,5531,5533,5535,5537],{"class":3413,"line":5520},49,[3411,5522,3439],{"class":3424},[3411,5524,5219],{"class":3442},[3411,5526,3446],{"class":3432},[3411,5528,5171],{"class":3449},[3411,5530,3495],{"class":3432},[3411,5532,5176],{"class":3428},[3411,5534,3511],{"class":3432},[3411,5536,4226],{"class":3428},[3411,5538,3433],{"class":3432},[3411,5540,5542,5544,5547,5550,5553,5556,5559,5561],{"class":3413,"line":5541},50,[3411,5543,4400],{"class":4143},[3411,5545,5546],{"class":3828}," \"host\"",[3411,5548,5549],{"class":3424}," in",[3411,5551,5552],{"class":3432}," config ",[3411,5554,5555],{"class":3424},"and",[3411,5557,5558],{"class":3828}," \"db\"",[3411,5560,5549],{"class":3424},[3411,5562,5563],{"class":3432}," config\n",[3411,5565,5567],{"class":3413,"line":5566},51,[3411,5568,3475],{"emptyLinePlaceholder":3474},[3411,5570,5572,5574,5576,5578,5580,5582,5584,5586,5588,5590,5592],{"class":3413,"line":5571},52,[3411,5573,3439],{"class":3424},[3411,5575,5262],{"class":3442},[3411,5577,3446],{"class":3432},[3411,5579,3450],{"class":3449},[3411,5581,3453],{"class":3432},[3411,5583,5271],{"class":3449},[3411,5585,3495],{"class":3432},[3411,5587,3508],{"class":3428},[3411,5589,3511],{"class":3432},[3411,5591,5280],{"class":3428},[3411,5593,3433],{"class":3432},[3411,5595,5597,5599,5601,5603,5606,5608,5610,5612,5614],{"class":3413,"line":5596},53,[3411,5598,3820],{"class":3442},[3411,5600,3446],{"class":3432},[3411,5602,3825],{"class":3424},[3411,5604,5605],{"class":3828},"\"[PG] Виконання: ",[3411,5607,3832],{"class":3424},[3411,5609,5271],{"class":3432},[3411,5611,3837],{"class":3424},[3411,5613,3849],{"class":3828},[3411,5615,3852],{"class":3432},[3411,5617,5619,5621],{"class":3413,"line":5618},54,[3411,5620,4400],{"class":4143},[3411,5622,5623],{"class":3432}," []\n",[3411,5625,5627],{"class":3413,"line":5626},55,[3411,5628,3475],{"emptyLinePlaceholder":3474},[3411,5630,5632],{"class":3413,"line":5631},56,[3411,5633,3475],{"emptyLinePlaceholder":3474},[3411,5635,5637],{"class":3413,"line":5636},57,[3411,5638,5639],{"class":3417},"# Тестуємо\n",[3411,5641,5643,5646,5649,5651,5653,5656,5658,5660,5662,5665],{"class":3413,"line":5642},58,[3411,5644,5645],{"class":4143},"if",[3411,5647,5648],{"class":3432}," PostgreSQLStore.validate_config({",[3411,5650,5484],{"class":3828},[3411,5652,3495],{"class":3432},[3411,5654,5655],{"class":3828},"\"localhost\"",[3411,5657,3453],{"class":3432},[3411,5659,5502],{"class":3828},[3411,5661,3495],{"class":3432},[3411,5663,5664],{"class":3828},"\"mydb\"",[3411,5666,5667],{"class":3432},"}):\n",[3411,5669,5671,5674,5676,5678,5680,5682,5684,5686,5688],{"class":3413,"line":5670},59,[3411,5672,5673],{"class":3432},"    store = PostgreSQLStore.from_config({",[3411,5675,5484],{"class":3828},[3411,5677,3495],{"class":3432},[3411,5679,5655],{"class":3828},[3411,5681,3453],{"class":3432},[3411,5683,5502],{"class":3828},[3411,5685,3495],{"class":3432},[3411,5687,5664],{"class":3828},[3411,5689,5690],{"class":3432},"})\n",[3411,5692,5694,5696,5699],{"class":3413,"line":5693},60,[3411,5695,4585],{"class":3442},[3411,5697,5698],{"class":3432},"(store.connection_string)   ",[3411,5700,5701],{"class":3417},"# postgresql:\u002F\u002Flocalhost:5432\u002Fmydb\n",[3411,5703,5705,5708,5711,5714],{"class":3413,"line":5704},61,[3411,5706,5707],{"class":3432},"    store.execute(",[3411,5709,5710],{"class":3828},"\"SELECT 1\"",[3411,5712,5713],{"class":3432},")        ",[3411,5715,5716],{"class":3417},"# [PG] Виконання: SELECT 1\n",[3591,5718,5720,5721,5724],{"id":5719},"механізм-реєстрації-register-без-наслідування","Механізм реєстрації: ",[3408,5722,5723],{},"register()"," без наслідування",[3394,5726,5727,5728,5731],{},"ABC надає потужний механізм «реєстрації» — клас може бути визнаний підкласом ABC ",[3547,5729,5730],{},"без явного наслідування",". Це особливо корисно для інтеграції зі сторонніми бібліотеками, вихідний код яких ви не контролюєте.",[3401,5733,5735],{"className":3403,"code":5734,"language":3405,"meta":3406,"style":3406},"from abc import ABC, abstractmethod\n\nclass Drawable(ABC):\n    @abstractmethod\n    def draw(self) -> None: ...\n\n# Клас зі сторонньої бібліотеки — ми не можемо змінити його код\nclass ThirdPartyShape:\n    def draw(self) -> None:\n        print(\"Third-party shape rendering\")\n\n# Реєструємо як підклас без зміни ThirdPartyShape\nDrawable.register(ThirdPartyShape)\n\nshape = ThirdPartyShape()\nprint(isinstance(shape, Drawable))    # True  ✅\nprint(issubclass(ThirdPartyShape, Drawable))  # True  ✅\n\n# Але! ABCMeta не перевіряє наявність методів при реєстрації\nclass Fake:\n    pass  # не має методу draw()\n\nDrawable.register(Fake)\nfake = Fake()\nprint(isinstance(fake, Drawable))  # True — навіть без draw()!\n",[3408,5736,5737,5747,5751,5760,5764,5781,5785,5790,5799,5815,5826,5830,5835,5840,5844,5849,5863,5877,5881,5886,5895,5903,5907,5912,5917],{"__ignoreMap":3406},[3411,5738,5739,5741,5743,5745],{"class":3413,"line":3414},[3411,5740,4144],{"class":4143},[3411,5742,4147],{"class":3432},[3411,5744,4150],{"class":4143},[3411,5746,4153],{"class":3432},[3411,5748,5749],{"class":3413,"line":3421},[3411,5750,3475],{"emptyLinePlaceholder":3474},[3411,5752,5753,5755,5758],{"class":3413,"line":3436},[3411,5754,3425],{"class":3424},[3411,5756,5757],{"class":3428}," Drawable",[3411,5759,4167],{"class":3432},[3411,5761,5762],{"class":3413,"line":3462},[3411,5763,4195],{"class":3442},[3411,5765,5766,5768,5771,5773,5775,5777,5779],{"class":3413,"line":3471},[3411,5767,3439],{"class":3424},[3411,5769,5770],{"class":3442}," draw",[3411,5772,3446],{"class":3432},[3411,5774,3450],{"class":3449},[3411,5776,3511],{"class":3432},[3411,5778,3514],{"class":3424},[3411,5780,4521],{"class":3432},[3411,5782,5783],{"class":3413,"line":3478},[3411,5784,3475],{"emptyLinePlaceholder":3474},[3411,5786,5787],{"class":3413,"line":3519},[3411,5788,5789],{"class":3417},"# Клас зі сторонньої бібліотеки — ми не можемо змінити його код\n",[3411,5791,5792,5794,5797],{"class":3413,"line":3525},[3411,5793,3425],{"class":3424},[3411,5795,5796],{"class":3428}," ThirdPartyShape",[3411,5798,3433],{"class":3432},[3411,5800,5801,5803,5805,5807,5809,5811,5813],{"class":3413,"line":3531},[3411,5802,3439],{"class":3424},[3411,5804,5770],{"class":3442},[3411,5806,3446],{"class":3432},[3411,5808,3450],{"class":3449},[3411,5810,3511],{"class":3432},[3411,5812,3514],{"class":3424},[3411,5814,3433],{"class":3432},[3411,5816,5817,5819,5821,5824],{"class":3413,"line":3949},[3411,5818,3820],{"class":3442},[3411,5820,3446],{"class":3432},[3411,5822,5823],{"class":3828},"\"Third-party shape rendering\"",[3411,5825,3852],{"class":3432},[3411,5827,5828],{"class":3413,"line":3954},[3411,5829,3475],{"emptyLinePlaceholder":3474},[3411,5831,5832],{"class":3413,"line":3960},[3411,5833,5834],{"class":3417},"# Реєструємо як підклас без зміни ThirdPartyShape\n",[3411,5836,5837],{"class":3413,"line":3966},[3411,5838,5839],{"class":3432},"Drawable.register(ThirdPartyShape)\n",[3411,5841,5842],{"class":3413,"line":3972},[3411,5843,3475],{"emptyLinePlaceholder":3474},[3411,5845,5846],{"class":3413,"line":3977},[3411,5847,5848],{"class":3432},"shape = ThirdPartyShape()\n",[3411,5850,5851,5853,5855,5857,5860],{"class":3413,"line":3982},[3411,5852,4939],{"class":3442},[3411,5854,3446],{"class":3432},[3411,5856,4944],{"class":3442},[3411,5858,5859],{"class":3432},"(shape, Drawable))    ",[3411,5861,5862],{"class":3417},"# True  ✅\n",[3411,5864,5865,5867,5869,5872,5875],{"class":3413,"line":3988},[3411,5866,4939],{"class":3442},[3411,5868,3446],{"class":3432},[3411,5870,5871],{"class":3442},"issubclass",[3411,5873,5874],{"class":3432},"(ThirdPartyShape, Drawable))  ",[3411,5876,5862],{"class":3417},[3411,5878,5879],{"class":3413,"line":3994},[3411,5880,3475],{"emptyLinePlaceholder":3474},[3411,5882,5883],{"class":3413,"line":4000},[3411,5884,5885],{"class":3417},"# Але! ABCMeta не перевіряє наявність методів при реєстрації\n",[3411,5887,5888,5890,5893],{"class":3413,"line":4005},[3411,5889,3425],{"class":3424},[3411,5891,5892],{"class":3428}," Fake",[3411,5894,3433],{"class":3432},[3411,5896,5897,5900],{"class":3413,"line":4011},[3411,5898,5899],{"class":4143},"    pass",[3411,5901,5902],{"class":3417},"  # не має методу draw()\n",[3411,5904,5905],{"class":3413,"line":4017},[3411,5906,3475],{"emptyLinePlaceholder":3474},[3411,5908,5909],{"class":3413,"line":4022},[3411,5910,5911],{"class":3432},"Drawable.register(Fake)\n",[3411,5913,5914],{"class":3413,"line":4028},[3411,5915,5916],{"class":3432},"fake = Fake()\n",[3411,5918,5919,5921,5923,5925,5928],{"class":3413,"line":4034},[3411,5920,4939],{"class":3442},[3411,5922,3446],{"class":3432},[3411,5924,4944],{"class":3442},[3411,5926,5927],{"class":3432},"(fake, Drawable))  ",[3411,5929,5930],{"class":3417},"# True — навіть без draw()!\n",[4422,5932,5933,5938,5939,5942,5943,5946,5947,5391,5949,5951,5952,5954],{},[3547,5934,5935,5936,5382],{},"Пастка ",[3408,5937,5723],{}," ABCMeta перевіряє наявність абстрактних методів лише при ",[3547,5940,5941],{},"наслідуванні",", але не при ",[3547,5944,5945],{},"реєстрації",". Зареєстрований клас проходить ",[3408,5948,4944],{},[3408,5950,5871],{}," перевірку, але не гарантує реалізацію методів. Використовуйте ",[3408,5953,5723],{}," обережно — лише якщо впевнені у сумісності стороннього класу.",[3591,5956,5958],{"id":5957},"заглянути-у-механіку-abcmeta","Заглянути у механіку ABCMeta",[3394,5960,5961,5962,5382],{},"Щоб зрозуміти, як ABC працює «під капотом», розглянемо альтернативний спосіб оголошення абстрактних класів через ",[3408,5963,5964],{},"metaclass=ABCMeta",[3401,5966,5968],{"className":3403,"code":5967,"language":3405,"meta":3406,"style":3406},"from abc import ABCMeta, abstractmethod\n\n# Ці два записи абсолютно еквівалентні:\n\n# Спосіб 1 (рекомендований): успадкування від ABC\nclass MyAbstractA(ABC):\n    @abstractmethod\n    def do_something(self) -> None: ...\n\n# Спосіб 2 (низькорівневий): явне вказання метакласу\nclass MyAbstractB(metaclass=ABCMeta):\n    @abstractmethod\n    def do_something(self) -> None: ...\n\n# ABC — це просто синтаксичний цукор для metaclass=ABCMeta:\n# class ABC(metaclass=ABCMeta): pass\n",[3408,5969,5970,5981,5985,5990,5994,5999,6008,6012,6029,6033,6038,6057,6061,6077,6081,6086],{"__ignoreMap":3406},[3411,5971,5972,5974,5976,5978],{"class":3413,"line":3414},[3411,5973,4144],{"class":4143},[3411,5975,4147],{"class":3432},[3411,5977,4150],{"class":4143},[3411,5979,5980],{"class":3432}," ABCMeta, abstractmethod\n",[3411,5982,5983],{"class":3413,"line":3421},[3411,5984,3475],{"emptyLinePlaceholder":3474},[3411,5986,5987],{"class":3413,"line":3436},[3411,5988,5989],{"class":3417},"# Ці два записи абсолютно еквівалентні:\n",[3411,5991,5992],{"class":3413,"line":3462},[3411,5993,3475],{"emptyLinePlaceholder":3474},[3411,5995,5996],{"class":3413,"line":3471},[3411,5997,5998],{"class":3417},"# Спосіб 1 (рекомендований): успадкування від ABC\n",[3411,6000,6001,6003,6006],{"class":3413,"line":3478},[3411,6002,3425],{"class":3424},[3411,6004,6005],{"class":3428}," MyAbstractA",[3411,6007,4167],{"class":3432},[3411,6009,6010],{"class":3413,"line":3519},[3411,6011,4195],{"class":3442},[3411,6013,6014,6016,6019,6021,6023,6025,6027],{"class":3413,"line":3525},[3411,6015,3439],{"class":3424},[3411,6017,6018],{"class":3442}," do_something",[3411,6020,3446],{"class":3432},[3411,6022,3450],{"class":3449},[3411,6024,3511],{"class":3432},[3411,6026,3514],{"class":3424},[3411,6028,4521],{"class":3432},[3411,6030,6031],{"class":3413,"line":3531},[3411,6032,3475],{"emptyLinePlaceholder":3474},[3411,6034,6035],{"class":3413,"line":3949},[3411,6036,6037],{"class":3417},"# Спосіб 2 (низькорівневий): явне вказання метакласу\n",[3411,6039,6040,6042,6045,6047,6050,6053,6055],{"class":3413,"line":3954},[3411,6041,3425],{"class":3424},[3411,6043,6044],{"class":3428}," MyAbstractB",[3411,6046,3446],{"class":3432},[3411,6048,6049],{"class":3428},"metaclass",[3411,6051,6052],{"class":3432},"=",[3411,6054,4118],{"class":3428},[3411,6056,3459],{"class":3432},[3411,6058,6059],{"class":3413,"line":3960},[3411,6060,4195],{"class":3442},[3411,6062,6063,6065,6067,6069,6071,6073,6075],{"class":3413,"line":3966},[3411,6064,3439],{"class":3424},[3411,6066,6018],{"class":3442},[3411,6068,3446],{"class":3432},[3411,6070,3450],{"class":3449},[3411,6072,3511],{"class":3432},[3411,6074,3514],{"class":3424},[3411,6076,4521],{"class":3432},[3411,6078,6079],{"class":3413,"line":3972},[3411,6080,3475],{"emptyLinePlaceholder":3474},[3411,6082,6083],{"class":3413,"line":3977},[3411,6084,6085],{"class":3417},"# ABC — це просто синтаксичний цукор для metaclass=ABCMeta:\n",[3411,6087,6088],{"class":3413,"line":3982},[3411,6089,6090],{"class":3417},"# class ABC(metaclass=ABCMeta): pass\n",[3394,6092,6093,6095,6096,6099,6100,6102,6103,6106,6107,6110,6111,6114,6115,6118,6119,6121,6122,3617],{},[3408,6094,4118],{}," — це метаклас. Під час створення класу він сканує ",[3408,6097,6098],{},"__dict__"," (та ",[3408,6101,6098],{}," батьків) на наявність атрибутів з ",[3408,6104,6105],{},"__isabstractmethod__ = True",". Список таких атрибутів зберігається у спеціальному атрибуті класу ",[3408,6108,6109],{},"__abstractmethods__"," (тип: ",[3408,6112,6113],{},"frozenset","). Під час кожного виклику ",[3408,6116,6117],{},"__call__"," (тобто при інстанціюванні) Python перевіряє ",[3408,6120,6109],{}," — і якщо він не порожній, кидає ",[3408,6123,5019],{},[3401,6125,6127],{"className":3403,"code":6126,"language":3405,"meta":3406,"style":3406},"from abc import ABC, abstractmethod\n\nclass Shape(ABC):\n    @abstractmethod\n    def area(self) -> float: ...\n\n    @abstractmethod\n    def perimeter(self) -> float: ...\n\n# Перегляд абстрактних методів\nprint(Shape.__abstractmethods__)  # frozenset({'area', 'perimeter'})\n\nclass Circle(Shape):\n    def __init__(self, r: float):\n        self.r = r\n\n    def area(self) -> float:\n        return 3.14159 * self.r ** 2\n\n    # perimeter() ще не реалізовано...\n\nprint(Circle.__abstractmethods__)  # frozenset({'perimeter'}) — залишився 1\n\nclass FullCircle(Circle):\n    def perimeter(self) -> float:\n        return 2 * 3.14159 * self.r\n\nprint(FullCircle.__abstractmethods__)  # frozenset() — порожній, можна інстанціювати\nc = FullCircle(5.0)  # ✅\n",[3408,6128,6129,6139,6143,6152,6156,6174,6178,6182,6199,6203,6208,6218,6222,6236,6257,6264,6268,6284,6302,6306,6311,6315,6325,6329,6343,6359,6378,6382,6392],{"__ignoreMap":3406},[3411,6130,6131,6133,6135,6137],{"class":3413,"line":3414},[3411,6132,4144],{"class":4143},[3411,6134,4147],{"class":3432},[3411,6136,4150],{"class":4143},[3411,6138,4153],{"class":3432},[3411,6140,6141],{"class":3413,"line":3421},[3411,6142,3475],{"emptyLinePlaceholder":3474},[3411,6144,6145,6147,6150],{"class":3413,"line":3436},[3411,6146,3425],{"class":3424},[3411,6148,6149],{"class":3428}," Shape",[3411,6151,4167],{"class":3432},[3411,6153,6154],{"class":3413,"line":3462},[3411,6155,4195],{"class":3442},[3411,6157,6158,6160,6163,6165,6167,6169,6172],{"class":3413,"line":3471},[3411,6159,3439],{"class":3424},[3411,6161,6162],{"class":3442}," area",[3411,6164,3446],{"class":3432},[3411,6166,3450],{"class":3449},[3411,6168,3511],{"class":3432},[3411,6170,6171],{"class":3428},"float",[3411,6173,4521],{"class":3432},[3411,6175,6176],{"class":3413,"line":3478},[3411,6177,3475],{"emptyLinePlaceholder":3474},[3411,6179,6180],{"class":3413,"line":3519},[3411,6181,4195],{"class":3442},[3411,6183,6184,6186,6189,6191,6193,6195,6197],{"class":3413,"line":3525},[3411,6185,3439],{"class":3424},[3411,6187,6188],{"class":3442}," perimeter",[3411,6190,3446],{"class":3432},[3411,6192,3450],{"class":3449},[3411,6194,3511],{"class":3432},[3411,6196,6171],{"class":3428},[3411,6198,4521],{"class":3432},[3411,6200,6201],{"class":3413,"line":3531},[3411,6202,3475],{"emptyLinePlaceholder":3474},[3411,6204,6205],{"class":3413,"line":3949},[3411,6206,6207],{"class":3417},"# Перегляд абстрактних методів\n",[3411,6209,6210,6212,6215],{"class":3413,"line":3954},[3411,6211,4939],{"class":3442},[3411,6213,6214],{"class":3432},"(Shape.__abstractmethods__)  ",[3411,6216,6217],{"class":3417},"# frozenset({'area', 'perimeter'})\n",[3411,6219,6220],{"class":3413,"line":3960},[3411,6221,3475],{"emptyLinePlaceholder":3474},[3411,6223,6224,6226,6229,6231,6234],{"class":3413,"line":3966},[3411,6225,3425],{"class":3424},[3411,6227,6228],{"class":3428}," Circle",[3411,6230,3446],{"class":3432},[3411,6232,6233],{"class":3428},"Shape",[3411,6235,3459],{"class":3432},[3411,6237,6238,6240,6242,6244,6246,6248,6251,6253,6255],{"class":3413,"line":3972},[3411,6239,3439],{"class":3424},[3411,6241,3443],{"class":3442},[3411,6243,3446],{"class":3432},[3411,6245,3450],{"class":3449},[3411,6247,3453],{"class":3432},[3411,6249,6250],{"class":3449},"r",[3411,6252,3495],{"class":3432},[3411,6254,6171],{"class":3428},[3411,6256,3459],{"class":3432},[3411,6258,6259,6261],{"class":3413,"line":3977},[3411,6260,3465],{"class":3424},[3411,6262,6263],{"class":3432},".r = r\n",[3411,6265,6266],{"class":3413,"line":3982},[3411,6267,3475],{"emptyLinePlaceholder":3474},[3411,6269,6270,6272,6274,6276,6278,6280,6282],{"class":3413,"line":3988},[3411,6271,3439],{"class":3424},[3411,6273,6162],{"class":3442},[3411,6275,3446],{"class":3432},[3411,6277,3450],{"class":3449},[3411,6279,3511],{"class":3432},[3411,6281,6171],{"class":3428},[3411,6283,3433],{"class":3432},[3411,6285,6286,6288,6291,6294,6296,6299],{"class":3413,"line":3994},[3411,6287,4400],{"class":4143},[3411,6289,6290],{"class":5495}," 3.14159",[3411,6292,6293],{"class":3432}," * ",[3411,6295,3450],{"class":3424},[3411,6297,6298],{"class":3432},".r ** ",[3411,6300,6301],{"class":5495},"2\n",[3411,6303,6304],{"class":3413,"line":4000},[3411,6305,3475],{"emptyLinePlaceholder":3474},[3411,6307,6308],{"class":3413,"line":4005},[3411,6309,6310],{"class":3417},"    # perimeter() ще не реалізовано...\n",[3411,6312,6313],{"class":3413,"line":4011},[3411,6314,3475],{"emptyLinePlaceholder":3474},[3411,6316,6317,6319,6322],{"class":3413,"line":4017},[3411,6318,4939],{"class":3442},[3411,6320,6321],{"class":3432},"(Circle.__abstractmethods__)  ",[3411,6323,6324],{"class":3417},"# frozenset({'perimeter'}) — залишився 1\n",[3411,6326,6327],{"class":3413,"line":4022},[3411,6328,3475],{"emptyLinePlaceholder":3474},[3411,6330,6331,6333,6336,6338,6341],{"class":3413,"line":4028},[3411,6332,3425],{"class":3424},[3411,6334,6335],{"class":3428}," FullCircle",[3411,6337,3446],{"class":3432},[3411,6339,6340],{"class":3428},"Circle",[3411,6342,3459],{"class":3432},[3411,6344,6345,6347,6349,6351,6353,6355,6357],{"class":3413,"line":4034},[3411,6346,3439],{"class":3424},[3411,6348,6188],{"class":3442},[3411,6350,3446],{"class":3432},[3411,6352,3450],{"class":3449},[3411,6354,3511],{"class":3432},[3411,6356,6171],{"class":3428},[3411,6358,3433],{"class":3432},[3411,6360,6361,6363,6366,6368,6371,6373,6375],{"class":3413,"line":4039},[3411,6362,4400],{"class":4143},[3411,6364,6365],{"class":5495}," 2",[3411,6367,6293],{"class":3432},[3411,6369,6370],{"class":5495},"3.14159",[3411,6372,6293],{"class":3432},[3411,6374,3450],{"class":3424},[3411,6376,6377],{"class":3432},".r\n",[3411,6379,6380],{"class":3413,"line":4045},[3411,6381,3475],{"emptyLinePlaceholder":3474},[3411,6383,6384,6386,6389],{"class":3413,"line":4051},[3411,6385,4939],{"class":3442},[3411,6387,6388],{"class":3432},"(FullCircle.__abstractmethods__)  ",[3411,6390,6391],{"class":3417},"# frozenset() — порожній, можна інстанціювати\n",[3411,6393,6394,6397,6400,6402],{"class":3413,"line":4056},[3411,6395,6396],{"class":3432},"c = FullCircle(",[3411,6398,6399],{"class":5495},"5.0",[3411,6401,4930],{"class":3432},[3411,6403,6404],{"class":3417},"# ✅\n",[4952,6406,6408,6417,6426,6435,6443],{"title":6407},"Дослідження __abstractmethods__",[4956,6409,6411,4964,6414],{"className":6410},[3413],[3411,6412,4963],{"className":6413},[4962],[3547,6415,6416],{},"python abc_internals.py",[4956,6418,6420,6421],{"className":6419},[3413],"Shape.__abstractmethods__ = ",[3411,6422,6425],{"className":6423},[6424],"text-blue-400","frozenset({'area', 'perimeter'})",[4956,6427,6429,6430],{"className":6428},[3413],"Circle.__abstractmethods__ = ",[3411,6431,6434],{"className":6432},[6433],"text-yellow-400","frozenset({'perimeter'})",[4956,6436,6438,6439],{"className":6437},[3413],"FullCircle.__abstractmethods__ = ",[3411,6440,6442],{"className":6441},[4990],"frozenset()",[4956,6444,6446,6447],{"className":6445},[3413],"Площа кола R=5: ",[3411,6448,6450],{"className":6449},[4990],"78.54",[3584,6452],{},[3389,6454,6456],{"id":6455},"частина-iii-протоколи-pep-544-структурна-типізація","Частина III: Протоколи (PEP 544) — Структурна типізація",[3591,6458,6460],{"id":6459},"передісторія-проблема-яку-вирішив-pep-544","Передісторія: проблема, яку вирішив PEP 544",[3394,6462,6463,6464,6466,6467,6470],{},"До Python 3.8 розробники мали два інструменти для опису типів: ",[3408,6465,3738],{}," (занадто жорстко, потрібне наслідування) та рядкові анотації типів ",[3408,6468,6469],{},"Any"," (занадто м'яко, відсутня будь-яка перевірка). Для сторонніх бібліотек ABC взагалі не підходив.",[3394,6472,6473,6474,6477],{},"Ситуацію погіршувало те, що традиційне duck typing у Python ",[3547,6475,6476],{},"не підтримувалося"," статичними аналізаторами. Уявіть таку ситуацію:",[3401,6479,6481],{"className":3403,"code":6480,"language":3405,"meta":3406,"style":3406},"# Без Protocol: mypy не розуміє, що чекати від 'transport'\ndef send_notification(transport, user_id: int, message: str) -> bool:\n    return transport.send(user_id, message)  # mypy: \"transport\" has type \"Any\"\n",[3408,6482,6483,6488,6522],{"__ignoreMap":3406},[3411,6484,6485],{"class":3413,"line":3414},[3411,6486,6487],{"class":3417},"# Без Protocol: mypy не розуміє, що чекати від 'transport'\n",[3411,6489,6490,6493,6496,6498,6500,6502,6504,6506,6508,6510,6512,6514,6516,6518,6520],{"class":3413,"line":3421},[3411,6491,6492],{"class":3424},"def",[3411,6494,6495],{"class":3442}," send_notification",[3411,6497,3446],{"class":3432},[3411,6499,3456],{"class":3449},[3411,6501,3453],{"class":3432},[3411,6503,3492],{"class":3449},[3411,6505,3495],{"class":3432},[3411,6507,3498],{"class":3428},[3411,6509,3453],{"class":3432},[3411,6511,3503],{"class":3449},[3411,6513,3495],{"class":3432},[3411,6515,3508],{"class":3428},[3411,6517,3511],{"class":3432},[3411,6519,4226],{"class":3428},[3411,6521,3433],{"class":3432},[3411,6523,6524,6527,6530],{"class":3413,"line":3436},[3411,6525,6526],{"class":4143},"    return",[3411,6528,6529],{"class":3432}," transport.send(user_id, message)  ",[3411,6531,6532],{"class":3417},"# mypy: \"transport\" has type \"Any\"\n",[3394,6534,6535,6537,6538,6540,6541,6544,6545,6548,6549,6551,6552,3617],{},[3408,6536,3560],{}," тут безсилий — він не знає, чи є у ",[3408,6539,3456],{}," метод ",[3408,6542,6543],{},"send",". ",[3547,6546,6547],{},"PEP 544",", прийнятий у Python 3.8, вирішив цю проблему, ввівши клас ",[3408,6550,3888],{}," з модуля ",[3408,6553,6554],{},"typing",[3591,6556,6558],{"id":6557},"синтаксис-protocol-опис-структури-без-наслідування","Синтаксис Protocol: опис структури без наслідування",[3401,6560,6562],{"className":3403,"code":6561,"language":3405,"meta":3406,"style":3406},"from typing import Protocol\n\nclass Notifier(Protocol):\n    \"\"\"\n    Протокол для транспортів сповіщень.\n    ВАЖЛИВО: Цей клас описує структуру, а не реалізацію.\n    Будь-який об'єкт, що має метод send() з такою сигнатурою,\n    автоматично відповідає цьому протоколу — без явного наслідування.\n    \"\"\"\n    def send(self, user_id: int, message: str) -> bool: ...\n\n    def close(self) -> None: ...\n",[3408,6563,6564,6576,6580,6593,6597,6602,6607,6612,6617,6621,6653,6657],{"__ignoreMap":3406},[3411,6565,6566,6568,6571,6573],{"class":3413,"line":3414},[3411,6567,4144],{"class":4143},[3411,6569,6570],{"class":3432}," typing ",[3411,6572,4150],{"class":4143},[3411,6574,6575],{"class":3432}," Protocol\n",[3411,6577,6578],{"class":3413,"line":3421},[3411,6579,3475],{"emptyLinePlaceholder":3474},[3411,6581,6582,6584,6587,6589,6591],{"class":3413,"line":3436},[3411,6583,3425],{"class":3424},[3411,6585,6586],{"class":3428}," Notifier",[3411,6588,3446],{"class":3432},[3411,6590,3888],{"class":3428},[3411,6592,3459],{"class":3432},[3411,6594,6595],{"class":3413,"line":3462},[3411,6596,4172],{"class":3828},[3411,6598,6599],{"class":3413,"line":3471},[3411,6600,6601],{"class":3828},"    Протокол для транспортів сповіщень.\n",[3411,6603,6604],{"class":3413,"line":3478},[3411,6605,6606],{"class":3828},"    ВАЖЛИВО: Цей клас описує структуру, а не реалізацію.\n",[3411,6608,6609],{"class":3413,"line":3519},[3411,6610,6611],{"class":3828},"    Будь-який об'єкт, що має метод send() з такою сигнатурою,\n",[3411,6613,6614],{"class":3413,"line":3525},[3411,6615,6616],{"class":3828},"    автоматично відповідає цьому протоколу — без явного наслідування.\n",[3411,6618,6619],{"class":3413,"line":3531},[3411,6620,4172],{"class":3828},[3411,6622,6623,6625,6627,6629,6631,6633,6635,6637,6639,6641,6643,6645,6647,6649,6651],{"class":3413,"line":3949},[3411,6624,3439],{"class":3424},[3411,6626,3483],{"class":3442},[3411,6628,3446],{"class":3432},[3411,6630,3450],{"class":3449},[3411,6632,3453],{"class":3432},[3411,6634,3492],{"class":3449},[3411,6636,3495],{"class":3432},[3411,6638,3498],{"class":3428},[3411,6640,3453],{"class":3432},[3411,6642,3503],{"class":3449},[3411,6644,3495],{"class":3432},[3411,6646,3508],{"class":3428},[3411,6648,3511],{"class":3432},[3411,6650,4226],{"class":3428},[3411,6652,4521],{"class":3432},[3411,6654,6655],{"class":3413,"line":3954},[3411,6656,3475],{"emptyLinePlaceholder":3474},[3411,6658,6659,6661,6663,6665,6667,6669,6671],{"class":3413,"line":3960},[3411,6660,3439],{"class":3424},[3411,6662,4295],{"class":3442},[3411,6664,3446],{"class":3432},[3411,6666,3450],{"class":3449},[3411,6668,3511],{"class":3432},[3411,6670,3514],{"class":3424},[3411,6672,4521],{"class":3432},[3394,6674,6675,6676,5382],{},"Тепер визначимо кілька реалізацій — ",[3547,6677,6678,6679],{},"жодна з них не успадковує від ",[3408,6680,3742],{},[3401,6682,6684],{"className":3403,"code":6683,"language":3405,"meta":3406,"style":3406},"# email_transport.py — зі своєї бібліотеки\nclass EmailTransport:\n    def send(self, user_id: int, message: str) -> bool:\n        print(f\"[EMAIL] → user {user_id}: {message}\")\n        return True\n\n    def close(self) -> None:\n        print(\"[EMAIL] Closed\")\n\n# sms_transport.py — з SDK стороннього провайдера (не змінюємо)\nclass SMSGatewayClient:\n    def send(self, user_id: int, message: str) -> bool:\n        print(f\"[SMS] → user {user_id}: {message}\")\n        return True\n\n    def close(self) -> None:\n        print(\"[SMS] Session terminated\")\n\n# Функція, що приймає будь-який Notifier\ndef send_notification(transport: Notifier, user_id: int, message: str) -> bool:\n    return transport.send(user_id, message)\n\n# Обидва класи відповідають протоколу — mypy це розуміє!\nemail = EmailTransport()\nsms = SMSGatewayClient()\n\nsend_notification(email, 1, \"Ваш рахунок підтверджено\")  # ✅\nsend_notification(sms,   2, \"Код: 4872\")                 # ✅\n",[3408,6685,6686,6691,6700,6732,6761,6767,6771,6787,6798,6802,6807,6816,6848,6877,6883,6887,6903,6914,6918,6923,6956,6963,6967,6972,6977,6982,6986,7003],{"__ignoreMap":3406},[3411,6687,6688],{"class":3413,"line":3414},[3411,6689,6690],{"class":3417},"# email_transport.py — зі своєї бібліотеки\n",[3411,6692,6693,6695,6698],{"class":3413,"line":3421},[3411,6694,3425],{"class":3424},[3411,6696,6697],{"class":3428}," EmailTransport",[3411,6699,3433],{"class":3432},[3411,6701,6702,6704,6706,6708,6710,6712,6714,6716,6718,6720,6722,6724,6726,6728,6730],{"class":3413,"line":3436},[3411,6703,3439],{"class":3424},[3411,6705,3483],{"class":3442},[3411,6707,3446],{"class":3432},[3411,6709,3450],{"class":3449},[3411,6711,3453],{"class":3432},[3411,6713,3492],{"class":3449},[3411,6715,3495],{"class":3432},[3411,6717,3498],{"class":3428},[3411,6719,3453],{"class":3432},[3411,6721,3503],{"class":3449},[3411,6723,3495],{"class":3432},[3411,6725,3508],{"class":3428},[3411,6727,3511],{"class":3432},[3411,6729,4226],{"class":3428},[3411,6731,3433],{"class":3432},[3411,6733,6734,6736,6738,6740,6743,6745,6747,6749,6751,6753,6755,6757,6759],{"class":3413,"line":3462},[3411,6735,3820],{"class":3442},[3411,6737,3446],{"class":3432},[3411,6739,3825],{"class":3424},[3411,6741,6742],{"class":3828},"\"[EMAIL] → user ",[3411,6744,3832],{"class":3424},[3411,6746,3492],{"class":3432},[3411,6748,3837],{"class":3424},[3411,6750,3495],{"class":3828},[3411,6752,3832],{"class":3424},[3411,6754,3503],{"class":3432},[3411,6756,3837],{"class":3424},[3411,6758,3849],{"class":3828},[3411,6760,3852],{"class":3432},[3411,6762,6763,6765],{"class":3413,"line":3471},[3411,6764,4400],{"class":4143},[3411,6766,4675],{"class":3424},[3411,6768,6769],{"class":3413,"line":3478},[3411,6770,3475],{"emptyLinePlaceholder":3474},[3411,6772,6773,6775,6777,6779,6781,6783,6785],{"class":3413,"line":3519},[3411,6774,3439],{"class":3424},[3411,6776,4295],{"class":3442},[3411,6778,3446],{"class":3432},[3411,6780,3450],{"class":3449},[3411,6782,3511],{"class":3432},[3411,6784,3514],{"class":3424},[3411,6786,3433],{"class":3432},[3411,6788,6789,6791,6793,6796],{"class":3413,"line":3525},[3411,6790,3820],{"class":3442},[3411,6792,3446],{"class":3432},[3411,6794,6795],{"class":3828},"\"[EMAIL] Closed\"",[3411,6797,3852],{"class":3432},[3411,6799,6800],{"class":3413,"line":3531},[3411,6801,3475],{"emptyLinePlaceholder":3474},[3411,6803,6804],{"class":3413,"line":3949},[3411,6805,6806],{"class":3417},"# sms_transport.py — з SDK стороннього провайдера (не змінюємо)\n",[3411,6808,6809,6811,6814],{"class":3413,"line":3954},[3411,6810,3425],{"class":3424},[3411,6812,6813],{"class":3428}," SMSGatewayClient",[3411,6815,3433],{"class":3432},[3411,6817,6818,6820,6822,6824,6826,6828,6830,6832,6834,6836,6838,6840,6842,6844,6846],{"class":3413,"line":3960},[3411,6819,3439],{"class":3424},[3411,6821,3483],{"class":3442},[3411,6823,3446],{"class":3432},[3411,6825,3450],{"class":3449},[3411,6827,3453],{"class":3432},[3411,6829,3492],{"class":3449},[3411,6831,3495],{"class":3432},[3411,6833,3498],{"class":3428},[3411,6835,3453],{"class":3432},[3411,6837,3503],{"class":3449},[3411,6839,3495],{"class":3432},[3411,6841,3508],{"class":3428},[3411,6843,3511],{"class":3432},[3411,6845,4226],{"class":3428},[3411,6847,3433],{"class":3432},[3411,6849,6850,6852,6854,6856,6859,6861,6863,6865,6867,6869,6871,6873,6875],{"class":3413,"line":3966},[3411,6851,3820],{"class":3442},[3411,6853,3446],{"class":3432},[3411,6855,3825],{"class":3424},[3411,6857,6858],{"class":3828},"\"[SMS] → user ",[3411,6860,3832],{"class":3424},[3411,6862,3492],{"class":3432},[3411,6864,3837],{"class":3424},[3411,6866,3495],{"class":3828},[3411,6868,3832],{"class":3424},[3411,6870,3503],{"class":3432},[3411,6872,3837],{"class":3424},[3411,6874,3849],{"class":3828},[3411,6876,3852],{"class":3432},[3411,6878,6879,6881],{"class":3413,"line":3972},[3411,6880,4400],{"class":4143},[3411,6882,4675],{"class":3424},[3411,6884,6885],{"class":3413,"line":3977},[3411,6886,3475],{"emptyLinePlaceholder":3474},[3411,6888,6889,6891,6893,6895,6897,6899,6901],{"class":3413,"line":3982},[3411,6890,3439],{"class":3424},[3411,6892,4295],{"class":3442},[3411,6894,3446],{"class":3432},[3411,6896,3450],{"class":3449},[3411,6898,3511],{"class":3432},[3411,6900,3514],{"class":3424},[3411,6902,3433],{"class":3432},[3411,6904,6905,6907,6909,6912],{"class":3413,"line":3988},[3411,6906,3820],{"class":3442},[3411,6908,3446],{"class":3432},[3411,6910,6911],{"class":3828},"\"[SMS] Session terminated\"",[3411,6913,3852],{"class":3432},[3411,6915,6916],{"class":3413,"line":3994},[3411,6917,3475],{"emptyLinePlaceholder":3474},[3411,6919,6920],{"class":3413,"line":4000},[3411,6921,6922],{"class":3417},"# Функція, що приймає будь-який Notifier\n",[3411,6924,6925,6927,6929,6931,6933,6936,6938,6940,6942,6944,6946,6948,6950,6952,6954],{"class":3413,"line":4005},[3411,6926,6492],{"class":3424},[3411,6928,6495],{"class":3442},[3411,6930,3446],{"class":3432},[3411,6932,3456],{"class":3449},[3411,6934,6935],{"class":3432},": Notifier, ",[3411,6937,3492],{"class":3449},[3411,6939,3495],{"class":3432},[3411,6941,3498],{"class":3428},[3411,6943,3453],{"class":3432},[3411,6945,3503],{"class":3449},[3411,6947,3495],{"class":3432},[3411,6949,3508],{"class":3428},[3411,6951,3511],{"class":3432},[3411,6953,4226],{"class":3428},[3411,6955,3433],{"class":3432},[3411,6957,6958,6960],{"class":3413,"line":4011},[3411,6959,6526],{"class":4143},[3411,6961,6962],{"class":3432}," transport.send(user_id, message)\n",[3411,6964,6965],{"class":3413,"line":4017},[3411,6966,3475],{"emptyLinePlaceholder":3474},[3411,6968,6969],{"class":3413,"line":4022},[3411,6970,6971],{"class":3417},"# Обидва класи відповідають протоколу — mypy це розуміє!\n",[3411,6973,6974],{"class":3413,"line":4028},[3411,6975,6976],{"class":3432},"email = EmailTransport()\n",[3411,6978,6979],{"class":3413,"line":4034},[3411,6980,6981],{"class":3432},"sms = SMSGatewayClient()\n",[3411,6983,6984],{"class":3413,"line":4039},[3411,6985,3475],{"emptyLinePlaceholder":3474},[3411,6987,6988,6991,6994,6996,6999,7001],{"class":3413,"line":4045},[3411,6989,6990],{"class":3432},"send_notification(email, ",[3411,6992,6993],{"class":5495},"1",[3411,6995,3453],{"class":3432},[3411,6997,6998],{"class":3828},"\"Ваш рахунок підтверджено\"",[3411,7000,4930],{"class":3432},[3411,7002,6404],{"class":3417},[3411,7004,7005,7008,7011,7013,7016,7019],{"class":3413,"line":4051},[3411,7006,7007],{"class":3432},"send_notification(sms,   ",[3411,7009,7010],{"class":5495},"2",[3411,7012,3453],{"class":3432},[3411,7014,7015],{"class":3828},"\"Код: 4872\"",[3411,7017,7018],{"class":3432},")                 ",[3411,7020,6404],{"class":3417},[3394,7022,7023,7024,7026,7027,7030,7031,4964,7033,7036],{},"Ключовий момент: ",[3408,7025,3560],{}," перевіряє сумісність ",[3408,7028,7029],{},"EmailTransport"," з ",[3408,7032,3742],{},[3547,7034,7035],{},"структурно"," — він порівнює сигнатури методів, а не перевіряє дерево наслідування.",[3591,7038,7040],{"id":7039},"статична-перевірка-mypy-та-protocol","Статична перевірка: mypy та Protocol",[3394,7042,7043,7044,7047],{},"Перевага Protocol стає очевидною, коли клас ",[3547,7045,7046],{},"не"," відповідає протоколу:",[3401,7049,7051],{"className":3403,"code":7050,"language":3405,"meta":3406,"style":3406},"# broken_transport.py\nfrom typing import Protocol\n\nclass Notifier(Protocol):\n    def send(self, user_id: int, message: str) -> bool: ...\n    def close(self) -> None: ...\n\nclass BrokenTransport:\n    # Неправильна сигнатура: замість user_id приймає username: str\n    def send(self, username: str, message: str) -> bool:\n        return True\n\n    # close() взагалі відсутній\n\ndef send_notification(transport: Notifier, user_id: int, msg: str) -> bool:\n    return transport.send(user_id, msg)\n\nbroken = BrokenTransport()\nsend_notification(broken, 1, \"test\")  # Runtime: працює (duck typing)\n                                       # mypy: ❌ ПОМИЛКА!\n",[3408,7052,7053,7058,7068,7072,7084,7116,7132,7136,7145,7150,7183,7189,7193,7198,7202,7235,7242,7246,7251,7268],{"__ignoreMap":3406},[3411,7054,7055],{"class":3413,"line":3414},[3411,7056,7057],{"class":3417},"# broken_transport.py\n",[3411,7059,7060,7062,7064,7066],{"class":3413,"line":3421},[3411,7061,4144],{"class":4143},[3411,7063,6570],{"class":3432},[3411,7065,4150],{"class":4143},[3411,7067,6575],{"class":3432},[3411,7069,7070],{"class":3413,"line":3436},[3411,7071,3475],{"emptyLinePlaceholder":3474},[3411,7073,7074,7076,7078,7080,7082],{"class":3413,"line":3462},[3411,7075,3425],{"class":3424},[3411,7077,6586],{"class":3428},[3411,7079,3446],{"class":3432},[3411,7081,3888],{"class":3428},[3411,7083,3459],{"class":3432},[3411,7085,7086,7088,7090,7092,7094,7096,7098,7100,7102,7104,7106,7108,7110,7112,7114],{"class":3413,"line":3471},[3411,7087,3439],{"class":3424},[3411,7089,3483],{"class":3442},[3411,7091,3446],{"class":3432},[3411,7093,3450],{"class":3449},[3411,7095,3453],{"class":3432},[3411,7097,3492],{"class":3449},[3411,7099,3495],{"class":3432},[3411,7101,3498],{"class":3428},[3411,7103,3453],{"class":3432},[3411,7105,3503],{"class":3449},[3411,7107,3495],{"class":3432},[3411,7109,3508],{"class":3428},[3411,7111,3511],{"class":3432},[3411,7113,4226],{"class":3428},[3411,7115,4521],{"class":3432},[3411,7117,7118,7120,7122,7124,7126,7128,7130],{"class":3413,"line":3478},[3411,7119,3439],{"class":3424},[3411,7121,4295],{"class":3442},[3411,7123,3446],{"class":3432},[3411,7125,3450],{"class":3449},[3411,7127,3511],{"class":3432},[3411,7129,3514],{"class":3424},[3411,7131,4521],{"class":3432},[3411,7133,7134],{"class":3413,"line":3519},[3411,7135,3475],{"emptyLinePlaceholder":3474},[3411,7137,7138,7140,7143],{"class":3413,"line":3525},[3411,7139,3425],{"class":3424},[3411,7141,7142],{"class":3428}," BrokenTransport",[3411,7144,3433],{"class":3432},[3411,7146,7147],{"class":3413,"line":3531},[3411,7148,7149],{"class":3417},"    # Неправильна сигнатура: замість user_id приймає username: str\n",[3411,7151,7152,7154,7156,7158,7160,7162,7165,7167,7169,7171,7173,7175,7177,7179,7181],{"class":3413,"line":3949},[3411,7153,3439],{"class":3424},[3411,7155,3483],{"class":3442},[3411,7157,3446],{"class":3432},[3411,7159,3450],{"class":3449},[3411,7161,3453],{"class":3432},[3411,7163,7164],{"class":3449},"username",[3411,7166,3495],{"class":3432},[3411,7168,3508],{"class":3428},[3411,7170,3453],{"class":3432},[3411,7172,3503],{"class":3449},[3411,7174,3495],{"class":3432},[3411,7176,3508],{"class":3428},[3411,7178,3511],{"class":3432},[3411,7180,4226],{"class":3428},[3411,7182,3433],{"class":3432},[3411,7184,7185,7187],{"class":3413,"line":3954},[3411,7186,4400],{"class":4143},[3411,7188,4675],{"class":3424},[3411,7190,7191],{"class":3413,"line":3960},[3411,7192,3475],{"emptyLinePlaceholder":3474},[3411,7194,7195],{"class":3413,"line":3966},[3411,7196,7197],{"class":3417},"    # close() взагалі відсутній\n",[3411,7199,7200],{"class":3413,"line":3972},[3411,7201,3475],{"emptyLinePlaceholder":3474},[3411,7203,7204,7206,7208,7210,7212,7214,7216,7218,7220,7222,7225,7227,7229,7231,7233],{"class":3413,"line":3977},[3411,7205,6492],{"class":3424},[3411,7207,6495],{"class":3442},[3411,7209,3446],{"class":3432},[3411,7211,3456],{"class":3449},[3411,7213,6935],{"class":3432},[3411,7215,3492],{"class":3449},[3411,7217,3495],{"class":3432},[3411,7219,3498],{"class":3428},[3411,7221,3453],{"class":3432},[3411,7223,7224],{"class":3449},"msg",[3411,7226,3495],{"class":3432},[3411,7228,3508],{"class":3428},[3411,7230,3511],{"class":3432},[3411,7232,4226],{"class":3428},[3411,7234,3433],{"class":3432},[3411,7236,7237,7239],{"class":3413,"line":3982},[3411,7238,6526],{"class":4143},[3411,7240,7241],{"class":3432}," transport.send(user_id, msg)\n",[3411,7243,7244],{"class":3413,"line":3988},[3411,7245,3475],{"emptyLinePlaceholder":3474},[3411,7247,7248],{"class":3413,"line":3994},[3411,7249,7250],{"class":3432},"broken = BrokenTransport()\n",[3411,7252,7253,7256,7258,7260,7263,7265],{"class":3413,"line":4000},[3411,7254,7255],{"class":3432},"send_notification(broken, ",[3411,7257,6993],{"class":5495},[3411,7259,3453],{"class":3432},[3411,7261,7262],{"class":3828},"\"test\"",[3411,7264,4930],{"class":3432},[3411,7266,7267],{"class":3417},"# Runtime: працює (duck typing)\n",[3411,7269,7270],{"class":3413,"line":4005},[3411,7271,7272],{"class":3417},"                                       # mypy: ❌ ПОМИЛКА!\n",[4952,7274,7276,7285,7293,7301,7308,7315,7322,7329,7336],{"title":7275},"mypy: виявлення порушення протоколу",[4956,7277,7279,4964,7282],{"className":7278},[3413],[3411,7280,4963],{"className":7281},[4962],[3547,7283,7284],{},"mypy broken_transport.py",[4956,7286,7288,7289],{"className":7287},[3413],"broken_transport.py:22: ",[3411,7290,7292],{"className":7291},[4975],"error: Argument 1 to \"send_notification\" has incompatible type \"BrokenTransport\"; expected \"Notifier\"  [arg-type]",[4956,7294,7288,7296],{"className":7295},[3413],[3411,7297,7300],{"className":7298},[7299],"text-gray-400","note: Following member(s) of \"BrokenTransport\" have conflicts:",[4956,7302,7288,7304],{"className":7303},[3413],[3411,7305,7307],{"className":7306},[6433],"note:     Expected:",[4956,7309,7288,7311],{"className":7310},[3413],[3411,7312,7314],{"className":7313},[6433],"note:         def send(self, user_id: int, message: str) -> bool",[4956,7316,7288,7318],{"className":7317},[3413],[3411,7319,7321],{"className":7320},[4975],"note:     Got:",[4956,7323,7288,7325],{"className":7324},[3413],[3411,7326,7328],{"className":7327},[4975],"note:         def send(self, username: str, message: str) -> bool",[4956,7330,7288,7332],{"className":7331},[3413],[3411,7333,7335],{"className":7334},[4975],"note:     \"close\" not found",[4956,7337,7339],{"className":7338},[3413],[3411,7340,7342],{"className":7341},[4975],"Found 1 error in 1 file (checked 1 source file)",[3394,7344,7345,7347,7348,7350,7351,7353,7354,7357],{},[3408,7346,3560],{}," точно вказує на проблему: невідповідність типу параметра (",[3408,7349,3508],{}," замість ",[3408,7352,3498],{},") та відсутність методу ",[3408,7355,7356],{},"close",". Ця помилка виловлюється ще до запуску програми — це і є цінність статичного аналізу.",[3591,7359,7361,7364],{"id":7360},"runtime_checkable-isinstance-для-протоколів",[3408,7362,7363],{},"runtime_checkable",": isinstance для протоколів",[3394,7366,7367,7368,4964,7370,4964,7373,7376,7377,7380,7381,5382],{},"За замовчуванням ",[3408,7369,3888],{},[3547,7371,7372],{},"не підтримує",[3408,7374,7375],{},"isinstance()","-перевірки — це навмисне проектне рішення (перевірка структурної сумісності у runtime є дорогою операцією). Але за допомогою декоратора ",[3408,7378,7379],{},"@runtime_checkable"," можна увімкнути підтримку ",[3408,7382,4944],{},[3401,7384,7386],{"className":3403,"code":7385,"language":3405,"meta":3406,"style":3406},"from typing import Protocol, runtime_checkable\n\n@runtime_checkable\nclass Drawable(Protocol):\n    def draw(self, x: int, y: int) -> None: ...\n    def resize(self, factor: float) -> None: ...\n\nclass Circle:\n    def draw(self, x: int, y: int) -> None:\n        print(f\"O @ ({x}, {y})\")\n\n    def resize(self, factor: float) -> None:\n        self.radius *= factor\n\nclass Square:\n    def draw(self, x: int, y: int) -> None:\n        print(f\"□ @ ({x}, {y})\")\n    # resize() відсутній!\n\nc = Circle()\ns = Square()\n\nprint(isinstance(c, Drawable))  # True  — Circle має обидва методи\nprint(isinstance(s, Drawable))  # False — Square не має resize()\n",[3408,7387,7388,7399,7403,7408,7420,7454,7480,7484,7492,7524,7554,7558,7582,7589,7593,7602,7634,7663,7668,7672,7677,7682,7686,7700],{"__ignoreMap":3406},[3411,7389,7390,7392,7394,7396],{"class":3413,"line":3414},[3411,7391,4144],{"class":4143},[3411,7393,6570],{"class":3432},[3411,7395,4150],{"class":4143},[3411,7397,7398],{"class":3432}," Protocol, runtime_checkable\n",[3411,7400,7401],{"class":3413,"line":3421},[3411,7402,3475],{"emptyLinePlaceholder":3474},[3411,7404,7405],{"class":3413,"line":3436},[3411,7406,7407],{"class":3442},"@runtime_checkable\n",[3411,7409,7410,7412,7414,7416,7418],{"class":3413,"line":3462},[3411,7411,3425],{"class":3424},[3411,7413,5757],{"class":3428},[3411,7415,3446],{"class":3432},[3411,7417,3888],{"class":3428},[3411,7419,3459],{"class":3432},[3411,7421,7422,7424,7426,7428,7430,7432,7435,7437,7439,7441,7444,7446,7448,7450,7452],{"class":3413,"line":3471},[3411,7423,3439],{"class":3424},[3411,7425,5770],{"class":3442},[3411,7427,3446],{"class":3432},[3411,7429,3450],{"class":3449},[3411,7431,3453],{"class":3432},[3411,7433,7434],{"class":3449},"x",[3411,7436,3495],{"class":3432},[3411,7438,3498],{"class":3428},[3411,7440,3453],{"class":3432},[3411,7442,7443],{"class":3449},"y",[3411,7445,3495],{"class":3432},[3411,7447,3498],{"class":3428},[3411,7449,3511],{"class":3432},[3411,7451,3514],{"class":3424},[3411,7453,4521],{"class":3432},[3411,7455,7456,7458,7461,7463,7465,7467,7470,7472,7474,7476,7478],{"class":3413,"line":3478},[3411,7457,3439],{"class":3424},[3411,7459,7460],{"class":3442}," resize",[3411,7462,3446],{"class":3432},[3411,7464,3450],{"class":3449},[3411,7466,3453],{"class":3432},[3411,7468,7469],{"class":3449},"factor",[3411,7471,3495],{"class":3432},[3411,7473,6171],{"class":3428},[3411,7475,3511],{"class":3432},[3411,7477,3514],{"class":3424},[3411,7479,4521],{"class":3432},[3411,7481,7482],{"class":3413,"line":3519},[3411,7483,3475],{"emptyLinePlaceholder":3474},[3411,7485,7486,7488,7490],{"class":3413,"line":3525},[3411,7487,3425],{"class":3424},[3411,7489,6228],{"class":3428},[3411,7491,3433],{"class":3432},[3411,7493,7494,7496,7498,7500,7502,7504,7506,7508,7510,7512,7514,7516,7518,7520,7522],{"class":3413,"line":3531},[3411,7495,3439],{"class":3424},[3411,7497,5770],{"class":3442},[3411,7499,3446],{"class":3432},[3411,7501,3450],{"class":3449},[3411,7503,3453],{"class":3432},[3411,7505,7434],{"class":3449},[3411,7507,3495],{"class":3432},[3411,7509,3498],{"class":3428},[3411,7511,3453],{"class":3432},[3411,7513,7443],{"class":3449},[3411,7515,3495],{"class":3432},[3411,7517,3498],{"class":3428},[3411,7519,3511],{"class":3432},[3411,7521,3514],{"class":3424},[3411,7523,3433],{"class":3432},[3411,7525,7526,7528,7530,7532,7535,7537,7539,7541,7543,7545,7547,7549,7552],{"class":3413,"line":3949},[3411,7527,3820],{"class":3442},[3411,7529,3446],{"class":3432},[3411,7531,3825],{"class":3424},[3411,7533,7534],{"class":3828},"\"O @ (",[3411,7536,3832],{"class":3424},[3411,7538,7434],{"class":3432},[3411,7540,3837],{"class":3424},[3411,7542,3453],{"class":3828},[3411,7544,3832],{"class":3424},[3411,7546,7443],{"class":3432},[3411,7548,3837],{"class":3424},[3411,7550,7551],{"class":3828},")\"",[3411,7553,3852],{"class":3432},[3411,7555,7556],{"class":3413,"line":3954},[3411,7557,3475],{"emptyLinePlaceholder":3474},[3411,7559,7560,7562,7564,7566,7568,7570,7572,7574,7576,7578,7580],{"class":3413,"line":3960},[3411,7561,3439],{"class":3424},[3411,7563,7460],{"class":3442},[3411,7565,3446],{"class":3432},[3411,7567,3450],{"class":3449},[3411,7569,3453],{"class":3432},[3411,7571,7469],{"class":3449},[3411,7573,3495],{"class":3432},[3411,7575,6171],{"class":3428},[3411,7577,3511],{"class":3432},[3411,7579,3514],{"class":3424},[3411,7581,3433],{"class":3432},[3411,7583,7584,7586],{"class":3413,"line":3966},[3411,7585,3465],{"class":3424},[3411,7587,7588],{"class":3432},".radius *= factor\n",[3411,7590,7591],{"class":3413,"line":3972},[3411,7592,3475],{"emptyLinePlaceholder":3474},[3411,7594,7595,7597,7600],{"class":3413,"line":3977},[3411,7596,3425],{"class":3424},[3411,7598,7599],{"class":3428}," Square",[3411,7601,3433],{"class":3432},[3411,7603,7604,7606,7608,7610,7612,7614,7616,7618,7620,7622,7624,7626,7628,7630,7632],{"class":3413,"line":3982},[3411,7605,3439],{"class":3424},[3411,7607,5770],{"class":3442},[3411,7609,3446],{"class":3432},[3411,7611,3450],{"class":3449},[3411,7613,3453],{"class":3432},[3411,7615,7434],{"class":3449},[3411,7617,3495],{"class":3432},[3411,7619,3498],{"class":3428},[3411,7621,3453],{"class":3432},[3411,7623,7443],{"class":3449},[3411,7625,3495],{"class":3432},[3411,7627,3498],{"class":3428},[3411,7629,3511],{"class":3432},[3411,7631,3514],{"class":3424},[3411,7633,3433],{"class":3432},[3411,7635,7636,7638,7640,7642,7645,7647,7649,7651,7653,7655,7657,7659,7661],{"class":3413,"line":3988},[3411,7637,3820],{"class":3442},[3411,7639,3446],{"class":3432},[3411,7641,3825],{"class":3424},[3411,7643,7644],{"class":3828},"\"□ @ (",[3411,7646,3832],{"class":3424},[3411,7648,7434],{"class":3432},[3411,7650,3837],{"class":3424},[3411,7652,3453],{"class":3828},[3411,7654,3832],{"class":3424},[3411,7656,7443],{"class":3432},[3411,7658,3837],{"class":3424},[3411,7660,7551],{"class":3828},[3411,7662,3852],{"class":3432},[3411,7664,7665],{"class":3413,"line":3994},[3411,7666,7667],{"class":3417},"    # resize() відсутній!\n",[3411,7669,7670],{"class":3413,"line":4000},[3411,7671,3475],{"emptyLinePlaceholder":3474},[3411,7673,7674],{"class":3413,"line":4005},[3411,7675,7676],{"class":3432},"c = Circle()\n",[3411,7678,7679],{"class":3413,"line":4011},[3411,7680,7681],{"class":3432},"s = Square()\n",[3411,7683,7684],{"class":3413,"line":4017},[3411,7685,3475],{"emptyLinePlaceholder":3474},[3411,7687,7688,7690,7692,7694,7697],{"class":3413,"line":4022},[3411,7689,4939],{"class":3442},[3411,7691,3446],{"class":3432},[3411,7693,4944],{"class":3442},[3411,7695,7696],{"class":3432},"(c, Drawable))  ",[3411,7698,7699],{"class":3417},"# True  — Circle має обидва методи\n",[3411,7701,7702,7704,7706,7708,7711],{"class":3413,"line":4028},[3411,7703,4939],{"class":3442},[3411,7705,3446],{"class":3432},[3411,7707,4944],{"class":3442},[3411,7709,7710],{"class":3432},"(s, Drawable))  ",[3411,7712,7713],{"class":3417},"# False — Square не має resize()\n",[4422,7715,7716,4964,7721,7723,7724,7727,7728,7730,7731,7734,7735,7350,7738,7741,7742,7744,7745,7747,7748,3617],{},[3547,7717,7718,7719,5382],{},"Обмеження ",[3408,7720,7363],{},[3408,7722,7375],{}," перевіряє лише ",[3547,7725,7726],{},"наявність"," атрибутів (через ",[3408,7729,4079],{},"), але ",[3547,7732,7733],{},"не перевіряє сигнатури"," методів. Клас із методом ",[3408,7736,7737],{},"draw(self)",[3408,7739,7740],{},"draw(self, x: int, y: int)"," пройде ",[3408,7743,4944],{},"-перевірку, але провалить перевірку ",[3408,7746,3560],{},". Для гарантії сумісності — використовуйте ",[3408,7749,3560],{},[3401,7751,7753],{"className":3403,"code":7752,"language":3405,"meta":3406,"style":3406},"@runtime_checkable\nclass Notifier(Protocol):\n    def send(self, user_id: int, message: str) -> bool: ...\n\nclass WrongSignature:\n    def send(self) -> None:   # ← неправильна сигнатура!\n        pass\n\nobj = WrongSignature()\nprint(isinstance(obj, Notifier))  # True (!!) — наявність методу є, сигнатура не перевіряється\n",[3408,7754,7755,7759,7771,7803,7807,7816,7836,7841,7845,7850],{"__ignoreMap":3406},[3411,7756,7757],{"class":3413,"line":3414},[3411,7758,7407],{"class":3442},[3411,7760,7761,7763,7765,7767,7769],{"class":3413,"line":3421},[3411,7762,3425],{"class":3424},[3411,7764,6586],{"class":3428},[3411,7766,3446],{"class":3432},[3411,7768,3888],{"class":3428},[3411,7770,3459],{"class":3432},[3411,7772,7773,7775,7777,7779,7781,7783,7785,7787,7789,7791,7793,7795,7797,7799,7801],{"class":3413,"line":3436},[3411,7774,3439],{"class":3424},[3411,7776,3483],{"class":3442},[3411,7778,3446],{"class":3432},[3411,7780,3450],{"class":3449},[3411,7782,3453],{"class":3432},[3411,7784,3492],{"class":3449},[3411,7786,3495],{"class":3432},[3411,7788,3498],{"class":3428},[3411,7790,3453],{"class":3432},[3411,7792,3503],{"class":3449},[3411,7794,3495],{"class":3432},[3411,7796,3508],{"class":3428},[3411,7798,3511],{"class":3432},[3411,7800,4226],{"class":3428},[3411,7802,4521],{"class":3432},[3411,7804,7805],{"class":3413,"line":3462},[3411,7806,3475],{"emptyLinePlaceholder":3474},[3411,7808,7809,7811,7814],{"class":3413,"line":3471},[3411,7810,3425],{"class":3424},[3411,7812,7813],{"class":3428}," WrongSignature",[3411,7815,3433],{"class":3432},[3411,7817,7818,7820,7822,7824,7826,7828,7830,7833],{"class":3413,"line":3478},[3411,7819,3439],{"class":3424},[3411,7821,3483],{"class":3442},[3411,7823,3446],{"class":3432},[3411,7825,3450],{"class":3449},[3411,7827,3511],{"class":3432},[3411,7829,3514],{"class":3424},[3411,7831,7832],{"class":3432},":   ",[3411,7834,7835],{"class":3417},"# ← неправильна сигнатура!\n",[3411,7837,7838],{"class":3413,"line":3519},[3411,7839,7840],{"class":4143},"        pass\n",[3411,7842,7843],{"class":3413,"line":3525},[3411,7844,3475],{"emptyLinePlaceholder":3474},[3411,7846,7847],{"class":3413,"line":3531},[3411,7848,7849],{"class":3432},"obj = WrongSignature()\n",[3411,7851,7852,7854,7856,7858,7861],{"class":3413,"line":3949},[3411,7853,4939],{"class":3442},[3411,7855,3446],{"class":3432},[3411,7857,4944],{"class":3442},[3411,7859,7860],{"class":3432},"(obj, Notifier))  ",[3411,7862,7863],{"class":3417},"# True (!!) — наявність методу є, сигнатура не перевіряється\n",[4952,7865,7867,7876,7883,7896],{"title":7866},"Демонстрація обмеження runtime_checkable",[4956,7868,7870,4964,7873],{"className":7869},[3413],[3411,7871,4963],{"className":7872},[4962],[3547,7874,7875],{},"python runtime_check_demo.py",[4956,7877,7879,7880],{"className":7878},[3413],"isinstance(c, Drawable): ",[3411,7881,4998],{"className":7882},[4990],[4956,7884,7886,7887,7891,7892],{"className":7885},[3413],"isinstance(s, Drawable): ",[3411,7888,7890],{"className":7889},[4975],"False","  ",[3411,7893,7895],{"className":7894},[7299],"# Square не має resize()",[4956,7897,7899,7900,7891,7903],{"className":7898},[3413],"isinstance(obj, Notifier): ",[3411,7901,4998],{"className":7902},[6433],[3411,7904,7906],{"className":7905},[7299],"# УВАГА: сигнатура не перевіряється!",[3591,7908,7910],{"id":7909},"protocol-з-атрибутами-даних","Protocol з атрибутами даних",[3394,7912,7913,7915,7916,5382],{},[3408,7914,3888],{}," може описувати не лише методи, а й ",[3547,7917,7918],{},"атрибути даних",[3401,7920,7922],{"className":3403,"code":7921,"language":3405,"meta":3406,"style":3406},"from typing import Protocol\n\nclass HasMetadata(Protocol):\n    name: str\n    version: str\n    author: str\n\n    def describe(self) -> str: ...\n\nclass PythonPackage:\n    def __init__(self, name: str, version: str, author: str):\n        self.name = name\n        self.version = version\n        self.author = author\n\n    def describe(self) -> str:\n        return f\"{self.name} v{self.version} by {self.author}\"\n\ndef print_package_info(pkg: HasMetadata) -> None:\n    print(f\"Package: {pkg.describe()}\")\n\np = PythonPackage(\"requests\", \"2.31.0\", \"Kenneth Reitz\")\nprint_package_info(p)  # mypy: ✅ — PythonPackage структурно відповідає HasMetadata\n",[3408,7923,7924,7934,7938,7951,7959,7966,7973,7977,7994,7998,8007,8046,8053,8060,8067,8071,8087,8126,8130,8149,8171,8175,8195],{"__ignoreMap":3406},[3411,7925,7926,7928,7930,7932],{"class":3413,"line":3414},[3411,7927,4144],{"class":4143},[3411,7929,6570],{"class":3432},[3411,7931,4150],{"class":4143},[3411,7933,6575],{"class":3432},[3411,7935,7936],{"class":3413,"line":3421},[3411,7937,3475],{"emptyLinePlaceholder":3474},[3411,7939,7940,7942,7945,7947,7949],{"class":3413,"line":3436},[3411,7941,3425],{"class":3424},[3411,7943,7944],{"class":3428}," HasMetadata",[3411,7946,3446],{"class":3432},[3411,7948,3888],{"class":3428},[3411,7950,3459],{"class":3432},[3411,7952,7953,7956],{"class":3413,"line":3462},[3411,7954,7955],{"class":3432},"    name: ",[3411,7957,7958],{"class":3428},"str\n",[3411,7960,7961,7964],{"class":3413,"line":3471},[3411,7962,7963],{"class":3432},"    version: ",[3411,7965,7958],{"class":3428},[3411,7967,7968,7971],{"class":3413,"line":3478},[3411,7969,7970],{"class":3432},"    author: ",[3411,7972,7958],{"class":3428},[3411,7974,7975],{"class":3413,"line":3519},[3411,7976,3475],{"emptyLinePlaceholder":3474},[3411,7978,7979,7981,7984,7986,7988,7990,7992],{"class":3413,"line":3525},[3411,7980,3439],{"class":3424},[3411,7982,7983],{"class":3442}," describe",[3411,7985,3446],{"class":3432},[3411,7987,3450],{"class":3449},[3411,7989,3511],{"class":3432},[3411,7991,3508],{"class":3428},[3411,7993,4521],{"class":3432},[3411,7995,7996],{"class":3413,"line":3531},[3411,7997,3475],{"emptyLinePlaceholder":3474},[3411,7999,8000,8002,8005],{"class":3413,"line":3949},[3411,8001,3425],{"class":3424},[3411,8003,8004],{"class":3428}," PythonPackage",[3411,8006,3433],{"class":3432},[3411,8008,8009,8011,8013,8015,8017,8019,8022,8024,8026,8028,8031,8033,8035,8037,8040,8042,8044],{"class":3413,"line":3954},[3411,8010,3439],{"class":3424},[3411,8012,3443],{"class":3442},[3411,8014,3446],{"class":3432},[3411,8016,3450],{"class":3449},[3411,8018,3453],{"class":3432},[3411,8020,8021],{"class":3449},"name",[3411,8023,3495],{"class":3432},[3411,8025,3508],{"class":3428},[3411,8027,3453],{"class":3432},[3411,8029,8030],{"class":3449},"version",[3411,8032,3495],{"class":3432},[3411,8034,3508],{"class":3428},[3411,8036,3453],{"class":3432},[3411,8038,8039],{"class":3449},"author",[3411,8041,3495],{"class":3432},[3411,8043,3508],{"class":3428},[3411,8045,3459],{"class":3432},[3411,8047,8048,8050],{"class":3413,"line":3960},[3411,8049,3465],{"class":3424},[3411,8051,8052],{"class":3432},".name = name\n",[3411,8054,8055,8057],{"class":3413,"line":3966},[3411,8056,3465],{"class":3424},[3411,8058,8059],{"class":3432},".version = version\n",[3411,8061,8062,8064],{"class":3413,"line":3972},[3411,8063,3465],{"class":3424},[3411,8065,8066],{"class":3432},".author = author\n",[3411,8068,8069],{"class":3413,"line":3977},[3411,8070,3475],{"emptyLinePlaceholder":3474},[3411,8072,8073,8075,8077,8079,8081,8083,8085],{"class":3413,"line":3982},[3411,8074,3439],{"class":3424},[3411,8076,7983],{"class":3442},[3411,8078,3446],{"class":3432},[3411,8080,3450],{"class":3449},[3411,8082,3511],{"class":3432},[3411,8084,3508],{"class":3428},[3411,8086,3433],{"class":3432},[3411,8088,8089,8091,8094,8096,8099,8102,8104,8107,8109,8112,8114,8117,8119,8122,8124],{"class":3413,"line":3988},[3411,8090,4400],{"class":4143},[3411,8092,8093],{"class":3424}," f",[3411,8095,3849],{"class":3828},[3411,8097,8098],{"class":3424},"{self",[3411,8100,8101],{"class":3432},".name",[3411,8103,3837],{"class":3424},[3411,8105,8106],{"class":3828}," v",[3411,8108,8098],{"class":3424},[3411,8110,8111],{"class":3432},".version",[3411,8113,3837],{"class":3424},[3411,8115,8116],{"class":3828}," by ",[3411,8118,8098],{"class":3424},[3411,8120,8121],{"class":3432},".author",[3411,8123,3837],{"class":3424},[3411,8125,5400],{"class":3828},[3411,8127,8128],{"class":3413,"line":3994},[3411,8129,3475],{"emptyLinePlaceholder":3474},[3411,8131,8132,8134,8137,8139,8142,8145,8147],{"class":3413,"line":4000},[3411,8133,6492],{"class":3424},[3411,8135,8136],{"class":3442}," print_package_info",[3411,8138,3446],{"class":3432},[3411,8140,8141],{"class":3449},"pkg",[3411,8143,8144],{"class":3432},": HasMetadata) -> ",[3411,8146,3514],{"class":3424},[3411,8148,3433],{"class":3432},[3411,8150,8151,8153,8155,8157,8160,8162,8165,8167,8169],{"class":3413,"line":4005},[3411,8152,4585],{"class":3442},[3411,8154,3446],{"class":3432},[3411,8156,3825],{"class":3424},[3411,8158,8159],{"class":3828},"\"Package: ",[3411,8161,3832],{"class":3424},[3411,8163,8164],{"class":3432},"pkg.describe()",[3411,8166,3837],{"class":3424},[3411,8168,3849],{"class":3828},[3411,8170,3852],{"class":3432},[3411,8172,8173],{"class":3413,"line":4011},[3411,8174,3475],{"emptyLinePlaceholder":3474},[3411,8176,8177,8180,8183,8185,8188,8190,8193],{"class":3413,"line":4017},[3411,8178,8179],{"class":3432},"p = PythonPackage(",[3411,8181,8182],{"class":3828},"\"requests\"",[3411,8184,3453],{"class":3432},[3411,8186,8187],{"class":3828},"\"2.31.0\"",[3411,8189,3453],{"class":3432},[3411,8191,8192],{"class":3828},"\"Kenneth Reitz\"",[3411,8194,3852],{"class":3432},[3411,8196,8197,8200],{"class":3413,"line":4022},[3411,8198,8199],{"class":3432},"print_package_info(p)  ",[3411,8201,8202],{"class":3417},"# mypy: ✅ — PythonPackage структурно відповідає HasMetadata\n",[3591,8204,8206],{"id":8205},"наслідування-між-protocol-класами","Наслідування між Protocol-класами",[3394,8208,8209],{},"Протоколи можуть успадковувати один одного, будуючи ієрархію контрактів:",[3401,8211,8213],{"className":3403,"code":8212,"language":3405,"meta":3406,"style":3406},"from typing import Protocol\n\nclass Readable(Protocol):\n    def read(self, n: int = -1) -> bytes: ...\n\nclass Writable(Protocol):\n    def write(self, data: bytes) -> int: ...\n\nclass Closeable(Protocol):\n    def close(self) -> None: ...\n\n# Комбінований протокол через множинне наслідування\nclass ReadWriteCloseable(Readable, Writable, Closeable, Protocol):\n    \"\"\"Описує повнофункціональний потік даних (як файловий об'єкт).\"\"\"\n    ...\n\nimport io\nbuf = io.BytesIO()\n# io.BytesIO реалізує read(), write(), close() — відповідає ReadWriteCloseable\ndef process_stream(stream: ReadWriteCloseable) -> None:\n    stream.write(b\"data\")\n    stream.read()\n    stream.close()\n\nprocess_stream(buf)  # mypy: ✅\n",[3408,8214,8215,8225,8229,8242,8274,8278,8291,8317,8321,8334,8350,8354,8359,8387,8392,8397,8401,8408,8413,8418,8437,8450,8455,8460,8464],{"__ignoreMap":3406},[3411,8216,8217,8219,8221,8223],{"class":3413,"line":3414},[3411,8218,4144],{"class":4143},[3411,8220,6570],{"class":3432},[3411,8222,4150],{"class":4143},[3411,8224,6575],{"class":3432},[3411,8226,8227],{"class":3413,"line":3421},[3411,8228,3475],{"emptyLinePlaceholder":3474},[3411,8230,8231,8233,8236,8238,8240],{"class":3413,"line":3436},[3411,8232,3425],{"class":3424},[3411,8234,8235],{"class":3428}," Readable",[3411,8237,3446],{"class":3432},[3411,8239,3888],{"class":3428},[3411,8241,3459],{"class":3432},[3411,8243,8244,8246,8249,8251,8253,8255,8258,8260,8262,8265,8267,8269,8272],{"class":3413,"line":3462},[3411,8245,3439],{"class":3424},[3411,8247,8248],{"class":3442}," read",[3411,8250,3446],{"class":3432},[3411,8252,3450],{"class":3449},[3411,8254,3453],{"class":3432},[3411,8256,8257],{"class":3449},"n",[3411,8259,3495],{"class":3432},[3411,8261,3498],{"class":3428},[3411,8263,8264],{"class":3432}," = -",[3411,8266,6993],{"class":5495},[3411,8268,3511],{"class":3432},[3411,8270,8271],{"class":3428},"bytes",[3411,8273,4521],{"class":3432},[3411,8275,8276],{"class":3413,"line":3471},[3411,8277,3475],{"emptyLinePlaceholder":3474},[3411,8279,8280,8282,8285,8287,8289],{"class":3413,"line":3478},[3411,8281,3425],{"class":3424},[3411,8283,8284],{"class":3428}," Writable",[3411,8286,3446],{"class":3432},[3411,8288,3888],{"class":3428},[3411,8290,3459],{"class":3432},[3411,8292,8293,8295,8298,8300,8302,8304,8307,8309,8311,8313,8315],{"class":3413,"line":3519},[3411,8294,3439],{"class":3424},[3411,8296,8297],{"class":3442}," write",[3411,8299,3446],{"class":3432},[3411,8301,3450],{"class":3449},[3411,8303,3453],{"class":3432},[3411,8305,8306],{"class":3449},"data",[3411,8308,3495],{"class":3432},[3411,8310,8271],{"class":3428},[3411,8312,3511],{"class":3432},[3411,8314,3498],{"class":3428},[3411,8316,4521],{"class":3432},[3411,8318,8319],{"class":3413,"line":3525},[3411,8320,3475],{"emptyLinePlaceholder":3474},[3411,8322,8323,8325,8328,8330,8332],{"class":3413,"line":3531},[3411,8324,3425],{"class":3424},[3411,8326,8327],{"class":3428}," Closeable",[3411,8329,3446],{"class":3432},[3411,8331,3888],{"class":3428},[3411,8333,3459],{"class":3432},[3411,8335,8336,8338,8340,8342,8344,8346,8348],{"class":3413,"line":3949},[3411,8337,3439],{"class":3424},[3411,8339,4295],{"class":3442},[3411,8341,3446],{"class":3432},[3411,8343,3450],{"class":3449},[3411,8345,3511],{"class":3432},[3411,8347,3514],{"class":3424},[3411,8349,4521],{"class":3432},[3411,8351,8352],{"class":3413,"line":3954},[3411,8353,3475],{"emptyLinePlaceholder":3474},[3411,8355,8356],{"class":3413,"line":3960},[3411,8357,8358],{"class":3417},"# Комбінований протокол через множинне наслідування\n",[3411,8360,8361,8363,8366,8368,8371,8373,8376,8378,8381,8383,8385],{"class":3413,"line":3966},[3411,8362,3425],{"class":3424},[3411,8364,8365],{"class":3428}," ReadWriteCloseable",[3411,8367,3446],{"class":3432},[3411,8369,8370],{"class":3428},"Readable",[3411,8372,3453],{"class":3432},[3411,8374,8375],{"class":3428},"Writable",[3411,8377,3453],{"class":3432},[3411,8379,8380],{"class":3428},"Closeable",[3411,8382,3453],{"class":3432},[3411,8384,3888],{"class":3428},[3411,8386,3459],{"class":3432},[3411,8388,8389],{"class":3413,"line":3972},[3411,8390,8391],{"class":3828},"    \"\"\"Описує повнофункціональний потік даних (як файловий об'єкт).\"\"\"\n",[3411,8393,8394],{"class":3413,"line":3977},[3411,8395,8396],{"class":3432},"    ...\n",[3411,8398,8399],{"class":3413,"line":3982},[3411,8400,3475],{"emptyLinePlaceholder":3474},[3411,8402,8403,8405],{"class":3413,"line":3988},[3411,8404,4150],{"class":4143},[3411,8406,8407],{"class":3432}," io\n",[3411,8409,8410],{"class":3413,"line":3994},[3411,8411,8412],{"class":3432},"buf = io.BytesIO()\n",[3411,8414,8415],{"class":3413,"line":4000},[3411,8416,8417],{"class":3417},"# io.BytesIO реалізує read(), write(), close() — відповідає ReadWriteCloseable\n",[3411,8419,8420,8422,8425,8427,8430,8433,8435],{"class":3413,"line":4005},[3411,8421,6492],{"class":3424},[3411,8423,8424],{"class":3442}," process_stream",[3411,8426,3446],{"class":3432},[3411,8428,8429],{"class":3449},"stream",[3411,8431,8432],{"class":3432},": ReadWriteCloseable) -> ",[3411,8434,3514],{"class":3424},[3411,8436,3433],{"class":3432},[3411,8438,8439,8442,8445,8448],{"class":3413,"line":4011},[3411,8440,8441],{"class":3432},"    stream.write(",[3411,8443,8444],{"class":3424},"b",[3411,8446,8447],{"class":3828},"\"data\"",[3411,8449,3852],{"class":3432},[3411,8451,8452],{"class":3413,"line":4017},[3411,8453,8454],{"class":3432},"    stream.read()\n",[3411,8456,8457],{"class":3413,"line":4022},[3411,8458,8459],{"class":3432},"    stream.close()\n",[3411,8461,8462],{"class":3413,"line":4028},[3411,8463,3475],{"emptyLinePlaceholder":3474},[3411,8465,8466,8469],{"class":3413,"line":4034},[3411,8467,8468],{"class":3432},"process_stream(buf)  ",[3411,8470,8471],{"class":3417},"# mypy: ✅\n",[3584,8473],{},[3389,8475,8477],{"id":8476},"частина-iv-порівняльний-аналіз-abc-проти-protocol","Частина IV: Порівняльний аналіз ABC проти Protocol",[3591,8479,8481],{"id":8480},"детальна-порівняльна-таблиця","Детальна порівняльна таблиця",[8483,8484,8485,8499],"table",{},[8486,8487,8488],"thead",{},[8489,8490,8491,8495,8497],"tr",{},[8492,8493,8494],"th",{},"Критерій",[8492,8496,3738],{},[8492,8498,3888],{},[8500,8501,8502,8516,8529,8542,8560,8573,8588,8601,8616,8629,8642],"tbody",{},[8489,8503,8504,8510,8513],{},[8505,8506,8507],"td",{},[3547,8508,8509],{},"Механізм відповідності",[8505,8511,8512],{},"Номінативний (IS-A)",[8505,8514,8515],{},"Структурний (HAS-A)",[8489,8517,8518,8523,8526],{},[8505,8519,8520],{},[3547,8521,8522],{},"Вимога до наслідування",[8505,8524,8525],{},"Обов'язкова",[8505,8527,8528],{},"Відсутня",[8489,8530,8531,8536,8539],{},[8505,8532,8533],{},[3547,8534,8535],{},"Зв'язування",[8505,8537,8538],{},"Раннє (explicit)",[8505,8540,8541],{},"Пізнє (implicit)",[8489,8543,8544,8551,8554],{},[8505,8545,8546],{},[3547,8547,8548,8549],{},"Перевірка ",[3408,8550,4944],{},[8505,8552,8553],{},"✅ Завжди",[8505,8555,8556,8557,8559],{},"✅ З ",[3408,8558,7379],{}," (лише наявність)",[8489,8561,8562,8567,8570],{},[8505,8563,8564],{},[3547,8565,8566],{},"Статичний аналіз (mypy)",[8505,8568,8569],{},"✅ Повна підтримка",[8505,8571,8572],{},"✅ Повна підтримка + сигнатури",[8489,8574,8575,8580,8585],{},[8505,8576,8577],{},[3547,8578,8579],{},"Сторонні бібліотеки",[8505,8581,8582,8584],{},[3408,8583,5723],{}," (без гарантій)",[8505,8586,8587],{},"✅ Повна підтримка (duck typing)",[8489,8589,8590,8595,8598],{},[8505,8591,8592],{},[3547,8593,8594],{},"Конкретні методи в базі",[8505,8596,8597],{},"✅ Підтримуються",[8505,8599,8600],{},"⚠️ Лише за умов",[8489,8602,8603,8608,8613],{},[8505,8604,8605],{},[3547,8606,8607],{},"Абстрактні властивості",[8505,8609,8610,8611],{},"✅ ",[3408,8612,5022],{},[8505,8614,8615],{},"✅ Атрибути у класі протоколу",[8489,8617,8618,8623,8626],{},[8505,8619,8620],{},[3547,8621,8622],{},"Документація контракту",[8505,8624,8625],{},"Явна, формальна",[8505,8627,8628],{},"Явна через сигнатури",[8489,8630,8631,8636,8639],{},[8505,8632,8633],{},[3547,8634,8635],{},"Гнучкість",[8505,8637,8638],{},"Низька (жорстка ієрархія)",[8505,8640,8641],{},"Висока",[8489,8643,8644,8649,8652],{},[8505,8645,8646],{},[3547,8647,8648],{},"Версія Python",[8505,8650,8651],{},"2.6+",[8505,8653,8654],{},"3.8+",[3591,8656,8658],{"id":8657},"схема-вибору-що-використовувати","Схема вибору: що використовувати",[3894,8660,8661],{},[3401,8662,8664],{"className":3898,"code":8663,"language":3900,"meta":3406,"style":3406},"@startuml\nskinparam style plain\nskinparam backgroundColor #ffffff\nskinparam ArrowColor #6366f1\n\nstart\n\n:Вам потрібно описати інтерфейс\u002Fконтракт;\n\nif (Сторонній код, яким ви\\nне керуєте?) then (Так)\n    :Використовуйте **Protocol**\\n(структурна типізація);\n    stop\nelse (Ні — весь код у вашому контролі)\n    if (Потрібні конкретні методи\\n у базовому класі\\n(шаблонний метод)?) then (Так)\n        :Використовуйте **ABC**\\nз конкретними методами;\n        stop\n    else (Ні)\n        if (Потрібна перевірка\\nruntime isinstance()\\nбез mypy?) then (Так)\n            :Використовуйте **ABC**\\n(надійна runtime-перевірка);\n            stop\n        else (Ні — використовуєте mypy)\n            if (Відкрита система\\n(нові реалізації від\\nзовнішніх розробників)?) then (Так)\n                :Використовуйте **Protocol**\\n(duck typing — найгнучкіший);\n                stop\n            else (Ні — закрита система)\n                :Обидва підходи підходять.\\nАBC — якщо важливо\\nвиявляти помилки раніше;\\nProtocol — для гнучкості;\n                stop\n            endif\n        endif\n    endif\nendif\n\n@enduml\n",[3408,8665,8666,8670,8674,8678,8682,8686,8691,8695,8700,8704,8709,8714,8719,8724,8729,8734,8739,8744,8749,8754,8759,8764,8769,8774,8779,8784,8789,8793,8798,8803,8808,8813,8817],{"__ignoreMap":3406},[3411,8667,8668],{"class":3413,"line":3414},[3411,8669,3907],{},[3411,8671,8672],{"class":3413,"line":3421},[3411,8673,3912],{},[3411,8675,8676],{"class":3413,"line":3436},[3411,8677,3917],{},[3411,8679,8680],{"class":3413,"line":3462},[3411,8681,3922],{},[3411,8683,8684],{"class":3413,"line":3471},[3411,8685,3475],{"emptyLinePlaceholder":3474},[3411,8687,8688],{"class":3413,"line":3478},[3411,8689,8690],{},"start\n",[3411,8692,8693],{"class":3413,"line":3519},[3411,8694,3475],{"emptyLinePlaceholder":3474},[3411,8696,8697],{"class":3413,"line":3525},[3411,8698,8699],{},":Вам потрібно описати інтерфейс\u002Fконтракт;\n",[3411,8701,8702],{"class":3413,"line":3531},[3411,8703,3475],{"emptyLinePlaceholder":3474},[3411,8705,8706],{"class":3413,"line":3949},[3411,8707,8708],{},"if (Сторонній код, яким ви\\nне керуєте?) then (Так)\n",[3411,8710,8711],{"class":3413,"line":3954},[3411,8712,8713],{},"    :Використовуйте **Protocol**\\n(структурна типізація);\n",[3411,8715,8716],{"class":3413,"line":3960},[3411,8717,8718],{},"    stop\n",[3411,8720,8721],{"class":3413,"line":3966},[3411,8722,8723],{},"else (Ні — весь код у вашому контролі)\n",[3411,8725,8726],{"class":3413,"line":3972},[3411,8727,8728],{},"    if (Потрібні конкретні методи\\n у базовому класі\\n(шаблонний метод)?) then (Так)\n",[3411,8730,8731],{"class":3413,"line":3977},[3411,8732,8733],{},"        :Використовуйте **ABC**\\nз конкретними методами;\n",[3411,8735,8736],{"class":3413,"line":3982},[3411,8737,8738],{},"        stop\n",[3411,8740,8741],{"class":3413,"line":3988},[3411,8742,8743],{},"    else (Ні)\n",[3411,8745,8746],{"class":3413,"line":3994},[3411,8747,8748],{},"        if (Потрібна перевірка\\nruntime isinstance()\\nбез mypy?) then (Так)\n",[3411,8750,8751],{"class":3413,"line":4000},[3411,8752,8753],{},"            :Використовуйте **ABC**\\n(надійна runtime-перевірка);\n",[3411,8755,8756],{"class":3413,"line":4005},[3411,8757,8758],{},"            stop\n",[3411,8760,8761],{"class":3413,"line":4011},[3411,8762,8763],{},"        else (Ні — використовуєте mypy)\n",[3411,8765,8766],{"class":3413,"line":4017},[3411,8767,8768],{},"            if (Відкрита система\\n(нові реалізації від\\nзовнішніх розробників)?) then (Так)\n",[3411,8770,8771],{"class":3413,"line":4022},[3411,8772,8773],{},"                :Використовуйте **Protocol**\\n(duck typing — найгнучкіший);\n",[3411,8775,8776],{"class":3413,"line":4028},[3411,8777,8778],{},"                stop\n",[3411,8780,8781],{"class":3413,"line":4034},[3411,8782,8783],{},"            else (Ні — закрита система)\n",[3411,8785,8786],{"class":3413,"line":4039},[3411,8787,8788],{},"                :Обидва підходи підходять.\\nАBC — якщо важливо\\nвиявляти помилки раніше;\\nProtocol — для гнучкості;\n",[3411,8790,8791],{"class":3413,"line":4045},[3411,8792,8778],{},[3411,8794,8795],{"class":3413,"line":4051},[3411,8796,8797],{},"            endif\n",[3411,8799,8800],{"class":3413,"line":4056},[3411,8801,8802],{},"        endif\n",[3411,8804,8805],{"class":3413,"line":4326},[3411,8806,8807],{},"    endif\n",[3411,8809,8810],{"class":3413,"line":4369},[3411,8811,8812],{},"endif\n",[3411,8814,8815],{"class":3413,"line":4374},[3411,8816,3475],{"emptyLinePlaceholder":3474},[3411,8818,8819],{"class":3413,"line":4380},[3411,8820,4059],{},[3584,8822],{},[3389,8824,8826],{"id":8825},"частина-v-глибокий-практичний-приклад","Частина V: Глибокий практичний приклад",[3591,8828,8830],{"id":8829},"система-плагінів-abc-protocol-разом","Система плагінів: ABC + Protocol разом",[3394,8832,8833],{},"Розглянемо реалістичний приклад: система обробки даних, де ми поєднуємо обидва підходи.",[3401,8835,8837],{"className":3403,"code":8836,"language":3405,"meta":3406,"style":3406},"# storage_system.py\nfrom abc import ABC, abstractmethod\nfrom typing import Protocol, runtime_checkable\nimport json\n\n\n# ── Protocol: Серіалізатор (структурна типізація) ─────────────────────────────\n# Будь-який об'єкт з методами dumps\u002Floads відповідає SerializerProtocol.\n# Це дозволяє використовувати json, msgpack, pickle без будь-якого наслідування.\n\n@runtime_checkable\nclass SerializerProtocol(Protocol):\n    \"\"\"Протокол серіалізатора: будь-що, що вміє dumps\u002Floads.\"\"\"\n\n    def dumps(self, data: object) -> bytes: ...\n    def loads(self, raw: bytes) -> object: ...\n\n\n# ── ABC: Базове сховище (номінативна типізація) ───────────────────────────────\n# Всі сховища МАЮ успадковувати від BaseStorage.\n# У нас є спільна логіка (save_with_retry, load_or_default) — тому ABC.\n\nclass BaseStorage(ABC):\n    \"\"\"\n    Абстрактне сховище даних.\n    Реалізує патерн «Шаблонний метод»: спільна обгортка, конкретна I\u002FO-логіка.\n    \"\"\"\n\n    def __init__(self, serializer: SerializerProtocol):\n        # Перевірка через runtime_checkable Protocol\n        if not isinstance(serializer, SerializerProtocol):\n            raise TypeError(\n                f\"serializer повинен реалізувати SerializerProtocol, \"\n                f\"отримано: {type(serializer).__name__}\"\n            )\n        self._serializer = serializer\n\n    @abstractmethod\n    def _write_raw(self, key: str, data: bytes) -> None:\n        \"\"\"Низькорівневий запис. Реалізується у конкретних підкласах.\"\"\"\n        ...\n\n    @abstractmethod\n    def _read_raw(self, key: str) -> bytes | None:\n        \"\"\"Низькорівневий читання. Повертає None якщо ключ відсутній.\"\"\"\n        ...\n\n    @abstractmethod\n    def delete(self, key: str) -> bool:\n        \"\"\"Видаляє запис. Повертає True якщо успішно.\"\"\"\n        ...\n\n    # ── Конкретні методи (шаблонний метод) ──────────────────────────────────\n\n    def save(self, key: str, data: object) -> None:\n        \"\"\"Серіалізує та зберігає об'єкт.\"\"\"\n        raw = self._serializer.dumps(data)\n        self._write_raw(key, raw)\n        print(f\"[Storage] Збережено: '{key}' ({len(raw)} bytes)\")\n\n    def load(self, key: str) -> object | None:\n        \"\"\"Завантажує та десеріалізує об'єкт.\"\"\"\n        raw = self._read_raw(key)\n        if raw is None:\n            return None\n        return self._serializer.loads(raw)\n\n    def load_or_default(self, key: str, default: object) -> object:\n        \"\"\"Завантажує або повертає значення за замовчуванням.\"\"\"\n        result = self.load(key)\n        return result if result is not None else default\n\n    def save_with_retry(self, key: str, data: object, retries: int = 3) -> bool:\n        \"\"\"Зберігає з повторними спробами у разі помилки.\"\"\"\n        for attempt in range(1, retries + 1):\n            try:\n                self.save(key, data)\n                return True\n            except OSError as e:\n                print(f\"[Storage] Спроба {attempt}\u002F{retries} невдала: {e}\")\n        return False\n\n\n# ── Конкретні реалізації сховищ ───────────────────────────────────────────────\n\nclass InMemoryStorage(BaseStorage):\n    \"\"\"Сховище у пам'яті — для тестів та кешування.\"\"\"\n\n    def __init__(self, serializer: SerializerProtocol):\n        super().__init__(serializer)\n        self._store: dict[str, bytes] = {}\n\n    def _write_raw(self, key: str, data: bytes) -> None:\n        self._store[key] = data\n\n    def _read_raw(self, key: str) -> bytes | None:\n        return self._store.get(key)\n\n    def delete(self, key: str) -> bool:\n        if key in self._store:\n            del self._store[key]\n            return True\n        return False\n\n    @property\n    def size(self) -> int:\n        return len(self._store)\n\n\nclass FileSystemStorage(BaseStorage):\n    \"\"\"Сховище у файловій системі.\"\"\"\n\n    def __init__(self, serializer: SerializerProtocol, directory: str = \"\u002Ftmp\"):\n        super().__init__(serializer)\n        self._dir = directory\n\n    def _write_raw(self, key: str, data: bytes) -> None:\n        path = f\"{self._dir}\u002F{key}.bin\"\n        with open(path, \"wb\") as f:\n            f.write(data)\n\n    def _read_raw(self, key: str) -> bytes | None:\n        path = f\"{self._dir}\u002F{key}.bin\"\n        try:\n            with open(path, \"rb\") as f:\n                return f.read()\n        except FileNotFoundError:\n            return None\n\n    def delete(self, key: str) -> bool:\n        import os\n        path = f\"{self._dir}\u002F{key}.bin\"\n        try:\n            os.remove(path)\n            return True\n        except FileNotFoundError:\n            return False\n\n\n# ── Серіалізатори (відповідають Protocol без наслідування) ────────────────────\n\nclass JsonSerializer:\n    \"\"\"JSON-серіалізатор. Відповідає SerializerProtocol структурно.\"\"\"\n\n    def dumps(self, data: object) -> bytes:\n        return json.dumps(data, ensure_ascii=False).encode(\"utf-8\")\n\n    def loads(self, raw: bytes) -> object:\n        return json.loads(raw.decode(\"utf-8\"))\n\n\n# Перевіряємо, що JsonSerializer відповідає протоколу\nserializer = JsonSerializer()\nprint(isinstance(serializer, SerializerProtocol))  # True ✅\n\n\n# ── Використання системи ──────────────────────────────────────────────────────\n\ndef demo_storage_system() -> None:\n    serializer = JsonSerializer()\n\n    # InMemoryStorage для тестів\n    mem_store = InMemoryStorage(serializer)\n    mem_store.save(\"user:1\", {\"name\": \"Олена\", \"age\": 28, \"active\": True})\n    mem_store.save(\"config\", {\"debug\": False, \"version\": \"2.0\"})\n\n    user = mem_store.load(\"user:1\")\n    print(f\"Завантажено: {user}\")\n\n    missing = mem_store.load_or_default(\"user:999\", {\"name\": \"Гість\"})\n    print(f\"Відсутній ключ: {missing}\")\n\n    print(f\"Всього записів: {mem_store.size}\")\n\n    deleted = mem_store.delete(\"config\")\n    print(f\"Видалено 'config': {deleted}\")\n    print(f\"Після видалення: {mem_store.size}\")\n\n    # isinstance перевірка — ABC гарантує коректність ієрархії\n    print(f\"mem_store is BaseStorage: {isinstance(mem_store, BaseStorage)}\")\n\n\nif __name__ == \"__main__\":\n    demo_storage_system()\n",[3408,8838,8839,8844,8854,8864,8871,8875,8879,8884,8889,8894,8898,8902,8915,8920,8924,8950,8976,8980,8984,8989,8994,8999,9003,9012,9016,9021,9026,9030,9034,9052,9057,9071,9081,9089,9111,9116,9123,9127,9131,9165,9170,9174,9178,9182,9212,9217,9221,9225,9229,9254,9259,9263,9267,9272,9276,9309,9314,9324,9331,9366,9370,9399,9405,9415,9431,9440,9450,9455,9490,9496,9507,9531,9536,9585,9591,9616,9624,9633,9641,9654,9695,9703,9708,9713,9719,9724,9739,9745,9750,9767,9781,9798,9803,9836,9844,9849,9878,9888,9893,9918,9933,9944,9951,9958,9963,9970,9988,10003,10008,10013,10027,10033,10038,10070,10081,10089,10094,10127,10155,10179,10185,10190,10219,10244,10252,10271,10279,10290,10297,10302,10327,10336,10361,10368,10374,10381,10390,10397,10402,10407,10413,10418,10428,10434,10439,10464,10487,10492,10517,10530,10535,10540,10546,10552,10567,10572,10577,10583,10588,10603,10609,10614,10620,10626,10667,10696,10701,10711,10734,10739,10759,10782,10787,10810,10815,10825,10848,10870,10875,10881,10906,10911,10916,10932],{"__ignoreMap":3406},[3411,8840,8841],{"class":3413,"line":3414},[3411,8842,8843],{"class":3417},"# storage_system.py\n",[3411,8845,8846,8848,8850,8852],{"class":3413,"line":3421},[3411,8847,4144],{"class":4143},[3411,8849,4147],{"class":3432},[3411,8851,4150],{"class":4143},[3411,8853,4153],{"class":3432},[3411,8855,8856,8858,8860,8862],{"class":3413,"line":3436},[3411,8857,4144],{"class":4143},[3411,8859,6570],{"class":3432},[3411,8861,4150],{"class":4143},[3411,8863,7398],{"class":3432},[3411,8865,8866,8868],{"class":3413,"line":3462},[3411,8867,4150],{"class":4143},[3411,8869,8870],{"class":3432}," json\n",[3411,8872,8873],{"class":3413,"line":3471},[3411,8874,3475],{"emptyLinePlaceholder":3474},[3411,8876,8877],{"class":3413,"line":3478},[3411,8878,3475],{"emptyLinePlaceholder":3474},[3411,8880,8881],{"class":3413,"line":3519},[3411,8882,8883],{"class":3417},"# ── Protocol: Серіалізатор (структурна типізація) ─────────────────────────────\n",[3411,8885,8886],{"class":3413,"line":3525},[3411,8887,8888],{"class":3417},"# Будь-який об'єкт з методами dumps\u002Floads відповідає SerializerProtocol.\n",[3411,8890,8891],{"class":3413,"line":3531},[3411,8892,8893],{"class":3417},"# Це дозволяє використовувати json, msgpack, pickle без будь-якого наслідування.\n",[3411,8895,8896],{"class":3413,"line":3949},[3411,8897,3475],{"emptyLinePlaceholder":3474},[3411,8899,8900],{"class":3413,"line":3954},[3411,8901,7407],{"class":3442},[3411,8903,8904,8906,8909,8911,8913],{"class":3413,"line":3960},[3411,8905,3425],{"class":3424},[3411,8907,8908],{"class":3428}," SerializerProtocol",[3411,8910,3446],{"class":3432},[3411,8912,3888],{"class":3428},[3411,8914,3459],{"class":3432},[3411,8916,8917],{"class":3413,"line":3966},[3411,8918,8919],{"class":3828},"    \"\"\"Протокол серіалізатора: будь-що, що вміє dumps\u002Floads.\"\"\"\n",[3411,8921,8922],{"class":3413,"line":3972},[3411,8923,3475],{"emptyLinePlaceholder":3474},[3411,8925,8926,8928,8931,8933,8935,8937,8939,8941,8944,8946,8948],{"class":3413,"line":3977},[3411,8927,3439],{"class":3424},[3411,8929,8930],{"class":3442}," dumps",[3411,8932,3446],{"class":3432},[3411,8934,3450],{"class":3449},[3411,8936,3453],{"class":3432},[3411,8938,8306],{"class":3449},[3411,8940,3495],{"class":3432},[3411,8942,8943],{"class":3428},"object",[3411,8945,3511],{"class":3432},[3411,8947,8271],{"class":3428},[3411,8949,4521],{"class":3432},[3411,8951,8952,8954,8957,8959,8961,8963,8966,8968,8970,8972,8974],{"class":3413,"line":3982},[3411,8953,3439],{"class":3424},[3411,8955,8956],{"class":3442}," loads",[3411,8958,3446],{"class":3432},[3411,8960,3450],{"class":3449},[3411,8962,3453],{"class":3432},[3411,8964,8965],{"class":3449},"raw",[3411,8967,3495],{"class":3432},[3411,8969,8271],{"class":3428},[3411,8971,3511],{"class":3432},[3411,8973,8943],{"class":3428},[3411,8975,4521],{"class":3432},[3411,8977,8978],{"class":3413,"line":3988},[3411,8979,3475],{"emptyLinePlaceholder":3474},[3411,8981,8982],{"class":3413,"line":3994},[3411,8983,3475],{"emptyLinePlaceholder":3474},[3411,8985,8986],{"class":3413,"line":4000},[3411,8987,8988],{"class":3417},"# ── ABC: Базове сховище (номінативна типізація) ───────────────────────────────\n",[3411,8990,8991],{"class":3413,"line":4005},[3411,8992,8993],{"class":3417},"# Всі сховища МАЮ успадковувати від BaseStorage.\n",[3411,8995,8996],{"class":3413,"line":4011},[3411,8997,8998],{"class":3417},"# У нас є спільна логіка (save_with_retry, load_or_default) — тому ABC.\n",[3411,9000,9001],{"class":3413,"line":4017},[3411,9002,3475],{"emptyLinePlaceholder":3474},[3411,9004,9005,9007,9010],{"class":3413,"line":4022},[3411,9006,3425],{"class":3424},[3411,9008,9009],{"class":3428}," BaseStorage",[3411,9011,4167],{"class":3432},[3411,9013,9014],{"class":3413,"line":4028},[3411,9015,4172],{"class":3828},[3411,9017,9018],{"class":3413,"line":4034},[3411,9019,9020],{"class":3828},"    Абстрактне сховище даних.\n",[3411,9022,9023],{"class":3413,"line":4039},[3411,9024,9025],{"class":3828},"    Реалізує патерн «Шаблонний метод»: спільна обгортка, конкретна I\u002FO-логіка.\n",[3411,9027,9028],{"class":3413,"line":4045},[3411,9029,4172],{"class":3828},[3411,9031,9032],{"class":3413,"line":4051},[3411,9033,3475],{"emptyLinePlaceholder":3474},[3411,9035,9036,9038,9040,9042,9044,9046,9049],{"class":3413,"line":4056},[3411,9037,3439],{"class":3424},[3411,9039,3443],{"class":3442},[3411,9041,3446],{"class":3432},[3411,9043,3450],{"class":3449},[3411,9045,3453],{"class":3432},[3411,9047,9048],{"class":3449},"serializer",[3411,9050,9051],{"class":3432},": SerializerProtocol):\n",[3411,9053,9054],{"class":3413,"line":4326},[3411,9055,9056],{"class":3417},"        # Перевірка через runtime_checkable Protocol\n",[3411,9058,9059,9062,9065,9068],{"class":3413,"line":4369},[3411,9060,9061],{"class":4143},"        if",[3411,9063,9064],{"class":3424}," not",[3411,9066,9067],{"class":3442}," isinstance",[3411,9069,9070],{"class":3432},"(serializer, SerializerProtocol):\n",[3411,9072,9073,9076,9078],{"class":3413,"line":4374},[3411,9074,9075],{"class":4143},"            raise",[3411,9077,4574],{"class":3428},[3411,9079,9080],{"class":3432},"(\n",[3411,9082,9083,9086],{"class":3413,"line":4380},[3411,9084,9085],{"class":3424},"                f",[3411,9087,9088],{"class":3828},"\"serializer повинен реалізувати SerializerProtocol, \"\n",[3411,9090,9091,9093,9096,9098,9101,9104,9107,9109],{"class":3413,"line":4386},[3411,9092,9085],{"class":3424},[3411,9094,9095],{"class":3828},"\"отримано: ",[3411,9097,3832],{"class":3424},[3411,9099,9100],{"class":3428},"type",[3411,9102,9103],{"class":3432},"(serializer).",[3411,9105,9106],{"class":3449},"__name__",[3411,9108,3837],{"class":3424},[3411,9110,5400],{"class":3828},[3411,9112,9113],{"class":3413,"line":4392},[3411,9114,9115],{"class":3432},"            )\n",[3411,9117,9118,9120],{"class":3413,"line":4397},[3411,9119,3465],{"class":3424},[3411,9121,9122],{"class":3432},"._serializer = serializer\n",[3411,9124,9125],{"class":3413,"line":4802},[3411,9126,3475],{"emptyLinePlaceholder":3474},[3411,9128,9129],{"class":3413,"line":4835},[3411,9130,4195],{"class":3442},[3411,9132,9133,9135,9138,9140,9142,9144,9147,9149,9151,9153,9155,9157,9159,9161,9163],{"class":3413,"line":4865},[3411,9134,3439],{"class":3424},[3411,9136,9137],{"class":3442}," _write_raw",[3411,9139,3446],{"class":3432},[3411,9141,3450],{"class":3449},[3411,9143,3453],{"class":3432},[3411,9145,9146],{"class":3449},"key",[3411,9148,3495],{"class":3432},[3411,9150,3508],{"class":3428},[3411,9152,3453],{"class":3432},[3411,9154,8306],{"class":3449},[3411,9156,3495],{"class":3432},[3411,9158,8271],{"class":3428},[3411,9160,3511],{"class":3432},[3411,9162,3514],{"class":3424},[3411,9164,3433],{"class":3432},[3411,9166,9167],{"class":3413,"line":4872},[3411,9168,9169],{"class":3828},"        \"\"\"Низькорівневий запис. Реалізується у конкретних підкласах.\"\"\"\n",[3411,9171,9172],{"class":3413,"line":4877},[3411,9173,4280],{"class":3432},[3411,9175,9176],{"class":3413,"line":4894},[3411,9177,3475],{"emptyLinePlaceholder":3474},[3411,9179,9180],{"class":3413,"line":4904},[3411,9181,4195],{"class":3442},[3411,9183,9184,9186,9189,9191,9193,9195,9197,9199,9201,9203,9205,9208,9210],{"class":3413,"line":4916},[3411,9185,3439],{"class":3424},[3411,9187,9188],{"class":3442}," _read_raw",[3411,9190,3446],{"class":3432},[3411,9192,3450],{"class":3449},[3411,9194,3453],{"class":3432},[3411,9196,9146],{"class":3449},[3411,9198,3495],{"class":3432},[3411,9200,3508],{"class":3428},[3411,9202,3511],{"class":3432},[3411,9204,8271],{"class":3428},[3411,9206,9207],{"class":3432}," | ",[3411,9209,3514],{"class":3424},[3411,9211,3433],{"class":3432},[3411,9213,9214],{"class":3413,"line":4921},[3411,9215,9216],{"class":3828},"        \"\"\"Низькорівневий читання. Повертає None якщо ключ відсутній.\"\"\"\n",[3411,9218,9219],{"class":3413,"line":4936},[3411,9220,4280],{"class":3432},[3411,9222,9223],{"class":3413,"line":5508},[3411,9224,3475],{"emptyLinePlaceholder":3474},[3411,9226,9227],{"class":3413,"line":5513},[3411,9228,4195],{"class":3442},[3411,9230,9231,9233,9236,9238,9240,9242,9244,9246,9248,9250,9252],{"class":3413,"line":5520},[3411,9232,3439],{"class":3424},[3411,9234,9235],{"class":3442}," delete",[3411,9237,3446],{"class":3432},[3411,9239,3450],{"class":3449},[3411,9241,3453],{"class":3432},[3411,9243,9146],{"class":3449},[3411,9245,3495],{"class":3432},[3411,9247,3508],{"class":3428},[3411,9249,3511],{"class":3432},[3411,9251,4226],{"class":3428},[3411,9253,3433],{"class":3432},[3411,9255,9256],{"class":3413,"line":5541},[3411,9257,9258],{"class":3828},"        \"\"\"Видаляє запис. Повертає True якщо успішно.\"\"\"\n",[3411,9260,9261],{"class":3413,"line":5566},[3411,9262,4280],{"class":3432},[3411,9264,9265],{"class":3413,"line":5571},[3411,9266,3475],{"emptyLinePlaceholder":3474},[3411,9268,9269],{"class":3413,"line":5596},[3411,9270,9271],{"class":3417},"    # ── Конкретні методи (шаблонний метод) ──────────────────────────────────\n",[3411,9273,9274],{"class":3413,"line":5618},[3411,9275,3475],{"emptyLinePlaceholder":3474},[3411,9277,9278,9280,9283,9285,9287,9289,9291,9293,9295,9297,9299,9301,9303,9305,9307],{"class":3413,"line":5626},[3411,9279,3439],{"class":3424},[3411,9281,9282],{"class":3442}," save",[3411,9284,3446],{"class":3432},[3411,9286,3450],{"class":3449},[3411,9288,3453],{"class":3432},[3411,9290,9146],{"class":3449},[3411,9292,3495],{"class":3432},[3411,9294,3508],{"class":3428},[3411,9296,3453],{"class":3432},[3411,9298,8306],{"class":3449},[3411,9300,3495],{"class":3432},[3411,9302,8943],{"class":3428},[3411,9304,3511],{"class":3432},[3411,9306,3514],{"class":3424},[3411,9308,3433],{"class":3432},[3411,9310,9311],{"class":3413,"line":5631},[3411,9312,9313],{"class":3828},"        \"\"\"Серіалізує та зберігає об'єкт.\"\"\"\n",[3411,9315,9316,9319,9321],{"class":3413,"line":5636},[3411,9317,9318],{"class":3432},"        raw = ",[3411,9320,3450],{"class":3424},[3411,9322,9323],{"class":3432},"._serializer.dumps(data)\n",[3411,9325,9326,9328],{"class":3413,"line":5642},[3411,9327,3465],{"class":3424},[3411,9329,9330],{"class":3432},"._write_raw(key, raw)\n",[3411,9332,9333,9335,9337,9339,9342,9344,9346,9348,9351,9353,9356,9359,9361,9364],{"class":3413,"line":5670},[3411,9334,3820],{"class":3442},[3411,9336,3446],{"class":3432},[3411,9338,3825],{"class":3424},[3411,9340,9341],{"class":3828},"\"[Storage] Збережено: '",[3411,9343,3832],{"class":3424},[3411,9345,9146],{"class":3432},[3411,9347,3837],{"class":3424},[3411,9349,9350],{"class":3828},"' (",[3411,9352,3832],{"class":3424},[3411,9354,9355],{"class":3442},"len",[3411,9357,9358],{"class":3432},"(raw)",[3411,9360,3837],{"class":3424},[3411,9362,9363],{"class":3828}," bytes)\"",[3411,9365,3852],{"class":3432},[3411,9367,9368],{"class":3413,"line":5693},[3411,9369,3475],{"emptyLinePlaceholder":3474},[3411,9371,9372,9374,9377,9379,9381,9383,9385,9387,9389,9391,9393,9395,9397],{"class":3413,"line":5704},[3411,9373,3439],{"class":3424},[3411,9375,9376],{"class":3442}," load",[3411,9378,3446],{"class":3432},[3411,9380,3450],{"class":3449},[3411,9382,3453],{"class":3432},[3411,9384,9146],{"class":3449},[3411,9386,3495],{"class":3432},[3411,9388,3508],{"class":3428},[3411,9390,3511],{"class":3432},[3411,9392,8943],{"class":3428},[3411,9394,9207],{"class":3432},[3411,9396,3514],{"class":3424},[3411,9398,3433],{"class":3432},[3411,9400,9402],{"class":3413,"line":9401},62,[3411,9403,9404],{"class":3828},"        \"\"\"Завантажує та десеріалізує об'єкт.\"\"\"\n",[3411,9406,9408,9410,9412],{"class":3413,"line":9407},63,[3411,9409,9318],{"class":3432},[3411,9411,3450],{"class":3424},[3411,9413,9414],{"class":3432},"._read_raw(key)\n",[3411,9416,9418,9420,9423,9426,9429],{"class":3413,"line":9417},64,[3411,9419,9061],{"class":4143},[3411,9421,9422],{"class":3432}," raw ",[3411,9424,9425],{"class":3424},"is",[3411,9427,9428],{"class":3424}," None",[3411,9430,3433],{"class":3432},[3411,9432,9434,9437],{"class":3413,"line":9433},65,[3411,9435,9436],{"class":4143},"            return",[3411,9438,9439],{"class":3424}," None\n",[3411,9441,9443,9445,9447],{"class":3413,"line":9442},66,[3411,9444,4400],{"class":4143},[3411,9446,5433],{"class":3424},[3411,9448,9449],{"class":3432},"._serializer.loads(raw)\n",[3411,9451,9453],{"class":3413,"line":9452},67,[3411,9454,3475],{"emptyLinePlaceholder":3474},[3411,9456,9458,9460,9463,9465,9467,9469,9471,9473,9475,9477,9480,9482,9484,9486,9488],{"class":3413,"line":9457},68,[3411,9459,3439],{"class":3424},[3411,9461,9462],{"class":3442}," load_or_default",[3411,9464,3446],{"class":3432},[3411,9466,3450],{"class":3449},[3411,9468,3453],{"class":3432},[3411,9470,9146],{"class":3449},[3411,9472,3495],{"class":3432},[3411,9474,3508],{"class":3428},[3411,9476,3453],{"class":3432},[3411,9478,9479],{"class":3449},"default",[3411,9481,3495],{"class":3432},[3411,9483,8943],{"class":3428},[3411,9485,3511],{"class":3432},[3411,9487,8943],{"class":3428},[3411,9489,3433],{"class":3432},[3411,9491,9493],{"class":3413,"line":9492},69,[3411,9494,9495],{"class":3828},"        \"\"\"Завантажує або повертає значення за замовчуванням.\"\"\"\n",[3411,9497,9499,9502,9504],{"class":3413,"line":9498},70,[3411,9500,9501],{"class":3432},"        result = ",[3411,9503,3450],{"class":3424},[3411,9505,9506],{"class":3432},".load(key)\n",[3411,9508,9510,9512,9515,9517,9519,9521,9523,9525,9528],{"class":3413,"line":9509},71,[3411,9511,4400],{"class":4143},[3411,9513,9514],{"class":3432}," result ",[3411,9516,5645],{"class":4143},[3411,9518,9514],{"class":3432},[3411,9520,9425],{"class":3424},[3411,9522,9064],{"class":3424},[3411,9524,9428],{"class":3424},[3411,9526,9527],{"class":4143}," else",[3411,9529,9530],{"class":3432}," default\n",[3411,9532,9534],{"class":3413,"line":9533},72,[3411,9535,3475],{"emptyLinePlaceholder":3474},[3411,9537,9539,9541,9544,9546,9548,9550,9552,9554,9556,9558,9560,9562,9564,9566,9569,9571,9573,9576,9579,9581,9583],{"class":3413,"line":9538},73,[3411,9540,3439],{"class":3424},[3411,9542,9543],{"class":3442}," save_with_retry",[3411,9545,3446],{"class":3432},[3411,9547,3450],{"class":3449},[3411,9549,3453],{"class":3432},[3411,9551,9146],{"class":3449},[3411,9553,3495],{"class":3432},[3411,9555,3508],{"class":3428},[3411,9557,3453],{"class":3432},[3411,9559,8306],{"class":3449},[3411,9561,3495],{"class":3432},[3411,9563,8943],{"class":3428},[3411,9565,3453],{"class":3432},[3411,9567,9568],{"class":3449},"retries",[3411,9570,3495],{"class":3432},[3411,9572,3498],{"class":3428},[3411,9574,9575],{"class":3432}," = ",[3411,9577,9578],{"class":5495},"3",[3411,9580,3511],{"class":3432},[3411,9582,4226],{"class":3428},[3411,9584,3433],{"class":3432},[3411,9586,9588],{"class":3413,"line":9587},74,[3411,9589,9590],{"class":3828},"        \"\"\"Зберігає з повторними спробами у разі помилки.\"\"\"\n",[3411,9592,9594,9597,9600,9602,9605,9607,9609,9612,9614],{"class":3413,"line":9593},75,[3411,9595,9596],{"class":4143},"        for",[3411,9598,9599],{"class":3432}," attempt ",[3411,9601,4417],{"class":4143},[3411,9603,9604],{"class":3442}," range",[3411,9606,3446],{"class":3432},[3411,9608,6993],{"class":5495},[3411,9610,9611],{"class":3432},", retries + ",[3411,9613,6993],{"class":5495},[3411,9615,3459],{"class":3432},[3411,9617,9619,9622],{"class":3413,"line":9618},76,[3411,9620,9621],{"class":4143},"            try",[3411,9623,3433],{"class":3432},[3411,9625,9627,9630],{"class":3413,"line":9626},77,[3411,9628,9629],{"class":3424},"                self",[3411,9631,9632],{"class":3432},".save(key, data)\n",[3411,9634,9636,9639],{"class":3413,"line":9635},78,[3411,9637,9638],{"class":4143},"                return",[3411,9640,4675],{"class":3424},[3411,9642,9644,9647,9650,9652],{"class":3413,"line":9643},79,[3411,9645,9646],{"class":4143},"            except",[3411,9648,9649],{"class":3428}," OSError",[3411,9651,4577],{"class":4143},[3411,9653,4580],{"class":3432},[3411,9655,9657,9660,9662,9664,9667,9669,9672,9674,9676,9678,9680,9682,9685,9687,9689,9691,9693],{"class":3413,"line":9656},80,[3411,9658,9659],{"class":3442},"                print",[3411,9661,3446],{"class":3432},[3411,9663,3825],{"class":3424},[3411,9665,9666],{"class":3828},"\"[Storage] Спроба ",[3411,9668,3832],{"class":3424},[3411,9670,9671],{"class":3432},"attempt",[3411,9673,3837],{"class":3424},[3411,9675,5391],{"class":3828},[3411,9677,3832],{"class":3424},[3411,9679,9568],{"class":3432},[3411,9681,3837],{"class":3424},[3411,9683,9684],{"class":3828}," невдала: ",[3411,9686,3832],{"class":3424},[3411,9688,4597],{"class":3432},[3411,9690,3837],{"class":3424},[3411,9692,3849],{"class":3828},[3411,9694,3852],{"class":3432},[3411,9696,9698,9700],{"class":3413,"line":9697},81,[3411,9699,4400],{"class":4143},[3411,9701,9702],{"class":3424}," False\n",[3411,9704,9706],{"class":3413,"line":9705},82,[3411,9707,3475],{"emptyLinePlaceholder":3474},[3411,9709,9711],{"class":3413,"line":9710},83,[3411,9712,3475],{"emptyLinePlaceholder":3474},[3411,9714,9716],{"class":3413,"line":9715},84,[3411,9717,9718],{"class":3417},"# ── Конкретні реалізації сховищ ───────────────────────────────────────────────\n",[3411,9720,9722],{"class":3413,"line":9721},85,[3411,9723,3475],{"emptyLinePlaceholder":3474},[3411,9725,9727,9729,9732,9734,9737],{"class":3413,"line":9726},86,[3411,9728,3425],{"class":3424},[3411,9730,9731],{"class":3428}," InMemoryStorage",[3411,9733,3446],{"class":3432},[3411,9735,9736],{"class":3428},"BaseStorage",[3411,9738,3459],{"class":3432},[3411,9740,9742],{"class":3413,"line":9741},87,[3411,9743,9744],{"class":3828},"    \"\"\"Сховище у пам'яті — для тестів та кешування.\"\"\"\n",[3411,9746,9748],{"class":3413,"line":9747},88,[3411,9749,3475],{"emptyLinePlaceholder":3474},[3411,9751,9753,9755,9757,9759,9761,9763,9765],{"class":3413,"line":9752},89,[3411,9754,3439],{"class":3424},[3411,9756,3443],{"class":3442},[3411,9758,3446],{"class":3432},[3411,9760,3450],{"class":3449},[3411,9762,3453],{"class":3432},[3411,9764,9048],{"class":3449},[3411,9766,9051],{"class":3432},[3411,9768,9770,9773,9776,9778],{"class":3413,"line":9769},90,[3411,9771,9772],{"class":3428},"        super",[3411,9774,9775],{"class":3432},"().",[3411,9777,4454],{"class":3442},[3411,9779,9780],{"class":3432},"(serializer)\n",[3411,9782,9784,9786,9789,9791,9793,9795],{"class":3413,"line":9783},91,[3411,9785,3465],{"class":3424},[3411,9787,9788],{"class":3432},"._store: dict[",[3411,9790,3508],{"class":3428},[3411,9792,3453],{"class":3432},[3411,9794,8271],{"class":3428},[3411,9796,9797],{"class":3432},"] = {}\n",[3411,9799,9801],{"class":3413,"line":9800},92,[3411,9802,3475],{"emptyLinePlaceholder":3474},[3411,9804,9806,9808,9810,9812,9814,9816,9818,9820,9822,9824,9826,9828,9830,9832,9834],{"class":3413,"line":9805},93,[3411,9807,3439],{"class":3424},[3411,9809,9137],{"class":3442},[3411,9811,3446],{"class":3432},[3411,9813,3450],{"class":3449},[3411,9815,3453],{"class":3432},[3411,9817,9146],{"class":3449},[3411,9819,3495],{"class":3432},[3411,9821,3508],{"class":3428},[3411,9823,3453],{"class":3432},[3411,9825,8306],{"class":3449},[3411,9827,3495],{"class":3432},[3411,9829,8271],{"class":3428},[3411,9831,3511],{"class":3432},[3411,9833,3514],{"class":3424},[3411,9835,3433],{"class":3432},[3411,9837,9839,9841],{"class":3413,"line":9838},94,[3411,9840,3465],{"class":3424},[3411,9842,9843],{"class":3432},"._store[key] = data\n",[3411,9845,9847],{"class":3413,"line":9846},95,[3411,9848,3475],{"emptyLinePlaceholder":3474},[3411,9850,9852,9854,9856,9858,9860,9862,9864,9866,9868,9870,9872,9874,9876],{"class":3413,"line":9851},96,[3411,9853,3439],{"class":3424},[3411,9855,9188],{"class":3442},[3411,9857,3446],{"class":3432},[3411,9859,3450],{"class":3449},[3411,9861,3453],{"class":3432},[3411,9863,9146],{"class":3449},[3411,9865,3495],{"class":3432},[3411,9867,3508],{"class":3428},[3411,9869,3511],{"class":3432},[3411,9871,8271],{"class":3428},[3411,9873,9207],{"class":3432},[3411,9875,3514],{"class":3424},[3411,9877,3433],{"class":3432},[3411,9879,9881,9883,9885],{"class":3413,"line":9880},97,[3411,9882,4400],{"class":4143},[3411,9884,5433],{"class":3424},[3411,9886,9887],{"class":3432},"._store.get(key)\n",[3411,9889,9891],{"class":3413,"line":9890},98,[3411,9892,3475],{"emptyLinePlaceholder":3474},[3411,9894,9896,9898,9900,9902,9904,9906,9908,9910,9912,9914,9916],{"class":3413,"line":9895},99,[3411,9897,3439],{"class":3424},[3411,9899,9235],{"class":3442},[3411,9901,3446],{"class":3432},[3411,9903,3450],{"class":3449},[3411,9905,3453],{"class":3432},[3411,9907,9146],{"class":3449},[3411,9909,3495],{"class":3432},[3411,9911,3508],{"class":3428},[3411,9913,3511],{"class":3432},[3411,9915,4226],{"class":3428},[3411,9917,3433],{"class":3432},[3411,9919,9921,9923,9926,9928,9930],{"class":3413,"line":9920},100,[3411,9922,9061],{"class":4143},[3411,9924,9925],{"class":3432}," key ",[3411,9927,4417],{"class":3424},[3411,9929,5433],{"class":3424},[3411,9931,9932],{"class":3432},"._store:\n",[3411,9934,9936,9939,9941],{"class":3413,"line":9935},101,[3411,9937,9938],{"class":4143},"            del",[3411,9940,5433],{"class":3424},[3411,9942,9943],{"class":3432},"._store[key]\n",[3411,9945,9947,9949],{"class":3413,"line":9946},102,[3411,9948,9436],{"class":4143},[3411,9950,4675],{"class":3424},[3411,9952,9954,9956],{"class":3413,"line":9953},103,[3411,9955,4400],{"class":4143},[3411,9957,9702],{"class":3424},[3411,9959,9961],{"class":3413,"line":9960},104,[3411,9962,3475],{"emptyLinePlaceholder":3474},[3411,9964,9966,9968],{"class":3413,"line":9965},105,[3411,9967,3697],{"class":3442},[3411,9969,5104],{"class":3428},[3411,9971,9973,9975,9978,9980,9982,9984,9986],{"class":3413,"line":9972},106,[3411,9974,3439],{"class":3424},[3411,9976,9977],{"class":3442}," size",[3411,9979,3446],{"class":3432},[3411,9981,3450],{"class":3449},[3411,9983,3511],{"class":3432},[3411,9985,3498],{"class":3428},[3411,9987,3433],{"class":3432},[3411,9989,9991,9993,9996,9998,10000],{"class":3413,"line":9990},107,[3411,9992,4400],{"class":4143},[3411,9994,9995],{"class":3442}," len",[3411,9997,3446],{"class":3432},[3411,9999,3450],{"class":3424},[3411,10001,10002],{"class":3432},"._store)\n",[3411,10004,10006],{"class":3413,"line":10005},108,[3411,10007,3475],{"emptyLinePlaceholder":3474},[3411,10009,10011],{"class":3413,"line":10010},109,[3411,10012,3475],{"emptyLinePlaceholder":3474},[3411,10014,10016,10018,10021,10023,10025],{"class":3413,"line":10015},110,[3411,10017,3425],{"class":3424},[3411,10019,10020],{"class":3428}," FileSystemStorage",[3411,10022,3446],{"class":3432},[3411,10024,9736],{"class":3428},[3411,10026,3459],{"class":3432},[3411,10028,10030],{"class":3413,"line":10029},111,[3411,10031,10032],{"class":3828},"    \"\"\"Сховище у файловій системі.\"\"\"\n",[3411,10034,10036],{"class":3413,"line":10035},112,[3411,10037,3475],{"emptyLinePlaceholder":3474},[3411,10039,10041,10043,10045,10047,10049,10051,10053,10056,10059,10061,10063,10065,10068],{"class":3413,"line":10040},113,[3411,10042,3439],{"class":3424},[3411,10044,3443],{"class":3442},[3411,10046,3446],{"class":3432},[3411,10048,3450],{"class":3449},[3411,10050,3453],{"class":3432},[3411,10052,9048],{"class":3449},[3411,10054,10055],{"class":3432},": SerializerProtocol, ",[3411,10057,10058],{"class":3449},"directory",[3411,10060,3495],{"class":3432},[3411,10062,3508],{"class":3428},[3411,10064,9575],{"class":3432},[3411,10066,10067],{"class":3828},"\"\u002Ftmp\"",[3411,10069,3459],{"class":3432},[3411,10071,10073,10075,10077,10079],{"class":3413,"line":10072},114,[3411,10074,9772],{"class":3428},[3411,10076,9775],{"class":3432},[3411,10078,4454],{"class":3442},[3411,10080,9780],{"class":3432},[3411,10082,10084,10086],{"class":3413,"line":10083},115,[3411,10085,3465],{"class":3424},[3411,10087,10088],{"class":3432},"._dir = directory\n",[3411,10090,10092],{"class":3413,"line":10091},116,[3411,10093,3475],{"emptyLinePlaceholder":3474},[3411,10095,10097,10099,10101,10103,10105,10107,10109,10111,10113,10115,10117,10119,10121,10123,10125],{"class":3413,"line":10096},117,[3411,10098,3439],{"class":3424},[3411,10100,9137],{"class":3442},[3411,10102,3446],{"class":3432},[3411,10104,3450],{"class":3449},[3411,10106,3453],{"class":3432},[3411,10108,9146],{"class":3449},[3411,10110,3495],{"class":3432},[3411,10112,3508],{"class":3428},[3411,10114,3453],{"class":3432},[3411,10116,8306],{"class":3449},[3411,10118,3495],{"class":3432},[3411,10120,8271],{"class":3428},[3411,10122,3511],{"class":3432},[3411,10124,3514],{"class":3424},[3411,10126,3433],{"class":3432},[3411,10128,10130,10133,10135,10137,10139,10142,10144,10146,10148,10150,10152],{"class":3413,"line":10129},118,[3411,10131,10132],{"class":3432},"        path = ",[3411,10134,3825],{"class":3424},[3411,10136,3849],{"class":3828},[3411,10138,8098],{"class":3424},[3411,10140,10141],{"class":3432},"._dir",[3411,10143,3837],{"class":3424},[3411,10145,5391],{"class":3828},[3411,10147,3832],{"class":3424},[3411,10149,9146],{"class":3432},[3411,10151,3837],{"class":3424},[3411,10153,10154],{"class":3828},".bin\"\n",[3411,10156,10158,10161,10164,10167,10170,10173,10176],{"class":3413,"line":10157},119,[3411,10159,10160],{"class":4143},"        with",[3411,10162,10163],{"class":3442}," open",[3411,10165,10166],{"class":3432},"(path, ",[3411,10168,10169],{"class":3828},"\"wb\"",[3411,10171,10172],{"class":3432},") ",[3411,10174,10175],{"class":4143},"as",[3411,10177,10178],{"class":3432}," f:\n",[3411,10180,10182],{"class":3413,"line":10181},120,[3411,10183,10184],{"class":3432},"            f.write(data)\n",[3411,10186,10188],{"class":3413,"line":10187},121,[3411,10189,3475],{"emptyLinePlaceholder":3474},[3411,10191,10193,10195,10197,10199,10201,10203,10205,10207,10209,10211,10213,10215,10217],{"class":3413,"line":10192},122,[3411,10194,3439],{"class":3424},[3411,10196,9188],{"class":3442},[3411,10198,3446],{"class":3432},[3411,10200,3450],{"class":3449},[3411,10202,3453],{"class":3432},[3411,10204,9146],{"class":3449},[3411,10206,3495],{"class":3432},[3411,10208,3508],{"class":3428},[3411,10210,3511],{"class":3432},[3411,10212,8271],{"class":3428},[3411,10214,9207],{"class":3432},[3411,10216,3514],{"class":3424},[3411,10218,3433],{"class":3432},[3411,10220,10222,10224,10226,10228,10230,10232,10234,10236,10238,10240,10242],{"class":3413,"line":10221},123,[3411,10223,10132],{"class":3432},[3411,10225,3825],{"class":3424},[3411,10227,3849],{"class":3828},[3411,10229,8098],{"class":3424},[3411,10231,10141],{"class":3432},[3411,10233,3837],{"class":3424},[3411,10235,5391],{"class":3828},[3411,10237,3832],{"class":3424},[3411,10239,9146],{"class":3432},[3411,10241,3837],{"class":3424},[3411,10243,10154],{"class":3828},[3411,10245,10247,10250],{"class":3413,"line":10246},124,[3411,10248,10249],{"class":4143},"        try",[3411,10251,3433],{"class":3432},[3411,10253,10255,10258,10260,10262,10265,10267,10269],{"class":3413,"line":10254},125,[3411,10256,10257],{"class":4143},"            with",[3411,10259,10163],{"class":3442},[3411,10261,10166],{"class":3432},[3411,10263,10264],{"class":3828},"\"rb\"",[3411,10266,10172],{"class":3432},[3411,10268,10175],{"class":4143},[3411,10270,10178],{"class":3432},[3411,10272,10274,10276],{"class":3413,"line":10273},126,[3411,10275,9638],{"class":4143},[3411,10277,10278],{"class":3432}," f.read()\n",[3411,10280,10282,10285,10288],{"class":3413,"line":10281},127,[3411,10283,10284],{"class":4143},"        except",[3411,10286,10287],{"class":3428}," FileNotFoundError",[3411,10289,3433],{"class":3432},[3411,10291,10293,10295],{"class":3413,"line":10292},128,[3411,10294,9436],{"class":4143},[3411,10296,9439],{"class":3424},[3411,10298,10300],{"class":3413,"line":10299},129,[3411,10301,3475],{"emptyLinePlaceholder":3474},[3411,10303,10305,10307,10309,10311,10313,10315,10317,10319,10321,10323,10325],{"class":3413,"line":10304},130,[3411,10306,3439],{"class":3424},[3411,10308,9235],{"class":3442},[3411,10310,3446],{"class":3432},[3411,10312,3450],{"class":3449},[3411,10314,3453],{"class":3432},[3411,10316,9146],{"class":3449},[3411,10318,3495],{"class":3432},[3411,10320,3508],{"class":3428},[3411,10322,3511],{"class":3432},[3411,10324,4226],{"class":3428},[3411,10326,3433],{"class":3432},[3411,10328,10330,10333],{"class":3413,"line":10329},131,[3411,10331,10332],{"class":4143},"        import",[3411,10334,10335],{"class":3432}," os\n",[3411,10337,10339,10341,10343,10345,10347,10349,10351,10353,10355,10357,10359],{"class":3413,"line":10338},132,[3411,10340,10132],{"class":3432},[3411,10342,3825],{"class":3424},[3411,10344,3849],{"class":3828},[3411,10346,8098],{"class":3424},[3411,10348,10141],{"class":3432},[3411,10350,3837],{"class":3424},[3411,10352,5391],{"class":3828},[3411,10354,3832],{"class":3424},[3411,10356,9146],{"class":3432},[3411,10358,3837],{"class":3424},[3411,10360,10154],{"class":3828},[3411,10362,10364,10366],{"class":3413,"line":10363},133,[3411,10365,10249],{"class":4143},[3411,10367,3433],{"class":3432},[3411,10369,10371],{"class":3413,"line":10370},134,[3411,10372,10373],{"class":3432},"            os.remove(path)\n",[3411,10375,10377,10379],{"class":3413,"line":10376},135,[3411,10378,9436],{"class":4143},[3411,10380,4675],{"class":3424},[3411,10382,10384,10386,10388],{"class":3413,"line":10383},136,[3411,10385,10284],{"class":4143},[3411,10387,10287],{"class":3428},[3411,10389,3433],{"class":3432},[3411,10391,10393,10395],{"class":3413,"line":10392},137,[3411,10394,9436],{"class":4143},[3411,10396,9702],{"class":3424},[3411,10398,10400],{"class":3413,"line":10399},138,[3411,10401,3475],{"emptyLinePlaceholder":3474},[3411,10403,10405],{"class":3413,"line":10404},139,[3411,10406,3475],{"emptyLinePlaceholder":3474},[3411,10408,10410],{"class":3413,"line":10409},140,[3411,10411,10412],{"class":3417},"# ── Серіалізатори (відповідають Protocol без наслідування) ────────────────────\n",[3411,10414,10416],{"class":3413,"line":10415},141,[3411,10417,3475],{"emptyLinePlaceholder":3474},[3411,10419,10421,10423,10426],{"class":3413,"line":10420},142,[3411,10422,3425],{"class":3424},[3411,10424,10425],{"class":3428}," JsonSerializer",[3411,10427,3433],{"class":3432},[3411,10429,10431],{"class":3413,"line":10430},143,[3411,10432,10433],{"class":3828},"    \"\"\"JSON-серіалізатор. Відповідає SerializerProtocol структурно.\"\"\"\n",[3411,10435,10437],{"class":3413,"line":10436},144,[3411,10438,3475],{"emptyLinePlaceholder":3474},[3411,10440,10442,10444,10446,10448,10450,10452,10454,10456,10458,10460,10462],{"class":3413,"line":10441},145,[3411,10443,3439],{"class":3424},[3411,10445,8930],{"class":3442},[3411,10447,3446],{"class":3432},[3411,10449,3450],{"class":3449},[3411,10451,3453],{"class":3432},[3411,10453,8306],{"class":3449},[3411,10455,3495],{"class":3432},[3411,10457,8943],{"class":3428},[3411,10459,3511],{"class":3432},[3411,10461,8271],{"class":3428},[3411,10463,3433],{"class":3432},[3411,10465,10467,10469,10472,10475,10477,10479,10482,10485],{"class":3413,"line":10466},146,[3411,10468,4400],{"class":4143},[3411,10470,10471],{"class":3432}," json.dumps(data, ",[3411,10473,10474],{"class":3449},"ensure_ascii",[3411,10476,6052],{"class":3432},[3411,10478,7890],{"class":3424},[3411,10480,10481],{"class":3432},").encode(",[3411,10483,10484],{"class":3828},"\"utf-8\"",[3411,10486,3852],{"class":3432},[3411,10488,10490],{"class":3413,"line":10489},147,[3411,10491,3475],{"emptyLinePlaceholder":3474},[3411,10493,10495,10497,10499,10501,10503,10505,10507,10509,10511,10513,10515],{"class":3413,"line":10494},148,[3411,10496,3439],{"class":3424},[3411,10498,8956],{"class":3442},[3411,10500,3446],{"class":3432},[3411,10502,3450],{"class":3449},[3411,10504,3453],{"class":3432},[3411,10506,8965],{"class":3449},[3411,10508,3495],{"class":3432},[3411,10510,8271],{"class":3428},[3411,10512,3511],{"class":3432},[3411,10514,8943],{"class":3428},[3411,10516,3433],{"class":3432},[3411,10518,10520,10522,10525,10527],{"class":3413,"line":10519},149,[3411,10521,4400],{"class":4143},[3411,10523,10524],{"class":3432}," json.loads(raw.decode(",[3411,10526,10484],{"class":3828},[3411,10528,10529],{"class":3432},"))\n",[3411,10531,10533],{"class":3413,"line":10532},150,[3411,10534,3475],{"emptyLinePlaceholder":3474},[3411,10536,10538],{"class":3413,"line":10537},151,[3411,10539,3475],{"emptyLinePlaceholder":3474},[3411,10541,10543],{"class":3413,"line":10542},152,[3411,10544,10545],{"class":3417},"# Перевіряємо, що JsonSerializer відповідає протоколу\n",[3411,10547,10549],{"class":3413,"line":10548},153,[3411,10550,10551],{"class":3432},"serializer = JsonSerializer()\n",[3411,10553,10555,10557,10559,10561,10564],{"class":3413,"line":10554},154,[3411,10556,4939],{"class":3442},[3411,10558,3446],{"class":3432},[3411,10560,4944],{"class":3442},[3411,10562,10563],{"class":3432},"(serializer, SerializerProtocol))  ",[3411,10565,10566],{"class":3417},"# True ✅\n",[3411,10568,10570],{"class":3413,"line":10569},155,[3411,10571,3475],{"emptyLinePlaceholder":3474},[3411,10573,10575],{"class":3413,"line":10574},156,[3411,10576,3475],{"emptyLinePlaceholder":3474},[3411,10578,10580],{"class":3413,"line":10579},157,[3411,10581,10582],{"class":3417},"# ── Використання системи ──────────────────────────────────────────────────────\n",[3411,10584,10586],{"class":3413,"line":10585},158,[3411,10587,3475],{"emptyLinePlaceholder":3474},[3411,10589,10591,10593,10596,10599,10601],{"class":3413,"line":10590},159,[3411,10592,6492],{"class":3424},[3411,10594,10595],{"class":3442}," demo_storage_system",[3411,10597,10598],{"class":3432},"() -> ",[3411,10600,3514],{"class":3424},[3411,10602,3433],{"class":3432},[3411,10604,10606],{"class":3413,"line":10605},160,[3411,10607,10608],{"class":3432},"    serializer = JsonSerializer()\n",[3411,10610,10612],{"class":3413,"line":10611},161,[3411,10613,3475],{"emptyLinePlaceholder":3474},[3411,10615,10617],{"class":3413,"line":10616},162,[3411,10618,10619],{"class":3417},"    # InMemoryStorage для тестів\n",[3411,10621,10623],{"class":3413,"line":10622},163,[3411,10624,10625],{"class":3432},"    mem_store = InMemoryStorage(serializer)\n",[3411,10627,10629,10632,10635,10638,10641,10643,10646,10648,10651,10653,10656,10658,10661,10663,10665],{"class":3413,"line":10628},164,[3411,10630,10631],{"class":3432},"    mem_store.save(",[3411,10633,10634],{"class":3828},"\"user:1\"",[3411,10636,10637],{"class":3432},", {",[3411,10639,10640],{"class":3828},"\"name\"",[3411,10642,3495],{"class":3432},[3411,10644,10645],{"class":3828},"\"Олена\"",[3411,10647,3453],{"class":3432},[3411,10649,10650],{"class":3828},"\"age\"",[3411,10652,3495],{"class":3432},[3411,10654,10655],{"class":5495},"28",[3411,10657,3453],{"class":3432},[3411,10659,10660],{"class":3828},"\"active\"",[3411,10662,3495],{"class":3432},[3411,10664,4998],{"class":3424},[3411,10666,5690],{"class":3432},[3411,10668,10670,10672,10675,10677,10680,10682,10684,10686,10689,10691,10694],{"class":3413,"line":10669},165,[3411,10671,10631],{"class":3432},[3411,10673,10674],{"class":3828},"\"config\"",[3411,10676,10637],{"class":3432},[3411,10678,10679],{"class":3828},"\"debug\"",[3411,10681,3495],{"class":3432},[3411,10683,7890],{"class":3424},[3411,10685,3453],{"class":3432},[3411,10687,10688],{"class":3828},"\"version\"",[3411,10690,3495],{"class":3432},[3411,10692,10693],{"class":3828},"\"2.0\"",[3411,10695,5690],{"class":3432},[3411,10697,10699],{"class":3413,"line":10698},166,[3411,10700,3475],{"emptyLinePlaceholder":3474},[3411,10702,10704,10707,10709],{"class":3413,"line":10703},167,[3411,10705,10706],{"class":3432},"    user = mem_store.load(",[3411,10708,10634],{"class":3828},[3411,10710,3852],{"class":3432},[3411,10712,10714,10716,10718,10720,10723,10725,10728,10730,10732],{"class":3413,"line":10713},168,[3411,10715,4585],{"class":3442},[3411,10717,3446],{"class":3432},[3411,10719,3825],{"class":3424},[3411,10721,10722],{"class":3828},"\"Завантажено: ",[3411,10724,3832],{"class":3424},[3411,10726,10727],{"class":3432},"user",[3411,10729,3837],{"class":3424},[3411,10731,3849],{"class":3828},[3411,10733,3852],{"class":3432},[3411,10735,10737],{"class":3413,"line":10736},169,[3411,10738,3475],{"emptyLinePlaceholder":3474},[3411,10740,10742,10745,10748,10750,10752,10754,10757],{"class":3413,"line":10741},170,[3411,10743,10744],{"class":3432},"    missing = mem_store.load_or_default(",[3411,10746,10747],{"class":3828},"\"user:999\"",[3411,10749,10637],{"class":3432},[3411,10751,10640],{"class":3828},[3411,10753,3495],{"class":3432},[3411,10755,10756],{"class":3828},"\"Гість\"",[3411,10758,5690],{"class":3432},[3411,10760,10762,10764,10766,10768,10771,10773,10776,10778,10780],{"class":3413,"line":10761},171,[3411,10763,4585],{"class":3442},[3411,10765,3446],{"class":3432},[3411,10767,3825],{"class":3424},[3411,10769,10770],{"class":3828},"\"Відсутній ключ: ",[3411,10772,3832],{"class":3424},[3411,10774,10775],{"class":3432},"missing",[3411,10777,3837],{"class":3424},[3411,10779,3849],{"class":3828},[3411,10781,3852],{"class":3432},[3411,10783,10785],{"class":3413,"line":10784},172,[3411,10786,3475],{"emptyLinePlaceholder":3474},[3411,10788,10790,10792,10794,10796,10799,10801,10804,10806,10808],{"class":3413,"line":10789},173,[3411,10791,4585],{"class":3442},[3411,10793,3446],{"class":3432},[3411,10795,3825],{"class":3424},[3411,10797,10798],{"class":3828},"\"Всього записів: ",[3411,10800,3832],{"class":3424},[3411,10802,10803],{"class":3432},"mem_store.size",[3411,10805,3837],{"class":3424},[3411,10807,3849],{"class":3828},[3411,10809,3852],{"class":3432},[3411,10811,10813],{"class":3413,"line":10812},174,[3411,10814,3475],{"emptyLinePlaceholder":3474},[3411,10816,10818,10821,10823],{"class":3413,"line":10817},175,[3411,10819,10820],{"class":3432},"    deleted = mem_store.delete(",[3411,10822,10674],{"class":3828},[3411,10824,3852],{"class":3432},[3411,10826,10828,10830,10832,10834,10837,10839,10842,10844,10846],{"class":3413,"line":10827},176,[3411,10829,4585],{"class":3442},[3411,10831,3446],{"class":3432},[3411,10833,3825],{"class":3424},[3411,10835,10836],{"class":3828},"\"Видалено 'config': ",[3411,10838,3832],{"class":3424},[3411,10840,10841],{"class":3432},"deleted",[3411,10843,3837],{"class":3424},[3411,10845,3849],{"class":3828},[3411,10847,3852],{"class":3432},[3411,10849,10851,10853,10855,10857,10860,10862,10864,10866,10868],{"class":3413,"line":10850},177,[3411,10852,4585],{"class":3442},[3411,10854,3446],{"class":3432},[3411,10856,3825],{"class":3424},[3411,10858,10859],{"class":3828},"\"Після видалення: ",[3411,10861,3832],{"class":3424},[3411,10863,10803],{"class":3432},[3411,10865,3837],{"class":3424},[3411,10867,3849],{"class":3828},[3411,10869,3852],{"class":3432},[3411,10871,10873],{"class":3413,"line":10872},178,[3411,10874,3475],{"emptyLinePlaceholder":3474},[3411,10876,10878],{"class":3413,"line":10877},179,[3411,10879,10880],{"class":3417},"    # isinstance перевірка — ABC гарантує коректність ієрархії\n",[3411,10882,10884,10886,10888,10890,10893,10895,10897,10900,10902,10904],{"class":3413,"line":10883},180,[3411,10885,4585],{"class":3442},[3411,10887,3446],{"class":3432},[3411,10889,3825],{"class":3424},[3411,10891,10892],{"class":3828},"\"mem_store is BaseStorage: ",[3411,10894,3832],{"class":3424},[3411,10896,4944],{"class":3442},[3411,10898,10899],{"class":3432},"(mem_store, BaseStorage)",[3411,10901,3837],{"class":3424},[3411,10903,3849],{"class":3828},[3411,10905,3852],{"class":3432},[3411,10907,10909],{"class":3413,"line":10908},181,[3411,10910,3475],{"emptyLinePlaceholder":3474},[3411,10912,10914],{"class":3413,"line":10913},182,[3411,10915,3475],{"emptyLinePlaceholder":3474},[3411,10917,10919,10921,10924,10927,10930],{"class":3413,"line":10918},183,[3411,10920,5645],{"class":4143},[3411,10922,10923],{"class":3449}," __name__",[3411,10925,10926],{"class":3432}," == ",[3411,10928,10929],{"class":3828},"\"__main__\"",[3411,10931,3433],{"class":3432},[3411,10933,10935],{"class":3413,"line":10934},184,[3411,10936,10937],{"class":3432},"    demo_storage_system()\n",[4952,10939,10941,10949,10956,10970,10981,10989,10997,11004,11011,11018],{"title":10940},"python storage_system.py",[4956,10942,10944,4964,10947],{"className":10943},[3413],[3411,10945,4963],{"className":10946},[4962],[3547,10948,10940],{},[4956,10950,10952,10953],{"className":10951},[3413],"isinstance(serializer, SerializerProtocol): ",[3411,10954,4998],{"className":10955},[4990],[4956,10957,10959,10960,10964,10965,10969],{"className":10958},[3413],"[Storage] Збережено: ",[3411,10961,10963],{"className":10962},[6424],"'user:1'"," (",[3411,10966,10968],{"className":10967},[6433],"46"," bytes)",[4956,10971,10959,10973,10964,10977,10969],{"className":10972},[3413],[3411,10974,10976],{"className":10975},[6424],"'config'",[3411,10978,10980],{"className":10979},[6433],"32",[4956,10982,10984,10985],{"className":10983},[3413],"Завантажено: ",[3411,10986,10988],{"className":10987},[4990],"{'name': 'Олена', 'age': 28, 'active': True}",[4956,10990,10992,10993],{"className":10991},[3413],"Відсутній ключ: ",[3411,10994,10996],{"className":10995},[7299],"{'name': 'Гість'}",[4956,10998,11000,11001],{"className":10999},[3413],"Всього записів: ",[3411,11002,7010],{"className":11003},[6433],[4956,11005,11007,11008],{"className":11006},[3413],"Видалено 'config': ",[3411,11009,4998],{"className":11010},[4990],[4956,11012,11014,11015],{"className":11013},[3413],"Після видалення: ",[3411,11016,6993],{"className":11017},[6433],[4956,11019,11021,11022],{"className":11020},[3413],"mem_store is BaseStorage: ",[3411,11023,4998],{"className":11024},[4990],[3584,11026],{},[3389,11028,11030],{"id":11029},"частина-vi-практичні-завдання","Частина VI: Практичні завдання",[3591,11032,11034],{"id":11033},"рівень-1-базовий-абстрактна-геометрія","Рівень 1 (Базовий): Абстрактна геометрія",[3394,11036,11037],{},"Створіть ієрархію геометричних фігур з використанням ABC.",[3394,11039,11040,11043,11044,11046,11047,3578,11050,11053,11054,11057,11058,3453,11061,3578,11063,3617],{},[3547,11041,11042],{},"Завдання:"," Визначте абстрактний клас ",[3408,11045,6233],{}," з абстрактними методами ",[3408,11048,11049],{},"area() -> float",[3408,11051,11052],{},"perimeter() -> float",", а також конкретним методом ",[3408,11055,11056],{},"describe() -> str",". Реалізуйте класи ",[3408,11059,11060],{},"Rectangle",[3408,11062,6340],{},[3408,11064,11065],{},"Triangle",[3401,11067,11069],{"className":3403,"code":11068,"language":3405,"meta":3406,"style":3406},"from abc import ABC, abstractmethod\nimport math\n\nclass Shape(ABC):\n    \"\"\"Абстрактна геометрична фігура.\"\"\"\n\n    @property\n    @abstractmethod\n    def area(self) -> float:\n        \"\"\"Площа фігури.\"\"\"\n        ...\n\n    @property\n    @abstractmethod\n    def perimeter(self) -> float:\n        \"\"\"Периметр фігури.\"\"\"\n        ...\n\n    def describe(self) -> str:\n        \"\"\"Конкретний метод: текстовий опис фігури.\"\"\"\n        return (\n            f\"{self.__class__.__name__}: \"\n            f\"площа={self.area:.2f}, периметр={self.perimeter:.2f}\"\n        )\n\n\nclass Rectangle(Shape):\n    def __init__(self, width: float, height: float):\n        self.width = width\n        self.height = height\n\n    @property\n    def area(self) -> float:\n        return self.width * self.height\n\n    @property\n    def perimeter(self) -> float:\n        return 2 * (self.width + self.height)\n\n\nclass Circle(Shape):\n    def __init__(self, radius: float):\n        self.radius = radius\n\n    @property\n    def area(self) -> float:\n        return math.pi * self.radius ** 2\n\n    @property\n    def perimeter(self) -> float:\n        return 2 * math.pi * self.radius\n\n\nclass Triangle(Shape):\n    def __init__(self, a: float, b: float, c: float):\n        if a + b \u003C= c or a + c \u003C= b or b + c \u003C= a:\n            raise ValueError(\"Недопустимі сторони трикутника\")\n        self.a, self.b, self.c = a, b, c\n\n    @property\n    def area(self) -> float:\n        s = self.perimeter \u002F 2\n        return math.sqrt(s * (s - self.a) * (s - self.b) * (s - self.c))\n\n    @property\n    def perimeter(self) -> float:\n        return self.a + self.b + self.c\n\n\n# Тест\nshapes: list[Shape] = [\n    Rectangle(4, 6),\n    Circle(5),\n    Triangle(3, 4, 5),\n]\n\nfor shape in shapes:\n    print(shape.describe())\n    print(f\"  isinstance(shape, Shape): {isinstance(shape, Shape)}\")\n",[3408,11070,11071,11081,11088,11092,11100,11105,11109,11115,11119,11135,11140,11144,11148,11154,11158,11174,11179,11183,11187,11203,11208,11215,11238,11265,11270,11274,11278,11291,11321,11328,11335,11339,11345,11361,11375,11379,11385,11401,11420,11424,11428,11440,11461,11468,11472,11478,11494,11508,11512,11518,11534,11548,11552,11556,11569,11607,11625,11639,11656,11660,11666,11682,11694,11716,11720,11726,11742,11761,11765,11769,11774,11779,11795,11805,11822,11827,11831,11843,11850],{"__ignoreMap":3406},[3411,11072,11073,11075,11077,11079],{"class":3413,"line":3414},[3411,11074,4144],{"class":4143},[3411,11076,4147],{"class":3432},[3411,11078,4150],{"class":4143},[3411,11080,4153],{"class":3432},[3411,11082,11083,11085],{"class":3413,"line":3421},[3411,11084,4150],{"class":4143},[3411,11086,11087],{"class":3432}," math\n",[3411,11089,11090],{"class":3413,"line":3436},[3411,11091,3475],{"emptyLinePlaceholder":3474},[3411,11093,11094,11096,11098],{"class":3413,"line":3462},[3411,11095,3425],{"class":3424},[3411,11097,6149],{"class":3428},[3411,11099,4167],{"class":3432},[3411,11101,11102],{"class":3413,"line":3471},[3411,11103,11104],{"class":3828},"    \"\"\"Абстрактна геометрична фігура.\"\"\"\n",[3411,11106,11107],{"class":3413,"line":3478},[3411,11108,3475],{"emptyLinePlaceholder":3474},[3411,11110,11111,11113],{"class":3413,"line":3519},[3411,11112,3697],{"class":3442},[3411,11114,5104],{"class":3428},[3411,11116,11117],{"class":3413,"line":3525},[3411,11118,4195],{"class":3442},[3411,11120,11121,11123,11125,11127,11129,11131,11133],{"class":3413,"line":3531},[3411,11122,3439],{"class":3424},[3411,11124,6162],{"class":3442},[3411,11126,3446],{"class":3432},[3411,11128,3450],{"class":3449},[3411,11130,3511],{"class":3432},[3411,11132,6171],{"class":3428},[3411,11134,3433],{"class":3432},[3411,11136,11137],{"class":3413,"line":3949},[3411,11138,11139],{"class":3828},"        \"\"\"Площа фігури.\"\"\"\n",[3411,11141,11142],{"class":3413,"line":3954},[3411,11143,4280],{"class":3432},[3411,11145,11146],{"class":3413,"line":3960},[3411,11147,3475],{"emptyLinePlaceholder":3474},[3411,11149,11150,11152],{"class":3413,"line":3966},[3411,11151,3697],{"class":3442},[3411,11153,5104],{"class":3428},[3411,11155,11156],{"class":3413,"line":3972},[3411,11157,4195],{"class":3442},[3411,11159,11160,11162,11164,11166,11168,11170,11172],{"class":3413,"line":3977},[3411,11161,3439],{"class":3424},[3411,11163,6188],{"class":3442},[3411,11165,3446],{"class":3432},[3411,11167,3450],{"class":3449},[3411,11169,3511],{"class":3432},[3411,11171,6171],{"class":3428},[3411,11173,3433],{"class":3432},[3411,11175,11176],{"class":3413,"line":3982},[3411,11177,11178],{"class":3828},"        \"\"\"Периметр фігури.\"\"\"\n",[3411,11180,11181],{"class":3413,"line":3988},[3411,11182,4280],{"class":3432},[3411,11184,11185],{"class":3413,"line":3994},[3411,11186,3475],{"emptyLinePlaceholder":3474},[3411,11188,11189,11191,11193,11195,11197,11199,11201],{"class":3413,"line":4000},[3411,11190,3439],{"class":3424},[3411,11192,7983],{"class":3442},[3411,11194,3446],{"class":3432},[3411,11196,3450],{"class":3449},[3411,11198,3511],{"class":3432},[3411,11200,3508],{"class":3428},[3411,11202,3433],{"class":3432},[3411,11204,11205],{"class":3413,"line":4005},[3411,11206,11207],{"class":3828},"        \"\"\"Конкретний метод: текстовий опис фігури.\"\"\"\n",[3411,11209,11210,11212],{"class":3413,"line":4011},[3411,11211,4400],{"class":4143},[3411,11213,11214],{"class":3432}," (\n",[3411,11216,11217,11220,11222,11224,11226,11229,11231,11233,11235],{"class":3413,"line":4017},[3411,11218,11219],{"class":3424},"            f",[3411,11221,3849],{"class":3828},[3411,11223,8098],{"class":3424},[3411,11225,3617],{"class":3432},[3411,11227,11228],{"class":3449},"__class__",[3411,11230,3617],{"class":3432},[3411,11232,9106],{"class":3449},[3411,11234,3837],{"class":3424},[3411,11236,11237],{"class":3828},": \"\n",[3411,11239,11240,11242,11245,11247,11250,11253,11256,11258,11261,11263],{"class":3413,"line":4022},[3411,11241,11219],{"class":3424},[3411,11243,11244],{"class":3828},"\"площа=",[3411,11246,8098],{"class":3424},[3411,11248,11249],{"class":3432},".area",[3411,11251,11252],{"class":3424},":.2f}",[3411,11254,11255],{"class":3828},", периметр=",[3411,11257,8098],{"class":3424},[3411,11259,11260],{"class":3432},".perimeter",[3411,11262,11252],{"class":3424},[3411,11264,5400],{"class":3828},[3411,11266,11267],{"class":3413,"line":4028},[3411,11268,11269],{"class":3432},"        )\n",[3411,11271,11272],{"class":3413,"line":4034},[3411,11273,3475],{"emptyLinePlaceholder":3474},[3411,11275,11276],{"class":3413,"line":4039},[3411,11277,3475],{"emptyLinePlaceholder":3474},[3411,11279,11280,11282,11285,11287,11289],{"class":3413,"line":4045},[3411,11281,3425],{"class":3424},[3411,11283,11284],{"class":3428}," Rectangle",[3411,11286,3446],{"class":3432},[3411,11288,6233],{"class":3428},[3411,11290,3459],{"class":3432},[3411,11292,11293,11295,11297,11299,11301,11303,11306,11308,11310,11312,11315,11317,11319],{"class":3413,"line":4051},[3411,11294,3439],{"class":3424},[3411,11296,3443],{"class":3442},[3411,11298,3446],{"class":3432},[3411,11300,3450],{"class":3449},[3411,11302,3453],{"class":3432},[3411,11304,11305],{"class":3449},"width",[3411,11307,3495],{"class":3432},[3411,11309,6171],{"class":3428},[3411,11311,3453],{"class":3432},[3411,11313,11314],{"class":3449},"height",[3411,11316,3495],{"class":3432},[3411,11318,6171],{"class":3428},[3411,11320,3459],{"class":3432},[3411,11322,11323,11325],{"class":3413,"line":4056},[3411,11324,3465],{"class":3424},[3411,11326,11327],{"class":3432},".width = width\n",[3411,11329,11330,11332],{"class":3413,"line":4326},[3411,11331,3465],{"class":3424},[3411,11333,11334],{"class":3432},".height = height\n",[3411,11336,11337],{"class":3413,"line":4369},[3411,11338,3475],{"emptyLinePlaceholder":3474},[3411,11340,11341,11343],{"class":3413,"line":4374},[3411,11342,3697],{"class":3442},[3411,11344,5104],{"class":3428},[3411,11346,11347,11349,11351,11353,11355,11357,11359],{"class":3413,"line":4380},[3411,11348,3439],{"class":3424},[3411,11350,6162],{"class":3442},[3411,11352,3446],{"class":3432},[3411,11354,3450],{"class":3449},[3411,11356,3511],{"class":3432},[3411,11358,6171],{"class":3428},[3411,11360,3433],{"class":3432},[3411,11362,11363,11365,11367,11370,11372],{"class":3413,"line":4386},[3411,11364,4400],{"class":4143},[3411,11366,5433],{"class":3424},[3411,11368,11369],{"class":3432},".width * ",[3411,11371,3450],{"class":3424},[3411,11373,11374],{"class":3432},".height\n",[3411,11376,11377],{"class":3413,"line":4392},[3411,11378,3475],{"emptyLinePlaceholder":3474},[3411,11380,11381,11383],{"class":3413,"line":4397},[3411,11382,3697],{"class":3442},[3411,11384,5104],{"class":3428},[3411,11386,11387,11389,11391,11393,11395,11397,11399],{"class":3413,"line":4802},[3411,11388,3439],{"class":3424},[3411,11390,6188],{"class":3442},[3411,11392,3446],{"class":3432},[3411,11394,3450],{"class":3449},[3411,11396,3511],{"class":3432},[3411,11398,6171],{"class":3428},[3411,11400,3433],{"class":3432},[3411,11402,11403,11405,11407,11410,11412,11415,11417],{"class":3413,"line":4835},[3411,11404,4400],{"class":4143},[3411,11406,6365],{"class":5495},[3411,11408,11409],{"class":3432}," * (",[3411,11411,3450],{"class":3424},[3411,11413,11414],{"class":3432},".width + ",[3411,11416,3450],{"class":3424},[3411,11418,11419],{"class":3432},".height)\n",[3411,11421,11422],{"class":3413,"line":4865},[3411,11423,3475],{"emptyLinePlaceholder":3474},[3411,11425,11426],{"class":3413,"line":4872},[3411,11427,3475],{"emptyLinePlaceholder":3474},[3411,11429,11430,11432,11434,11436,11438],{"class":3413,"line":4877},[3411,11431,3425],{"class":3424},[3411,11433,6228],{"class":3428},[3411,11435,3446],{"class":3432},[3411,11437,6233],{"class":3428},[3411,11439,3459],{"class":3432},[3411,11441,11442,11444,11446,11448,11450,11452,11455,11457,11459],{"class":3413,"line":4894},[3411,11443,3439],{"class":3424},[3411,11445,3443],{"class":3442},[3411,11447,3446],{"class":3432},[3411,11449,3450],{"class":3449},[3411,11451,3453],{"class":3432},[3411,11453,11454],{"class":3449},"radius",[3411,11456,3495],{"class":3432},[3411,11458,6171],{"class":3428},[3411,11460,3459],{"class":3432},[3411,11462,11463,11465],{"class":3413,"line":4904},[3411,11464,3465],{"class":3424},[3411,11466,11467],{"class":3432},".radius = radius\n",[3411,11469,11470],{"class":3413,"line":4916},[3411,11471,3475],{"emptyLinePlaceholder":3474},[3411,11473,11474,11476],{"class":3413,"line":4921},[3411,11475,3697],{"class":3442},[3411,11477,5104],{"class":3428},[3411,11479,11480,11482,11484,11486,11488,11490,11492],{"class":3413,"line":4936},[3411,11481,3439],{"class":3424},[3411,11483,6162],{"class":3442},[3411,11485,3446],{"class":3432},[3411,11487,3450],{"class":3449},[3411,11489,3511],{"class":3432},[3411,11491,6171],{"class":3428},[3411,11493,3433],{"class":3432},[3411,11495,11496,11498,11501,11503,11506],{"class":3413,"line":5508},[3411,11497,4400],{"class":4143},[3411,11499,11500],{"class":3432}," math.pi * ",[3411,11502,3450],{"class":3424},[3411,11504,11505],{"class":3432},".radius ** ",[3411,11507,6301],{"class":5495},[3411,11509,11510],{"class":3413,"line":5513},[3411,11511,3475],{"emptyLinePlaceholder":3474},[3411,11513,11514,11516],{"class":3413,"line":5520},[3411,11515,3697],{"class":3442},[3411,11517,5104],{"class":3428},[3411,11519,11520,11522,11524,11526,11528,11530,11532],{"class":3413,"line":5541},[3411,11521,3439],{"class":3424},[3411,11523,6188],{"class":3442},[3411,11525,3446],{"class":3432},[3411,11527,3450],{"class":3449},[3411,11529,3511],{"class":3432},[3411,11531,6171],{"class":3428},[3411,11533,3433],{"class":3432},[3411,11535,11536,11538,11540,11543,11545],{"class":3413,"line":5566},[3411,11537,4400],{"class":4143},[3411,11539,6365],{"class":5495},[3411,11541,11542],{"class":3432}," * math.pi * ",[3411,11544,3450],{"class":3424},[3411,11546,11547],{"class":3432},".radius\n",[3411,11549,11550],{"class":3413,"line":5571},[3411,11551,3475],{"emptyLinePlaceholder":3474},[3411,11553,11554],{"class":3413,"line":5596},[3411,11555,3475],{"emptyLinePlaceholder":3474},[3411,11557,11558,11560,11563,11565,11567],{"class":3413,"line":5618},[3411,11559,3425],{"class":3424},[3411,11561,11562],{"class":3428}," Triangle",[3411,11564,3446],{"class":3432},[3411,11566,6233],{"class":3428},[3411,11568,3459],{"class":3432},[3411,11570,11571,11573,11575,11577,11579,11581,11584,11586,11588,11590,11592,11594,11596,11598,11601,11603,11605],{"class":3413,"line":5626},[3411,11572,3439],{"class":3424},[3411,11574,3443],{"class":3442},[3411,11576,3446],{"class":3432},[3411,11578,3450],{"class":3449},[3411,11580,3453],{"class":3432},[3411,11582,11583],{"class":3449},"a",[3411,11585,3495],{"class":3432},[3411,11587,6171],{"class":3428},[3411,11589,3453],{"class":3432},[3411,11591,8444],{"class":3449},[3411,11593,3495],{"class":3432},[3411,11595,6171],{"class":3428},[3411,11597,3453],{"class":3432},[3411,11599,11600],{"class":3449},"c",[3411,11602,3495],{"class":3432},[3411,11604,6171],{"class":3428},[3411,11606,3459],{"class":3432},[3411,11608,11609,11611,11614,11617,11620,11622],{"class":3413,"line":5631},[3411,11610,9061],{"class":4143},[3411,11612,11613],{"class":3432}," a + b \u003C= c ",[3411,11615,11616],{"class":3424},"or",[3411,11618,11619],{"class":3432}," a + c \u003C= b ",[3411,11621,11616],{"class":3424},[3411,11623,11624],{"class":3432}," b + c \u003C= a:\n",[3411,11626,11627,11629,11632,11634,11637],{"class":3413,"line":5636},[3411,11628,9075],{"class":4143},[3411,11630,11631],{"class":3428}," ValueError",[3411,11633,3446],{"class":3432},[3411,11635,11636],{"class":3828},"\"Недопустимі сторони трикутника\"",[3411,11638,3852],{"class":3432},[3411,11640,11641,11643,11646,11648,11651,11653],{"class":3413,"line":5642},[3411,11642,3465],{"class":3424},[3411,11644,11645],{"class":3432},".a, ",[3411,11647,3450],{"class":3424},[3411,11649,11650],{"class":3432},".b, ",[3411,11652,3450],{"class":3424},[3411,11654,11655],{"class":3432},".c = a, b, c\n",[3411,11657,11658],{"class":3413,"line":5670},[3411,11659,3475],{"emptyLinePlaceholder":3474},[3411,11661,11662,11664],{"class":3413,"line":5693},[3411,11663,3697],{"class":3442},[3411,11665,5104],{"class":3428},[3411,11667,11668,11670,11672,11674,11676,11678,11680],{"class":3413,"line":5704},[3411,11669,3439],{"class":3424},[3411,11671,6162],{"class":3442},[3411,11673,3446],{"class":3432},[3411,11675,3450],{"class":3449},[3411,11677,3511],{"class":3432},[3411,11679,6171],{"class":3428},[3411,11681,3433],{"class":3432},[3411,11683,11684,11687,11689,11692],{"class":3413,"line":9401},[3411,11685,11686],{"class":3432},"        s = ",[3411,11688,3450],{"class":3424},[3411,11690,11691],{"class":3432},".perimeter \u002F ",[3411,11693,6301],{"class":5495},[3411,11695,11696,11698,11701,11703,11706,11708,11711,11713],{"class":3413,"line":9407},[3411,11697,4400],{"class":4143},[3411,11699,11700],{"class":3432}," math.sqrt(s * (s - ",[3411,11702,3450],{"class":3424},[3411,11704,11705],{"class":3432},".a) * (s - ",[3411,11707,3450],{"class":3424},[3411,11709,11710],{"class":3432},".b) * (s - ",[3411,11712,3450],{"class":3424},[3411,11714,11715],{"class":3432},".c))\n",[3411,11717,11718],{"class":3413,"line":9417},[3411,11719,3475],{"emptyLinePlaceholder":3474},[3411,11721,11722,11724],{"class":3413,"line":9433},[3411,11723,3697],{"class":3442},[3411,11725,5104],{"class":3428},[3411,11727,11728,11730,11732,11734,11736,11738,11740],{"class":3413,"line":9442},[3411,11729,3439],{"class":3424},[3411,11731,6188],{"class":3442},[3411,11733,3446],{"class":3432},[3411,11735,3450],{"class":3449},[3411,11737,3511],{"class":3432},[3411,11739,6171],{"class":3428},[3411,11741,3433],{"class":3432},[3411,11743,11744,11746,11748,11751,11753,11756,11758],{"class":3413,"line":9452},[3411,11745,4400],{"class":4143},[3411,11747,5433],{"class":3424},[3411,11749,11750],{"class":3432},".a + ",[3411,11752,3450],{"class":3424},[3411,11754,11755],{"class":3432},".b + ",[3411,11757,3450],{"class":3424},[3411,11759,11760],{"class":3432},".c\n",[3411,11762,11763],{"class":3413,"line":9457},[3411,11764,3475],{"emptyLinePlaceholder":3474},[3411,11766,11767],{"class":3413,"line":9492},[3411,11768,3475],{"emptyLinePlaceholder":3474},[3411,11770,11771],{"class":3413,"line":9498},[3411,11772,11773],{"class":3417},"# Тест\n",[3411,11775,11776],{"class":3413,"line":9509},[3411,11777,11778],{"class":3432},"shapes: list[Shape] = [\n",[3411,11780,11781,11784,11787,11789,11792],{"class":3413,"line":9533},[3411,11782,11783],{"class":3432},"    Rectangle(",[3411,11785,11786],{"class":5495},"4",[3411,11788,3453],{"class":3432},[3411,11790,11791],{"class":5495},"6",[3411,11793,11794],{"class":3432},"),\n",[3411,11796,11797,11800,11803],{"class":3413,"line":9538},[3411,11798,11799],{"class":3432},"    Circle(",[3411,11801,11802],{"class":5495},"5",[3411,11804,11794],{"class":3432},[3411,11806,11807,11810,11812,11814,11816,11818,11820],{"class":3413,"line":9587},[3411,11808,11809],{"class":3432},"    Triangle(",[3411,11811,9578],{"class":5495},[3411,11813,3453],{"class":3432},[3411,11815,11786],{"class":5495},[3411,11817,3453],{"class":3432},[3411,11819,11802],{"class":5495},[3411,11821,11794],{"class":3432},[3411,11823,11824],{"class":3413,"line":9593},[3411,11825,11826],{"class":3432},"]\n",[3411,11828,11829],{"class":3413,"line":9618},[3411,11830,3475],{"emptyLinePlaceholder":3474},[3411,11832,11833,11835,11838,11840],{"class":3413,"line":9626},[3411,11834,4411],{"class":4143},[3411,11836,11837],{"class":3432}," shape ",[3411,11839,4417],{"class":4143},[3411,11841,11842],{"class":3432}," shapes:\n",[3411,11844,11845,11847],{"class":3413,"line":9635},[3411,11846,4585],{"class":3442},[3411,11848,11849],{"class":3432},"(shape.describe())\n",[3411,11851,11852,11854,11856,11858,11861,11863,11865,11868,11870,11872],{"class":3413,"line":9643},[3411,11853,4585],{"class":3442},[3411,11855,3446],{"class":3432},[3411,11857,3825],{"class":3424},[3411,11859,11860],{"class":3828},"\"  isinstance(shape, Shape): ",[3411,11862,3832],{"class":3424},[3411,11864,4944],{"class":3442},[3411,11866,11867],{"class":3432},"(shape, Shape)",[3411,11869,3837],{"class":3424},[3411,11871,3849],{"class":3828},[3411,11873,3852],{"class":3432},[4952,11875,11877,11885,11897,11904,11915,11921,11933],{"title":11876},"python shapes_abc.py",[4956,11878,11880,4964,11883],{"className":11879},[3413],[3411,11881,4963],{"className":11882},[4962],[3547,11884,11876],{},[4956,11886,11888,11889,11255,11893],{"className":11887},[3413],"Rectangle: площа=",[3411,11890,11892],{"className":11891},[4990],"24.00",[3411,11894,11896],{"className":11895},[4990],"20.00",[4956,11898,11900,11901],{"className":11899},[3413],"  isinstance(shape, Shape): ",[3411,11902,4998],{"className":11903},[4990],[4956,11905,11907,11908,11255,11911],{"className":11906},[3413],"Circle: площа=",[3411,11909,6450],{"className":11910},[4990],[3411,11912,11914],{"className":11913},[4990],"31.42",[4956,11916,11900,11918],{"className":11917},[3413],[3411,11919,4998],{"className":11920},[4990],[4956,11922,11924,11925,11255,11929],{"className":11923},[3413],"Triangle: площа=",[3411,11926,11928],{"className":11927},[4990],"6.00",[3411,11930,11932],{"className":11931},[4990],"12.00",[4956,11934,11900,11936],{"className":11935},[3413],[3411,11937,4998],{"className":11938},[4990],[3584,11940],{},[3591,11942,11944],{"id":11943},"рівень-2-середній-система-кешування-з-protocol","Рівень 2 (Середній): Система кешування з Protocol",[3394,11946,11947,11949,11950,11952],{},[3547,11948,11042],{}," Реалізуйте систему кешування з використанням ",[3408,11951,3888],{}," для описання інтерфейсу кешу. Система повинна підтримувати декілька бекендів (пам'ять, Redis-подібний TTL-кеш) без примусового наслідування.",[3401,11954,11956],{"className":3403,"code":11955,"language":3405,"meta":3406,"style":3406},"# cache_system.py\nfrom typing import Protocol, runtime_checkable\nfrom datetime import datetime, timedelta\n\n\n@runtime_checkable\nclass CacheBackend(Protocol):\n    \"\"\"\n    Протокол кеш-бекенду.\n    Будь-який клас з методами get\u002Fset\u002Fdelete\u002Fclear\n    автоматично відповідає цьому протоколу.\n    \"\"\"\n\n    def get(self, key: str) -> object | None: ...\n    def set(self, key: str, value: object, ttl: int | None = None) -> None: ...\n    def delete(self, key: str) -> bool: ...\n    def clear(self) -> None: ...\n\n\nclass SimpleMemoryCache:\n    \"\"\"Простий кеш у пам'яті без TTL. Відповідає CacheBackend.\"\"\"\n\n    def __init__(self):\n        self._store: dict[str, object] = {}\n\n    def get(self, key: str) -> object | None:\n        return self._store.get(key)\n\n    def set(self, key: str, value: object, ttl: int | None = None) -> None:\n        self._store[key] = value  # TTL ігнорується\n\n    def delete(self, key: str) -> bool:\n        return self._store.pop(key, None) is not None\n\n    def clear(self) -> None:\n        self._store.clear()\n\n\nclass TTLMemoryCache:\n    \"\"\"Кеш з підтримкою TTL (Time-To-Live). Відповідає CacheBackend.\"\"\"\n\n    def __init__(self):\n        self._store: dict[str, tuple[object, datetime | None]] = {}\n\n    def get(self, key: str) -> object | None:\n        entry = self._store.get(key)\n        if entry is None:\n            return None\n        value, expires_at = entry\n        if expires_at is not None and datetime.now() > expires_at:\n            del self._store[key]\n            return None\n        return value\n\n    def set(self, key: str, value: object, ttl: int | None = None) -> None:\n        expires_at = datetime.now() + timedelta(seconds=ttl) if ttl else None\n        self._store[key] = (value, expires_at)\n\n    def delete(self, key: str) -> bool:\n        return self._store.pop(key, None) is not None\n\n    def clear(self) -> None:\n        self._store.clear()\n\n\n# Сервіс кешування — залежить від протоколу, не від конкретного класу\nclass CacheService:\n    \"\"\"Сервіс кешування — приймає будь-який CacheBackend.\"\"\"\n\n    def __init__(self, backend: CacheBackend):\n        if not isinstance(backend, CacheBackend):\n            raise TypeError(f\"Очікується CacheBackend, отримано: {type(backend)}\")\n        self._backend = backend\n        self._hits = 0\n        self._misses = 0\n\n    def get_or_compute(self, key: str, compute_fn, ttl: int | None = None) -> object:\n        \"\"\"Повертає значення з кешу або обчислює і кешує його.\"\"\"\n        cached = self._backend.get(key)\n        if cached is not None:\n            self._hits += 1\n            return cached\n        self._misses += 1\n        value = compute_fn()\n        self._backend.set(key, value, ttl)\n        return value\n\n    @property\n    def stats(self) -> dict:\n        total = self._hits + self._misses\n        hit_rate = self._hits \u002F total * 100 if total > 0 else 0\n        return {\"hits\": self._hits, \"misses\": self._misses, \"hit_rate\": f\"{hit_rate:.1f}%\"}\n\n\n# Демонстрація\nimport time\n\n# Використання з TTLMemoryCache\nservice = CacheService(TTLMemoryCache())\n\ndef expensive_query() -> dict:\n    print(\"  [DB] Виконання дорогого запиту...\")\n    time.sleep(0.01)  # Імітація роботи\n    return {\"users\": 1500, \"active\": 1200}\n\nprint(\"Перший запит (cache miss):\")\nresult = service.get_or_compute(\"stats:dashboard\", expensive_query, ttl=60)\nprint(f\"  Результат: {result}\")\n\nprint(\"Другий запит (cache hit):\")\nresult = service.get_or_compute(\"stats:dashboard\", expensive_query, ttl=60)\nprint(f\"  Результат: {result}\")\n\nprint(f\"Статистика: {service.stats}\")\n\n# Перевіряємо відповідність Protocol\nprint(f\"\\nisinstance(TTLMemoryCache(), CacheBackend): {isinstance(TTLMemoryCache(), CacheBackend)}\")\nprint(f\"isinstance(SimpleMemoryCache(), CacheBackend): {isinstance(SimpleMemoryCache(), CacheBackend)}\")\n",[3408,11957,11958,11963,11973,11985,11989,11993,11997,12010,12014,12019,12024,12029,12033,12037,12066,12117,12141,12158,12162,12166,12175,12180,12184,12196,12210,12214,12242,12250,12254,12302,12312,12316,12340,12359,12363,12379,12386,12390,12394,12403,12408,12412,12424,12445,12449,12477,12486,12499,12505,12510,12529,12537,12543,12550,12554,12602,12623,12630,12634,12658,12676,12680,12696,12702,12706,12710,12715,12724,12729,12733,12751,12762,12788,12795,12805,12814,12818,12864,12869,12879,12894,12905,12912,12921,12926,12933,12939,12943,12949,12966,12981,13008,13057,13061,13065,13070,13077,13081,13086,13091,13095,13108,13119,13132,13157,13161,13172,13192,13214,13218,13229,13245,13265,13269,13291,13295,13300,13330],{"__ignoreMap":3406},[3411,11959,11960],{"class":3413,"line":3414},[3411,11961,11962],{"class":3417},"# cache_system.py\n",[3411,11964,11965,11967,11969,11971],{"class":3413,"line":3421},[3411,11966,4144],{"class":4143},[3411,11968,6570],{"class":3432},[3411,11970,4150],{"class":4143},[3411,11972,7398],{"class":3432},[3411,11974,11975,11977,11980,11982],{"class":3413,"line":3436},[3411,11976,4144],{"class":4143},[3411,11978,11979],{"class":3432}," datetime ",[3411,11981,4150],{"class":4143},[3411,11983,11984],{"class":3432}," datetime, timedelta\n",[3411,11986,11987],{"class":3413,"line":3462},[3411,11988,3475],{"emptyLinePlaceholder":3474},[3411,11990,11991],{"class":3413,"line":3471},[3411,11992,3475],{"emptyLinePlaceholder":3474},[3411,11994,11995],{"class":3413,"line":3478},[3411,11996,7407],{"class":3442},[3411,11998,11999,12001,12004,12006,12008],{"class":3413,"line":3519},[3411,12000,3425],{"class":3424},[3411,12002,12003],{"class":3428}," CacheBackend",[3411,12005,3446],{"class":3432},[3411,12007,3888],{"class":3428},[3411,12009,3459],{"class":3432},[3411,12011,12012],{"class":3413,"line":3525},[3411,12013,4172],{"class":3828},[3411,12015,12016],{"class":3413,"line":3531},[3411,12017,12018],{"class":3828},"    Протокол кеш-бекенду.\n",[3411,12020,12021],{"class":3413,"line":3949},[3411,12022,12023],{"class":3828},"    Будь-який клас з методами get\u002Fset\u002Fdelete\u002Fclear\n",[3411,12025,12026],{"class":3413,"line":3954},[3411,12027,12028],{"class":3828},"    автоматично відповідає цьому протоколу.\n",[3411,12030,12031],{"class":3413,"line":3960},[3411,12032,4172],{"class":3828},[3411,12034,12035],{"class":3413,"line":3966},[3411,12036,3475],{"emptyLinePlaceholder":3474},[3411,12038,12039,12041,12044,12046,12048,12050,12052,12054,12056,12058,12060,12062,12064],{"class":3413,"line":3972},[3411,12040,3439],{"class":3424},[3411,12042,12043],{"class":3442}," get",[3411,12045,3446],{"class":3432},[3411,12047,3450],{"class":3449},[3411,12049,3453],{"class":3432},[3411,12051,9146],{"class":3449},[3411,12053,3495],{"class":3432},[3411,12055,3508],{"class":3428},[3411,12057,3511],{"class":3432},[3411,12059,8943],{"class":3428},[3411,12061,9207],{"class":3432},[3411,12063,3514],{"class":3424},[3411,12065,4521],{"class":3432},[3411,12067,12068,12070,12073,12075,12077,12079,12081,12083,12085,12087,12090,12092,12094,12096,12099,12101,12103,12105,12107,12109,12111,12113,12115],{"class":3413,"line":3977},[3411,12069,3439],{"class":3424},[3411,12071,12072],{"class":3428}," set",[3411,12074,3446],{"class":3432},[3411,12076,3450],{"class":3449},[3411,12078,3453],{"class":3432},[3411,12080,9146],{"class":3449},[3411,12082,3495],{"class":3432},[3411,12084,3508],{"class":3428},[3411,12086,3453],{"class":3432},[3411,12088,12089],{"class":3449},"value",[3411,12091,3495],{"class":3432},[3411,12093,8943],{"class":3428},[3411,12095,3453],{"class":3432},[3411,12097,12098],{"class":3449},"ttl",[3411,12100,3495],{"class":3432},[3411,12102,3498],{"class":3428},[3411,12104,9207],{"class":3432},[3411,12106,3514],{"class":3424},[3411,12108,9575],{"class":3432},[3411,12110,3514],{"class":3424},[3411,12112,3511],{"class":3432},[3411,12114,3514],{"class":3424},[3411,12116,4521],{"class":3432},[3411,12118,12119,12121,12123,12125,12127,12129,12131,12133,12135,12137,12139],{"class":3413,"line":3982},[3411,12120,3439],{"class":3424},[3411,12122,9235],{"class":3442},[3411,12124,3446],{"class":3432},[3411,12126,3450],{"class":3449},[3411,12128,3453],{"class":3432},[3411,12130,9146],{"class":3449},[3411,12132,3495],{"class":3432},[3411,12134,3508],{"class":3428},[3411,12136,3511],{"class":3432},[3411,12138,4226],{"class":3428},[3411,12140,4521],{"class":3432},[3411,12142,12143,12145,12148,12150,12152,12154,12156],{"class":3413,"line":3988},[3411,12144,3439],{"class":3424},[3411,12146,12147],{"class":3442}," clear",[3411,12149,3446],{"class":3432},[3411,12151,3450],{"class":3449},[3411,12153,3511],{"class":3432},[3411,12155,3514],{"class":3424},[3411,12157,4521],{"class":3432},[3411,12159,12160],{"class":3413,"line":3994},[3411,12161,3475],{"emptyLinePlaceholder":3474},[3411,12163,12164],{"class":3413,"line":4000},[3411,12165,3475],{"emptyLinePlaceholder":3474},[3411,12167,12168,12170,12173],{"class":3413,"line":4005},[3411,12169,3425],{"class":3424},[3411,12171,12172],{"class":3428}," SimpleMemoryCache",[3411,12174,3433],{"class":3432},[3411,12176,12177],{"class":3413,"line":4011},[3411,12178,12179],{"class":3828},"    \"\"\"Простий кеш у пам'яті без TTL. Відповідає CacheBackend.\"\"\"\n",[3411,12181,12182],{"class":3413,"line":4017},[3411,12183,3475],{"emptyLinePlaceholder":3474},[3411,12185,12186,12188,12190,12192,12194],{"class":3413,"line":4022},[3411,12187,3439],{"class":3424},[3411,12189,3443],{"class":3442},[3411,12191,3446],{"class":3432},[3411,12193,3450],{"class":3449},[3411,12195,3459],{"class":3432},[3411,12197,12198,12200,12202,12204,12206,12208],{"class":3413,"line":4028},[3411,12199,3465],{"class":3424},[3411,12201,9788],{"class":3432},[3411,12203,3508],{"class":3428},[3411,12205,3453],{"class":3432},[3411,12207,8943],{"class":3428},[3411,12209,9797],{"class":3432},[3411,12211,12212],{"class":3413,"line":4034},[3411,12213,3475],{"emptyLinePlaceholder":3474},[3411,12215,12216,12218,12220,12222,12224,12226,12228,12230,12232,12234,12236,12238,12240],{"class":3413,"line":4039},[3411,12217,3439],{"class":3424},[3411,12219,12043],{"class":3442},[3411,12221,3446],{"class":3432},[3411,12223,3450],{"class":3449},[3411,12225,3453],{"class":3432},[3411,12227,9146],{"class":3449},[3411,12229,3495],{"class":3432},[3411,12231,3508],{"class":3428},[3411,12233,3511],{"class":3432},[3411,12235,8943],{"class":3428},[3411,12237,9207],{"class":3432},[3411,12239,3514],{"class":3424},[3411,12241,3433],{"class":3432},[3411,12243,12244,12246,12248],{"class":3413,"line":4045},[3411,12245,4400],{"class":4143},[3411,12247,5433],{"class":3424},[3411,12249,9887],{"class":3432},[3411,12251,12252],{"class":3413,"line":4051},[3411,12253,3475],{"emptyLinePlaceholder":3474},[3411,12255,12256,12258,12260,12262,12264,12266,12268,12270,12272,12274,12276,12278,12280,12282,12284,12286,12288,12290,12292,12294,12296,12298,12300],{"class":3413,"line":4056},[3411,12257,3439],{"class":3424},[3411,12259,12072],{"class":3428},[3411,12261,3446],{"class":3432},[3411,12263,3450],{"class":3449},[3411,12265,3453],{"class":3432},[3411,12267,9146],{"class":3449},[3411,12269,3495],{"class":3432},[3411,12271,3508],{"class":3428},[3411,12273,3453],{"class":3432},[3411,12275,12089],{"class":3449},[3411,12277,3495],{"class":3432},[3411,12279,8943],{"class":3428},[3411,12281,3453],{"class":3432},[3411,12283,12098],{"class":3449},[3411,12285,3495],{"class":3432},[3411,12287,3498],{"class":3428},[3411,12289,9207],{"class":3432},[3411,12291,3514],{"class":3424},[3411,12293,9575],{"class":3432},[3411,12295,3514],{"class":3424},[3411,12297,3511],{"class":3432},[3411,12299,3514],{"class":3424},[3411,12301,3433],{"class":3432},[3411,12303,12304,12306,12309],{"class":3413,"line":4326},[3411,12305,3465],{"class":3424},[3411,12307,12308],{"class":3432},"._store[key] = value  ",[3411,12310,12311],{"class":3417},"# TTL ігнорується\n",[3411,12313,12314],{"class":3413,"line":4369},[3411,12315,3475],{"emptyLinePlaceholder":3474},[3411,12317,12318,12320,12322,12324,12326,12328,12330,12332,12334,12336,12338],{"class":3413,"line":4374},[3411,12319,3439],{"class":3424},[3411,12321,9235],{"class":3442},[3411,12323,3446],{"class":3432},[3411,12325,3450],{"class":3449},[3411,12327,3453],{"class":3432},[3411,12329,9146],{"class":3449},[3411,12331,3495],{"class":3432},[3411,12333,3508],{"class":3428},[3411,12335,3511],{"class":3432},[3411,12337,4226],{"class":3428},[3411,12339,3433],{"class":3432},[3411,12341,12342,12344,12346,12349,12351,12353,12355,12357],{"class":3413,"line":4380},[3411,12343,4400],{"class":4143},[3411,12345,5433],{"class":3424},[3411,12347,12348],{"class":3432},"._store.pop(key, ",[3411,12350,3514],{"class":3424},[3411,12352,10172],{"class":3432},[3411,12354,9425],{"class":3424},[3411,12356,9064],{"class":3424},[3411,12358,9439],{"class":3424},[3411,12360,12361],{"class":3413,"line":4386},[3411,12362,3475],{"emptyLinePlaceholder":3474},[3411,12364,12365,12367,12369,12371,12373,12375,12377],{"class":3413,"line":4392},[3411,12366,3439],{"class":3424},[3411,12368,12147],{"class":3442},[3411,12370,3446],{"class":3432},[3411,12372,3450],{"class":3449},[3411,12374,3511],{"class":3432},[3411,12376,3514],{"class":3424},[3411,12378,3433],{"class":3432},[3411,12380,12381,12383],{"class":3413,"line":4397},[3411,12382,3465],{"class":3424},[3411,12384,12385],{"class":3432},"._store.clear()\n",[3411,12387,12388],{"class":3413,"line":4802},[3411,12389,3475],{"emptyLinePlaceholder":3474},[3411,12391,12392],{"class":3413,"line":4835},[3411,12393,3475],{"emptyLinePlaceholder":3474},[3411,12395,12396,12398,12401],{"class":3413,"line":4865},[3411,12397,3425],{"class":3424},[3411,12399,12400],{"class":3428}," TTLMemoryCache",[3411,12402,3433],{"class":3432},[3411,12404,12405],{"class":3413,"line":4872},[3411,12406,12407],{"class":3828},"    \"\"\"Кеш з підтримкою TTL (Time-To-Live). Відповідає CacheBackend.\"\"\"\n",[3411,12409,12410],{"class":3413,"line":4877},[3411,12411,3475],{"emptyLinePlaceholder":3474},[3411,12413,12414,12416,12418,12420,12422],{"class":3413,"line":4894},[3411,12415,3439],{"class":3424},[3411,12417,3443],{"class":3442},[3411,12419,3446],{"class":3432},[3411,12421,3450],{"class":3449},[3411,12423,3459],{"class":3432},[3411,12425,12426,12428,12430,12432,12435,12437,12440,12442],{"class":3413,"line":4904},[3411,12427,3465],{"class":3424},[3411,12429,9788],{"class":3432},[3411,12431,3508],{"class":3428},[3411,12433,12434],{"class":3432},", tuple[",[3411,12436,8943],{"class":3428},[3411,12438,12439],{"class":3432},", datetime | ",[3411,12441,3514],{"class":3424},[3411,12443,12444],{"class":3432},"]] = {}\n",[3411,12446,12447],{"class":3413,"line":4916},[3411,12448,3475],{"emptyLinePlaceholder":3474},[3411,12450,12451,12453,12455,12457,12459,12461,12463,12465,12467,12469,12471,12473,12475],{"class":3413,"line":4921},[3411,12452,3439],{"class":3424},[3411,12454,12043],{"class":3442},[3411,12456,3446],{"class":3432},[3411,12458,3450],{"class":3449},[3411,12460,3453],{"class":3432},[3411,12462,9146],{"class":3449},[3411,12464,3495],{"class":3432},[3411,12466,3508],{"class":3428},[3411,12468,3511],{"class":3432},[3411,12470,8943],{"class":3428},[3411,12472,9207],{"class":3432},[3411,12474,3514],{"class":3424},[3411,12476,3433],{"class":3432},[3411,12478,12479,12482,12484],{"class":3413,"line":4936},[3411,12480,12481],{"class":3432},"        entry = ",[3411,12483,3450],{"class":3424},[3411,12485,9887],{"class":3432},[3411,12487,12488,12490,12493,12495,12497],{"class":3413,"line":5508},[3411,12489,9061],{"class":4143},[3411,12491,12492],{"class":3432}," entry ",[3411,12494,9425],{"class":3424},[3411,12496,9428],{"class":3424},[3411,12498,3433],{"class":3432},[3411,12500,12501,12503],{"class":3413,"line":5513},[3411,12502,9436],{"class":4143},[3411,12504,9439],{"class":3424},[3411,12506,12507],{"class":3413,"line":5520},[3411,12508,12509],{"class":3432},"        value, expires_at = entry\n",[3411,12511,12512,12514,12517,12519,12521,12523,12526],{"class":3413,"line":5541},[3411,12513,9061],{"class":4143},[3411,12515,12516],{"class":3432}," expires_at ",[3411,12518,9425],{"class":3424},[3411,12520,9064],{"class":3424},[3411,12522,9428],{"class":3424},[3411,12524,12525],{"class":3424}," and",[3411,12527,12528],{"class":3432}," datetime.now() > expires_at:\n",[3411,12530,12531,12533,12535],{"class":3413,"line":5566},[3411,12532,9938],{"class":4143},[3411,12534,5433],{"class":3424},[3411,12536,9943],{"class":3432},[3411,12538,12539,12541],{"class":3413,"line":5571},[3411,12540,9436],{"class":4143},[3411,12542,9439],{"class":3424},[3411,12544,12545,12547],{"class":3413,"line":5596},[3411,12546,4400],{"class":4143},[3411,12548,12549],{"class":3432}," value\n",[3411,12551,12552],{"class":3413,"line":5618},[3411,12553,3475],{"emptyLinePlaceholder":3474},[3411,12555,12556,12558,12560,12562,12564,12566,12568,12570,12572,12574,12576,12578,12580,12582,12584,12586,12588,12590,12592,12594,12596,12598,12600],{"class":3413,"line":5626},[3411,12557,3439],{"class":3424},[3411,12559,12072],{"class":3428},[3411,12561,3446],{"class":3432},[3411,12563,3450],{"class":3449},[3411,12565,3453],{"class":3432},[3411,12567,9146],{"class":3449},[3411,12569,3495],{"class":3432},[3411,12571,3508],{"class":3428},[3411,12573,3453],{"class":3432},[3411,12575,12089],{"class":3449},[3411,12577,3495],{"class":3432},[3411,12579,8943],{"class":3428},[3411,12581,3453],{"class":3432},[3411,12583,12098],{"class":3449},[3411,12585,3495],{"class":3432},[3411,12587,3498],{"class":3428},[3411,12589,9207],{"class":3432},[3411,12591,3514],{"class":3424},[3411,12593,9575],{"class":3432},[3411,12595,3514],{"class":3424},[3411,12597,3511],{"class":3432},[3411,12599,3514],{"class":3424},[3411,12601,3433],{"class":3432},[3411,12603,12604,12607,12610,12613,12615,12618,12621],{"class":3413,"line":5631},[3411,12605,12606],{"class":3432},"        expires_at = datetime.now() + timedelta(",[3411,12608,12609],{"class":3449},"seconds",[3411,12611,12612],{"class":3432},"=ttl) ",[3411,12614,5645],{"class":4143},[3411,12616,12617],{"class":3432}," ttl ",[3411,12619,12620],{"class":4143},"else",[3411,12622,9439],{"class":3424},[3411,12624,12625,12627],{"class":3413,"line":5636},[3411,12626,3465],{"class":3424},[3411,12628,12629],{"class":3432},"._store[key] = (value, expires_at)\n",[3411,12631,12632],{"class":3413,"line":5642},[3411,12633,3475],{"emptyLinePlaceholder":3474},[3411,12635,12636,12638,12640,12642,12644,12646,12648,12650,12652,12654,12656],{"class":3413,"line":5670},[3411,12637,3439],{"class":3424},[3411,12639,9235],{"class":3442},[3411,12641,3446],{"class":3432},[3411,12643,3450],{"class":3449},[3411,12645,3453],{"class":3432},[3411,12647,9146],{"class":3449},[3411,12649,3495],{"class":3432},[3411,12651,3508],{"class":3428},[3411,12653,3511],{"class":3432},[3411,12655,4226],{"class":3428},[3411,12657,3433],{"class":3432},[3411,12659,12660,12662,12664,12666,12668,12670,12672,12674],{"class":3413,"line":5693},[3411,12661,4400],{"class":4143},[3411,12663,5433],{"class":3424},[3411,12665,12348],{"class":3432},[3411,12667,3514],{"class":3424},[3411,12669,10172],{"class":3432},[3411,12671,9425],{"class":3424},[3411,12673,9064],{"class":3424},[3411,12675,9439],{"class":3424},[3411,12677,12678],{"class":3413,"line":5704},[3411,12679,3475],{"emptyLinePlaceholder":3474},[3411,12681,12682,12684,12686,12688,12690,12692,12694],{"class":3413,"line":9401},[3411,12683,3439],{"class":3424},[3411,12685,12147],{"class":3442},[3411,12687,3446],{"class":3432},[3411,12689,3450],{"class":3449},[3411,12691,3511],{"class":3432},[3411,12693,3514],{"class":3424},[3411,12695,3433],{"class":3432},[3411,12697,12698,12700],{"class":3413,"line":9407},[3411,12699,3465],{"class":3424},[3411,12701,12385],{"class":3432},[3411,12703,12704],{"class":3413,"line":9417},[3411,12705,3475],{"emptyLinePlaceholder":3474},[3411,12707,12708],{"class":3413,"line":9433},[3411,12709,3475],{"emptyLinePlaceholder":3474},[3411,12711,12712],{"class":3413,"line":9442},[3411,12713,12714],{"class":3417},"# Сервіс кешування — залежить від протоколу, не від конкретного класу\n",[3411,12716,12717,12719,12722],{"class":3413,"line":9452},[3411,12718,3425],{"class":3424},[3411,12720,12721],{"class":3428}," CacheService",[3411,12723,3433],{"class":3432},[3411,12725,12726],{"class":3413,"line":9457},[3411,12727,12728],{"class":3828},"    \"\"\"Сервіс кешування — приймає будь-який CacheBackend.\"\"\"\n",[3411,12730,12731],{"class":3413,"line":9492},[3411,12732,3475],{"emptyLinePlaceholder":3474},[3411,12734,12735,12737,12739,12741,12743,12745,12748],{"class":3413,"line":9498},[3411,12736,3439],{"class":3424},[3411,12738,3443],{"class":3442},[3411,12740,3446],{"class":3432},[3411,12742,3450],{"class":3449},[3411,12744,3453],{"class":3432},[3411,12746,12747],{"class":3449},"backend",[3411,12749,12750],{"class":3432},": CacheBackend):\n",[3411,12752,12753,12755,12757,12759],{"class":3413,"line":9509},[3411,12754,9061],{"class":4143},[3411,12756,9064],{"class":3424},[3411,12758,9067],{"class":3442},[3411,12760,12761],{"class":3432},"(backend, CacheBackend):\n",[3411,12763,12764,12766,12768,12770,12772,12775,12777,12779,12782,12784,12786],{"class":3413,"line":9533},[3411,12765,9075],{"class":4143},[3411,12767,4574],{"class":3428},[3411,12769,3446],{"class":3432},[3411,12771,3825],{"class":3424},[3411,12773,12774],{"class":3828},"\"Очікується CacheBackend, отримано: ",[3411,12776,3832],{"class":3424},[3411,12778,9100],{"class":3428},[3411,12780,12781],{"class":3432},"(backend)",[3411,12783,3837],{"class":3424},[3411,12785,3849],{"class":3828},[3411,12787,3852],{"class":3432},[3411,12789,12790,12792],{"class":3413,"line":9538},[3411,12791,3465],{"class":3424},[3411,12793,12794],{"class":3432},"._backend = backend\n",[3411,12796,12797,12799,12802],{"class":3413,"line":9587},[3411,12798,3465],{"class":3424},[3411,12800,12801],{"class":3432},"._hits = ",[3411,12803,12804],{"class":5495},"0\n",[3411,12806,12807,12809,12812],{"class":3413,"line":9593},[3411,12808,3465],{"class":3424},[3411,12810,12811],{"class":3432},"._misses = ",[3411,12813,12804],{"class":5495},[3411,12815,12816],{"class":3413,"line":9618},[3411,12817,3475],{"emptyLinePlaceholder":3474},[3411,12819,12820,12822,12825,12827,12829,12831,12833,12835,12837,12839,12842,12844,12846,12848,12850,12852,12854,12856,12858,12860,12862],{"class":3413,"line":9626},[3411,12821,3439],{"class":3424},[3411,12823,12824],{"class":3442}," get_or_compute",[3411,12826,3446],{"class":3432},[3411,12828,3450],{"class":3449},[3411,12830,3453],{"class":3432},[3411,12832,9146],{"class":3449},[3411,12834,3495],{"class":3432},[3411,12836,3508],{"class":3428},[3411,12838,3453],{"class":3432},[3411,12840,12841],{"class":3449},"compute_fn",[3411,12843,3453],{"class":3432},[3411,12845,12098],{"class":3449},[3411,12847,3495],{"class":3432},[3411,12849,3498],{"class":3428},[3411,12851,9207],{"class":3432},[3411,12853,3514],{"class":3424},[3411,12855,9575],{"class":3432},[3411,12857,3514],{"class":3424},[3411,12859,3511],{"class":3432},[3411,12861,8943],{"class":3428},[3411,12863,3433],{"class":3432},[3411,12865,12866],{"class":3413,"line":9635},[3411,12867,12868],{"class":3828},"        \"\"\"Повертає значення з кешу або обчислює і кешує його.\"\"\"\n",[3411,12870,12871,12874,12876],{"class":3413,"line":9643},[3411,12872,12873],{"class":3432},"        cached = ",[3411,12875,3450],{"class":3424},[3411,12877,12878],{"class":3432},"._backend.get(key)\n",[3411,12880,12881,12883,12886,12888,12890,12892],{"class":3413,"line":9656},[3411,12882,9061],{"class":4143},[3411,12884,12885],{"class":3432}," cached ",[3411,12887,9425],{"class":3424},[3411,12889,9064],{"class":3424},[3411,12891,9428],{"class":3424},[3411,12893,3433],{"class":3432},[3411,12895,12896,12899,12902],{"class":3413,"line":9697},[3411,12897,12898],{"class":3424},"            self",[3411,12900,12901],{"class":3432},"._hits += ",[3411,12903,12904],{"class":5495},"1\n",[3411,12906,12907,12909],{"class":3413,"line":9705},[3411,12908,9436],{"class":4143},[3411,12910,12911],{"class":3432}," cached\n",[3411,12913,12914,12916,12919],{"class":3413,"line":9710},[3411,12915,3465],{"class":3424},[3411,12917,12918],{"class":3432},"._misses += ",[3411,12920,12904],{"class":5495},[3411,12922,12923],{"class":3413,"line":9715},[3411,12924,12925],{"class":3432},"        value = compute_fn()\n",[3411,12927,12928,12930],{"class":3413,"line":9721},[3411,12929,3465],{"class":3424},[3411,12931,12932],{"class":3432},"._backend.set(key, value, ttl)\n",[3411,12934,12935,12937],{"class":3413,"line":9726},[3411,12936,4400],{"class":4143},[3411,12938,12549],{"class":3432},[3411,12940,12941],{"class":3413,"line":9741},[3411,12942,3475],{"emptyLinePlaceholder":3474},[3411,12944,12945,12947],{"class":3413,"line":9747},[3411,12946,3697],{"class":3442},[3411,12948,5104],{"class":3428},[3411,12950,12951,12953,12956,12958,12960,12962,12964],{"class":3413,"line":9752},[3411,12952,3439],{"class":3424},[3411,12954,12955],{"class":3442}," stats",[3411,12957,3446],{"class":3432},[3411,12959,3450],{"class":3449},[3411,12961,3511],{"class":3432},[3411,12963,5176],{"class":3428},[3411,12965,3433],{"class":3432},[3411,12967,12968,12971,12973,12976,12978],{"class":3413,"line":9769},[3411,12969,12970],{"class":3432},"        total = ",[3411,12972,3450],{"class":3424},[3411,12974,12975],{"class":3432},"._hits + ",[3411,12977,3450],{"class":3424},[3411,12979,12980],{"class":3432},"._misses\n",[3411,12982,12983,12986,12988,12991,12994,12997,13000,13003,13005],{"class":3413,"line":9783},[3411,12984,12985],{"class":3432},"        hit_rate = ",[3411,12987,3450],{"class":3424},[3411,12989,12990],{"class":3432},"._hits \u002F total * ",[3411,12992,12993],{"class":5495},"100",[3411,12995,12996],{"class":4143}," if",[3411,12998,12999],{"class":3432}," total > ",[3411,13001,13002],{"class":5495},"0",[3411,13004,9527],{"class":4143},[3411,13006,13007],{"class":5495}," 0\n",[3411,13009,13010,13012,13015,13018,13020,13022,13025,13028,13030,13032,13035,13038,13040,13042,13044,13046,13049,13052,13055],{"class":3413,"line":9800},[3411,13011,4400],{"class":4143},[3411,13013,13014],{"class":3432}," {",[3411,13016,13017],{"class":3828},"\"hits\"",[3411,13019,3495],{"class":3432},[3411,13021,3450],{"class":3424},[3411,13023,13024],{"class":3432},"._hits, ",[3411,13026,13027],{"class":3828},"\"misses\"",[3411,13029,3495],{"class":3432},[3411,13031,3450],{"class":3424},[3411,13033,13034],{"class":3432},"._misses, ",[3411,13036,13037],{"class":3828},"\"hit_rate\"",[3411,13039,3495],{"class":3432},[3411,13041,3825],{"class":3424},[3411,13043,3849],{"class":3828},[3411,13045,3832],{"class":3424},[3411,13047,13048],{"class":3432},"hit_rate",[3411,13050,13051],{"class":3424},":.1f}",[3411,13053,13054],{"class":3828},"%\"",[3411,13056,3670],{"class":3432},[3411,13058,13059],{"class":3413,"line":9805},[3411,13060,3475],{"emptyLinePlaceholder":3474},[3411,13062,13063],{"class":3413,"line":9838},[3411,13064,3475],{"emptyLinePlaceholder":3474},[3411,13066,13067],{"class":3413,"line":9846},[3411,13068,13069],{"class":3417},"# Демонстрація\n",[3411,13071,13072,13074],{"class":3413,"line":9851},[3411,13073,4150],{"class":4143},[3411,13075,13076],{"class":3432}," time\n",[3411,13078,13079],{"class":3413,"line":9880},[3411,13080,3475],{"emptyLinePlaceholder":3474},[3411,13082,13083],{"class":3413,"line":9890},[3411,13084,13085],{"class":3417},"# Використання з TTLMemoryCache\n",[3411,13087,13088],{"class":3413,"line":9895},[3411,13089,13090],{"class":3432},"service = CacheService(TTLMemoryCache())\n",[3411,13092,13093],{"class":3413,"line":9920},[3411,13094,3475],{"emptyLinePlaceholder":3474},[3411,13096,13097,13099,13102,13104,13106],{"class":3413,"line":9935},[3411,13098,6492],{"class":3424},[3411,13100,13101],{"class":3442}," expensive_query",[3411,13103,10598],{"class":3432},[3411,13105,5176],{"class":3428},[3411,13107,3433],{"class":3432},[3411,13109,13110,13112,13114,13117],{"class":3413,"line":9946},[3411,13111,4585],{"class":3442},[3411,13113,3446],{"class":3432},[3411,13115,13116],{"class":3828},"\"  [DB] Виконання дорогого запиту...\"",[3411,13118,3852],{"class":3432},[3411,13120,13121,13124,13127,13129],{"class":3413,"line":9953},[3411,13122,13123],{"class":3432},"    time.sleep(",[3411,13125,13126],{"class":5495},"0.01",[3411,13128,4930],{"class":3432},[3411,13130,13131],{"class":3417},"# Імітація роботи\n",[3411,13133,13134,13136,13138,13141,13143,13146,13148,13150,13152,13155],{"class":3413,"line":9960},[3411,13135,6526],{"class":4143},[3411,13137,13014],{"class":3432},[3411,13139,13140],{"class":3828},"\"users\"",[3411,13142,3495],{"class":3432},[3411,13144,13145],{"class":5495},"1500",[3411,13147,3453],{"class":3432},[3411,13149,10660],{"class":3828},[3411,13151,3495],{"class":3432},[3411,13153,13154],{"class":5495},"1200",[3411,13156,3670],{"class":3432},[3411,13158,13159],{"class":3413,"line":9965},[3411,13160,3475],{"emptyLinePlaceholder":3474},[3411,13162,13163,13165,13167,13170],{"class":3413,"line":9972},[3411,13164,4939],{"class":3442},[3411,13166,3446],{"class":3432},[3411,13168,13169],{"class":3828},"\"Перший запит (cache miss):\"",[3411,13171,3852],{"class":3432},[3411,13173,13174,13177,13180,13183,13185,13187,13190],{"class":3413,"line":9990},[3411,13175,13176],{"class":3432},"result = service.get_or_compute(",[3411,13178,13179],{"class":3828},"\"stats:dashboard\"",[3411,13181,13182],{"class":3432},", expensive_query, ",[3411,13184,12098],{"class":3449},[3411,13186,6052],{"class":3432},[3411,13188,13189],{"class":5495},"60",[3411,13191,3852],{"class":3432},[3411,13193,13194,13196,13198,13200,13203,13205,13208,13210,13212],{"class":3413,"line":10005},[3411,13195,4939],{"class":3442},[3411,13197,3446],{"class":3432},[3411,13199,3825],{"class":3424},[3411,13201,13202],{"class":3828},"\"  Результат: ",[3411,13204,3832],{"class":3424},[3411,13206,13207],{"class":3432},"result",[3411,13209,3837],{"class":3424},[3411,13211,3849],{"class":3828},[3411,13213,3852],{"class":3432},[3411,13215,13216],{"class":3413,"line":10010},[3411,13217,3475],{"emptyLinePlaceholder":3474},[3411,13219,13220,13222,13224,13227],{"class":3413,"line":10015},[3411,13221,4939],{"class":3442},[3411,13223,3446],{"class":3432},[3411,13225,13226],{"class":3828},"\"Другий запит (cache hit):\"",[3411,13228,3852],{"class":3432},[3411,13230,13231,13233,13235,13237,13239,13241,13243],{"class":3413,"line":10029},[3411,13232,13176],{"class":3432},[3411,13234,13179],{"class":3828},[3411,13236,13182],{"class":3432},[3411,13238,12098],{"class":3449},[3411,13240,6052],{"class":3432},[3411,13242,13189],{"class":5495},[3411,13244,3852],{"class":3432},[3411,13246,13247,13249,13251,13253,13255,13257,13259,13261,13263],{"class":3413,"line":10035},[3411,13248,4939],{"class":3442},[3411,13250,3446],{"class":3432},[3411,13252,3825],{"class":3424},[3411,13254,13202],{"class":3828},[3411,13256,3832],{"class":3424},[3411,13258,13207],{"class":3432},[3411,13260,3837],{"class":3424},[3411,13262,3849],{"class":3828},[3411,13264,3852],{"class":3432},[3411,13266,13267],{"class":3413,"line":10040},[3411,13268,3475],{"emptyLinePlaceholder":3474},[3411,13270,13271,13273,13275,13277,13280,13282,13285,13287,13289],{"class":3413,"line":10072},[3411,13272,4939],{"class":3442},[3411,13274,3446],{"class":3432},[3411,13276,3825],{"class":3424},[3411,13278,13279],{"class":3828},"\"Статистика: ",[3411,13281,3832],{"class":3424},[3411,13283,13284],{"class":3432},"service.stats",[3411,13286,3837],{"class":3424},[3411,13288,3849],{"class":3828},[3411,13290,3852],{"class":3432},[3411,13292,13293],{"class":3413,"line":10083},[3411,13294,3475],{"emptyLinePlaceholder":3474},[3411,13296,13297],{"class":3413,"line":10091},[3411,13298,13299],{"class":3417},"# Перевіряємо відповідність Protocol\n",[3411,13301,13302,13304,13306,13308,13310,13314,13317,13319,13321,13324,13326,13328],{"class":3413,"line":10096},[3411,13303,4939],{"class":3442},[3411,13305,3446],{"class":3432},[3411,13307,3825],{"class":3424},[3411,13309,3849],{"class":3828},[3411,13311,13313],{"class":13312},"sjcCO","\\n",[3411,13315,13316],{"class":3828},"isinstance(TTLMemoryCache(), CacheBackend): ",[3411,13318,3832],{"class":3424},[3411,13320,4944],{"class":3442},[3411,13322,13323],{"class":3432},"(TTLMemoryCache(), CacheBackend)",[3411,13325,3837],{"class":3424},[3411,13327,3849],{"class":3828},[3411,13329,3852],{"class":3432},[3411,13331,13332,13334,13336,13338,13341,13343,13345,13348,13350,13352],{"class":3413,"line":10129},[3411,13333,4939],{"class":3442},[3411,13335,3446],{"class":3432},[3411,13337,3825],{"class":3424},[3411,13339,13340],{"class":3828},"\"isinstance(SimpleMemoryCache(), CacheBackend): ",[3411,13342,3832],{"class":3424},[3411,13344,4944],{"class":3442},[3411,13346,13347],{"class":3432},"(SimpleMemoryCache(), CacheBackend)",[3411,13349,3837],{"class":3424},[3411,13351,3849],{"class":3828},[3411,13353,3852],{"class":3432},[4952,13355,13357,13365,13369,13377,13385,13389,13399,13407,13410,13416],{"title":13356},"python cache_system.py",[4956,13358,13360,4964,13363],{"className":13359},[3413],[3411,13361,4963],{"className":13362},[4962],[3547,13364,13356],{},[4956,13366,13368],{"className":13367},[3413],"Перший запит (cache miss):",[4956,13370,13372,13373],{"className":13371},[3413],"  [DB] ",[3411,13374,13376],{"className":13375},[6433],"Виконання дорогого запиту...",[4956,13378,13380,13381],{"className":13379},[3413],"  Результат: ",[3411,13382,13384],{"className":13383},[4990],"{'users': 1500, 'active': 1200}",[4956,13386,13388],{"className":13387},[3413],"Другий запит (cache hit):",[4956,13390,13380,13392,4964,13395],{"className":13391},[3413],[3411,13393,13384],{"className":13394},[4990],[3411,13396,13398],{"className":13397},[7299],"# без запиту до DB!",[4956,13400,13402,13403],{"className":13401},[3413],"Статистика: ",[3411,13404,13406],{"className":13405},[6424],"{'hits': 1, 'misses': 1, 'hit_rate': '50.0%'}",[4956,13408],{"className":13409},[3413],[4956,13411,13316,13413],{"className":13412},[3413],[3411,13414,4998],{"className":13415},[4990],[4956,13417,13419,13420],{"className":13418},[3413],"isinstance(SimpleMemoryCache(), CacheBackend): ",[3411,13421,4998],{"className":13422},[4990],[3584,13424],{},[3591,13426,13428],{"id":13427},"рівень-3-advanced-реєстр-плагінів-та-валідація-через-mypy","Рівень 3 (Advanced): Реєстр плагінів та валідація через mypy",[3394,13430,13431,13433,13434,3617],{},[3547,13432,11042],{}," Побудуйте систему плагінів для обробки файлів різних форматів, що використовує ABC для базового класу плагіна та Protocol для опису можливостей. Додайте автоматичний реєстр плагінів через ",[3408,13435,13436],{},"__init_subclass__",[3401,13438,13440],{"className":3403,"code":13439,"language":3405,"meta":3406,"style":3406},"# plugin_system.py\nfrom abc import ABC, abstractmethod\nfrom typing import Protocol, ClassVar, runtime_checkable\n\n\n# ── Протоколи можливостей ─────────────────────────────────────────────────────\n\n@runtime_checkable\nclass Streamable(Protocol):\n    \"\"\"Протокол для плагінів, що підтримують потокову обробку.\"\"\"\n\n    def process_stream(self, chunk_size: int = 1024) -> \"Generator[bytes, None, None]\": ...\n\n\n@runtime_checkable\nclass Compressible(Protocol):\n    \"\"\"Протокол для плагінів, що підтримують стиснення.\"\"\"\n\n    def compress(self, data: bytes, level: int = 6) -> bytes: ...\n    def decompress(self, data: bytes) -> bytes: ...\n\n\n# ── Базовий клас плагіна (ABC) ────────────────────────────────────────────────\n\nclass FileProcessorPlugin(ABC):\n    \"\"\"\n    Базовий клас для всіх плагінів обробки файлів.\n    Реалізує автоматичну реєстрацію через __init_subclass__.\n    \"\"\"\n\n    # Реєстр усіх зареєстрованих плагінів: {розширення: клас}\n    _registry: ClassVar[dict[str, type[\"FileProcessorPlugin\"]]] = {}\n\n    def __init_subclass__(cls, extensions: list[str] | None = None, **kwargs) -> None:\n        \"\"\"\n        Викликається автоматично при оголошенні підкласу.\n        Реєструє плагін для вказаних розширень файлів.\n        \"\"\"\n        super().__init_subclass__(**kwargs)\n        if extensions:\n            for ext in extensions:\n                FileProcessorPlugin._registry[ext.lower()] = cls\n                print(f\"[Registry] Плагін {cls.__name__!r} зареєстровано для '.{ext}'\")\n\n    @classmethod\n    def for_extension(cls, ext: str) -> \"FileProcessorPlugin | None\":\n        \"\"\"Фабричний метод: повертає екземпляр плагіна для розширення.\"\"\"\n        plugin_cls = cls._registry.get(ext.lower())\n        return plugin_cls() if plugin_cls else None\n\n    @classmethod\n    def supported_extensions(cls) -> list[str]:\n        return list(cls._registry.keys())\n\n    @property\n    @abstractmethod\n    def name(self) -> str:\n        \"\"\"Людиночитана назва плагіна.\"\"\"\n        ...\n\n    @abstractmethod\n    def can_process(self, filepath: str) -> bool:\n        \"\"\"Перевіряє, чи може плагін обробити файл.\"\"\"\n        ...\n\n    @abstractmethod\n    def process(self, data: bytes) -> bytes:\n        \"\"\"Виконує основну обробку даних.\"\"\"\n        ...\n\n    def get_capabilities(self) -> list[str]:\n        \"\"\"Повертає список підтримуваних можливостей.\"\"\"\n        caps = [\"process\"]\n        if isinstance(self, Streamable):\n            caps.append(\"streaming\")\n        if isinstance(self, Compressible):\n            caps.append(\"compression\")\n        return caps\n\n\n# ── Конкретні плагіни ─────────────────────────────────────────────────────────\n\nclass JsonPlugin(FileProcessorPlugin, extensions=[\"json\", \"jsonl\"]):\n    \"\"\"Плагін для обробки JSON-файлів.\"\"\"\n\n    @property\n    def name(self) -> str:\n        return \"JSON Processor\"\n\n    def can_process(self, filepath: str) -> bool:\n        return filepath.endswith((\".json\", \".jsonl\"))\n\n    def process(self, data: bytes) -> bytes:\n        import json\n        parsed = json.loads(data)\n        # Мінімізація: видалення пробілів\n        return json.dumps(parsed, separators=(\",\", \":\")).encode()\n\n\nclass CsvPlugin(FileProcessorPlugin, extensions=[\"csv\", \"tsv\"]):\n    \"\"\"Плагін для обробки CSV-файлів з підтримкою стиснення.\"\"\"\n\n    @property\n    def name(self) -> str:\n        return \"CSV Processor\"\n\n    def can_process(self, filepath: str) -> bool:\n        return filepath.endswith((\".csv\", \".tsv\"))\n\n    def process(self, data: bytes) -> bytes:\n        lines = data.decode().splitlines()\n        cleaned = [line.strip() for line in lines if line.strip()]\n        return \"\\n\".join(cleaned).encode()\n\n    def compress(self, data: bytes, level: int = 6) -> bytes:\n        import zlib\n        return zlib.compress(data, level)\n\n    def decompress(self, data: bytes) -> bytes:\n        import zlib\n        return zlib.decompress(data)\n\n\n# ── Демонстрація реєстру ──────────────────────────────────────────────────────\n\nprint(\"\\n--- Реєстр плагінів ---\")\nprint(f\"Підтримувані розширення: {FileProcessorPlugin.supported_extensions()}\")\n\n# Автоматичний вибір плагіна за розширенням\nfor filename in [\"data.json\", \"report.csv\", \"image.png\"]:\n    ext = filename.rsplit(\".\", 1)[-1]\n    plugin = FileProcessorPlugin.for_extension(ext)\n    if plugin:\n        print(f\"\\nФайл: {filename}\")\n        print(f\"  Плагін: {plugin.name}\")\n        print(f\"  Можливості: {plugin.get_capabilities()}\")\n        print(f\"  Підтримує стиснення: {isinstance(plugin, Compressible)}\")\n    else:\n        print(f\"\\nФайл: {filename} → ❌ Плагін не знайдено\")\n",[3408,13441,13442,13447,13457,13468,13472,13476,13481,13485,13489,13502,13507,13511,13542,13546,13550,13554,13567,13572,13576,13614,13639,13643,13647,13652,13656,13665,13669,13674,13679,13683,13687,13692,13708,13712,13753,13757,13762,13767,13771,13782,13789,13801,13809,13845,13849,13855,13881,13886,13896,13912,13916,13922,13940,13954,13958,13964,13968,13985,13990,13994,13998,14002,14028,14033,14037,14041,14045,14070,14075,14079,14083,14100,14105,14115,14128,14138,14151,14160,14167,14171,14175,14180,14184,14214,14219,14223,14229,14245,14252,14256,14280,14297,14301,14325,14331,14336,14341,14365,14369,14373,14400,14405,14409,14415,14431,14438,14442,14466,14482,14486,14510,14515,14535,14549,14553,14589,14596,14603,14607,14631,14637,14644,14648,14652,14657,14661,14676,14698,14702,14707,14734,14753,14758,14766,14792,14814,14836,14860,14867],{"__ignoreMap":3406},[3411,13443,13444],{"class":3413,"line":3414},[3411,13445,13446],{"class":3417},"# plugin_system.py\n",[3411,13448,13449,13451,13453,13455],{"class":3413,"line":3421},[3411,13450,4144],{"class":4143},[3411,13452,4147],{"class":3432},[3411,13454,4150],{"class":4143},[3411,13456,4153],{"class":3432},[3411,13458,13459,13461,13463,13465],{"class":3413,"line":3436},[3411,13460,4144],{"class":4143},[3411,13462,6570],{"class":3432},[3411,13464,4150],{"class":4143},[3411,13466,13467],{"class":3432}," Protocol, ClassVar, runtime_checkable\n",[3411,13469,13470],{"class":3413,"line":3462},[3411,13471,3475],{"emptyLinePlaceholder":3474},[3411,13473,13474],{"class":3413,"line":3471},[3411,13475,3475],{"emptyLinePlaceholder":3474},[3411,13477,13478],{"class":3413,"line":3478},[3411,13479,13480],{"class":3417},"# ── Протоколи можливостей ─────────────────────────────────────────────────────\n",[3411,13482,13483],{"class":3413,"line":3519},[3411,13484,3475],{"emptyLinePlaceholder":3474},[3411,13486,13487],{"class":3413,"line":3525},[3411,13488,7407],{"class":3442},[3411,13490,13491,13493,13496,13498,13500],{"class":3413,"line":3531},[3411,13492,3425],{"class":3424},[3411,13494,13495],{"class":3428}," Streamable",[3411,13497,3446],{"class":3432},[3411,13499,3888],{"class":3428},[3411,13501,3459],{"class":3432},[3411,13503,13504],{"class":3413,"line":3949},[3411,13505,13506],{"class":3828},"    \"\"\"Протокол для плагінів, що підтримують потокову обробку.\"\"\"\n",[3411,13508,13509],{"class":3413,"line":3954},[3411,13510,3475],{"emptyLinePlaceholder":3474},[3411,13512,13513,13515,13517,13519,13521,13523,13526,13528,13530,13532,13535,13537,13540],{"class":3413,"line":3960},[3411,13514,3439],{"class":3424},[3411,13516,8424],{"class":3442},[3411,13518,3446],{"class":3432},[3411,13520,3450],{"class":3449},[3411,13522,3453],{"class":3432},[3411,13524,13525],{"class":3449},"chunk_size",[3411,13527,3495],{"class":3432},[3411,13529,3498],{"class":3428},[3411,13531,9575],{"class":3432},[3411,13533,13534],{"class":5495},"1024",[3411,13536,3511],{"class":3432},[3411,13538,13539],{"class":3828},"\"Generator[bytes, None, None]\"",[3411,13541,4521],{"class":3432},[3411,13543,13544],{"class":3413,"line":3966},[3411,13545,3475],{"emptyLinePlaceholder":3474},[3411,13547,13548],{"class":3413,"line":3972},[3411,13549,3475],{"emptyLinePlaceholder":3474},[3411,13551,13552],{"class":3413,"line":3977},[3411,13553,7407],{"class":3442},[3411,13555,13556,13558,13561,13563,13565],{"class":3413,"line":3982},[3411,13557,3425],{"class":3424},[3411,13559,13560],{"class":3428}," Compressible",[3411,13562,3446],{"class":3432},[3411,13564,3888],{"class":3428},[3411,13566,3459],{"class":3432},[3411,13568,13569],{"class":3413,"line":3988},[3411,13570,13571],{"class":3828},"    \"\"\"Протокол для плагінів, що підтримують стиснення.\"\"\"\n",[3411,13573,13574],{"class":3413,"line":3994},[3411,13575,3475],{"emptyLinePlaceholder":3474},[3411,13577,13578,13580,13583,13585,13587,13589,13591,13593,13595,13597,13600,13602,13604,13606,13608,13610,13612],{"class":3413,"line":4000},[3411,13579,3439],{"class":3424},[3411,13581,13582],{"class":3442}," compress",[3411,13584,3446],{"class":3432},[3411,13586,3450],{"class":3449},[3411,13588,3453],{"class":3432},[3411,13590,8306],{"class":3449},[3411,13592,3495],{"class":3432},[3411,13594,8271],{"class":3428},[3411,13596,3453],{"class":3432},[3411,13598,13599],{"class":3449},"level",[3411,13601,3495],{"class":3432},[3411,13603,3498],{"class":3428},[3411,13605,9575],{"class":3432},[3411,13607,11791],{"class":5495},[3411,13609,3511],{"class":3432},[3411,13611,8271],{"class":3428},[3411,13613,4521],{"class":3432},[3411,13615,13616,13618,13621,13623,13625,13627,13629,13631,13633,13635,13637],{"class":3413,"line":4005},[3411,13617,3439],{"class":3424},[3411,13619,13620],{"class":3442}," decompress",[3411,13622,3446],{"class":3432},[3411,13624,3450],{"class":3449},[3411,13626,3453],{"class":3432},[3411,13628,8306],{"class":3449},[3411,13630,3495],{"class":3432},[3411,13632,8271],{"class":3428},[3411,13634,3511],{"class":3432},[3411,13636,8271],{"class":3428},[3411,13638,4521],{"class":3432},[3411,13640,13641],{"class":3413,"line":4011},[3411,13642,3475],{"emptyLinePlaceholder":3474},[3411,13644,13645],{"class":3413,"line":4017},[3411,13646,3475],{"emptyLinePlaceholder":3474},[3411,13648,13649],{"class":3413,"line":4022},[3411,13650,13651],{"class":3417},"# ── Базовий клас плагіна (ABC) ────────────────────────────────────────────────\n",[3411,13653,13654],{"class":3413,"line":4028},[3411,13655,3475],{"emptyLinePlaceholder":3474},[3411,13657,13658,13660,13663],{"class":3413,"line":4034},[3411,13659,3425],{"class":3424},[3411,13661,13662],{"class":3428}," FileProcessorPlugin",[3411,13664,4167],{"class":3432},[3411,13666,13667],{"class":3413,"line":4039},[3411,13668,4172],{"class":3828},[3411,13670,13671],{"class":3413,"line":4045},[3411,13672,13673],{"class":3828},"    Базовий клас для всіх плагінів обробки файлів.\n",[3411,13675,13676],{"class":3413,"line":4051},[3411,13677,13678],{"class":3828},"    Реалізує автоматичну реєстрацію через __init_subclass__.\n",[3411,13680,13681],{"class":3413,"line":4056},[3411,13682,4172],{"class":3828},[3411,13684,13685],{"class":3413,"line":4326},[3411,13686,3475],{"emptyLinePlaceholder":3474},[3411,13688,13689],{"class":3413,"line":4369},[3411,13690,13691],{"class":3417},"    # Реєстр усіх зареєстрованих плагінів: {розширення: клас}\n",[3411,13693,13694,13697,13699,13702,13705],{"class":3413,"line":4374},[3411,13695,13696],{"class":3432},"    _registry: ClassVar[dict[",[3411,13698,3508],{"class":3428},[3411,13700,13701],{"class":3432},", type[",[3411,13703,13704],{"class":3828},"\"FileProcessorPlugin\"",[3411,13706,13707],{"class":3432},"]]] = {}\n",[3411,13709,13710],{"class":3413,"line":4380},[3411,13711,3475],{"emptyLinePlaceholder":3474},[3411,13713,13714,13716,13719,13721,13723,13725,13728,13730,13732,13735,13737,13739,13741,13744,13747,13749,13751],{"class":3413,"line":4386},[3411,13715,3439],{"class":3424},[3411,13717,13718],{"class":3442}," __init_subclass__",[3411,13720,3446],{"class":3432},[3411,13722,5166],{"class":3449},[3411,13724,3453],{"class":3432},[3411,13726,13727],{"class":3449},"extensions",[3411,13729,4343],{"class":3432},[3411,13731,3508],{"class":3428},[3411,13733,13734],{"class":3432},"] | ",[3411,13736,3514],{"class":3424},[3411,13738,9575],{"class":3432},[3411,13740,3514],{"class":3424},[3411,13742,13743],{"class":3432},", **",[3411,13745,13746],{"class":3449},"kwargs",[3411,13748,3511],{"class":3432},[3411,13750,3514],{"class":3424},[3411,13752,3433],{"class":3432},[3411,13754,13755],{"class":3413,"line":4392},[3411,13756,4233],{"class":3828},[3411,13758,13759],{"class":3413,"line":4397},[3411,13760,13761],{"class":3828},"        Викликається автоматично при оголошенні підкласу.\n",[3411,13763,13764],{"class":3413,"line":4802},[3411,13765,13766],{"class":3828},"        Реєструє плагін для вказаних розширень файлів.\n",[3411,13768,13769],{"class":3413,"line":4835},[3411,13770,4233],{"class":3828},[3411,13772,13773,13775,13777,13779],{"class":3413,"line":4865},[3411,13774,9772],{"class":3428},[3411,13776,9775],{"class":3432},[3411,13778,13436],{"class":3442},[3411,13780,13781],{"class":3432},"(**kwargs)\n",[3411,13783,13784,13786],{"class":3413,"line":4872},[3411,13785,9061],{"class":4143},[3411,13787,13788],{"class":3432}," extensions:\n",[3411,13790,13791,13794,13797,13799],{"class":3413,"line":4877},[3411,13792,13793],{"class":4143},"            for",[3411,13795,13796],{"class":3432}," ext ",[3411,13798,4417],{"class":4143},[3411,13800,13788],{"class":3432},[3411,13802,13803,13806],{"class":3413,"line":4894},[3411,13804,13805],{"class":3432},"                FileProcessorPlugin._registry[ext.lower()] = ",[3411,13807,13808],{"class":3424},"cls\n",[3411,13810,13811,13813,13815,13817,13820,13823,13825,13827,13830,13833,13835,13838,13840,13843],{"class":3413,"line":4904},[3411,13812,9659],{"class":3442},[3411,13814,3446],{"class":3432},[3411,13816,3825],{"class":3424},[3411,13818,13819],{"class":3828},"\"[Registry] Плагін ",[3411,13821,13822],{"class":3424},"{cls",[3411,13824,3617],{"class":3432},[3411,13826,9106],{"class":3449},[3411,13828,13829],{"class":3424},"!r}",[3411,13831,13832],{"class":3828}," зареєстровано для '.",[3411,13834,3832],{"class":3424},[3411,13836,13837],{"class":3432},"ext",[3411,13839,3837],{"class":3424},[3411,13841,13842],{"class":3828},"'\"",[3411,13844,3852],{"class":3432},[3411,13846,13847],{"class":3413,"line":4916},[3411,13848,3475],{"emptyLinePlaceholder":3474},[3411,13850,13851,13853],{"class":3413,"line":4921},[3411,13852,3697],{"class":3442},[3411,13854,5150],{"class":3428},[3411,13856,13857,13859,13862,13864,13866,13868,13870,13872,13874,13876,13879],{"class":3413,"line":4936},[3411,13858,3439],{"class":3424},[3411,13860,13861],{"class":3442}," for_extension",[3411,13863,3446],{"class":3432},[3411,13865,5166],{"class":3449},[3411,13867,3453],{"class":3432},[3411,13869,13837],{"class":3449},[3411,13871,3495],{"class":3432},[3411,13873,3508],{"class":3428},[3411,13875,3511],{"class":3432},[3411,13877,13878],{"class":3828},"\"FileProcessorPlugin | None\"",[3411,13880,3433],{"class":3432},[3411,13882,13883],{"class":3413,"line":5508},[3411,13884,13885],{"class":3828},"        \"\"\"Фабричний метод: повертає екземпляр плагіна для розширення.\"\"\"\n",[3411,13887,13888,13891,13893],{"class":3413,"line":5513},[3411,13889,13890],{"class":3432},"        plugin_cls = ",[3411,13892,5166],{"class":3424},[3411,13894,13895],{"class":3432},"._registry.get(ext.lower())\n",[3411,13897,13898,13900,13903,13905,13908,13910],{"class":3413,"line":5520},[3411,13899,4400],{"class":4143},[3411,13901,13902],{"class":3432}," plugin_cls() ",[3411,13904,5645],{"class":4143},[3411,13906,13907],{"class":3432}," plugin_cls ",[3411,13909,12620],{"class":4143},[3411,13911,9439],{"class":3424},[3411,13913,13914],{"class":3413,"line":5541},[3411,13915,3475],{"emptyLinePlaceholder":3474},[3411,13917,13918,13920],{"class":3413,"line":5566},[3411,13919,3697],{"class":3442},[3411,13921,5150],{"class":3428},[3411,13923,13924,13926,13929,13931,13933,13936,13938],{"class":3413,"line":5571},[3411,13925,3439],{"class":3424},[3411,13927,13928],{"class":3442}," supported_extensions",[3411,13930,3446],{"class":3432},[3411,13932,5166],{"class":3449},[3411,13934,13935],{"class":3432},") -> list[",[3411,13937,3508],{"class":3428},[3411,13939,4366],{"class":3432},[3411,13941,13942,13944,13947,13949,13951],{"class":3413,"line":5596},[3411,13943,4400],{"class":4143},[3411,13945,13946],{"class":3428}," list",[3411,13948,3446],{"class":3432},[3411,13950,5166],{"class":3424},[3411,13952,13953],{"class":3432},"._registry.keys())\n",[3411,13955,13956],{"class":3413,"line":5618},[3411,13957,3475],{"emptyLinePlaceholder":3474},[3411,13959,13960,13962],{"class":3413,"line":5626},[3411,13961,3697],{"class":3442},[3411,13963,5104],{"class":3428},[3411,13965,13966],{"class":3413,"line":5631},[3411,13967,4195],{"class":3442},[3411,13969,13970,13972,13975,13977,13979,13981,13983],{"class":3413,"line":5636},[3411,13971,3439],{"class":3424},[3411,13973,13974],{"class":3442}," name",[3411,13976,3446],{"class":3432},[3411,13978,3450],{"class":3449},[3411,13980,3511],{"class":3432},[3411,13982,3508],{"class":3428},[3411,13984,3433],{"class":3432},[3411,13986,13987],{"class":3413,"line":5642},[3411,13988,13989],{"class":3828},"        \"\"\"Людиночитана назва плагіна.\"\"\"\n",[3411,13991,13992],{"class":3413,"line":5670},[3411,13993,4280],{"class":3432},[3411,13995,13996],{"class":3413,"line":5693},[3411,13997,3475],{"emptyLinePlaceholder":3474},[3411,13999,14000],{"class":3413,"line":5704},[3411,14001,4195],{"class":3442},[3411,14003,14004,14006,14009,14011,14013,14015,14018,14020,14022,14024,14026],{"class":3413,"line":9401},[3411,14005,3439],{"class":3424},[3411,14007,14008],{"class":3442}," can_process",[3411,14010,3446],{"class":3432},[3411,14012,3450],{"class":3449},[3411,14014,3453],{"class":3432},[3411,14016,14017],{"class":3449},"filepath",[3411,14019,3495],{"class":3432},[3411,14021,3508],{"class":3428},[3411,14023,3511],{"class":3432},[3411,14025,4226],{"class":3428},[3411,14027,3433],{"class":3432},[3411,14029,14030],{"class":3413,"line":9407},[3411,14031,14032],{"class":3828},"        \"\"\"Перевіряє, чи може плагін обробити файл.\"\"\"\n",[3411,14034,14035],{"class":3413,"line":9417},[3411,14036,4280],{"class":3432},[3411,14038,14039],{"class":3413,"line":9433},[3411,14040,3475],{"emptyLinePlaceholder":3474},[3411,14042,14043],{"class":3413,"line":9442},[3411,14044,4195],{"class":3442},[3411,14046,14047,14049,14052,14054,14056,14058,14060,14062,14064,14066,14068],{"class":3413,"line":9452},[3411,14048,3439],{"class":3424},[3411,14050,14051],{"class":3442}," process",[3411,14053,3446],{"class":3432},[3411,14055,3450],{"class":3449},[3411,14057,3453],{"class":3432},[3411,14059,8306],{"class":3449},[3411,14061,3495],{"class":3432},[3411,14063,8271],{"class":3428},[3411,14065,3511],{"class":3432},[3411,14067,8271],{"class":3428},[3411,14069,3433],{"class":3432},[3411,14071,14072],{"class":3413,"line":9457},[3411,14073,14074],{"class":3828},"        \"\"\"Виконує основну обробку даних.\"\"\"\n",[3411,14076,14077],{"class":3413,"line":9492},[3411,14078,4280],{"class":3432},[3411,14080,14081],{"class":3413,"line":9498},[3411,14082,3475],{"emptyLinePlaceholder":3474},[3411,14084,14085,14087,14090,14092,14094,14096,14098],{"class":3413,"line":9509},[3411,14086,3439],{"class":3424},[3411,14088,14089],{"class":3442}," get_capabilities",[3411,14091,3446],{"class":3432},[3411,14093,3450],{"class":3449},[3411,14095,13935],{"class":3432},[3411,14097,3508],{"class":3428},[3411,14099,4366],{"class":3432},[3411,14101,14102],{"class":3413,"line":9533},[3411,14103,14104],{"class":3828},"        \"\"\"Повертає список підтримуваних можливостей.\"\"\"\n",[3411,14106,14107,14110,14113],{"class":3413,"line":9538},[3411,14108,14109],{"class":3432},"        caps = [",[3411,14111,14112],{"class":3828},"\"process\"",[3411,14114,11826],{"class":3432},[3411,14116,14117,14119,14121,14123,14125],{"class":3413,"line":9587},[3411,14118,9061],{"class":4143},[3411,14120,9067],{"class":3442},[3411,14122,3446],{"class":3432},[3411,14124,3450],{"class":3424},[3411,14126,14127],{"class":3432},", Streamable):\n",[3411,14129,14130,14133,14136],{"class":3413,"line":9593},[3411,14131,14132],{"class":3432},"            caps.append(",[3411,14134,14135],{"class":3828},"\"streaming\"",[3411,14137,3852],{"class":3432},[3411,14139,14140,14142,14144,14146,14148],{"class":3413,"line":9618},[3411,14141,9061],{"class":4143},[3411,14143,9067],{"class":3442},[3411,14145,3446],{"class":3432},[3411,14147,3450],{"class":3424},[3411,14149,14150],{"class":3432},", Compressible):\n",[3411,14152,14153,14155,14158],{"class":3413,"line":9626},[3411,14154,14132],{"class":3432},[3411,14156,14157],{"class":3828},"\"compression\"",[3411,14159,3852],{"class":3432},[3411,14161,14162,14164],{"class":3413,"line":9635},[3411,14163,4400],{"class":4143},[3411,14165,14166],{"class":3432}," caps\n",[3411,14168,14169],{"class":3413,"line":9643},[3411,14170,3475],{"emptyLinePlaceholder":3474},[3411,14172,14173],{"class":3413,"line":9656},[3411,14174,3475],{"emptyLinePlaceholder":3474},[3411,14176,14177],{"class":3413,"line":9697},[3411,14178,14179],{"class":3417},"# ── Конкретні плагіни ─────────────────────────────────────────────────────────\n",[3411,14181,14182],{"class":3413,"line":9705},[3411,14183,3475],{"emptyLinePlaceholder":3474},[3411,14185,14186,14188,14191,14193,14196,14198,14200,14203,14206,14208,14211],{"class":3413,"line":9710},[3411,14187,3425],{"class":3424},[3411,14189,14190],{"class":3428}," JsonPlugin",[3411,14192,3446],{"class":3432},[3411,14194,14195],{"class":3428},"FileProcessorPlugin",[3411,14197,3453],{"class":3432},[3411,14199,13727],{"class":3449},[3411,14201,14202],{"class":3432},"=[",[3411,14204,14205],{"class":3828},"\"json\"",[3411,14207,3453],{"class":3432},[3411,14209,14210],{"class":3828},"\"jsonl\"",[3411,14212,14213],{"class":3432},"]):\n",[3411,14215,14216],{"class":3413,"line":9715},[3411,14217,14218],{"class":3828},"    \"\"\"Плагін для обробки JSON-файлів.\"\"\"\n",[3411,14220,14221],{"class":3413,"line":9721},[3411,14222,3475],{"emptyLinePlaceholder":3474},[3411,14224,14225,14227],{"class":3413,"line":9726},[3411,14226,3697],{"class":3442},[3411,14228,5104],{"class":3428},[3411,14230,14231,14233,14235,14237,14239,14241,14243],{"class":3413,"line":9741},[3411,14232,3439],{"class":3424},[3411,14234,13974],{"class":3442},[3411,14236,3446],{"class":3432},[3411,14238,3450],{"class":3449},[3411,14240,3511],{"class":3432},[3411,14242,3508],{"class":3428},[3411,14244,3433],{"class":3432},[3411,14246,14247,14249],{"class":3413,"line":9747},[3411,14248,4400],{"class":4143},[3411,14250,14251],{"class":3828}," \"JSON Processor\"\n",[3411,14253,14254],{"class":3413,"line":9752},[3411,14255,3475],{"emptyLinePlaceholder":3474},[3411,14257,14258,14260,14262,14264,14266,14268,14270,14272,14274,14276,14278],{"class":3413,"line":9769},[3411,14259,3439],{"class":3424},[3411,14261,14008],{"class":3442},[3411,14263,3446],{"class":3432},[3411,14265,3450],{"class":3449},[3411,14267,3453],{"class":3432},[3411,14269,14017],{"class":3449},[3411,14271,3495],{"class":3432},[3411,14273,3508],{"class":3428},[3411,14275,3511],{"class":3432},[3411,14277,4226],{"class":3428},[3411,14279,3433],{"class":3432},[3411,14281,14282,14284,14287,14290,14292,14295],{"class":3413,"line":9783},[3411,14283,4400],{"class":4143},[3411,14285,14286],{"class":3432}," filepath.endswith((",[3411,14288,14289],{"class":3828},"\".json\"",[3411,14291,3453],{"class":3432},[3411,14293,14294],{"class":3828},"\".jsonl\"",[3411,14296,10529],{"class":3432},[3411,14298,14299],{"class":3413,"line":9800},[3411,14300,3475],{"emptyLinePlaceholder":3474},[3411,14302,14303,14305,14307,14309,14311,14313,14315,14317,14319,14321,14323],{"class":3413,"line":9805},[3411,14304,3439],{"class":3424},[3411,14306,14051],{"class":3442},[3411,14308,3446],{"class":3432},[3411,14310,3450],{"class":3449},[3411,14312,3453],{"class":3432},[3411,14314,8306],{"class":3449},[3411,14316,3495],{"class":3432},[3411,14318,8271],{"class":3428},[3411,14320,3511],{"class":3432},[3411,14322,8271],{"class":3428},[3411,14324,3433],{"class":3432},[3411,14326,14327,14329],{"class":3413,"line":9838},[3411,14328,10332],{"class":4143},[3411,14330,8870],{"class":3432},[3411,14332,14333],{"class":3413,"line":9846},[3411,14334,14335],{"class":3432},"        parsed = json.loads(data)\n",[3411,14337,14338],{"class":3413,"line":9851},[3411,14339,14340],{"class":3417},"        # Мінімізація: видалення пробілів\n",[3411,14342,14343,14345,14348,14351,14354,14357,14359,14362],{"class":3413,"line":9880},[3411,14344,4400],{"class":4143},[3411,14346,14347],{"class":3432}," json.dumps(parsed, ",[3411,14349,14350],{"class":3449},"separators",[3411,14352,14353],{"class":3432},"=(",[3411,14355,14356],{"class":3828},"\",\"",[3411,14358,3453],{"class":3432},[3411,14360,14361],{"class":3828},"\":\"",[3411,14363,14364],{"class":3432},")).encode()\n",[3411,14366,14367],{"class":3413,"line":9890},[3411,14368,3475],{"emptyLinePlaceholder":3474},[3411,14370,14371],{"class":3413,"line":9895},[3411,14372,3475],{"emptyLinePlaceholder":3474},[3411,14374,14375,14377,14380,14382,14384,14386,14388,14390,14393,14395,14398],{"class":3413,"line":9920},[3411,14376,3425],{"class":3424},[3411,14378,14379],{"class":3428}," CsvPlugin",[3411,14381,3446],{"class":3432},[3411,14383,14195],{"class":3428},[3411,14385,3453],{"class":3432},[3411,14387,13727],{"class":3449},[3411,14389,14202],{"class":3432},[3411,14391,14392],{"class":3828},"\"csv\"",[3411,14394,3453],{"class":3432},[3411,14396,14397],{"class":3828},"\"tsv\"",[3411,14399,14213],{"class":3432},[3411,14401,14402],{"class":3413,"line":9935},[3411,14403,14404],{"class":3828},"    \"\"\"Плагін для обробки CSV-файлів з підтримкою стиснення.\"\"\"\n",[3411,14406,14407],{"class":3413,"line":9946},[3411,14408,3475],{"emptyLinePlaceholder":3474},[3411,14410,14411,14413],{"class":3413,"line":9953},[3411,14412,3697],{"class":3442},[3411,14414,5104],{"class":3428},[3411,14416,14417,14419,14421,14423,14425,14427,14429],{"class":3413,"line":9960},[3411,14418,3439],{"class":3424},[3411,14420,13974],{"class":3442},[3411,14422,3446],{"class":3432},[3411,14424,3450],{"class":3449},[3411,14426,3511],{"class":3432},[3411,14428,3508],{"class":3428},[3411,14430,3433],{"class":3432},[3411,14432,14433,14435],{"class":3413,"line":9965},[3411,14434,4400],{"class":4143},[3411,14436,14437],{"class":3828}," \"CSV Processor\"\n",[3411,14439,14440],{"class":3413,"line":9972},[3411,14441,3475],{"emptyLinePlaceholder":3474},[3411,14443,14444,14446,14448,14450,14452,14454,14456,14458,14460,14462,14464],{"class":3413,"line":9990},[3411,14445,3439],{"class":3424},[3411,14447,14008],{"class":3442},[3411,14449,3446],{"class":3432},[3411,14451,3450],{"class":3449},[3411,14453,3453],{"class":3432},[3411,14455,14017],{"class":3449},[3411,14457,3495],{"class":3432},[3411,14459,3508],{"class":3428},[3411,14461,3511],{"class":3432},[3411,14463,4226],{"class":3428},[3411,14465,3433],{"class":3432},[3411,14467,14468,14470,14472,14475,14477,14480],{"class":3413,"line":10005},[3411,14469,4400],{"class":4143},[3411,14471,14286],{"class":3432},[3411,14473,14474],{"class":3828},"\".csv\"",[3411,14476,3453],{"class":3432},[3411,14478,14479],{"class":3828},"\".tsv\"",[3411,14481,10529],{"class":3432},[3411,14483,14484],{"class":3413,"line":10010},[3411,14485,3475],{"emptyLinePlaceholder":3474},[3411,14487,14488,14490,14492,14494,14496,14498,14500,14502,14504,14506,14508],{"class":3413,"line":10015},[3411,14489,3439],{"class":3424},[3411,14491,14051],{"class":3442},[3411,14493,3446],{"class":3432},[3411,14495,3450],{"class":3449},[3411,14497,3453],{"class":3432},[3411,14499,8306],{"class":3449},[3411,14501,3495],{"class":3432},[3411,14503,8271],{"class":3428},[3411,14505,3511],{"class":3432},[3411,14507,8271],{"class":3428},[3411,14509,3433],{"class":3432},[3411,14511,14512],{"class":3413,"line":10029},[3411,14513,14514],{"class":3432},"        lines = data.decode().splitlines()\n",[3411,14516,14517,14520,14522,14525,14527,14530,14532],{"class":3413,"line":10035},[3411,14518,14519],{"class":3432},"        cleaned = [line.strip() ",[3411,14521,4411],{"class":4143},[3411,14523,14524],{"class":3432}," line ",[3411,14526,4417],{"class":4143},[3411,14528,14529],{"class":3432}," lines ",[3411,14531,5645],{"class":4143},[3411,14533,14534],{"class":3432}," line.strip()]\n",[3411,14536,14537,14539,14542,14544,14546],{"class":3413,"line":10040},[3411,14538,4400],{"class":4143},[3411,14540,14541],{"class":3828}," \"",[3411,14543,13313],{"class":13312},[3411,14545,3849],{"class":3828},[3411,14547,14548],{"class":3432},".join(cleaned).encode()\n",[3411,14550,14551],{"class":3413,"line":10072},[3411,14552,3475],{"emptyLinePlaceholder":3474},[3411,14554,14555,14557,14559,14561,14563,14565,14567,14569,14571,14573,14575,14577,14579,14581,14583,14585,14587],{"class":3413,"line":10083},[3411,14556,3439],{"class":3424},[3411,14558,13582],{"class":3442},[3411,14560,3446],{"class":3432},[3411,14562,3450],{"class":3449},[3411,14564,3453],{"class":3432},[3411,14566,8306],{"class":3449},[3411,14568,3495],{"class":3432},[3411,14570,8271],{"class":3428},[3411,14572,3453],{"class":3432},[3411,14574,13599],{"class":3449},[3411,14576,3495],{"class":3432},[3411,14578,3498],{"class":3428},[3411,14580,9575],{"class":3432},[3411,14582,11791],{"class":5495},[3411,14584,3511],{"class":3432},[3411,14586,8271],{"class":3428},[3411,14588,3433],{"class":3432},[3411,14590,14591,14593],{"class":3413,"line":10091},[3411,14592,10332],{"class":4143},[3411,14594,14595],{"class":3432}," zlib\n",[3411,14597,14598,14600],{"class":3413,"line":10096},[3411,14599,4400],{"class":4143},[3411,14601,14602],{"class":3432}," zlib.compress(data, level)\n",[3411,14604,14605],{"class":3413,"line":10129},[3411,14606,3475],{"emptyLinePlaceholder":3474},[3411,14608,14609,14611,14613,14615,14617,14619,14621,14623,14625,14627,14629],{"class":3413,"line":10157},[3411,14610,3439],{"class":3424},[3411,14612,13620],{"class":3442},[3411,14614,3446],{"class":3432},[3411,14616,3450],{"class":3449},[3411,14618,3453],{"class":3432},[3411,14620,8306],{"class":3449},[3411,14622,3495],{"class":3432},[3411,14624,8271],{"class":3428},[3411,14626,3511],{"class":3432},[3411,14628,8271],{"class":3428},[3411,14630,3433],{"class":3432},[3411,14632,14633,14635],{"class":3413,"line":10181},[3411,14634,10332],{"class":4143},[3411,14636,14595],{"class":3432},[3411,14638,14639,14641],{"class":3413,"line":10187},[3411,14640,4400],{"class":4143},[3411,14642,14643],{"class":3432}," zlib.decompress(data)\n",[3411,14645,14646],{"class":3413,"line":10192},[3411,14647,3475],{"emptyLinePlaceholder":3474},[3411,14649,14650],{"class":3413,"line":10221},[3411,14651,3475],{"emptyLinePlaceholder":3474},[3411,14653,14654],{"class":3413,"line":10246},[3411,14655,14656],{"class":3417},"# ── Демонстрація реєстру ──────────────────────────────────────────────────────\n",[3411,14658,14659],{"class":3413,"line":10254},[3411,14660,3475],{"emptyLinePlaceholder":3474},[3411,14662,14663,14665,14667,14669,14671,14674],{"class":3413,"line":10273},[3411,14664,4939],{"class":3442},[3411,14666,3446],{"class":3432},[3411,14668,3849],{"class":3828},[3411,14670,13313],{"class":13312},[3411,14672,14673],{"class":3828},"--- Реєстр плагінів ---\"",[3411,14675,3852],{"class":3432},[3411,14677,14678,14680,14682,14684,14687,14689,14692,14694,14696],{"class":3413,"line":10281},[3411,14679,4939],{"class":3442},[3411,14681,3446],{"class":3432},[3411,14683,3825],{"class":3424},[3411,14685,14686],{"class":3828},"\"Підтримувані розширення: ",[3411,14688,3832],{"class":3424},[3411,14690,14691],{"class":3432},"FileProcessorPlugin.supported_extensions()",[3411,14693,3837],{"class":3424},[3411,14695,3849],{"class":3828},[3411,14697,3852],{"class":3432},[3411,14699,14700],{"class":3413,"line":10292},[3411,14701,3475],{"emptyLinePlaceholder":3474},[3411,14703,14704],{"class":3413,"line":10299},[3411,14705,14706],{"class":3417},"# Автоматичний вибір плагіна за розширенням\n",[3411,14708,14709,14711,14714,14716,14719,14722,14724,14727,14729,14732],{"class":3413,"line":10304},[3411,14710,4411],{"class":4143},[3411,14712,14713],{"class":3432}," filename ",[3411,14715,4417],{"class":4143},[3411,14717,14718],{"class":3432}," [",[3411,14720,14721],{"class":3828},"\"data.json\"",[3411,14723,3453],{"class":3432},[3411,14725,14726],{"class":3828},"\"report.csv\"",[3411,14728,3453],{"class":3432},[3411,14730,14731],{"class":3828},"\"image.png\"",[3411,14733,4366],{"class":3432},[3411,14735,14736,14739,14742,14744,14746,14749,14751],{"class":3413,"line":10329},[3411,14737,14738],{"class":3432},"    ext = filename.rsplit(",[3411,14740,14741],{"class":3828},"\".\"",[3411,14743,3453],{"class":3432},[3411,14745,6993],{"class":5495},[3411,14747,14748],{"class":3432},")[-",[3411,14750,6993],{"class":5495},[3411,14752,11826],{"class":3432},[3411,14754,14755],{"class":3413,"line":10338},[3411,14756,14757],{"class":3432},"    plugin = FileProcessorPlugin.for_extension(ext)\n",[3411,14759,14760,14763],{"class":3413,"line":10363},[3411,14761,14762],{"class":4143},"    if",[3411,14764,14765],{"class":3432}," plugin:\n",[3411,14767,14768,14770,14772,14774,14776,14778,14781,14783,14786,14788,14790],{"class":3413,"line":10370},[3411,14769,3820],{"class":3442},[3411,14771,3446],{"class":3432},[3411,14773,3825],{"class":3424},[3411,14775,3849],{"class":3828},[3411,14777,13313],{"class":13312},[3411,14779,14780],{"class":3828},"Файл: ",[3411,14782,3832],{"class":3424},[3411,14784,14785],{"class":3432},"filename",[3411,14787,3837],{"class":3424},[3411,14789,3849],{"class":3828},[3411,14791,3852],{"class":3432},[3411,14793,14794,14796,14798,14800,14803,14805,14808,14810,14812],{"class":3413,"line":10376},[3411,14795,3820],{"class":3442},[3411,14797,3446],{"class":3432},[3411,14799,3825],{"class":3424},[3411,14801,14802],{"class":3828},"\"  Плагін: ",[3411,14804,3832],{"class":3424},[3411,14806,14807],{"class":3432},"plugin.name",[3411,14809,3837],{"class":3424},[3411,14811,3849],{"class":3828},[3411,14813,3852],{"class":3432},[3411,14815,14816,14818,14820,14822,14825,14827,14830,14832,14834],{"class":3413,"line":10383},[3411,14817,3820],{"class":3442},[3411,14819,3446],{"class":3432},[3411,14821,3825],{"class":3424},[3411,14823,14824],{"class":3828},"\"  Можливості: ",[3411,14826,3832],{"class":3424},[3411,14828,14829],{"class":3432},"plugin.get_capabilities()",[3411,14831,3837],{"class":3424},[3411,14833,3849],{"class":3828},[3411,14835,3852],{"class":3432},[3411,14837,14838,14840,14842,14844,14847,14849,14851,14854,14856,14858],{"class":3413,"line":10392},[3411,14839,3820],{"class":3442},[3411,14841,3446],{"class":3432},[3411,14843,3825],{"class":3424},[3411,14845,14846],{"class":3828},"\"  Підтримує стиснення: ",[3411,14848,3832],{"class":3424},[3411,14850,4944],{"class":3442},[3411,14852,14853],{"class":3432},"(plugin, Compressible)",[3411,14855,3837],{"class":3424},[3411,14857,3849],{"class":3828},[3411,14859,3852],{"class":3432},[3411,14861,14862,14865],{"class":3413,"line":10399},[3411,14863,14864],{"class":4143},"    else",[3411,14866,3433],{"class":3432},[3411,14868,14869,14871,14873,14875,14877,14879,14881,14883,14885,14887,14890],{"class":3413,"line":10404},[3411,14870,3820],{"class":3442},[3411,14872,3446],{"class":3432},[3411,14874,3825],{"class":3424},[3411,14876,3849],{"class":3828},[3411,14878,13313],{"class":13312},[3411,14880,14780],{"class":3828},[3411,14882,3832],{"class":3424},[3411,14884,14785],{"class":3432},[3411,14886,3837],{"class":3424},[3411,14888,14889],{"class":3828}," → ❌ Плагін не знайдено\"",[3411,14891,3852],{"class":3432},[4952,14893,14895,14903,14912,14919,14927,14934,14937,14941,14949,14952,14959,14967,14975,14982,14985,14992,14999,15006,15012,15015],{"title":14894},"python plugin_system.py",[4956,14896,14898,4964,14901],{"className":14897},[3413],[3411,14899,4963],{"className":14900},[4962],[3547,14902,14894],{},[4956,14904,14906,14907,14911],{"className":14905},[3413],"[Registry] Плагін ",[3411,14908,14910],{"className":14909},[6424],"'JsonPlugin'"," зареєстровано для '.json'",[4956,14913,14906,14915,14918],{"className":14914},[3413],[3411,14916,14910],{"className":14917},[6424]," зареєстровано для '.jsonl'",[4956,14920,14906,14922,14926],{"className":14921},[3413],[3411,14923,14925],{"className":14924},[6424],"'CsvPlugin'"," зареєстровано для '.csv'",[4956,14928,14906,14930,14933],{"className":14929},[3413],[3411,14931,14925],{"className":14932},[6424]," зареєстровано для '.tsv'",[4956,14935],{"className":14936},[3413],[4956,14938,14940],{"className":14939},[3413],"--- Реєстр плагінів ---",[4956,14942,14944,14945],{"className":14943},[3413],"Підтримувані розширення: ",[3411,14946,14948],{"className":14947},[4990],"['json', 'jsonl', 'csv', 'tsv']",[4956,14950],{"className":14951},[3413],[4956,14953,14780,14955],{"className":14954},[3413],[3411,14956,14958],{"className":14957},[6424],"data.json",[4956,14960,14962,14963],{"className":14961},[3413],"  Плагін: ",[3411,14964,14966],{"className":14965},[4990],"JSON Processor",[4956,14968,14970,14971],{"className":14969},[3413],"  Можливості: ",[3411,14972,14974],{"className":14973},[4990],"['process']",[4956,14976,14978,14979],{"className":14977},[3413],"  Підтримує стиснення: ",[3411,14980,7890],{"className":14981},[4975],[4956,14983],{"className":14984},[3413],[4956,14986,14780,14988],{"className":14987},[3413],[3411,14989,14991],{"className":14990},[6424],"report.csv",[4956,14993,14962,14995],{"className":14994},[3413],[3411,14996,14998],{"className":14997},[4990],"CSV Processor",[4956,15000,14970,15002],{"className":15001},[3413],[3411,15003,15005],{"className":15004},[4990],"['process', 'compression']",[4956,15007,14978,15009],{"className":15008},[3413],[3411,15010,4998],{"className":15011},[4990],[4956,15013],{"className":15014},[3413],[4956,15016,15018,15019],{"className":15017},[3413],"Файл: image.png → ",[3411,15020,15022],{"className":15021},[4975],"❌ Плагін не знайдено",[3584,15024],{},[3389,15026,15028],{"id":15027},"підсумок","Підсумок",[3394,15030,15031],{},"У цій статті ми дослідили два фундаментально різних підходи до абстракції в Python:",[3394,15033,15034,15036,15037,15040,15041,15043],{},[3547,15035,3577],{}," — це інструмент ",[3547,15038,15039],{},"номінативної типізації",". Він вимагає явного наслідування, гарантує перевірку реалізації методів на рівні інстанціювання та надає надійну ",[3408,15042,4944],{},"-перевірку. ABC ідеально підходить для замкнених ієрархій у коді, який ви контролюєте, особливо коли базовий клас містить спільну логіку (патерн «Шаблонний метод»).",[3394,15045,15046,15036,15049,15052,15053,15055],{},[3547,15047,15048],{},"Protocol (PEP 544)",[3547,15050,15051],{},"структурної типізації",". Він не вимагає наслідування, інтегрується з будь-яким сторонім кодом та забезпечує повноцінну статичну перевірку через ",[3408,15054,3560],{},". Protocol — це правильний вибір для відкритих систем, плагінів та роботи з бібліотеками, які ви не можете змінити.",[3394,15057,15058,15059,15062],{},"На практиці найкращі системи ",[3547,15060,15061],{},"поєднують обидва підходи",": ABC для основної ієрархії з бізнес-логікою та Protocol для опису зовнішніх залежностей і інтеграційних точок.",[3868,15064,15065,15066,15068,15069,15071,15072,15074,15075,3617],{},"Якщо ви пишете код з Python 3.8+ та використовуєте ",[3408,15067,3560],{}," — надавайте перевагу ",[3408,15070,3888],{}," для нових інтерфейсів. Він більш гнучкий і краще описує Python-філософію duck typing. Залишайте ",[3408,15073,3738],{}," там, де вам потрібна спільна реалізація або строга runtime-перевірка через ",[3408,15076,4944],{},[15078,15079,15080],"style",{},"html pre.shiki code .spJ8K, html code.shiki .spJ8K{--shiki-light:#008000;--shiki-default:#6A9955;--shiki-dark:#6A9955}html pre.shiki code .su1O8, html code.shiki .su1O8{--shiki-light:#0000FF;--shiki-default:#569CD6;--shiki-dark:#569CD6}html pre.shiki code .sN1BT, html code.shiki .sN1BT{--shiki-light:#267F99;--shiki-default:#4EC9B0;--shiki-dark:#4EC9B0}html pre.shiki code .sHH4Y, html code.shiki .sHH4Y{--shiki-light:#000000;--shiki-default:#D4D4D4;--shiki-dark:#D4D4D4}html pre.shiki code .s8Opu, html code.shiki .s8Opu{--shiki-light:#795E26;--shiki-default:#DCDCAA;--shiki-dark:#DCDCAA}html pre.shiki code .siwwj, html code.shiki .siwwj{--shiki-light:#001080;--shiki-default:#9CDCFE;--shiki-dark:#9CDCFE}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sbdoH, html code.shiki .sbdoH{--shiki-light:#A31515;--shiki-default:#CE9178;--shiki-dark:#CE9178}html pre.shiki code .s8xlr, html code.shiki .s8xlr{--shiki-light:#AF00DB;--shiki-default:#C586C0;--shiki-dark:#C586C0}html pre.shiki code .sJj4R, html code.shiki .sJj4R{--shiki-light:#098658;--shiki-default:#B5CEA8;--shiki-dark:#B5CEA8}html pre.shiki code .sjcCO, html code.shiki .sjcCO{--shiki-light:#EE0000;--shiki-default:#D7BA7D;--shiki-dark:#D7BA7D}",{"title":3406,"searchDepth":3421,"depth":3421,"links":15082},[15083,15084,15088,15097,15106,15110,15113,15118],{"id":3391,"depth":3421,"text":3392},{"id":3588,"depth":3421,"text":3589,"children":15085},[15086,15087],{"id":3593,"depth":3436,"text":3594},{"id":3749,"depth":3436,"text":3750},{"id":4064,"depth":3421,"text":4065,"children":15089},[15090,15091,15092,15093,15094,15096],{"id":4068,"depth":3436,"text":4069},{"id":4100,"depth":3436,"text":4101},{"id":4443,"depth":3436,"text":4444},{"id":5001,"depth":3436,"text":5002},{"id":5719,"depth":3436,"text":15095},"Механізм реєстрації: register() без наслідування",{"id":5957,"depth":3436,"text":5958},{"id":6455,"depth":3421,"text":6456,"children":15098},[15099,15100,15101,15102,15104,15105],{"id":6459,"depth":3436,"text":6460},{"id":6557,"depth":3436,"text":6558},{"id":7039,"depth":3436,"text":7040},{"id":7360,"depth":3436,"text":15103},"runtime_checkable: isinstance для протоколів",{"id":7909,"depth":3436,"text":7910},{"id":8205,"depth":3436,"text":8206},{"id":8476,"depth":3421,"text":8477,"children":15107},[15108,15109],{"id":8480,"depth":3436,"text":8481},{"id":8657,"depth":3436,"text":8658},{"id":8825,"depth":3421,"text":8826,"children":15111},[15112],{"id":8829,"depth":3436,"text":8830},{"id":11029,"depth":3421,"text":11030,"children":15114},[15115,15116,15117],{"id":11033,"depth":3436,"text":11034},{"id":11943,"depth":3436,"text":11944},{"id":13427,"depth":3436,"text":13428},{"id":15027,"depth":3421,"text":15028},"Глибокий аналіз двох підходів до абстракції в Python — формальних Abstract Base Classes (ABC) та структурних Protocols (PEP 544). Номінативна та структурна типізація, mypy, runtime-перевірки та вибір правильного інструменту для production-коду.","md",null,{},{"title":2569,"description":15119},"HX_gGbhNttpdNsDsdP1PnPDL5yHSyAjfMF51r453lsw",[15126,15128],{"title":2565,"path":2566,"stem":2567,"description":15127,"children":-1},"Глибоке дослідження механізмів наслідування в Python — від одиночного наслідування та перевизначення методів до cooperative multiple inheritance, внутрішнього влаштування super() як проксі-об'єкта, детального математичного аналізу C3-лінеаризації, CPython Internals MRO та патерну Mixins.",{"title":2573,"path":2574,"stem":2575,"description":15129,"children":-1},"Глибоке дослідження системи dunder-методів Python — від рядкового представлення та арифметики до контейнерних протоколів і контекстних менеджерів. Розуміння того, як Python реалізує оператори та вбудовані функції через спеціальні методи об'єктів.",1783248143936]