[{"data":1,"prerenderedAt":25455},["ShallowReactive",2],{"navigation_docs":3,"-python-inheritance-mro":3379,"-python-inheritance-mro-surround":25450},[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":2565,"body":3381,"description":25444,"extension":25445,"links":25446,"meta":25447,"navigation":3553,"path":2566,"seo":25448,"stem":2567,"__hash__":25449},"docs\u002F05.python\u002F03.inheritance-mro.md",{"type":3382,"value":3383,"toc":25373},"minimark",[3384,3393,3398,3402,3757,3760,3768,3795,3798,3802,3807,3816,3827,4756,4802,4823,4832,4851,5065,5086,5088,5101,5104,5142,5622,5738,5756,5758,5765,5772,5783,5800,5803,6029,6054,6192,6201,6218,6419,6452,6458,6502,6716,6724,6741,7402,7444,7464,7466,7473,7479,7485,8045,8122,8150,8152,8156,8160,8163,8336,8577,8581,8592,8725,8742,8749,8772,8774,8778,8782,8788,8831,8842,8864,8871,8906,8908,8912,8978,8983,9022,9032,9047,9255,9276,9319,9343,9345,9349,9358,9471,9510,9521,9523,9533,9753,9795,9797,9801,9810,9819,9911,9918,9960,9970,9976,10289,10317,10321,10340,10382,10858,10884,10888,11175,11214,11221,11223,11227,11231,11251,11258,11735,11762,11778,12256,12266,12284,12286,12290,12294,12307,12317,12578,12621,12625,13519,14047,14410,14532,14534,14538,14541,14592,14970,14972,14979,14990,15130,15135,15771,15786,15788,15792,15796,15802,15819,16922,17045,17047,17051,17054,18261,18401,18403,18409,18412,20214,20344,20346,20350,20357,20479,20483,20800,20804,24965,25282,25284,25288,25369],[3385,3386,3388,3389],"h1",{"id":3387},"наслідування-mro-та-суперсила-super","Наслідування, MRO та суперсила ",[3390,3391,3392],"code",{},"super()",[3394,3395,3397],"h2",{"id":3396},"вступ-коли-код-перестає-масштабуватися","Вступ: Коли код перестає масштабуватися",[3399,3400,3401],"p",{},"Уявіть: ви розробляєте систему сповіщень для SaaS-платформи. Є три канали — Email, SMS, Push. Кожен потребує логування, авторизації та серіалізації для збереження в базу даних. Перша версія виглядає так:",[3403,3404,3409],"pre",{"className":3405,"code":3406,"language":3407,"meta":3408,"style":3408},"language-python shiki shiki-themes light-plus dark-plus dark-plus","# ❌ Монолітний підхід — катастрофа при масштабуванні\nclass EmailNotification:\n    def send(self, user, msg): ...\n    def log(self, msg): print(f\"[EMAIL] {msg}\")     # дублювання\n    def to_json(self): return json.dumps(self.__dict__)  # дублювання\n\nclass SmsNotification:\n    def send(self, user, msg): ...\n    def log(self, msg): print(f\"[SMS] {msg}\")       # те саме!\n    def to_json(self): return json.dumps(self.__dict__)  # те саме!\n\nclass PushNotification:\n    def send(self, user, msg): ...\n    def log(self, msg): print(f\"[PUSH] {msg}\")      # і знову те саме!\n    def to_json(self): return json.dumps(self.__dict__)  # і знову!\n","python","",[3390,3410,3411,3420,3435,3466,3514,3548,3555,3565,3586,3626,3653,3658,3668,3689,3729],{"__ignoreMap":3408},[3412,3413,3416],"span",{"class":3414,"line":3415},"line",1,[3412,3417,3419],{"class":3418},"spJ8K","# ❌ Монолітний підхід — катастрофа при масштабуванні\n",[3412,3421,3423,3427,3431],{"class":3414,"line":3422},2,[3412,3424,3426],{"class":3425},"su1O8","class",[3412,3428,3430],{"class":3429},"sN1BT"," EmailNotification",[3412,3432,3434],{"class":3433},"sHH4Y",":\n",[3412,3436,3438,3441,3445,3448,3452,3455,3458,3460,3463],{"class":3414,"line":3437},3,[3412,3439,3440],{"class":3425},"    def",[3412,3442,3444],{"class":3443},"s8Opu"," send",[3412,3446,3447],{"class":3433},"(",[3412,3449,3451],{"class":3450},"siwwj","self",[3412,3453,3454],{"class":3433},", ",[3412,3456,3457],{"class":3450},"user",[3412,3459,3454],{"class":3433},[3412,3461,3462],{"class":3450},"msg",[3412,3464,3465],{"class":3433},"): ...\n",[3412,3467,3469,3471,3474,3476,3478,3480,3482,3485,3488,3490,3493,3497,3500,3502,3505,3508,3511],{"class":3414,"line":3468},4,[3412,3470,3440],{"class":3425},[3412,3472,3473],{"class":3443}," log",[3412,3475,3447],{"class":3433},[3412,3477,3451],{"class":3450},[3412,3479,3454],{"class":3433},[3412,3481,3462],{"class":3450},[3412,3483,3484],{"class":3433},"): ",[3412,3486,3487],{"class":3443},"print",[3412,3489,3447],{"class":3433},[3412,3491,3492],{"class":3425},"f",[3412,3494,3496],{"class":3495},"sbdoH","\"[EMAIL] ",[3412,3498,3499],{"class":3425},"{",[3412,3501,3462],{"class":3433},[3412,3503,3504],{"class":3425},"}",[3412,3506,3507],{"class":3495},"\"",[3412,3509,3510],{"class":3433},")     ",[3412,3512,3513],{"class":3418},"# дублювання\n",[3412,3515,3517,3519,3522,3524,3526,3528,3532,3535,3537,3540,3543,3546],{"class":3414,"line":3516},5,[3412,3518,3440],{"class":3425},[3412,3520,3521],{"class":3443}," to_json",[3412,3523,3447],{"class":3433},[3412,3525,3451],{"class":3450},[3412,3527,3484],{"class":3433},[3412,3529,3531],{"class":3530},"s8xlr","return",[3412,3533,3534],{"class":3433}," json.dumps(",[3412,3536,3451],{"class":3425},[3412,3538,3539],{"class":3433},".",[3412,3541,3542],{"class":3450},"__dict__",[3412,3544,3545],{"class":3433},")  ",[3412,3547,3513],{"class":3418},[3412,3549,3551],{"class":3414,"line":3550},6,[3412,3552,3554],{"emptyLinePlaceholder":3553},true,"\n",[3412,3556,3558,3560,3563],{"class":3414,"line":3557},7,[3412,3559,3426],{"class":3425},[3412,3561,3562],{"class":3429}," SmsNotification",[3412,3564,3434],{"class":3433},[3412,3566,3568,3570,3572,3574,3576,3578,3580,3582,3584],{"class":3414,"line":3567},8,[3412,3569,3440],{"class":3425},[3412,3571,3444],{"class":3443},[3412,3573,3447],{"class":3433},[3412,3575,3451],{"class":3450},[3412,3577,3454],{"class":3433},[3412,3579,3457],{"class":3450},[3412,3581,3454],{"class":3433},[3412,3583,3462],{"class":3450},[3412,3585,3465],{"class":3433},[3412,3587,3589,3591,3593,3595,3597,3599,3601,3603,3605,3607,3609,3612,3614,3616,3618,3620,3623],{"class":3414,"line":3588},9,[3412,3590,3440],{"class":3425},[3412,3592,3473],{"class":3443},[3412,3594,3447],{"class":3433},[3412,3596,3451],{"class":3450},[3412,3598,3454],{"class":3433},[3412,3600,3462],{"class":3450},[3412,3602,3484],{"class":3433},[3412,3604,3487],{"class":3443},[3412,3606,3447],{"class":3433},[3412,3608,3492],{"class":3425},[3412,3610,3611],{"class":3495},"\"[SMS] ",[3412,3613,3499],{"class":3425},[3412,3615,3462],{"class":3433},[3412,3617,3504],{"class":3425},[3412,3619,3507],{"class":3495},[3412,3621,3622],{"class":3433},")       ",[3412,3624,3625],{"class":3418},"# те саме!\n",[3412,3627,3629,3631,3633,3635,3637,3639,3641,3643,3645,3647,3649,3651],{"class":3414,"line":3628},10,[3412,3630,3440],{"class":3425},[3412,3632,3521],{"class":3443},[3412,3634,3447],{"class":3433},[3412,3636,3451],{"class":3450},[3412,3638,3484],{"class":3433},[3412,3640,3531],{"class":3530},[3412,3642,3534],{"class":3433},[3412,3644,3451],{"class":3425},[3412,3646,3539],{"class":3433},[3412,3648,3542],{"class":3450},[3412,3650,3545],{"class":3433},[3412,3652,3625],{"class":3418},[3412,3654,3656],{"class":3414,"line":3655},11,[3412,3657,3554],{"emptyLinePlaceholder":3553},[3412,3659,3661,3663,3666],{"class":3414,"line":3660},12,[3412,3662,3426],{"class":3425},[3412,3664,3665],{"class":3429}," PushNotification",[3412,3667,3434],{"class":3433},[3412,3669,3671,3673,3675,3677,3679,3681,3683,3685,3687],{"class":3414,"line":3670},13,[3412,3672,3440],{"class":3425},[3412,3674,3444],{"class":3443},[3412,3676,3447],{"class":3433},[3412,3678,3451],{"class":3450},[3412,3680,3454],{"class":3433},[3412,3682,3457],{"class":3450},[3412,3684,3454],{"class":3433},[3412,3686,3462],{"class":3450},[3412,3688,3465],{"class":3433},[3412,3690,3692,3694,3696,3698,3700,3702,3704,3706,3708,3710,3712,3715,3717,3719,3721,3723,3726],{"class":3414,"line":3691},14,[3412,3693,3440],{"class":3425},[3412,3695,3473],{"class":3443},[3412,3697,3447],{"class":3433},[3412,3699,3451],{"class":3450},[3412,3701,3454],{"class":3433},[3412,3703,3462],{"class":3450},[3412,3705,3484],{"class":3433},[3412,3707,3487],{"class":3443},[3412,3709,3447],{"class":3433},[3412,3711,3492],{"class":3425},[3412,3713,3714],{"class":3495},"\"[PUSH] ",[3412,3716,3499],{"class":3425},[3412,3718,3462],{"class":3433},[3412,3720,3504],{"class":3425},[3412,3722,3507],{"class":3495},[3412,3724,3725],{"class":3433},")      ",[3412,3727,3728],{"class":3418},"# і знову те саме!\n",[3412,3730,3732,3734,3736,3738,3740,3742,3744,3746,3748,3750,3752,3754],{"class":3414,"line":3731},15,[3412,3733,3440],{"class":3425},[3412,3735,3521],{"class":3443},[3412,3737,3447],{"class":3433},[3412,3739,3451],{"class":3450},[3412,3741,3484],{"class":3433},[3412,3743,3531],{"class":3530},[3412,3745,3534],{"class":3433},[3412,3747,3451],{"class":3425},[3412,3749,3539],{"class":3433},[3412,3751,3542],{"class":3450},[3412,3753,3545],{"class":3433},[3412,3755,3756],{"class":3418},"# і знову!\n",[3399,3758,3759],{},"Три класи — і вже три копії однакового коду. Через місяць вимога змінилась: логування тепер у JSON-форматі. Вам доведеться змінювати код у трьох місцях — і це гарантований шлях до помилок.",[3399,3761,3762,3763,3767],{},"Саме для вирішення цієї проблеми існує ",[3764,3765,3766],"strong",{},"наслідування",": механізм, що дозволяє визначити поведінку один раз і розподілити її між усіма нащадками. Але наслідування — це не просто «підключення батьківського коду». У Python під ним ховається складна система пошуку методів (MRO), кооперативний виклик конструкторів і архітектурний патерн Mixins.",[3769,3770,3771,3777,3782,3787],"card-group",{},[3772,3773,3776],"card",{"icon":3774,"title":3775},"i-heroicons-document-duplicate","Проблема дублювання","Без наслідування зміна в одній «спільній» функції потребує правки у кожному класі окремо. Це порушує принцип DRY (Don't Repeat Yourself).",[3772,3778,3781],{"icon":3779,"title":3780},"i-heroicons-arrow-trending-up","Проблема ієрархії","Глибокі ієрархії без чіткого механізму пошуку методів перетворюються на «спагетті» — неможливо зрозуміти, який метод буде викликано.",[3772,3783,3786],{"icon":3784,"title":3785},"i-heroicons-exclamation-triangle","Проблема конфліктів","При множинному наслідуванні кілька батьків можуть мати однойменні методи. Без суворого алгоритму вирішення конфліктів поведінка непередбачувана.",[3772,3788,3791,3792,3794],{"icon":3789,"title":3790},"i-heroicons-check-circle","Python-рішення","Python вирішує всі три проблеми: ",[3390,3793,3392],{}," як проксі-об'єкт по MRO, C3-лінеаризація для детермінованого порядку, Mixins для горизонтального розподілу поведінки.",[3796,3797],"hr",{},[3394,3799,3801],{"id":3800},"частина-i-одиночне-наслідування-та-перевизначення-методів","Частина I: Одиночне наслідування та перевизначення методів",[3803,3804,3806],"h3",{"id":3805},"базовий-синтаксис-та-відношення-is-a","Базовий синтаксис та відношення IS-A",[3399,3808,3809,3812,3813,3539],{},[3764,3810,3811],{},"Одиночне наслідування"," — це коли клас має рівно одного безпосереднього батька. У Python базовий клас вказується в дужках після назви дочірнього класу. Якщо батько не вказаний явно, клас автоматично наслідує від вбудованого ",[3390,3814,3815],{},"object",[3399,3817,3818,3819,3822,3823,3826],{},"Головне правило: використовуйте наслідування ",[3764,3820,3821],{},"лише"," тоді, коли між сутностями є чітке відношення ",[3764,3824,3825],{},"IS-A"," («є об'єктом типу»). Для відношення HAS-A («містить у собі») — використовуйте композицію.",[3828,3829,3830,4138,4345,4609],"code-tree",{},[3403,3831,3834],{"className":3405,"code":3832,"filename":3833,"language":3407,"meta":3408,"style":3408},"# Базовий клас транспортного засобу\nclass Vehicle:\n    \"\"\"Базовий клас для всіх транспортних засобів.\"\"\"\n\n    def __init__(self, brand: str, year: int):\n        self.brand = brand\n        self.year = year\n        self._fuel_level: float = 100.0  # захищений атрибут\n\n    def start_engine(self) -> str:\n        return f\"Двигун {self.brand} ({self.year}) запущено.\"\n\n    def refuel(self, liters: float) -> None:\n        self._fuel_level = min(100.0, self._fuel_level + liters)\n        print(f\"Заправлено {liters}л. Рівень: {self._fuel_level:.1f}%\")\n\n    def __repr__(self) -> str:\n        return f\"{self.__class__.__name__}(brand={self.brand!r}, year={self.year})\"\n","vehicle.py",[3390,3835,3836,3841,3850,3855,3859,3894,3902,3909,3929,3933,3951,3983,3987,4014,4035,4070,4075,4093],{"__ignoreMap":3408},[3412,3837,3838],{"class":3414,"line":3415},[3412,3839,3840],{"class":3418},"# Базовий клас транспортного засобу\n",[3412,3842,3843,3845,3848],{"class":3414,"line":3422},[3412,3844,3426],{"class":3425},[3412,3846,3847],{"class":3429}," Vehicle",[3412,3849,3434],{"class":3433},[3412,3851,3852],{"class":3414,"line":3437},[3412,3853,3854],{"class":3495},"    \"\"\"Базовий клас для всіх транспортних засобів.\"\"\"\n",[3412,3856,3857],{"class":3414,"line":3468},[3412,3858,3554],{"emptyLinePlaceholder":3553},[3412,3860,3861,3863,3866,3868,3870,3872,3875,3878,3881,3883,3886,3888,3891],{"class":3414,"line":3516},[3412,3862,3440],{"class":3425},[3412,3864,3865],{"class":3443}," __init__",[3412,3867,3447],{"class":3433},[3412,3869,3451],{"class":3450},[3412,3871,3454],{"class":3433},[3412,3873,3874],{"class":3450},"brand",[3412,3876,3877],{"class":3433},": ",[3412,3879,3880],{"class":3429},"str",[3412,3882,3454],{"class":3433},[3412,3884,3885],{"class":3450},"year",[3412,3887,3877],{"class":3433},[3412,3889,3890],{"class":3429},"int",[3412,3892,3893],{"class":3433},"):\n",[3412,3895,3896,3899],{"class":3414,"line":3550},[3412,3897,3898],{"class":3425},"        self",[3412,3900,3901],{"class":3433},".brand = brand\n",[3412,3903,3904,3906],{"class":3414,"line":3557},[3412,3905,3898],{"class":3425},[3412,3907,3908],{"class":3433},".year = year\n",[3412,3910,3911,3913,3916,3919,3922,3926],{"class":3414,"line":3567},[3412,3912,3898],{"class":3425},[3412,3914,3915],{"class":3433},"._fuel_level: ",[3412,3917,3918],{"class":3429},"float",[3412,3920,3921],{"class":3433}," = ",[3412,3923,3925],{"class":3924},"sJj4R","100.0",[3412,3927,3928],{"class":3418},"  # захищений атрибут\n",[3412,3930,3931],{"class":3414,"line":3588},[3412,3932,3554],{"emptyLinePlaceholder":3553},[3412,3934,3935,3937,3940,3942,3944,3947,3949],{"class":3414,"line":3628},[3412,3936,3440],{"class":3425},[3412,3938,3939],{"class":3443}," start_engine",[3412,3941,3447],{"class":3433},[3412,3943,3451],{"class":3450},[3412,3945,3946],{"class":3433},") -> ",[3412,3948,3880],{"class":3429},[3412,3950,3434],{"class":3433},[3412,3952,3953,3956,3959,3962,3965,3968,3970,3973,3975,3978,3980],{"class":3414,"line":3655},[3412,3954,3955],{"class":3530},"        return",[3412,3957,3958],{"class":3425}," f",[3412,3960,3961],{"class":3495},"\"Двигун ",[3412,3963,3964],{"class":3425},"{self",[3412,3966,3967],{"class":3433},".brand",[3412,3969,3504],{"class":3425},[3412,3971,3972],{"class":3495}," (",[3412,3974,3964],{"class":3425},[3412,3976,3977],{"class":3433},".year",[3412,3979,3504],{"class":3425},[3412,3981,3982],{"class":3495},") запущено.\"\n",[3412,3984,3985],{"class":3414,"line":3660},[3412,3986,3554],{"emptyLinePlaceholder":3553},[3412,3988,3989,3991,3994,3996,3998,4000,4003,4005,4007,4009,4012],{"class":3414,"line":3670},[3412,3990,3440],{"class":3425},[3412,3992,3993],{"class":3443}," refuel",[3412,3995,3447],{"class":3433},[3412,3997,3451],{"class":3450},[3412,3999,3454],{"class":3433},[3412,4001,4002],{"class":3450},"liters",[3412,4004,3877],{"class":3433},[3412,4006,3918],{"class":3429},[3412,4008,3946],{"class":3433},[3412,4010,4011],{"class":3425},"None",[3412,4013,3434],{"class":3433},[3412,4015,4016,4018,4021,4024,4026,4028,4030,4032],{"class":3414,"line":3691},[3412,4017,3898],{"class":3425},[3412,4019,4020],{"class":3433},"._fuel_level = ",[3412,4022,4023],{"class":3443},"min",[3412,4025,3447],{"class":3433},[3412,4027,3925],{"class":3924},[3412,4029,3454],{"class":3433},[3412,4031,3451],{"class":3425},[3412,4033,4034],{"class":3433},"._fuel_level + liters)\n",[3412,4036,4037,4040,4042,4044,4047,4049,4051,4053,4056,4058,4061,4064,4067],{"class":3414,"line":3731},[3412,4038,4039],{"class":3443},"        print",[3412,4041,3447],{"class":3433},[3412,4043,3492],{"class":3425},[3412,4045,4046],{"class":3495},"\"Заправлено ",[3412,4048,3499],{"class":3425},[3412,4050,4002],{"class":3433},[3412,4052,3504],{"class":3425},[3412,4054,4055],{"class":3495},"л. Рівень: ",[3412,4057,3964],{"class":3425},[3412,4059,4060],{"class":3433},"._fuel_level",[3412,4062,4063],{"class":3425},":.1f}",[3412,4065,4066],{"class":3495},"%\"",[3412,4068,4069],{"class":3433},")\n",[3412,4071,4073],{"class":3414,"line":4072},16,[3412,4074,3554],{"emptyLinePlaceholder":3553},[3412,4076,4078,4080,4083,4085,4087,4089,4091],{"class":3414,"line":4077},17,[3412,4079,3440],{"class":3425},[3412,4081,4082],{"class":3443}," __repr__",[3412,4084,3447],{"class":3433},[3412,4086,3451],{"class":3450},[3412,4088,3946],{"class":3433},[3412,4090,3880],{"class":3429},[3412,4092,3434],{"class":3433},[3412,4094,4096,4098,4100,4102,4104,4106,4109,4111,4114,4116,4119,4121,4123,4126,4129,4131,4133,4135],{"class":3414,"line":4095},18,[3412,4097,3955],{"class":3530},[3412,4099,3958],{"class":3425},[3412,4101,3507],{"class":3495},[3412,4103,3964],{"class":3425},[3412,4105,3539],{"class":3433},[3412,4107,4108],{"class":3450},"__class__",[3412,4110,3539],{"class":3433},[3412,4112,4113],{"class":3450},"__name__",[3412,4115,3504],{"class":3425},[3412,4117,4118],{"class":3495},"(brand=",[3412,4120,3964],{"class":3425},[3412,4122,3967],{"class":3433},[3412,4124,4125],{"class":3425},"!r}",[3412,4127,4128],{"class":3495},", year=",[3412,4130,3964],{"class":3425},[3412,4132,3977],{"class":3433},[3412,4134,3504],{"class":3425},[3412,4136,4137],{"class":3495},")\"\n",[3403,4139,4142],{"className":3405,"code":4140,"filename":4141,"language":3407,"meta":3408,"style":3408},"from vehicle import Vehicle\n\nclass Car(Vehicle):\n    \"\"\"Car IS-A Vehicle з власними функціями.\"\"\"\n\n    def __init__(self, brand: str, year: int, num_doors: int = 4):\n        # Виклик батьківського конструктора через super()\n        super().__init__(brand, year)\n        self.num_doors = num_doors\n\n    # Перевизначення (Method Overriding): власна реалізація для Car\n    def start_engine(self) -> str:\n        base = super().start_engine()\n        return f\"{base} [Car mode: ремені пристебнуті]\"\n\n    def honk(self) -> str:\n        return f\"{self.brand}: Бі-біп!\"\n","car.py",[3390,4143,4144,4158,4162,4176,4181,4185,4227,4232,4246,4253,4257,4262,4278,4289,4307,4311,4328],{"__ignoreMap":3408},[3412,4145,4146,4149,4152,4155],{"class":3414,"line":3415},[3412,4147,4148],{"class":3530},"from",[3412,4150,4151],{"class":3433}," vehicle ",[3412,4153,4154],{"class":3530},"import",[3412,4156,4157],{"class":3433}," Vehicle\n",[3412,4159,4160],{"class":3414,"line":3422},[3412,4161,3554],{"emptyLinePlaceholder":3553},[3412,4163,4164,4166,4169,4171,4174],{"class":3414,"line":3437},[3412,4165,3426],{"class":3425},[3412,4167,4168],{"class":3429}," Car",[3412,4170,3447],{"class":3433},[3412,4172,4173],{"class":3429},"Vehicle",[3412,4175,3893],{"class":3433},[3412,4177,4178],{"class":3414,"line":3468},[3412,4179,4180],{"class":3495},"    \"\"\"Car IS-A Vehicle з власними функціями.\"\"\"\n",[3412,4182,4183],{"class":3414,"line":3516},[3412,4184,3554],{"emptyLinePlaceholder":3553},[3412,4186,4187,4189,4191,4193,4195,4197,4199,4201,4203,4205,4207,4209,4211,4213,4216,4218,4220,4222,4225],{"class":3414,"line":3550},[3412,4188,3440],{"class":3425},[3412,4190,3865],{"class":3443},[3412,4192,3447],{"class":3433},[3412,4194,3451],{"class":3450},[3412,4196,3454],{"class":3433},[3412,4198,3874],{"class":3450},[3412,4200,3877],{"class":3433},[3412,4202,3880],{"class":3429},[3412,4204,3454],{"class":3433},[3412,4206,3885],{"class":3450},[3412,4208,3877],{"class":3433},[3412,4210,3890],{"class":3429},[3412,4212,3454],{"class":3433},[3412,4214,4215],{"class":3450},"num_doors",[3412,4217,3877],{"class":3433},[3412,4219,3890],{"class":3429},[3412,4221,3921],{"class":3433},[3412,4223,4224],{"class":3924},"4",[3412,4226,3893],{"class":3433},[3412,4228,4229],{"class":3414,"line":3557},[3412,4230,4231],{"class":3418},"        # Виклик батьківського конструктора через super()\n",[3412,4233,4234,4237,4240,4243],{"class":3414,"line":3567},[3412,4235,4236],{"class":3429},"        super",[3412,4238,4239],{"class":3433},"().",[3412,4241,4242],{"class":3443},"__init__",[3412,4244,4245],{"class":3433},"(brand, year)\n",[3412,4247,4248,4250],{"class":3414,"line":3588},[3412,4249,3898],{"class":3425},[3412,4251,4252],{"class":3433},".num_doors = num_doors\n",[3412,4254,4255],{"class":3414,"line":3628},[3412,4256,3554],{"emptyLinePlaceholder":3553},[3412,4258,4259],{"class":3414,"line":3655},[3412,4260,4261],{"class":3418},"    # Перевизначення (Method Overriding): власна реалізація для Car\n",[3412,4263,4264,4266,4268,4270,4272,4274,4276],{"class":3414,"line":3660},[3412,4265,3440],{"class":3425},[3412,4267,3939],{"class":3443},[3412,4269,3447],{"class":3433},[3412,4271,3451],{"class":3450},[3412,4273,3946],{"class":3433},[3412,4275,3880],{"class":3429},[3412,4277,3434],{"class":3433},[3412,4279,4280,4283,4286],{"class":3414,"line":3670},[3412,4281,4282],{"class":3433},"        base = ",[3412,4284,4285],{"class":3429},"super",[3412,4287,4288],{"class":3433},"().start_engine()\n",[3412,4290,4291,4293,4295,4297,4299,4302,4304],{"class":3414,"line":3691},[3412,4292,3955],{"class":3530},[3412,4294,3958],{"class":3425},[3412,4296,3507],{"class":3495},[3412,4298,3499],{"class":3425},[3412,4300,4301],{"class":3433},"base",[3412,4303,3504],{"class":3425},[3412,4305,4306],{"class":3495}," [Car mode: ремені пристебнуті]\"\n",[3412,4308,4309],{"class":3414,"line":3731},[3412,4310,3554],{"emptyLinePlaceholder":3553},[3412,4312,4313,4315,4318,4320,4322,4324,4326],{"class":3414,"line":4072},[3412,4314,3440],{"class":3425},[3412,4316,4317],{"class":3443}," honk",[3412,4319,3447],{"class":3433},[3412,4321,3451],{"class":3450},[3412,4323,3946],{"class":3433},[3412,4325,3880],{"class":3429},[3412,4327,3434],{"class":3433},[3412,4329,4330,4332,4334,4336,4338,4340,4342],{"class":3414,"line":4077},[3412,4331,3955],{"class":3530},[3412,4333,3958],{"class":3425},[3412,4335,3507],{"class":3495},[3412,4337,3964],{"class":3425},[3412,4339,3967],{"class":3433},[3412,4341,3504],{"class":3425},[3412,4343,4344],{"class":3495},": Бі-біп!\"\n",[3403,4346,4349],{"className":3405,"code":4347,"filename":4348,"language":3407,"meta":3408,"style":3408},"from car import Car\n\nclass ElectricCar(Car):\n    \"\"\"ElectricCar IS-A Car IS-A Vehicle: дворівневе наслідування.\"\"\"\n\n    def __init__(self, brand: str, year: int, battery_kwh: float):\n        super().__init__(brand, year)      # Car.__init__\n        self.battery_kwh = battery_kwh\n        self._charge_level: float = 100.0\n\n    # Перевизначення: повністю інша логіка запуску для електромобіля\n    def start_engine(self) -> str:\n        return f\"{self.brand}: Тихий запуск електромотора. Заряд: {self._charge_level}%\"\n\n    # Новий метод, специфічний для ElectricCar\n    def charge(self, kwh: float) -> None:\n        self._charge_level = min(100.0, self._charge_level + kwh \u002F self.battery_kwh * 100)\n        print(f\"Заряджено +{kwh}кВт·год. Рівень: {self._charge_level:.1f}%\")\n","electric_car.py",[3390,4350,4351,4363,4367,4381,4386,4390,4427,4441,4448,4462,4466,4471,4487,4514,4518,4523,4549,4579],{"__ignoreMap":3408},[3412,4352,4353,4355,4358,4360],{"class":3414,"line":3415},[3412,4354,4148],{"class":3530},[3412,4356,4357],{"class":3433}," car ",[3412,4359,4154],{"class":3530},[3412,4361,4362],{"class":3433}," Car\n",[3412,4364,4365],{"class":3414,"line":3422},[3412,4366,3554],{"emptyLinePlaceholder":3553},[3412,4368,4369,4371,4374,4376,4379],{"class":3414,"line":3437},[3412,4370,3426],{"class":3425},[3412,4372,4373],{"class":3429}," ElectricCar",[3412,4375,3447],{"class":3433},[3412,4377,4378],{"class":3429},"Car",[3412,4380,3893],{"class":3433},[3412,4382,4383],{"class":3414,"line":3468},[3412,4384,4385],{"class":3495},"    \"\"\"ElectricCar IS-A Car IS-A Vehicle: дворівневе наслідування.\"\"\"\n",[3412,4387,4388],{"class":3414,"line":3516},[3412,4389,3554],{"emptyLinePlaceholder":3553},[3412,4391,4392,4394,4396,4398,4400,4402,4404,4406,4408,4410,4412,4414,4416,4418,4421,4423,4425],{"class":3414,"line":3550},[3412,4393,3440],{"class":3425},[3412,4395,3865],{"class":3443},[3412,4397,3447],{"class":3433},[3412,4399,3451],{"class":3450},[3412,4401,3454],{"class":3433},[3412,4403,3874],{"class":3450},[3412,4405,3877],{"class":3433},[3412,4407,3880],{"class":3429},[3412,4409,3454],{"class":3433},[3412,4411,3885],{"class":3450},[3412,4413,3877],{"class":3433},[3412,4415,3890],{"class":3429},[3412,4417,3454],{"class":3433},[3412,4419,4420],{"class":3450},"battery_kwh",[3412,4422,3877],{"class":3433},[3412,4424,3918],{"class":3429},[3412,4426,3893],{"class":3433},[3412,4428,4429,4431,4433,4435,4438],{"class":3414,"line":3557},[3412,4430,4236],{"class":3429},[3412,4432,4239],{"class":3433},[3412,4434,4242],{"class":3443},[3412,4436,4437],{"class":3433},"(brand, year)      ",[3412,4439,4440],{"class":3418},"# Car.__init__\n",[3412,4442,4443,4445],{"class":3414,"line":3567},[3412,4444,3898],{"class":3425},[3412,4446,4447],{"class":3433},".battery_kwh = battery_kwh\n",[3412,4449,4450,4452,4455,4457,4459],{"class":3414,"line":3588},[3412,4451,3898],{"class":3425},[3412,4453,4454],{"class":3433},"._charge_level: ",[3412,4456,3918],{"class":3429},[3412,4458,3921],{"class":3433},[3412,4460,4461],{"class":3924},"100.0\n",[3412,4463,4464],{"class":3414,"line":3628},[3412,4465,3554],{"emptyLinePlaceholder":3553},[3412,4467,4468],{"class":3414,"line":3655},[3412,4469,4470],{"class":3418},"    # Перевизначення: повністю інша логіка запуску для електромобіля\n",[3412,4472,4473,4475,4477,4479,4481,4483,4485],{"class":3414,"line":3660},[3412,4474,3440],{"class":3425},[3412,4476,3939],{"class":3443},[3412,4478,3447],{"class":3433},[3412,4480,3451],{"class":3450},[3412,4482,3946],{"class":3433},[3412,4484,3880],{"class":3429},[3412,4486,3434],{"class":3433},[3412,4488,4489,4491,4493,4495,4497,4499,4501,4504,4506,4509,4511],{"class":3414,"line":3670},[3412,4490,3955],{"class":3530},[3412,4492,3958],{"class":3425},[3412,4494,3507],{"class":3495},[3412,4496,3964],{"class":3425},[3412,4498,3967],{"class":3433},[3412,4500,3504],{"class":3425},[3412,4502,4503],{"class":3495},": Тихий запуск електромотора. Заряд: ",[3412,4505,3964],{"class":3425},[3412,4507,4508],{"class":3433},"._charge_level",[3412,4510,3504],{"class":3425},[3412,4512,4513],{"class":3495},"%\"\n",[3412,4515,4516],{"class":3414,"line":3691},[3412,4517,3554],{"emptyLinePlaceholder":3553},[3412,4519,4520],{"class":3414,"line":3731},[3412,4521,4522],{"class":3418},"    # Новий метод, специфічний для ElectricCar\n",[3412,4524,4525,4527,4530,4532,4534,4536,4539,4541,4543,4545,4547],{"class":3414,"line":4072},[3412,4526,3440],{"class":3425},[3412,4528,4529],{"class":3443}," charge",[3412,4531,3447],{"class":3433},[3412,4533,3451],{"class":3450},[3412,4535,3454],{"class":3433},[3412,4537,4538],{"class":3450},"kwh",[3412,4540,3877],{"class":3433},[3412,4542,3918],{"class":3429},[3412,4544,3946],{"class":3433},[3412,4546,4011],{"class":3425},[3412,4548,3434],{"class":3433},[3412,4550,4551,4553,4556,4558,4560,4562,4564,4566,4569,4571,4574,4577],{"class":3414,"line":4077},[3412,4552,3898],{"class":3425},[3412,4554,4555],{"class":3433},"._charge_level = ",[3412,4557,4023],{"class":3443},[3412,4559,3447],{"class":3433},[3412,4561,3925],{"class":3924},[3412,4563,3454],{"class":3433},[3412,4565,3451],{"class":3425},[3412,4567,4568],{"class":3433},"._charge_level + kwh \u002F ",[3412,4570,3451],{"class":3425},[3412,4572,4573],{"class":3433},".battery_kwh * ",[3412,4575,4576],{"class":3924},"100",[3412,4578,4069],{"class":3433},[3412,4580,4581,4583,4585,4587,4590,4592,4594,4596,4599,4601,4603,4605,4607],{"class":3414,"line":4095},[3412,4582,4039],{"class":3443},[3412,4584,3447],{"class":3433},[3412,4586,3492],{"class":3425},[3412,4588,4589],{"class":3495},"\"Заряджено +",[3412,4591,3499],{"class":3425},[3412,4593,4538],{"class":3433},[3412,4595,3504],{"class":3425},[3412,4597,4598],{"class":3495},"кВт·год. Рівень: ",[3412,4600,3964],{"class":3425},[3412,4602,4508],{"class":3433},[3412,4604,4063],{"class":3425},[3412,4606,4066],{"class":3495},[3412,4608,4069],{"class":3433},[3403,4610,4613],{"className":3405,"code":4611,"filename":4612,"language":3407,"meta":3408,"style":3408},"from vehicle import Vehicle\nfrom car import Car\nfrom electric_car import ElectricCar\n\nvehicles: list[Vehicle] = [\n    Vehicle(\"Generic\", 2020),\n    Car(\"Toyota\", 2022, num_doors=4),\n    ElectricCar(\"Tesla\", 2024, battery_kwh=75.0),\n]\n\n# Поліморфізм: один і той самий виклик — різна поведінка\nfor v in vehicles:\n    print(v.start_engine())\n","main.py",[3390,4614,4615,4625,4635,4647,4651,4656,4672,4696,4720,4725,4729,4734,4748],{"__ignoreMap":3408},[3412,4616,4617,4619,4621,4623],{"class":3414,"line":3415},[3412,4618,4148],{"class":3530},[3412,4620,4151],{"class":3433},[3412,4622,4154],{"class":3530},[3412,4624,4157],{"class":3433},[3412,4626,4627,4629,4631,4633],{"class":3414,"line":3422},[3412,4628,4148],{"class":3530},[3412,4630,4357],{"class":3433},[3412,4632,4154],{"class":3530},[3412,4634,4362],{"class":3433},[3412,4636,4637,4639,4642,4644],{"class":3414,"line":3437},[3412,4638,4148],{"class":3530},[3412,4640,4641],{"class":3433}," electric_car ",[3412,4643,4154],{"class":3530},[3412,4645,4646],{"class":3433}," ElectricCar\n",[3412,4648,4649],{"class":3414,"line":3468},[3412,4650,3554],{"emptyLinePlaceholder":3553},[3412,4652,4653],{"class":3414,"line":3516},[3412,4654,4655],{"class":3433},"vehicles: list[Vehicle] = [\n",[3412,4657,4658,4661,4664,4666,4669],{"class":3414,"line":3550},[3412,4659,4660],{"class":3433},"    Vehicle(",[3412,4662,4663],{"class":3495},"\"Generic\"",[3412,4665,3454],{"class":3433},[3412,4667,4668],{"class":3924},"2020",[3412,4670,4671],{"class":3433},"),\n",[3412,4673,4674,4677,4680,4682,4685,4687,4689,4692,4694],{"class":3414,"line":3557},[3412,4675,4676],{"class":3433},"    Car(",[3412,4678,4679],{"class":3495},"\"Toyota\"",[3412,4681,3454],{"class":3433},[3412,4683,4684],{"class":3924},"2022",[3412,4686,3454],{"class":3433},[3412,4688,4215],{"class":3450},[3412,4690,4691],{"class":3433},"=",[3412,4693,4224],{"class":3924},[3412,4695,4671],{"class":3433},[3412,4697,4698,4701,4704,4706,4709,4711,4713,4715,4718],{"class":3414,"line":3567},[3412,4699,4700],{"class":3433},"    ElectricCar(",[3412,4702,4703],{"class":3495},"\"Tesla\"",[3412,4705,3454],{"class":3433},[3412,4707,4708],{"class":3924},"2024",[3412,4710,3454],{"class":3433},[3412,4712,4420],{"class":3450},[3412,4714,4691],{"class":3433},[3412,4716,4717],{"class":3924},"75.0",[3412,4719,4671],{"class":3433},[3412,4721,4722],{"class":3414,"line":3588},[3412,4723,4724],{"class":3433},"]\n",[3412,4726,4727],{"class":3414,"line":3628},[3412,4728,3554],{"emptyLinePlaceholder":3553},[3412,4730,4731],{"class":3414,"line":3655},[3412,4732,4733],{"class":3418},"# Поліморфізм: один і той самий виклик — різна поведінка\n",[3412,4735,4736,4739,4742,4745],{"class":3414,"line":3660},[3412,4737,4738],{"class":3530},"for",[3412,4740,4741],{"class":3433}," v ",[3412,4743,4744],{"class":3530},"in",[3412,4746,4747],{"class":3433}," vehicles:\n",[3412,4749,4750,4753],{"class":3414,"line":3670},[3412,4751,4752],{"class":3443},"    print",[3412,4754,4755],{"class":3433},"(v.start_engine())\n",[4757,4758,4760,4772,4781,4794],"terminal-preview",{"title":4759},"python main.py",[4761,4762,4764,4769,4770],"div",{"className":4763},[3414],[3412,4765,4768],{"className":4766},[4767],"opacity-40","$"," ",[3764,4771,4759],{},[4761,4773,4775,4776,4780],{"className":4774},[3414],"Двигун Generic (",[3412,4777,4668],{"className":4778},[4779],"text-yellow-400",") запущено.",[4761,4782,4784,4785,4788,4789],{"className":4783},[3414],"Двигун Toyota (",[3412,4786,4684],{"className":4787},[4779],") запущено. ",[3412,4790,4793],{"className":4791},[4792],"text-blue-400","[Car mode: ремені пристебнуті]",[4761,4795,4797],{"className":4796},[3414],[3412,4798,4801],{"className":4799},[4800],"text-green-400","Tesla: Тихий запуск електромотора. Заряд: 100.0%",[3399,4803,4804,4807,4808,4811,4812,4815,4816,4819,4820,3539],{},[3764,4805,4806],{},"Перевизначення методів (Method Overriding)"," — ключовий механізм поліморфізму. Коли Python шукає метод на об'єкті ",[3390,4809,4810],{},"Tesla.start_engine()",", він перевіряє класи в порядку MRO і знаходить першу відповідну реалізацію. У нашому випадку — в ",[3390,4813,4814],{},"ElectricCar",", ігноруючи ",[3390,4817,4818],{},"Car.start_engine"," та ",[3390,4821,4822],{},"Vehicle.start_engine",[3803,4824,4826,4828,4829,4831],{"id":4825},"__dict__-екземпляра-проти-__dict__-класу",[3390,4827,3542],{}," екземпляра проти ",[3390,4830,3542],{}," класу",[3399,4833,4834,4835,4838,4839,4842,4843,4846,4847,4850],{},"Важливо розуміти відмінність між атрибутами, що зберігаються безпосередньо на ",[3764,4836,4837],{},"екземплярі",", та тими, що належать ",[3764,4840,4841],{},"класу",". Python шукає атрибут спочатку в ",[3390,4844,4845],{},"obj.__dict__",", а потім — у ",[3390,4848,4849],{},"type(obj).__dict__"," по MRO.",[3403,4852,4854],{"className":3405,"code":4853,"language":3407,"meta":3408,"style":3408},"class Vehicle:\n    wheels = 4  # атрибут класу — спільний для всіх екземплярів\n\n    def __init__(self, brand: str):\n        self.brand = brand  # атрибут екземпляра — індивідуальний\n\ncar1 = Vehicle(\"Toyota\")\ncar2 = Vehicle(\"Honda\")\n\n# Атрибути класу — спільні\nprint(Vehicle.wheels)   # 4\nprint(car1.wheels)      # 4 ← знайдено у Vehicle.__dict__, НЕ у car1.__dict__\nprint(car2.wheels)      # 4\n\n# Перевіряємо: car1.__dict__ не містить 'wheels'\nprint(car1.__dict__)    # {'brand': 'Toyota'} ← лише атрибут екземпляра!\nprint(Vehicle.__dict__.keys())  # ..., 'wheels', ...\n\n# Якщо присвоїти 'wheels' через екземпляр — він стає атрибутом ЕКЗЕМПЛЯРА,\n# і ТІНЬУЄ (shadows) атрибут класу\ncar1.wheels = 2\nprint(car1.wheels)   # 2  ← тепер з car1.__dict__\nprint(car2.wheels)   # 4  ← Vehicle.__dict__ незмінений\nprint(Vehicle.wheels)# 4  ← Vehicle.__dict__ незмінений\n",[3390,4855,4856,4864,4874,4878,4898,4908,4912,4921,4931,4935,4940,4950,4960,4969,4973,4978,4993,5008,5012,5018,5024,5033,5044,5055],{"__ignoreMap":3408},[3412,4857,4858,4860,4862],{"class":3414,"line":3415},[3412,4859,3426],{"class":3425},[3412,4861,3847],{"class":3429},[3412,4863,3434],{"class":3433},[3412,4865,4866,4869,4871],{"class":3414,"line":3422},[3412,4867,4868],{"class":3433},"    wheels = ",[3412,4870,4224],{"class":3924},[3412,4872,4873],{"class":3418},"  # атрибут класу — спільний для всіх екземплярів\n",[3412,4875,4876],{"class":3414,"line":3437},[3412,4877,3554],{"emptyLinePlaceholder":3553},[3412,4879,4880,4882,4884,4886,4888,4890,4892,4894,4896],{"class":3414,"line":3468},[3412,4881,3440],{"class":3425},[3412,4883,3865],{"class":3443},[3412,4885,3447],{"class":3433},[3412,4887,3451],{"class":3450},[3412,4889,3454],{"class":3433},[3412,4891,3874],{"class":3450},[3412,4893,3877],{"class":3433},[3412,4895,3880],{"class":3429},[3412,4897,3893],{"class":3433},[3412,4899,4900,4902,4905],{"class":3414,"line":3516},[3412,4901,3898],{"class":3425},[3412,4903,4904],{"class":3433},".brand = brand  ",[3412,4906,4907],{"class":3418},"# атрибут екземпляра — індивідуальний\n",[3412,4909,4910],{"class":3414,"line":3550},[3412,4911,3554],{"emptyLinePlaceholder":3553},[3412,4913,4914,4917,4919],{"class":3414,"line":3557},[3412,4915,4916],{"class":3433},"car1 = Vehicle(",[3412,4918,4679],{"class":3495},[3412,4920,4069],{"class":3433},[3412,4922,4923,4926,4929],{"class":3414,"line":3567},[3412,4924,4925],{"class":3433},"car2 = Vehicle(",[3412,4927,4928],{"class":3495},"\"Honda\"",[3412,4930,4069],{"class":3433},[3412,4932,4933],{"class":3414,"line":3588},[3412,4934,3554],{"emptyLinePlaceholder":3553},[3412,4936,4937],{"class":3414,"line":3628},[3412,4938,4939],{"class":3418},"# Атрибути класу — спільні\n",[3412,4941,4942,4944,4947],{"class":3414,"line":3655},[3412,4943,3487],{"class":3443},[3412,4945,4946],{"class":3433},"(Vehicle.wheels)   ",[3412,4948,4949],{"class":3418},"# 4\n",[3412,4951,4952,4954,4957],{"class":3414,"line":3660},[3412,4953,3487],{"class":3443},[3412,4955,4956],{"class":3433},"(car1.wheels)      ",[3412,4958,4959],{"class":3418},"# 4 ← знайдено у Vehicle.__dict__, НЕ у car1.__dict__\n",[3412,4961,4962,4964,4967],{"class":3414,"line":3670},[3412,4963,3487],{"class":3443},[3412,4965,4966],{"class":3433},"(car2.wheels)      ",[3412,4968,4949],{"class":3418},[3412,4970,4971],{"class":3414,"line":3691},[3412,4972,3554],{"emptyLinePlaceholder":3553},[3412,4974,4975],{"class":3414,"line":3731},[3412,4976,4977],{"class":3418},"# Перевіряємо: car1.__dict__ не містить 'wheels'\n",[3412,4979,4980,4982,4985,4987,4990],{"class":3414,"line":4072},[3412,4981,3487],{"class":3443},[3412,4983,4984],{"class":3433},"(car1.",[3412,4986,3542],{"class":3450},[3412,4988,4989],{"class":3433},")    ",[3412,4991,4992],{"class":3418},"# {'brand': 'Toyota'} ← лише атрибут екземпляра!\n",[3412,4994,4995,4997,5000,5002,5005],{"class":3414,"line":4077},[3412,4996,3487],{"class":3443},[3412,4998,4999],{"class":3433},"(Vehicle.",[3412,5001,3542],{"class":3450},[3412,5003,5004],{"class":3433},".keys())  ",[3412,5006,5007],{"class":3418},"# ..., 'wheels', ...\n",[3412,5009,5010],{"class":3414,"line":4095},[3412,5011,3554],{"emptyLinePlaceholder":3553},[3412,5013,5015],{"class":3414,"line":5014},19,[3412,5016,5017],{"class":3418},"# Якщо присвоїти 'wheels' через екземпляр — він стає атрибутом ЕКЗЕМПЛЯРА,\n",[3412,5019,5021],{"class":3414,"line":5020},20,[3412,5022,5023],{"class":3418},"# і ТІНЬУЄ (shadows) атрибут класу\n",[3412,5025,5027,5030],{"class":3414,"line":5026},21,[3412,5028,5029],{"class":3433},"car1.wheels = ",[3412,5031,5032],{"class":3924},"2\n",[3412,5034,5036,5038,5041],{"class":3414,"line":5035},22,[3412,5037,3487],{"class":3443},[3412,5039,5040],{"class":3433},"(car1.wheels)   ",[3412,5042,5043],{"class":3418},"# 2  ← тепер з car1.__dict__\n",[3412,5045,5047,5049,5052],{"class":3414,"line":5046},23,[3412,5048,3487],{"class":3443},[3412,5050,5051],{"class":3433},"(car2.wheels)   ",[3412,5053,5054],{"class":3418},"# 4  ← Vehicle.__dict__ незмінений\n",[3412,5056,5058,5060,5063],{"class":3414,"line":5057},24,[3412,5059,3487],{"class":3443},[3412,5061,5062],{"class":3433},"(Vehicle.wheels)",[3412,5064,5054],{"class":3418},[5066,5067,5068,5071,5072,3972,5075,5078,5079,5082,5083,5085],"warning",{},[3764,5069,5070],{},"Типова пастка:"," зміна мутабельного атрибута класу (наприклад, списку) ",[3764,5073,5074],{},"через клас",[3390,5076,5077],{},"Vehicle.tags.append(...)",") змінює його для всіх екземплярів. Тоді як присвоєння через екземпляр (",[3390,5080,5081],{},"car.tags = [...]",") лише створює новий атрибут екземпляра, не змінюючи класовий. Тому мутабельні атрибути (списки, словники) завжди ініціалізуйте у ",[3390,5084,4242],{},", а не на рівні класу.",[3796,5087],{},[3803,5089,5091,5092,3454,5095,4819,5098],{"id":5090},"дослідження-ієрархії-__base__-__bases__-та-__subclasses__","Дослідження ієрархії: ",[3390,5093,5094],{},"__base__",[3390,5096,5097],{},"__bases__",[3390,5099,5100],{},"__subclasses__()",[3399,5102,5103],{},"Python зберігає повну інформацію про ієрархію наслідування безпосередньо в об'єктах класів. Ці dunder-атрибути є вашими інструментами для дослідження та налагодження ієрархій.",[5105,5106,5107,5115,5126,5133],"field-group",{},[5108,5109,5111,5112,3539],"field",{"name":4301,"type":5110},"type","Перший (або єдиний) безпосередній батьківський клас. При одиночному наслідуванні — завжди є єдиним батьком. При множинному — перший у списку. Еквівалентно ",[3390,5113,5114],{},"cls.__bases__[0]",[5108,5116,5119,5120,5123,5124,3539],{"name":5117,"type":5118},"bases","tuple[type, ...]","Кортеж ",[3764,5121,5122],{},"усіх"," безпосередніх батьківських класів. При одиночному наслідуванні — кортеж з одного елемента. При множинному — кортеж у порядку оголошення. Порожній лише у ",[3390,5125,3815],{},[5108,5127,5129,5130,5132],{"name":5128,"type":5118},"mro","Повна черга пошуку методів (Method Resolution Order) — кортеж класів від поточного до ",[3390,5131,3815],{},". Визначається алгоритмом C3-лінеаризації при створенні класу.",[5108,5134,5137,5138,5141],{"name":5135,"type":5136},"subclasses()","() -> list[type]","Метод, що повертає ",[3764,5139,5140],{},"список усіх живих прямих підкласів"," даного класу. «Живих» — тобто тих, чий код був завантажений (виконаний) інтерпретатором. Корисний для динамічних реєстрів та плагін-систем.",[3403,5143,5145],{"className":3405,"code":5144,"language":3407,"meta":3408,"style":3408},"from vehicle import Vehicle\nfrom car import Car\nfrom electric_car import ElectricCar\n\n# ── Перевірка ієрархії ────────────────────────────────────────────────────────\n\nprint(\"ElectricCar.__base__:  \", ElectricCar.__base__)\n# ElectricCar.__base__:   \u003Cclass 'car.Car'>\n\nprint(\"ElectricCar.__bases__: \", ElectricCar.__bases__)\n# ElectricCar.__bases__:  (\u003Cclass 'car.Car'>,)\n\nprint(\"Car.__base__:          \", Car.__base__)\n# Car.__base__:            \u003Cclass 'vehicle.Vehicle'>\n\nprint(\"Vehicle.__base__:      \", Vehicle.__base__)\n# Vehicle.__base__:        \u003Cclass 'object'>   ← всі класи неявно від object\n\nprint(\"object.__base__:       \", object.__base__)\n# object.__base__:         None               ← вершина ієрархії\n\n# ── MRO для ElectricCar ───────────────────────────────────────────────────────\nprint(\"\\nElectricCar.__mro__:\")\nfor cls in ElectricCar.__mro__:\n    print(f\"  {cls}\")\n# \u003Cclass 'electric_car.ElectricCar'>\n# \u003Cclass 'car.Car'>\n# \u003Cclass 'vehicle.Vehicle'>\n# \u003Cclass 'object'>\n\n# ── Живі підкласи ─────────────────────────────────────────────────────────────\nprint(\"\\nVehicle.__subclasses__(): \", Vehicle.__subclasses__())\n# [\u003Cclass 'car.Car'>]\n# (ElectricCar тут не відображений — він є підкласом Car, а не Vehicle напряму)\n\nprint(\"Car.__subclasses__():     \", Car.__subclasses__())\n# [\u003Cclass 'electric_car.ElectricCar'>]\n\n# ── isinstance та issubclass ──────────────────────────────────────────────────\ntesla = ElectricCar(\"Tesla\", 2024, 75.0)\n\n# isinstance перевіряє весь MRO-ланцюжок!\nprint(isinstance(tesla, ElectricCar))  # True  — прямий тип\nprint(isinstance(tesla, Car))          # True  — батько\nprint(isinstance(tesla, Vehicle))      # True  — дід\nprint(isinstance(tesla, object))       # True  — завжди True\n\n# issubclass: перевірка на рівні класів\nprint(issubclass(ElectricCar, Car))     # True\nprint(issubclass(ElectricCar, Vehicle)) # True\nprint(issubclass(Car, ElectricCar))     # False — зворотній напрям неможливий\n",[3390,5146,5147,5157,5167,5177,5181,5186,5190,5202,5207,5211,5227,5232,5236,5248,5253,5257,5269,5274,5278,5294,5299,5303,5308,5325,5343,5362,5368,5374,5380,5386,5391,5397,5420,5426,5432,5437,5454,5460,5465,5471,5489,5494,5500,5516,5531,5546,5566,5571,5577,5593,5607],{"__ignoreMap":3408},[3412,5148,5149,5151,5153,5155],{"class":3414,"line":3415},[3412,5150,4148],{"class":3530},[3412,5152,4151],{"class":3433},[3412,5154,4154],{"class":3530},[3412,5156,4157],{"class":3433},[3412,5158,5159,5161,5163,5165],{"class":3414,"line":3422},[3412,5160,4148],{"class":3530},[3412,5162,4357],{"class":3433},[3412,5164,4154],{"class":3530},[3412,5166,4362],{"class":3433},[3412,5168,5169,5171,5173,5175],{"class":3414,"line":3437},[3412,5170,4148],{"class":3530},[3412,5172,4641],{"class":3433},[3412,5174,4154],{"class":3530},[3412,5176,4646],{"class":3433},[3412,5178,5179],{"class":3414,"line":3468},[3412,5180,3554],{"emptyLinePlaceholder":3553},[3412,5182,5183],{"class":3414,"line":3516},[3412,5184,5185],{"class":3418},"# ── Перевірка ієрархії ────────────────────────────────────────────────────────\n",[3412,5187,5188],{"class":3414,"line":3550},[3412,5189,3554],{"emptyLinePlaceholder":3553},[3412,5191,5192,5194,5196,5199],{"class":3414,"line":3557},[3412,5193,3487],{"class":3443},[3412,5195,3447],{"class":3433},[3412,5197,5198],{"class":3495},"\"ElectricCar.__base__:  \"",[3412,5200,5201],{"class":3433},", ElectricCar.__base__)\n",[3412,5203,5204],{"class":3414,"line":3567},[3412,5205,5206],{"class":3418},"# ElectricCar.__base__:   \u003Cclass 'car.Car'>\n",[3412,5208,5209],{"class":3414,"line":3588},[3412,5210,3554],{"emptyLinePlaceholder":3553},[3412,5212,5213,5215,5217,5220,5223,5225],{"class":3414,"line":3628},[3412,5214,3487],{"class":3443},[3412,5216,3447],{"class":3433},[3412,5218,5219],{"class":3495},"\"ElectricCar.__bases__: \"",[3412,5221,5222],{"class":3433},", ElectricCar.",[3412,5224,5097],{"class":3450},[3412,5226,4069],{"class":3433},[3412,5228,5229],{"class":3414,"line":3655},[3412,5230,5231],{"class":3418},"# ElectricCar.__bases__:  (\u003Cclass 'car.Car'>,)\n",[3412,5233,5234],{"class":3414,"line":3660},[3412,5235,3554],{"emptyLinePlaceholder":3553},[3412,5237,5238,5240,5242,5245],{"class":3414,"line":3670},[3412,5239,3487],{"class":3443},[3412,5241,3447],{"class":3433},[3412,5243,5244],{"class":3495},"\"Car.__base__:          \"",[3412,5246,5247],{"class":3433},", Car.__base__)\n",[3412,5249,5250],{"class":3414,"line":3691},[3412,5251,5252],{"class":3418},"# Car.__base__:            \u003Cclass 'vehicle.Vehicle'>\n",[3412,5254,5255],{"class":3414,"line":3731},[3412,5256,3554],{"emptyLinePlaceholder":3553},[3412,5258,5259,5261,5263,5266],{"class":3414,"line":4072},[3412,5260,3487],{"class":3443},[3412,5262,3447],{"class":3433},[3412,5264,5265],{"class":3495},"\"Vehicle.__base__:      \"",[3412,5267,5268],{"class":3433},", Vehicle.__base__)\n",[3412,5270,5271],{"class":3414,"line":4077},[3412,5272,5273],{"class":3418},"# Vehicle.__base__:        \u003Cclass 'object'>   ← всі класи неявно від object\n",[3412,5275,5276],{"class":3414,"line":4095},[3412,5277,3554],{"emptyLinePlaceholder":3553},[3412,5279,5280,5282,5284,5287,5289,5291],{"class":3414,"line":5014},[3412,5281,3487],{"class":3443},[3412,5283,3447],{"class":3433},[3412,5285,5286],{"class":3495},"\"object.__base__:       \"",[3412,5288,3454],{"class":3433},[3412,5290,3815],{"class":3429},[3412,5292,5293],{"class":3433},".__base__)\n",[3412,5295,5296],{"class":3414,"line":5020},[3412,5297,5298],{"class":3418},"# object.__base__:         None               ← вершина ієрархії\n",[3412,5300,5301],{"class":3414,"line":5026},[3412,5302,3554],{"emptyLinePlaceholder":3553},[3412,5304,5305],{"class":3414,"line":5035},[3412,5306,5307],{"class":3418},"# ── MRO для ElectricCar ───────────────────────────────────────────────────────\n",[3412,5309,5310,5312,5314,5316,5320,5323],{"class":3414,"line":5046},[3412,5311,3487],{"class":3443},[3412,5313,3447],{"class":3433},[3412,5315,3507],{"class":3495},[3412,5317,5319],{"class":5318},"sjcCO","\\n",[3412,5321,5322],{"class":3495},"ElectricCar.__mro__:\"",[3412,5324,4069],{"class":3433},[3412,5326,5327,5329,5332,5335,5338,5341],{"class":3414,"line":5057},[3412,5328,4738],{"class":3530},[3412,5330,5331],{"class":3425}," cls",[3412,5333,5334],{"class":3530}," in",[3412,5336,5337],{"class":3433}," ElectricCar.",[3412,5339,5340],{"class":3450},"__mro__",[3412,5342,3434],{"class":3433},[3412,5344,5346,5348,5350,5352,5355,5358,5360],{"class":3414,"line":5345},25,[3412,5347,4752],{"class":3443},[3412,5349,3447],{"class":3433},[3412,5351,3492],{"class":3425},[3412,5353,5354],{"class":3495},"\"  ",[3412,5356,5357],{"class":3425},"{cls}",[3412,5359,3507],{"class":3495},[3412,5361,4069],{"class":3433},[3412,5363,5365],{"class":3414,"line":5364},26,[3412,5366,5367],{"class":3418},"# \u003Cclass 'electric_car.ElectricCar'>\n",[3412,5369,5371],{"class":3414,"line":5370},27,[3412,5372,5373],{"class":3418},"# \u003Cclass 'car.Car'>\n",[3412,5375,5377],{"class":3414,"line":5376},28,[3412,5378,5379],{"class":3418},"# \u003Cclass 'vehicle.Vehicle'>\n",[3412,5381,5383],{"class":3414,"line":5382},29,[3412,5384,5385],{"class":3418},"# \u003Cclass 'object'>\n",[3412,5387,5389],{"class":3414,"line":5388},30,[3412,5390,3554],{"emptyLinePlaceholder":3553},[3412,5392,5394],{"class":3414,"line":5393},31,[3412,5395,5396],{"class":3418},"# ── Живі підкласи ─────────────────────────────────────────────────────────────\n",[3412,5398,5400,5402,5404,5406,5408,5411,5414,5417],{"class":3414,"line":5399},32,[3412,5401,3487],{"class":3443},[3412,5403,3447],{"class":3433},[3412,5405,3507],{"class":3495},[3412,5407,5319],{"class":5318},[3412,5409,5410],{"class":3495},"Vehicle.__subclasses__(): \"",[3412,5412,5413],{"class":3433},", Vehicle.",[3412,5415,5416],{"class":3450},"__subclasses__",[3412,5418,5419],{"class":3433},"())\n",[3412,5421,5423],{"class":3414,"line":5422},33,[3412,5424,5425],{"class":3418},"# [\u003Cclass 'car.Car'>]\n",[3412,5427,5429],{"class":3414,"line":5428},34,[3412,5430,5431],{"class":3418},"# (ElectricCar тут не відображений — він є підкласом Car, а не Vehicle напряму)\n",[3412,5433,5435],{"class":3414,"line":5434},35,[3412,5436,3554],{"emptyLinePlaceholder":3553},[3412,5438,5440,5442,5444,5447,5450,5452],{"class":3414,"line":5439},36,[3412,5441,3487],{"class":3443},[3412,5443,3447],{"class":3433},[3412,5445,5446],{"class":3495},"\"Car.__subclasses__():     \"",[3412,5448,5449],{"class":3433},", Car.",[3412,5451,5416],{"class":3450},[3412,5453,5419],{"class":3433},[3412,5455,5457],{"class":3414,"line":5456},37,[3412,5458,5459],{"class":3418},"# [\u003Cclass 'electric_car.ElectricCar'>]\n",[3412,5461,5463],{"class":3414,"line":5462},38,[3412,5464,3554],{"emptyLinePlaceholder":3553},[3412,5466,5468],{"class":3414,"line":5467},39,[3412,5469,5470],{"class":3418},"# ── isinstance та issubclass ──────────────────────────────────────────────────\n",[3412,5472,5474,5477,5479,5481,5483,5485,5487],{"class":3414,"line":5473},40,[3412,5475,5476],{"class":3433},"tesla = ElectricCar(",[3412,5478,4703],{"class":3495},[3412,5480,3454],{"class":3433},[3412,5482,4708],{"class":3924},[3412,5484,3454],{"class":3433},[3412,5486,4717],{"class":3924},[3412,5488,4069],{"class":3433},[3412,5490,5492],{"class":3414,"line":5491},41,[3412,5493,3554],{"emptyLinePlaceholder":3553},[3412,5495,5497],{"class":3414,"line":5496},42,[3412,5498,5499],{"class":3418},"# isinstance перевіряє весь MRO-ланцюжок!\n",[3412,5501,5503,5505,5507,5510,5513],{"class":3414,"line":5502},43,[3412,5504,3487],{"class":3443},[3412,5506,3447],{"class":3433},[3412,5508,5509],{"class":3443},"isinstance",[3412,5511,5512],{"class":3433},"(tesla, ElectricCar))  ",[3412,5514,5515],{"class":3418},"# True  — прямий тип\n",[3412,5517,5519,5521,5523,5525,5528],{"class":3414,"line":5518},44,[3412,5520,3487],{"class":3443},[3412,5522,3447],{"class":3433},[3412,5524,5509],{"class":3443},[3412,5526,5527],{"class":3433},"(tesla, Car))          ",[3412,5529,5530],{"class":3418},"# True  — батько\n",[3412,5532,5534,5536,5538,5540,5543],{"class":3414,"line":5533},45,[3412,5535,3487],{"class":3443},[3412,5537,3447],{"class":3433},[3412,5539,5509],{"class":3443},[3412,5541,5542],{"class":3433},"(tesla, Vehicle))      ",[3412,5544,5545],{"class":3418},"# True  — дід\n",[3412,5547,5549,5551,5553,5555,5558,5560,5563],{"class":3414,"line":5548},46,[3412,5550,3487],{"class":3443},[3412,5552,3447],{"class":3433},[3412,5554,5509],{"class":3443},[3412,5556,5557],{"class":3433},"(tesla, ",[3412,5559,3815],{"class":3429},[3412,5561,5562],{"class":3433},"))       ",[3412,5564,5565],{"class":3418},"# True  — завжди True\n",[3412,5567,5569],{"class":3414,"line":5568},47,[3412,5570,3554],{"emptyLinePlaceholder":3553},[3412,5572,5574],{"class":3414,"line":5573},48,[3412,5575,5576],{"class":3418},"# issubclass: перевірка на рівні класів\n",[3412,5578,5580,5582,5584,5587,5590],{"class":3414,"line":5579},49,[3412,5581,3487],{"class":3443},[3412,5583,3447],{"class":3433},[3412,5585,5586],{"class":3443},"issubclass",[3412,5588,5589],{"class":3433},"(ElectricCar, Car))     ",[3412,5591,5592],{"class":3418},"# True\n",[3412,5594,5596,5598,5600,5602,5605],{"class":3414,"line":5595},50,[3412,5597,3487],{"class":3443},[3412,5599,3447],{"class":3433},[3412,5601,5586],{"class":3443},[3412,5603,5604],{"class":3433},"(ElectricCar, Vehicle)) ",[3412,5606,5592],{"class":3418},[3412,5608,5610,5612,5614,5616,5619],{"class":3414,"line":5609},51,[3412,5611,3487],{"class":3443},[3412,5613,3447],{"class":3433},[3412,5615,5586],{"class":3443},[3412,5617,5618],{"class":3433},"(Car, ElectricCar))     ",[3412,5620,5621],{"class":3418},"# False — зворотній напрям неможливий\n",[4757,5623,5625,5634,5642,5650,5658,5666,5674,5677,5681,5689,5696,5703,5709,5712,5725],{"title":5624},"Дослідження ієрархії наслідування",[4761,5626,5628,4769,5631],{"className":5627},[3414],[3412,5629,4768],{"className":5630},[4767],[3764,5632,5633],{},"python hierarchy_inspect.py",[4761,5635,5637,5638],{"className":5636},[3414],"ElectricCar.__base__:   ",[3412,5639,5641],{"className":5640},[4792],"\u003Cclass 'car.Car'>",[4761,5643,5645,5646],{"className":5644},[3414],"ElectricCar.__bases__:  ",[3412,5647,5649],{"className":5648},[4792],"(\u003Cclass 'car.Car'>,)",[4761,5651,5653,5654],{"className":5652},[3414],"Car.__base__:           ",[3412,5655,5657],{"className":5656},[4792],"\u003Cclass 'vehicle.Vehicle'>",[4761,5659,5661,5662],{"className":5660},[3414],"Vehicle.__base__:       ",[3412,5663,5665],{"className":5664},[4792],"\u003Cclass 'object'>",[4761,5667,5669,5670],{"className":5668},[3414],"object.__base__:        ",[3412,5671,4011],{"className":5672},[5673],"text-gray-400",[4761,5675],{"className":5676},[3414],[4761,5678,5680],{"className":5679},[3414],"ElectricCar.__mro__:",[4761,5682,5684,5685],{"className":5683},[3414],"  ",[3412,5686,5688],{"className":5687},[4800],"\u003Cclass 'ElectricCar'>",[4761,5690,5684,5692],{"className":5691},[3414],[3412,5693,5695],{"className":5694},[4779],"\u003Cclass 'Car'>",[4761,5697,5684,5699],{"className":5698},[3414],[3412,5700,5702],{"className":5701},[4779],"\u003Cclass 'Vehicle'>",[4761,5704,5684,5706],{"className":5705},[3414],[3412,5707,5665],{"className":5708},[5673],[4761,5710],{"className":5711},[3414],[4761,5713,5715,5716,5720,5721],{"className":5714},[3414],"isinstance(tesla, Vehicle): ",[3412,5717,5719],{"className":5718},[4800],"True","   ",[3412,5722,5724],{"className":5723},[5673],"# весь ланцюжок перевірено!",[4761,5726,5728,5729,5684,5734],{"className":5727},[3414],"issubclass(Car, ElectricCar): ",[3412,5730,5733],{"className":5731},[5732],"text-rose-400","False",[3412,5735,5737],{"className":5736},[5673],"# зворотній напрям",[5739,5740,5741,5745,5746,4819,5749,5752,5753,5755],"tip",{},[3764,5742,5743],{},[3390,5744,5100],{}," — потужний інструмент для реалізації ",[3764,5747,5748],{},"реєстрів плагінів",[3764,5750,5751],{},"фабричних методів",". Якщо всі плагіни наслідують від базового класу, ви можете динамічно отримати їх список без явної реєстрації. Але пам'ятайте: клас з'являється у ",[3390,5754,5100],{}," лише після того, як його модуль буде імпортований.",[3796,5757],{},[3394,5759,5761,5762,5764],{"id":5760},"частина-ii-super-проксі-обєкт-а-не-батьківський-клас","Частина II: ",[3390,5763,3392],{}," — проксі-об'єкт, а не батьківський клас",[3803,5766,5768,5769,5771],{"id":5767},"чому-super-це-не-просто-виклик-батька","Чому ",[3390,5770,3392],{}," — це не просто «виклик батька»",[3399,5773,5774,5775,5778,5779,5782],{},"Більшість розробників-початківців читають ",[3390,5776,5777],{},"super().__init__(...)"," як «виклик конструктора батьківського класу». Це ",[3764,5780,5781],{},"хибна ментальна модель",", яка ламається при першій же зустрічі з множинним наслідуванням.",[3399,5784,5785,5791,5792,5795,5796,5799],{},[3764,5786,5787,5788,5790],{},"Реальна поведінка ",[3390,5789,3392],{},":"," він повертає ",[3764,5793,5794],{},"проксі-об'єкт",", який делегує виклик методів на ",[3764,5797,5798],{},"наступний клас у черзі MRO"," від поточного, а не на безпосереднього батька.",[3399,5801,5802],{},"Різниця стає очевидною в ієрархії з множинним наслідуванням:",[3403,5804,5806],{"className":3405,"code":5805,"language":3407,"meta":3408,"style":3408},"class A:\n    def greet(self):\n        print(\"A.greet\")\n        super().greet()   # Наступний у MRO — не object, а може бути B або C!\n\nclass B(A):\n    def greet(self):\n        print(\"B.greet\")\n        super().greet()   # Наступний у MRO від B в контексті D — це C\n\nclass C(A):\n    def greet(self):\n        print(\"C.greet\")\n        super().greet()   # Наступний у MRO від C — object (немає greet — AttributeError)\n\nclass D(B, C):\n    def greet(self):\n        print(\"D.greet\")\n        super().greet()   # Наступний у MRO від D — B\n\n# MRO: [D, B, C, A, object]\nd = D()\nd.greet()\n# D.greet → B.greet → C.greet → A.greet\n",[3390,5807,5808,5817,5830,5841,5851,5855,5869,5881,5892,5901,5905,5918,5930,5941,5950,5954,5973,5985,5996,6005,6009,6014,6019,6024],{"__ignoreMap":3408},[3412,5809,5810,5812,5815],{"class":3414,"line":3415},[3412,5811,3426],{"class":3425},[3412,5813,5814],{"class":3429}," A",[3412,5816,3434],{"class":3433},[3412,5818,5819,5821,5824,5826,5828],{"class":3414,"line":3422},[3412,5820,3440],{"class":3425},[3412,5822,5823],{"class":3443}," greet",[3412,5825,3447],{"class":3433},[3412,5827,3451],{"class":3450},[3412,5829,3893],{"class":3433},[3412,5831,5832,5834,5836,5839],{"class":3414,"line":3437},[3412,5833,4039],{"class":3443},[3412,5835,3447],{"class":3433},[3412,5837,5838],{"class":3495},"\"A.greet\"",[3412,5840,4069],{"class":3433},[3412,5842,5843,5845,5848],{"class":3414,"line":3468},[3412,5844,4236],{"class":3429},[3412,5846,5847],{"class":3433},"().greet()   ",[3412,5849,5850],{"class":3418},"# Наступний у MRO — не object, а може бути B або C!\n",[3412,5852,5853],{"class":3414,"line":3516},[3412,5854,3554],{"emptyLinePlaceholder":3553},[3412,5856,5857,5859,5862,5864,5867],{"class":3414,"line":3550},[3412,5858,3426],{"class":3425},[3412,5860,5861],{"class":3429}," B",[3412,5863,3447],{"class":3433},[3412,5865,5866],{"class":3429},"A",[3412,5868,3893],{"class":3433},[3412,5870,5871,5873,5875,5877,5879],{"class":3414,"line":3557},[3412,5872,3440],{"class":3425},[3412,5874,5823],{"class":3443},[3412,5876,3447],{"class":3433},[3412,5878,3451],{"class":3450},[3412,5880,3893],{"class":3433},[3412,5882,5883,5885,5887,5890],{"class":3414,"line":3567},[3412,5884,4039],{"class":3443},[3412,5886,3447],{"class":3433},[3412,5888,5889],{"class":3495},"\"B.greet\"",[3412,5891,4069],{"class":3433},[3412,5893,5894,5896,5898],{"class":3414,"line":3588},[3412,5895,4236],{"class":3429},[3412,5897,5847],{"class":3433},[3412,5899,5900],{"class":3418},"# Наступний у MRO від B в контексті D — це C\n",[3412,5902,5903],{"class":3414,"line":3628},[3412,5904,3554],{"emptyLinePlaceholder":3553},[3412,5906,5907,5909,5912,5914,5916],{"class":3414,"line":3655},[3412,5908,3426],{"class":3425},[3412,5910,5911],{"class":3429}," C",[3412,5913,3447],{"class":3433},[3412,5915,5866],{"class":3429},[3412,5917,3893],{"class":3433},[3412,5919,5920,5922,5924,5926,5928],{"class":3414,"line":3660},[3412,5921,3440],{"class":3425},[3412,5923,5823],{"class":3443},[3412,5925,3447],{"class":3433},[3412,5927,3451],{"class":3450},[3412,5929,3893],{"class":3433},[3412,5931,5932,5934,5936,5939],{"class":3414,"line":3670},[3412,5933,4039],{"class":3443},[3412,5935,3447],{"class":3433},[3412,5937,5938],{"class":3495},"\"C.greet\"",[3412,5940,4069],{"class":3433},[3412,5942,5943,5945,5947],{"class":3414,"line":3691},[3412,5944,4236],{"class":3429},[3412,5946,5847],{"class":3433},[3412,5948,5949],{"class":3418},"# Наступний у MRO від C — object (немає greet — AttributeError)\n",[3412,5951,5952],{"class":3414,"line":3731},[3412,5953,3554],{"emptyLinePlaceholder":3553},[3412,5955,5956,5958,5961,5963,5966,5968,5971],{"class":3414,"line":4072},[3412,5957,3426],{"class":3425},[3412,5959,5960],{"class":3429}," D",[3412,5962,3447],{"class":3433},[3412,5964,5965],{"class":3429},"B",[3412,5967,3454],{"class":3433},[3412,5969,5970],{"class":3429},"C",[3412,5972,3893],{"class":3433},[3412,5974,5975,5977,5979,5981,5983],{"class":3414,"line":4077},[3412,5976,3440],{"class":3425},[3412,5978,5823],{"class":3443},[3412,5980,3447],{"class":3433},[3412,5982,3451],{"class":3450},[3412,5984,3893],{"class":3433},[3412,5986,5987,5989,5991,5994],{"class":3414,"line":4095},[3412,5988,4039],{"class":3443},[3412,5990,3447],{"class":3433},[3412,5992,5993],{"class":3495},"\"D.greet\"",[3412,5995,4069],{"class":3433},[3412,5997,5998,6000,6002],{"class":3414,"line":5014},[3412,5999,4236],{"class":3429},[3412,6001,5847],{"class":3433},[3412,6003,6004],{"class":3418},"# Наступний у MRO від D — B\n",[3412,6006,6007],{"class":3414,"line":5020},[3412,6008,3554],{"emptyLinePlaceholder":3553},[3412,6010,6011],{"class":3414,"line":5026},[3412,6012,6013],{"class":3418},"# MRO: [D, B, C, A, object]\n",[3412,6015,6016],{"class":3414,"line":5035},[3412,6017,6018],{"class":3433},"d = D()\n",[3412,6020,6021],{"class":3414,"line":5046},[3412,6022,6023],{"class":3433},"d.greet()\n",[3412,6025,6026],{"class":3414,"line":5057},[3412,6027,6028],{"class":3418},"# D.greet → B.greet → C.greet → A.greet\n",[3399,6030,6031,6032,6034,6035,6038,6039,6042,6043,6046,6047,6050,6051,3539],{},"Якби ",[3390,6033,3392],{}," у ",[3390,6036,6037],{},"B.greet"," означав «виклик батька» (",[3390,6040,6041],{},"A.greet","), то ",[3390,6044,6045],{},"C.greet"," ніколи б не виконався. Але завдяки MRO виклик проходить ",[3764,6048,6049],{},"через увесь ланцюжок"," — саме це називається ",[3764,6052,6053],{},"кооперативним наслідуванням (cooperative inheritance)",[6055,6056,6057],"plant-uml",{},[3403,6058,6062],{"className":6059,"code":6060,"language":6061,"meta":3408,"style":3408},"language-plantuml shiki shiki-themes light-plus dark-plus dark-plus","@startuml\nskinparam style plain\nskinparam backgroundColor #ffffff\nskinparam ArrowColor #6366f1\nskinparam sequence {\n    ParticipantBackgroundColor #f3f4f6\n    ParticipantBorderColor #d1d5db\n    LifeLineBackgroundColor #e0e7ff\n}\n\nparticipant \"d: D\" as D #d1fae5\nparticipant \"B\" as B #dbeafe\nparticipant \"C\" as C #fef3c7\nparticipant \"A\" as A #fee2e2\nparticipant \"object\" as OBJ #f3f4f6\n\nD -> B : super().greet()\\n[MRO: D→**B**→C→A]\nnote right of B\n  super() у B — це не A!\n  Це наступний у MRO\n  в контексті об'єкта D\nend note\nB -> C : super().greet()\\n[MRO: D→B→**C**→A]\nC -> A : super().greet()\\n[MRO: D→B→C→**A**]\nA -> OBJ : super().greet()\\n[object не має greet → тихо]\n@enduml\n","plantuml",[3390,6063,6064,6069,6074,6079,6084,6089,6094,6099,6104,6109,6113,6118,6123,6128,6133,6138,6142,6147,6152,6157,6162,6167,6172,6177,6182,6187],{"__ignoreMap":3408},[3412,6065,6066],{"class":3414,"line":3415},[3412,6067,6068],{},"@startuml\n",[3412,6070,6071],{"class":3414,"line":3422},[3412,6072,6073],{},"skinparam style plain\n",[3412,6075,6076],{"class":3414,"line":3437},[3412,6077,6078],{},"skinparam backgroundColor #ffffff\n",[3412,6080,6081],{"class":3414,"line":3468},[3412,6082,6083],{},"skinparam ArrowColor #6366f1\n",[3412,6085,6086],{"class":3414,"line":3516},[3412,6087,6088],{},"skinparam sequence {\n",[3412,6090,6091],{"class":3414,"line":3550},[3412,6092,6093],{},"    ParticipantBackgroundColor #f3f4f6\n",[3412,6095,6096],{"class":3414,"line":3557},[3412,6097,6098],{},"    ParticipantBorderColor #d1d5db\n",[3412,6100,6101],{"class":3414,"line":3567},[3412,6102,6103],{},"    LifeLineBackgroundColor #e0e7ff\n",[3412,6105,6106],{"class":3414,"line":3588},[3412,6107,6108],{},"}\n",[3412,6110,6111],{"class":3414,"line":3628},[3412,6112,3554],{"emptyLinePlaceholder":3553},[3412,6114,6115],{"class":3414,"line":3655},[3412,6116,6117],{},"participant \"d: D\" as D #d1fae5\n",[3412,6119,6120],{"class":3414,"line":3660},[3412,6121,6122],{},"participant \"B\" as B #dbeafe\n",[3412,6124,6125],{"class":3414,"line":3670},[3412,6126,6127],{},"participant \"C\" as C #fef3c7\n",[3412,6129,6130],{"class":3414,"line":3691},[3412,6131,6132],{},"participant \"A\" as A #fee2e2\n",[3412,6134,6135],{"class":3414,"line":3731},[3412,6136,6137],{},"participant \"object\" as OBJ #f3f4f6\n",[3412,6139,6140],{"class":3414,"line":4072},[3412,6141,3554],{"emptyLinePlaceholder":3553},[3412,6143,6144],{"class":3414,"line":4077},[3412,6145,6146],{},"D -> B : super().greet()\\n[MRO: D→**B**→C→A]\n",[3412,6148,6149],{"class":3414,"line":4095},[3412,6150,6151],{},"note right of B\n",[3412,6153,6154],{"class":3414,"line":5014},[3412,6155,6156],{},"  super() у B — це не A!\n",[3412,6158,6159],{"class":3414,"line":5020},[3412,6160,6161],{},"  Це наступний у MRO\n",[3412,6163,6164],{"class":3414,"line":5026},[3412,6165,6166],{},"  в контексті об'єкта D\n",[3412,6168,6169],{"class":3414,"line":5035},[3412,6170,6171],{},"end note\n",[3412,6173,6174],{"class":3414,"line":5046},[3412,6175,6176],{},"B -> C : super().greet()\\n[MRO: D→B→**C**→A]\n",[3412,6178,6179],{"class":3414,"line":5057},[3412,6180,6181],{},"C -> A : super().greet()\\n[MRO: D→B→C→**A**]\n",[3412,6183,6184],{"class":3414,"line":5345},[3412,6185,6186],{},"A -> OBJ : super().greet()\\n[object не має greet → тихо]\n",[3412,6188,6189],{"class":3414,"line":5364},[3412,6190,6191],{},"@enduml\n",[3803,6193,6195,6197,6198,6200],{"id":6194},"__class__-як-cell-variable-чому-super-без-аргументів-не-є-магією",[3390,6196,4108],{}," як cell variable: чому ",[3390,6199,3392],{}," без аргументів не є магією",[3399,6202,6203,6204,6206,6207,6210,6211,6214,6215,6217],{},"У Python 3 ",[3390,6205,3392],{}," без аргументів — це не магія, а ",[3764,6208,6209],{},"синтаксичний цукор"," компілятора. Коли Python компілює метод класу, він ",[3764,6212,6213],{},"автоматично"," додає приховану клітинну змінну (cell variable) ",[3390,6216,4108],{},", яка замикається (closure) на об'єкт класу, в якому описано метод.",[3403,6219,6221],{"className":3405,"code":6220,"language":3407,"meta":3408,"style":3408},"class MyClass:\n    def my_method(self):\n        # Компілятор автоматично перетворює super() на super(__class__, self)\n        # де __class__ — це cell variable, що замикається на MyClass\n\n        # Ці два записи АБСОЛЮТНО еквівалентні:\n        super().some_method()          # синтаксичний цукор Python 3\n        super(__class__, self).some_method()  # явна форма Python 2\u002F3\n\n# Перевіримо: __class__ доступна у будь-якому методі\nclass Inspector:\n    def check(self):\n        # __class__ — це Inspector, незалежно від того, хто викликає метод\n        print(f\"__class__ = {__class__}\")\n        print(f\"type(self) = {type(self)}\")\n\nclass Child(Inspector):\n    pass\n\nChild().check()\n# __class__ = \u003Cclass 'Inspector'>  ← НЕЗМІННО: клас, де описано метод\n# type(self) = \u003Cclass 'Child'>     ← тип реального об'єкта\n",[3390,6222,6223,6232,6245,6250,6255,6259,6264,6274,6292,6296,6301,6310,6323,6328,6349,6377,6381,6395,6400,6404,6409,6414],{"__ignoreMap":3408},[3412,6224,6225,6227,6230],{"class":3414,"line":3415},[3412,6226,3426],{"class":3425},[3412,6228,6229],{"class":3429}," MyClass",[3412,6231,3434],{"class":3433},[3412,6233,6234,6236,6239,6241,6243],{"class":3414,"line":3422},[3412,6235,3440],{"class":3425},[3412,6237,6238],{"class":3443}," my_method",[3412,6240,3447],{"class":3433},[3412,6242,3451],{"class":3450},[3412,6244,3893],{"class":3433},[3412,6246,6247],{"class":3414,"line":3437},[3412,6248,6249],{"class":3418},"        # Компілятор автоматично перетворює super() на super(__class__, self)\n",[3412,6251,6252],{"class":3414,"line":3468},[3412,6253,6254],{"class":3418},"        # де __class__ — це cell variable, що замикається на MyClass\n",[3412,6256,6257],{"class":3414,"line":3516},[3412,6258,3554],{"emptyLinePlaceholder":3553},[3412,6260,6261],{"class":3414,"line":3550},[3412,6262,6263],{"class":3418},"        # Ці два записи АБСОЛЮТНО еквівалентні:\n",[3412,6265,6266,6268,6271],{"class":3414,"line":3557},[3412,6267,4236],{"class":3429},[3412,6269,6270],{"class":3433},"().some_method()          ",[3412,6272,6273],{"class":3418},"# синтаксичний цукор Python 3\n",[3412,6275,6276,6278,6280,6282,6284,6286,6289],{"class":3414,"line":3567},[3412,6277,4236],{"class":3429},[3412,6279,3447],{"class":3433},[3412,6281,4108],{"class":3450},[3412,6283,3454],{"class":3433},[3412,6285,3451],{"class":3425},[3412,6287,6288],{"class":3433},").some_method()  ",[3412,6290,6291],{"class":3418},"# явна форма Python 2\u002F3\n",[3412,6293,6294],{"class":3414,"line":3588},[3412,6295,3554],{"emptyLinePlaceholder":3553},[3412,6297,6298],{"class":3414,"line":3628},[3412,6299,6300],{"class":3418},"# Перевіримо: __class__ доступна у будь-якому методі\n",[3412,6302,6303,6305,6308],{"class":3414,"line":3655},[3412,6304,3426],{"class":3425},[3412,6306,6307],{"class":3429}," Inspector",[3412,6309,3434],{"class":3433},[3412,6311,6312,6314,6317,6319,6321],{"class":3414,"line":3660},[3412,6313,3440],{"class":3425},[3412,6315,6316],{"class":3443}," check",[3412,6318,3447],{"class":3433},[3412,6320,3451],{"class":3450},[3412,6322,3893],{"class":3433},[3412,6324,6325],{"class":3414,"line":3670},[3412,6326,6327],{"class":3418},"        # __class__ — це Inspector, незалежно від того, хто викликає метод\n",[3412,6329,6330,6332,6334,6336,6339,6341,6343,6345,6347],{"class":3414,"line":3691},[3412,6331,4039],{"class":3443},[3412,6333,3447],{"class":3433},[3412,6335,3492],{"class":3425},[3412,6337,6338],{"class":3495},"\"__class__ = ",[3412,6340,3499],{"class":3425},[3412,6342,4108],{"class":3450},[3412,6344,3504],{"class":3425},[3412,6346,3507],{"class":3495},[3412,6348,4069],{"class":3433},[3412,6350,6351,6353,6355,6357,6360,6362,6364,6366,6368,6371,6373,6375],{"class":3414,"line":3731},[3412,6352,4039],{"class":3443},[3412,6354,3447],{"class":3433},[3412,6356,3492],{"class":3425},[3412,6358,6359],{"class":3495},"\"type(self) = ",[3412,6361,3499],{"class":3425},[3412,6363,5110],{"class":3429},[3412,6365,3447],{"class":3433},[3412,6367,3451],{"class":3425},[3412,6369,6370],{"class":3433},")",[3412,6372,3504],{"class":3425},[3412,6374,3507],{"class":3495},[3412,6376,4069],{"class":3433},[3412,6378,6379],{"class":3414,"line":4072},[3412,6380,3554],{"emptyLinePlaceholder":3553},[3412,6382,6383,6385,6388,6390,6393],{"class":3414,"line":4077},[3412,6384,3426],{"class":3425},[3412,6386,6387],{"class":3429}," Child",[3412,6389,3447],{"class":3433},[3412,6391,6392],{"class":3429},"Inspector",[3412,6394,3893],{"class":3433},[3412,6396,6397],{"class":3414,"line":4095},[3412,6398,6399],{"class":3530},"    pass\n",[3412,6401,6402],{"class":3414,"line":5014},[3412,6403,3554],{"emptyLinePlaceholder":3553},[3412,6405,6406],{"class":3414,"line":5020},[3412,6407,6408],{"class":3433},"Child().check()\n",[3412,6410,6411],{"class":3414,"line":5026},[3412,6412,6413],{"class":3418},"# __class__ = \u003Cclass 'Inspector'>  ← НЕЗМІННО: клас, де описано метод\n",[3412,6415,6416],{"class":3414,"line":5035},[3412,6417,6418],{"class":3418},"# type(self) = \u003Cclass 'Child'>     ← тип реального об'єкта\n",[5066,6420,6421,6424,6425,6427,6428,6430,6431,6434,6435,6438,6439,6441,6442,6444,6445,6448,6449,3539],{},[3764,6422,6423],{},"Критична пастка:"," оскільки ",[3390,6426,3392],{}," без аргументів використовує ",[3390,6429,4108],{}," (cell variable), він ",[3764,6432,6433],{},"працює лише всередині методу класу",". Якщо ви витягнете метод назовні (наприклад, через ",[3390,6436,6437],{},"unwrapped = MyClass.my_method"," або у декораторі, що замінює функцію), ",[3390,6440,4108],{}," може бути недоступною, і ",[3390,6443,3392],{}," кине ",[3390,6446,6447],{},"RuntimeError",". У таких сценаріях використовуйте явну форму ",[3390,6450,6451],{},"super(MyClass, self)",[3803,6453,6455,6456],{"id":6454},"повна-сигнатура-super","Повна сигнатура ",[3390,6457,3392],{},[5105,6459,6460,6480],{},[5108,6461,6462,6463,6466,6467,6470,6471,6473,6474,6476,6477,6479],{"name":5110,"type":3426},"Клас, що є ",[3764,6464,6465],{},"відправною точкою"," в черзі MRO. Пошук методів починається з ",[3764,6468,6469],{},"наступного"," класу після ",[3390,6472,5110],{}," у черзі MRO. При ",[3390,6475,3392],{}," без аргументів — компілятор підставляє поточний клас (",[3390,6478,4108],{},").",[5108,6481,6484,6485,6488,6489,6491,6492,6494,6495,6034,6498,6501],{"name":6482,"type":6483},"object_or_type","instance | class","Об'єкт (екземпляр) або підклас, ",[3764,6486,6487],{},"чий MRO використовується"," для пошуку. При ",[3390,6490,3392],{}," без аргументів — компілятор підставляє ",[3390,6493,3451],{}," (або ",[3390,6496,6497],{},"cls",[3390,6499,6500],{},"@classmethod","). Це визначає, який саме MRO-ланцюжок буде обходитися.",[6503,6504,6505,6581,6650],"tabs",{},[6506,6507,6509],"tabs-item",{"label":6508},"Python 3 (сучасний)",[3403,6510,6512],{"className":3405,"code":6511,"language":3407,"meta":3408,"style":3408},"class Manager(Employee):\n    def __init__(self, name, salary, team_size):\n        # Python автоматично підставляє super(Manager, self)\n        # __class__ = Manager (cell variable)\n        # self = поточний екземпляр\n        super().__init__(name, salary)\n",[3390,6513,6514,6528,6555,6560,6565,6570],{"__ignoreMap":3408},[3412,6515,6516,6518,6521,6523,6526],{"class":3414,"line":3415},[3412,6517,3426],{"class":3425},[3412,6519,6520],{"class":3429}," Manager",[3412,6522,3447],{"class":3433},[3412,6524,6525],{"class":3429},"Employee",[3412,6527,3893],{"class":3433},[3412,6529,6530,6532,6534,6536,6538,6540,6543,6545,6548,6550,6553],{"class":3414,"line":3422},[3412,6531,3440],{"class":3425},[3412,6533,3865],{"class":3443},[3412,6535,3447],{"class":3433},[3412,6537,3451],{"class":3450},[3412,6539,3454],{"class":3433},[3412,6541,6542],{"class":3450},"name",[3412,6544,3454],{"class":3433},[3412,6546,6547],{"class":3450},"salary",[3412,6549,3454],{"class":3433},[3412,6551,6552],{"class":3450},"team_size",[3412,6554,3893],{"class":3433},[3412,6556,6557],{"class":3414,"line":3437},[3412,6558,6559],{"class":3418},"        # Python автоматично підставляє super(Manager, self)\n",[3412,6561,6562],{"class":3414,"line":3468},[3412,6563,6564],{"class":3418},"        # __class__ = Manager (cell variable)\n",[3412,6566,6567],{"class":3414,"line":3516},[3412,6568,6569],{"class":3418},"        # self = поточний екземпляр\n",[3412,6571,6572,6574,6576,6578],{"class":3414,"line":3550},[3412,6573,4236],{"class":3429},[3412,6575,4239],{"class":3433},[3412,6577,4242],{"class":3443},[3412,6579,6580],{"class":3433},"(name, salary)\n",[6506,6582,6584],{"label":6583},"Python 2 \u002F явна форма",[3403,6585,6587],{"className":3405,"code":6586,"language":3407,"meta":3408,"style":3408},"class Manager(Employee):\n    def __init__(self, name, salary, team_size):\n        # Явне вказання — необхідно у Python 2,\n        # корисно в Python 3 при динамічному використанні\n        super(Manager, self).__init__(name, salary)\n",[3390,6588,6589,6601,6625,6630,6635],{"__ignoreMap":3408},[3412,6590,6591,6593,6595,6597,6599],{"class":3414,"line":3415},[3412,6592,3426],{"class":3425},[3412,6594,6520],{"class":3429},[3412,6596,3447],{"class":3433},[3412,6598,6525],{"class":3429},[3412,6600,3893],{"class":3433},[3412,6602,6603,6605,6607,6609,6611,6613,6615,6617,6619,6621,6623],{"class":3414,"line":3422},[3412,6604,3440],{"class":3425},[3412,6606,3865],{"class":3443},[3412,6608,3447],{"class":3433},[3412,6610,3451],{"class":3450},[3412,6612,3454],{"class":3433},[3412,6614,6542],{"class":3450},[3412,6616,3454],{"class":3433},[3412,6618,6547],{"class":3450},[3412,6620,3454],{"class":3433},[3412,6622,6552],{"class":3450},[3412,6624,3893],{"class":3433},[3412,6626,6627],{"class":3414,"line":3437},[3412,6628,6629],{"class":3418},"        # Явне вказання — необхідно у Python 2,\n",[3412,6631,6632],{"class":3414,"line":3468},[3412,6633,6634],{"class":3418},"        # корисно в Python 3 при динамічному використанні\n",[3412,6636,6637,6639,6642,6644,6646,6648],{"class":3414,"line":3516},[3412,6638,4236],{"class":3429},[3412,6640,6641],{"class":3433},"(Manager, ",[3412,6643,3451],{"class":3425},[3412,6645,6479],{"class":3433},[3412,6647,4242],{"class":3443},[3412,6649,6580],{"class":3433},[6506,6651,6653],{"label":6652},"Нестандартне використання",[3403,6654,6656],{"className":3405,"code":6655,"language":3407,"meta":3408,"style":3408},"class GrandChild(Child):\n    def method(self):\n        # «Перескок» через рівень: шукати починаючи ПІСЛЯ Child\n        super(Child, self).method()\n\n        # УВАГА: це може пропустити важливу логіку Child.method!\n        # Використовуйте тільки коли точно знаєте MRO.\n",[3390,6657,6658,6672,6685,6690,6702,6706,6711],{"__ignoreMap":3408},[3412,6659,6660,6662,6665,6667,6670],{"class":3414,"line":3415},[3412,6661,3426],{"class":3425},[3412,6663,6664],{"class":3429}," GrandChild",[3412,6666,3447],{"class":3433},[3412,6668,6669],{"class":3429},"Child",[3412,6671,3893],{"class":3433},[3412,6673,6674,6676,6679,6681,6683],{"class":3414,"line":3422},[3412,6675,3440],{"class":3425},[3412,6677,6678],{"class":3443}," method",[3412,6680,3447],{"class":3433},[3412,6682,3451],{"class":3450},[3412,6684,3893],{"class":3433},[3412,6686,6687],{"class":3414,"line":3437},[3412,6688,6689],{"class":3418},"        # «Перескок» через рівень: шукати починаючи ПІСЛЯ Child\n",[3412,6691,6692,6694,6697,6699],{"class":3414,"line":3468},[3412,6693,4236],{"class":3429},[3412,6695,6696],{"class":3433},"(Child, ",[3412,6698,3451],{"class":3425},[3412,6700,6701],{"class":3433},").method()\n",[3412,6703,6704],{"class":3414,"line":3516},[3412,6705,3554],{"emptyLinePlaceholder":3553},[3412,6707,6708],{"class":3414,"line":3550},[3412,6709,6710],{"class":3418},"        # УВАГА: це може пропустити важливу логіку Child.method!\n",[3412,6712,6713],{"class":3414,"line":3557},[3412,6714,6715],{"class":3418},"        # Використовуйте тільки коли точно знаєте MRO.\n",[3803,6717,6719,6034,6721,6723],{"id":6718},"super-у-classmethod-фабричний-патерн",[3390,6720,3392],{},[3390,6722,6500],{},": фабричний патерн",[3399,6725,6726,6727,6034,6729,6731,6732,6734,6735,6737,6738,5790],{},"Особливість ",[3390,6728,3392],{},[3390,6730,6500],{}," — він отримує клас (",[3390,6733,6497],{},"), а не екземпляр (",[3390,6736,3451],{},"). Це ключовий інструмент для ",[3764,6739,6740],{},"успадкованих фабричних методів",[3403,6742,6744],{"className":3405,"code":6743,"language":3407,"meta":3408,"style":3408},"# factory_pattern.py\nimport json\nfrom datetime import datetime\n\nclass BaseRecord:\n    \"\"\"Базовий запис з фабричним методом.\"\"\"\n\n    def __init__(self, record_id: int, created_at: str):\n        self.record_id = record_id\n        self.created_at = created_at\n\n    @classmethod\n    def from_dict(cls, data: dict) -> \"BaseRecord\":\n        \"\"\"\n        Фабричний метод: створює екземпляр з словника.\n        cls — це реальний клас, на якому викликається метод!\n        Завдяки цьому підкласи автоматично отримують правильний тип.\n        \"\"\"\n        return cls(\n            record_id=data[\"id\"],\n            created_at=data.get(\"created_at\", datetime.now().isoformat())\n        )\n\n    @classmethod\n    def from_json(cls, json_str: str) -> \"BaseRecord\":\n        return cls.from_dict(json.loads(json_str))\n\n\nclass UserRecord(BaseRecord):\n    \"\"\"Підклас з розширеним конструктором.\"\"\"\n\n    def __init__(self, record_id: int, created_at: str, username: str, email: str):\n        super().__init__(record_id, created_at)\n        self.username = username\n        self.email = email\n\n    @classmethod\n    def from_dict(cls, data: dict) -> \"UserRecord\":\n        # Викликаємо from_dict батька через super() у classmethod\n        # super() тут — super(UserRecord, cls), тобто BaseRecord\n        base_instance = super().from_dict(data)\n\n        # Але нам потрібен UserRecord, тому використовуємо cls напряму\n        return cls(\n            record_id=data[\"id\"],\n            created_at=data.get(\"created_at\", datetime.now().isoformat()),\n            username=data[\"username\"],\n            email=data[\"email\"],\n        )\n\n    def __repr__(self) -> str:\n        return f\"UserRecord(id={self.record_id}, user={self.username!r})\"\n\n\n# Тест\ndata = {\"id\": 42, \"username\": \"arakviel\", \"email\": \"arakviel@example.com\"}\n\n# Виклик from_dict на базовому класі → BaseRecord\nbase = BaseRecord.from_dict({\"id\": 1, \"created_at\": \"2024-01-01\"})\nprint(type(base))    # \u003Cclass 'BaseRecord'>\n\n# Виклик from_dict на підкласі → UserRecord (cls = UserRecord)\nuser = UserRecord.from_dict(data)\nprint(type(user))    # \u003Cclass 'UserRecord'>\nprint(user)          # UserRecord(id=42, user='arakviel')\n",[3390,6745,6746,6751,6758,6770,6774,6783,6788,6792,6822,6829,6836,6840,6848,6876,6881,6886,6891,6896,6900,6909,6923,6937,6942,6946,6952,6978,6987,6991,6995,7009,7014,7018,7064,7075,7082,7089,7093,7099,7124,7129,7134,7144,7148,7153,7161,7171,7182,7194,7206,7210,7214,7230,7259,7264,7269,7275,7308,7313,7319,7344,7359,7364,7370,7376,7391],{"__ignoreMap":3408},[3412,6747,6748],{"class":3414,"line":3415},[3412,6749,6750],{"class":3418},"# factory_pattern.py\n",[3412,6752,6753,6755],{"class":3414,"line":3422},[3412,6754,4154],{"class":3530},[3412,6756,6757],{"class":3433}," json\n",[3412,6759,6760,6762,6765,6767],{"class":3414,"line":3437},[3412,6761,4148],{"class":3530},[3412,6763,6764],{"class":3433}," datetime ",[3412,6766,4154],{"class":3530},[3412,6768,6769],{"class":3433}," datetime\n",[3412,6771,6772],{"class":3414,"line":3468},[3412,6773,3554],{"emptyLinePlaceholder":3553},[3412,6775,6776,6778,6781],{"class":3414,"line":3516},[3412,6777,3426],{"class":3425},[3412,6779,6780],{"class":3429}," BaseRecord",[3412,6782,3434],{"class":3433},[3412,6784,6785],{"class":3414,"line":3550},[3412,6786,6787],{"class":3495},"    \"\"\"Базовий запис з фабричним методом.\"\"\"\n",[3412,6789,6790],{"class":3414,"line":3557},[3412,6791,3554],{"emptyLinePlaceholder":3553},[3412,6793,6794,6796,6798,6800,6802,6804,6807,6809,6811,6813,6816,6818,6820],{"class":3414,"line":3567},[3412,6795,3440],{"class":3425},[3412,6797,3865],{"class":3443},[3412,6799,3447],{"class":3433},[3412,6801,3451],{"class":3450},[3412,6803,3454],{"class":3433},[3412,6805,6806],{"class":3450},"record_id",[3412,6808,3877],{"class":3433},[3412,6810,3890],{"class":3429},[3412,6812,3454],{"class":3433},[3412,6814,6815],{"class":3450},"created_at",[3412,6817,3877],{"class":3433},[3412,6819,3880],{"class":3429},[3412,6821,3893],{"class":3433},[3412,6823,6824,6826],{"class":3414,"line":3588},[3412,6825,3898],{"class":3425},[3412,6827,6828],{"class":3433},".record_id = record_id\n",[3412,6830,6831,6833],{"class":3414,"line":3628},[3412,6832,3898],{"class":3425},[3412,6834,6835],{"class":3433},".created_at = created_at\n",[3412,6837,6838],{"class":3414,"line":3655},[3412,6839,3554],{"emptyLinePlaceholder":3553},[3412,6841,6842,6845],{"class":3414,"line":3660},[3412,6843,6844],{"class":3443},"    @",[3412,6846,6847],{"class":3429},"classmethod\n",[3412,6849,6850,6852,6855,6857,6859,6861,6864,6866,6869,6871,6874],{"class":3414,"line":3670},[3412,6851,3440],{"class":3425},[3412,6853,6854],{"class":3443}," from_dict",[3412,6856,3447],{"class":3433},[3412,6858,6497],{"class":3450},[3412,6860,3454],{"class":3433},[3412,6862,6863],{"class":3450},"data",[3412,6865,3877],{"class":3433},[3412,6867,6868],{"class":3429},"dict",[3412,6870,3946],{"class":3433},[3412,6872,6873],{"class":3495},"\"BaseRecord\"",[3412,6875,3434],{"class":3433},[3412,6877,6878],{"class":3414,"line":3691},[3412,6879,6880],{"class":3495},"        \"\"\"\n",[3412,6882,6883],{"class":3414,"line":3731},[3412,6884,6885],{"class":3495},"        Фабричний метод: створює екземпляр з словника.\n",[3412,6887,6888],{"class":3414,"line":4072},[3412,6889,6890],{"class":3495},"        cls — це реальний клас, на якому викликається метод!\n",[3412,6892,6893],{"class":3414,"line":4077},[3412,6894,6895],{"class":3495},"        Завдяки цьому підкласи автоматично отримують правильний тип.\n",[3412,6897,6898],{"class":3414,"line":4095},[3412,6899,6880],{"class":3495},[3412,6901,6902,6904,6906],{"class":3414,"line":5014},[3412,6903,3955],{"class":3530},[3412,6905,5331],{"class":3425},[3412,6907,6908],{"class":3433},"(\n",[3412,6910,6911,6914,6917,6920],{"class":3414,"line":5020},[3412,6912,6913],{"class":3450},"            record_id",[3412,6915,6916],{"class":3433},"=data[",[3412,6918,6919],{"class":3495},"\"id\"",[3412,6921,6922],{"class":3433},"],\n",[3412,6924,6925,6928,6931,6934],{"class":3414,"line":5026},[3412,6926,6927],{"class":3450},"            created_at",[3412,6929,6930],{"class":3433},"=data.get(",[3412,6932,6933],{"class":3495},"\"created_at\"",[3412,6935,6936],{"class":3433},", datetime.now().isoformat())\n",[3412,6938,6939],{"class":3414,"line":5035},[3412,6940,6941],{"class":3433},"        )\n",[3412,6943,6944],{"class":3414,"line":5046},[3412,6945,3554],{"emptyLinePlaceholder":3553},[3412,6947,6948,6950],{"class":3414,"line":5057},[3412,6949,6844],{"class":3443},[3412,6951,6847],{"class":3429},[3412,6953,6954,6956,6959,6961,6963,6965,6968,6970,6972,6974,6976],{"class":3414,"line":5345},[3412,6955,3440],{"class":3425},[3412,6957,6958],{"class":3443}," from_json",[3412,6960,3447],{"class":3433},[3412,6962,6497],{"class":3450},[3412,6964,3454],{"class":3433},[3412,6966,6967],{"class":3450},"json_str",[3412,6969,3877],{"class":3433},[3412,6971,3880],{"class":3429},[3412,6973,3946],{"class":3433},[3412,6975,6873],{"class":3495},[3412,6977,3434],{"class":3433},[3412,6979,6980,6982,6984],{"class":3414,"line":5364},[3412,6981,3955],{"class":3530},[3412,6983,5331],{"class":3425},[3412,6985,6986],{"class":3433},".from_dict(json.loads(json_str))\n",[3412,6988,6989],{"class":3414,"line":5370},[3412,6990,3554],{"emptyLinePlaceholder":3553},[3412,6992,6993],{"class":3414,"line":5376},[3412,6994,3554],{"emptyLinePlaceholder":3553},[3412,6996,6997,6999,7002,7004,7007],{"class":3414,"line":5382},[3412,6998,3426],{"class":3425},[3412,7000,7001],{"class":3429}," UserRecord",[3412,7003,3447],{"class":3433},[3412,7005,7006],{"class":3429},"BaseRecord",[3412,7008,3893],{"class":3433},[3412,7010,7011],{"class":3414,"line":5388},[3412,7012,7013],{"class":3495},"    \"\"\"Підклас з розширеним конструктором.\"\"\"\n",[3412,7015,7016],{"class":3414,"line":5393},[3412,7017,3554],{"emptyLinePlaceholder":3553},[3412,7019,7020,7022,7024,7026,7028,7030,7032,7034,7036,7038,7040,7042,7044,7046,7049,7051,7053,7055,7058,7060,7062],{"class":3414,"line":5399},[3412,7021,3440],{"class":3425},[3412,7023,3865],{"class":3443},[3412,7025,3447],{"class":3433},[3412,7027,3451],{"class":3450},[3412,7029,3454],{"class":3433},[3412,7031,6806],{"class":3450},[3412,7033,3877],{"class":3433},[3412,7035,3890],{"class":3429},[3412,7037,3454],{"class":3433},[3412,7039,6815],{"class":3450},[3412,7041,3877],{"class":3433},[3412,7043,3880],{"class":3429},[3412,7045,3454],{"class":3433},[3412,7047,7048],{"class":3450},"username",[3412,7050,3877],{"class":3433},[3412,7052,3880],{"class":3429},[3412,7054,3454],{"class":3433},[3412,7056,7057],{"class":3450},"email",[3412,7059,3877],{"class":3433},[3412,7061,3880],{"class":3429},[3412,7063,3893],{"class":3433},[3412,7065,7066,7068,7070,7072],{"class":3414,"line":5422},[3412,7067,4236],{"class":3429},[3412,7069,4239],{"class":3433},[3412,7071,4242],{"class":3443},[3412,7073,7074],{"class":3433},"(record_id, created_at)\n",[3412,7076,7077,7079],{"class":3414,"line":5428},[3412,7078,3898],{"class":3425},[3412,7080,7081],{"class":3433},".username = username\n",[3412,7083,7084,7086],{"class":3414,"line":5434},[3412,7085,3898],{"class":3425},[3412,7087,7088],{"class":3433},".email = email\n",[3412,7090,7091],{"class":3414,"line":5439},[3412,7092,3554],{"emptyLinePlaceholder":3553},[3412,7094,7095,7097],{"class":3414,"line":5456},[3412,7096,6844],{"class":3443},[3412,7098,6847],{"class":3429},[3412,7100,7101,7103,7105,7107,7109,7111,7113,7115,7117,7119,7122],{"class":3414,"line":5462},[3412,7102,3440],{"class":3425},[3412,7104,6854],{"class":3443},[3412,7106,3447],{"class":3433},[3412,7108,6497],{"class":3450},[3412,7110,3454],{"class":3433},[3412,7112,6863],{"class":3450},[3412,7114,3877],{"class":3433},[3412,7116,6868],{"class":3429},[3412,7118,3946],{"class":3433},[3412,7120,7121],{"class":3495},"\"UserRecord\"",[3412,7123,3434],{"class":3433},[3412,7125,7126],{"class":3414,"line":5467},[3412,7127,7128],{"class":3418},"        # Викликаємо from_dict батька через super() у classmethod\n",[3412,7130,7131],{"class":3414,"line":5473},[3412,7132,7133],{"class":3418},"        # super() тут — super(UserRecord, cls), тобто BaseRecord\n",[3412,7135,7136,7139,7141],{"class":3414,"line":5491},[3412,7137,7138],{"class":3433},"        base_instance = ",[3412,7140,4285],{"class":3429},[3412,7142,7143],{"class":3433},"().from_dict(data)\n",[3412,7145,7146],{"class":3414,"line":5496},[3412,7147,3554],{"emptyLinePlaceholder":3553},[3412,7149,7150],{"class":3414,"line":5502},[3412,7151,7152],{"class":3418},"        # Але нам потрібен UserRecord, тому використовуємо cls напряму\n",[3412,7154,7155,7157,7159],{"class":3414,"line":5518},[3412,7156,3955],{"class":3530},[3412,7158,5331],{"class":3425},[3412,7160,6908],{"class":3433},[3412,7162,7163,7165,7167,7169],{"class":3414,"line":5533},[3412,7164,6913],{"class":3450},[3412,7166,6916],{"class":3433},[3412,7168,6919],{"class":3495},[3412,7170,6922],{"class":3433},[3412,7172,7173,7175,7177,7179],{"class":3414,"line":5548},[3412,7174,6927],{"class":3450},[3412,7176,6930],{"class":3433},[3412,7178,6933],{"class":3495},[3412,7180,7181],{"class":3433},", datetime.now().isoformat()),\n",[3412,7183,7184,7187,7189,7192],{"class":3414,"line":5568},[3412,7185,7186],{"class":3450},"            username",[3412,7188,6916],{"class":3433},[3412,7190,7191],{"class":3495},"\"username\"",[3412,7193,6922],{"class":3433},[3412,7195,7196,7199,7201,7204],{"class":3414,"line":5573},[3412,7197,7198],{"class":3450},"            email",[3412,7200,6916],{"class":3433},[3412,7202,7203],{"class":3495},"\"email\"",[3412,7205,6922],{"class":3433},[3412,7207,7208],{"class":3414,"line":5579},[3412,7209,6941],{"class":3433},[3412,7211,7212],{"class":3414,"line":5595},[3412,7213,3554],{"emptyLinePlaceholder":3553},[3412,7215,7216,7218,7220,7222,7224,7226,7228],{"class":3414,"line":5609},[3412,7217,3440],{"class":3425},[3412,7219,4082],{"class":3443},[3412,7221,3447],{"class":3433},[3412,7223,3451],{"class":3450},[3412,7225,3946],{"class":3433},[3412,7227,3880],{"class":3429},[3412,7229,3434],{"class":3433},[3412,7231,7233,7235,7237,7240,7242,7245,7247,7250,7252,7255,7257],{"class":3414,"line":7232},52,[3412,7234,3955],{"class":3530},[3412,7236,3958],{"class":3425},[3412,7238,7239],{"class":3495},"\"UserRecord(id=",[3412,7241,3964],{"class":3425},[3412,7243,7244],{"class":3433},".record_id",[3412,7246,3504],{"class":3425},[3412,7248,7249],{"class":3495},", user=",[3412,7251,3964],{"class":3425},[3412,7253,7254],{"class":3433},".username",[3412,7256,4125],{"class":3425},[3412,7258,4137],{"class":3495},[3412,7260,7262],{"class":3414,"line":7261},53,[3412,7263,3554],{"emptyLinePlaceholder":3553},[3412,7265,7267],{"class":3414,"line":7266},54,[3412,7268,3554],{"emptyLinePlaceholder":3553},[3412,7270,7272],{"class":3414,"line":7271},55,[3412,7273,7274],{"class":3418},"# Тест\n",[3412,7276,7278,7281,7283,7285,7288,7290,7292,7294,7297,7299,7301,7303,7306],{"class":3414,"line":7277},56,[3412,7279,7280],{"class":3433},"data = {",[3412,7282,6919],{"class":3495},[3412,7284,3877],{"class":3433},[3412,7286,7287],{"class":3924},"42",[3412,7289,3454],{"class":3433},[3412,7291,7191],{"class":3495},[3412,7293,3877],{"class":3433},[3412,7295,7296],{"class":3495},"\"arakviel\"",[3412,7298,3454],{"class":3433},[3412,7300,7203],{"class":3495},[3412,7302,3877],{"class":3433},[3412,7304,7305],{"class":3495},"\"arakviel@example.com\"",[3412,7307,6108],{"class":3433},[3412,7309,7311],{"class":3414,"line":7310},57,[3412,7312,3554],{"emptyLinePlaceholder":3553},[3412,7314,7316],{"class":3414,"line":7315},58,[3412,7317,7318],{"class":3418},"# Виклик from_dict на базовому класі → BaseRecord\n",[3412,7320,7322,7325,7327,7329,7332,7334,7336,7338,7341],{"class":3414,"line":7321},59,[3412,7323,7324],{"class":3433},"base = BaseRecord.from_dict({",[3412,7326,6919],{"class":3495},[3412,7328,3877],{"class":3433},[3412,7330,7331],{"class":3924},"1",[3412,7333,3454],{"class":3433},[3412,7335,6933],{"class":3495},[3412,7337,3877],{"class":3433},[3412,7339,7340],{"class":3495},"\"2024-01-01\"",[3412,7342,7343],{"class":3433},"})\n",[3412,7345,7347,7349,7351,7353,7356],{"class":3414,"line":7346},60,[3412,7348,3487],{"class":3443},[3412,7350,3447],{"class":3433},[3412,7352,5110],{"class":3429},[3412,7354,7355],{"class":3433},"(base))    ",[3412,7357,7358],{"class":3418},"# \u003Cclass 'BaseRecord'>\n",[3412,7360,7362],{"class":3414,"line":7361},61,[3412,7363,3554],{"emptyLinePlaceholder":3553},[3412,7365,7367],{"class":3414,"line":7366},62,[3412,7368,7369],{"class":3418},"# Виклик from_dict на підкласі → UserRecord (cls = UserRecord)\n",[3412,7371,7373],{"class":3414,"line":7372},63,[3412,7374,7375],{"class":3433},"user = UserRecord.from_dict(data)\n",[3412,7377,7379,7381,7383,7385,7388],{"class":3414,"line":7378},64,[3412,7380,3487],{"class":3443},[3412,7382,3447],{"class":3433},[3412,7384,5110],{"class":3429},[3412,7386,7387],{"class":3433},"(user))    ",[3412,7389,7390],{"class":3418},"# \u003Cclass 'UserRecord'>\n",[3412,7392,7394,7396,7399],{"class":3414,"line":7393},65,[3412,7395,3487],{"class":3443},[3412,7397,7398],{"class":3433},"(user)          ",[3412,7400,7401],{"class":3418},"# UserRecord(id=42, user='arakviel')\n",[4757,7403,7405,7413,7421,7433],{"title":7404},"python factory_pattern.py",[4761,7406,7408,4769,7411],{"className":7407},[3414],[3412,7409,4768],{"className":7410},[4767],[3764,7412,7404],{},[4761,7414,7416,7417],{"className":7415},[3414],"type(base): ",[3412,7418,7420],{"className":7419},[4792],"\u003Cclass 'BaseRecord'>",[4761,7422,7424,7425,5684,7429],{"className":7423},[3414],"type(user): ",[3412,7426,7428],{"className":7427},[4800],"\u003Cclass 'UserRecord'>",[3412,7430,7432],{"className":7431},[5673],"# правильний підклас!",[4761,7434,7436,7437,7249,7440,6370],{"className":7435},[3414],"UserRecord(id=",[3412,7438,7287],{"className":7439},[4779],[3412,7441,7443],{"className":7442},[4800],"'arakviel'",[5739,7445,7446,7449,7450,7452,7453,7455,7456,7459,7460,7463],{},[3764,7447,7448],{},"Патерн «успадкований фабричний метод»"," — один з найважливіших застосувань ",[3390,7451,6500],{},". Завдяки тому, що ",[3390,7454,6497],{}," містить реальний клас (а не той, де описаний метод), метод ",[3390,7457,7458],{},"from_dict"," або ",[3390,7461,7462],{},"from_json"," визначений один раз у базовому класі, але автоматично повертає правильний підтип при виклику через підклас.",[3796,7465],{},[3803,7467,7469,7470],{"id":7468},"кооперативне-наслідування-та-args-kwargs","Кооперативне наслідування та ",[3390,7471,7472],{},"*args, **kwargs",[3399,7474,7475,7476,7478],{},"Коли класи проектуються для роботи у складних ієрархіях, виникає проблема несумісності сигнатур конструкторів ",[3390,7477,4242],{},". Кожен клас приймає свій набір аргументів, але повинен передати «зайві» аргументи далі по MRO.",[3399,7480,7481,7482,5790],{},"Стандартне рішення — патерн ",[3390,7483,7484],{},"**kwargs",[3403,7486,7488],{"className":3405,"code":7487,"language":3407,"meta":3408,"style":3408},"# cooperative_init.py\n\nclass GraphicElement:\n    def __init__(self, color: str = \"black\", **kwargs):\n        print(f\"  → GraphicElement.__init__(color={color!r})\")\n        super().__init__(**kwargs)  # передаємо залишок далі по MRO\n        self.color = color\n\n\nclass ClickableElement:\n    def __init__(self, on_click: str = \"none\", **kwargs):\n        print(f\"  → ClickableElement.__init__(on_click={on_click!r})\")\n        super().__init__(**kwargs)  # передаємо залишок далі\n        self.on_click = on_click\n\n\nclass ResizableElement:\n    def __init__(self, min_size: int = 10, **kwargs):\n        print(f\"  → ResizableElement.__init__(min_size={min_size})\")\n        super().__init__(**kwargs)\n        self.min_size = min_size\n\n\n# Button наслідує всіх трьох\nclass Button(GraphicElement, ClickableElement, ResizableElement):\n    def __init__(self, label: str, **kwargs):\n        print(f\"  → Button.__init__(label={label!r})\")\n        super().__init__(**kwargs)\n        self.label = label\n\n\nprint(\"MRO:\", [c.__name__ for c in Button.__mro__])\n# MRO: ['Button', 'GraphicElement', 'ClickableElement', 'ResizableElement', 'object']\n\nprint(\"\\nСтворення кнопки:\")\nbtn = Button(\n    label=\"Submit\",\n    color=\"blue\",\n    on_click=\"submit_form\",\n    min_size=20,\n)\n\nprint(f\"\\nАтрибути: label={btn.label!r}, color={btn.color!r}, \"\n      f\"on_click={btn.on_click!r}, min_size={btn.min_size}\")\n",[3390,7489,7490,7495,7499,7508,7540,7562,7576,7583,7587,7591,7600,7630,7651,7664,7671,7675,7679,7688,7718,7739,7750,7757,7761,7765,7770,7794,7819,7840,7850,7857,7861,7865,7895,7900,7904,7919,7924,7937,7949,7961,7973,7977,7981,8016],{"__ignoreMap":3408},[3412,7491,7492],{"class":3414,"line":3415},[3412,7493,7494],{"class":3418},"# cooperative_init.py\n",[3412,7496,7497],{"class":3414,"line":3422},[3412,7498,3554],{"emptyLinePlaceholder":3553},[3412,7500,7501,7503,7506],{"class":3414,"line":3437},[3412,7502,3426],{"class":3425},[3412,7504,7505],{"class":3429}," GraphicElement",[3412,7507,3434],{"class":3433},[3412,7509,7510,7512,7514,7516,7518,7520,7523,7525,7527,7529,7532,7535,7538],{"class":3414,"line":3468},[3412,7511,3440],{"class":3425},[3412,7513,3865],{"class":3443},[3412,7515,3447],{"class":3433},[3412,7517,3451],{"class":3450},[3412,7519,3454],{"class":3433},[3412,7521,7522],{"class":3450},"color",[3412,7524,3877],{"class":3433},[3412,7526,3880],{"class":3429},[3412,7528,3921],{"class":3433},[3412,7530,7531],{"class":3495},"\"black\"",[3412,7533,7534],{"class":3433},", **",[3412,7536,7537],{"class":3450},"kwargs",[3412,7539,3893],{"class":3433},[3412,7541,7542,7544,7546,7548,7551,7553,7555,7557,7560],{"class":3414,"line":3516},[3412,7543,4039],{"class":3443},[3412,7545,3447],{"class":3433},[3412,7547,3492],{"class":3425},[3412,7549,7550],{"class":3495},"\"  → GraphicElement.__init__(color=",[3412,7552,3499],{"class":3425},[3412,7554,7522],{"class":3433},[3412,7556,4125],{"class":3425},[3412,7558,7559],{"class":3495},")\"",[3412,7561,4069],{"class":3433},[3412,7563,7564,7566,7568,7570,7573],{"class":3414,"line":3550},[3412,7565,4236],{"class":3429},[3412,7567,4239],{"class":3433},[3412,7569,4242],{"class":3443},[3412,7571,7572],{"class":3433},"(**kwargs)  ",[3412,7574,7575],{"class":3418},"# передаємо залишок далі по MRO\n",[3412,7577,7578,7580],{"class":3414,"line":3557},[3412,7579,3898],{"class":3425},[3412,7581,7582],{"class":3433},".color = color\n",[3412,7584,7585],{"class":3414,"line":3567},[3412,7586,3554],{"emptyLinePlaceholder":3553},[3412,7588,7589],{"class":3414,"line":3588},[3412,7590,3554],{"emptyLinePlaceholder":3553},[3412,7592,7593,7595,7598],{"class":3414,"line":3628},[3412,7594,3426],{"class":3425},[3412,7596,7597],{"class":3429}," ClickableElement",[3412,7599,3434],{"class":3433},[3412,7601,7602,7604,7606,7608,7610,7612,7615,7617,7619,7621,7624,7626,7628],{"class":3414,"line":3655},[3412,7603,3440],{"class":3425},[3412,7605,3865],{"class":3443},[3412,7607,3447],{"class":3433},[3412,7609,3451],{"class":3450},[3412,7611,3454],{"class":3433},[3412,7613,7614],{"class":3450},"on_click",[3412,7616,3877],{"class":3433},[3412,7618,3880],{"class":3429},[3412,7620,3921],{"class":3433},[3412,7622,7623],{"class":3495},"\"none\"",[3412,7625,7534],{"class":3433},[3412,7627,7537],{"class":3450},[3412,7629,3893],{"class":3433},[3412,7631,7632,7634,7636,7638,7641,7643,7645,7647,7649],{"class":3414,"line":3660},[3412,7633,4039],{"class":3443},[3412,7635,3447],{"class":3433},[3412,7637,3492],{"class":3425},[3412,7639,7640],{"class":3495},"\"  → ClickableElement.__init__(on_click=",[3412,7642,3499],{"class":3425},[3412,7644,7614],{"class":3433},[3412,7646,4125],{"class":3425},[3412,7648,7559],{"class":3495},[3412,7650,4069],{"class":3433},[3412,7652,7653,7655,7657,7659,7661],{"class":3414,"line":3670},[3412,7654,4236],{"class":3429},[3412,7656,4239],{"class":3433},[3412,7658,4242],{"class":3443},[3412,7660,7572],{"class":3433},[3412,7662,7663],{"class":3418},"# передаємо залишок далі\n",[3412,7665,7666,7668],{"class":3414,"line":3691},[3412,7667,3898],{"class":3425},[3412,7669,7670],{"class":3433},".on_click = on_click\n",[3412,7672,7673],{"class":3414,"line":3731},[3412,7674,3554],{"emptyLinePlaceholder":3553},[3412,7676,7677],{"class":3414,"line":4072},[3412,7678,3554],{"emptyLinePlaceholder":3553},[3412,7680,7681,7683,7686],{"class":3414,"line":4077},[3412,7682,3426],{"class":3425},[3412,7684,7685],{"class":3429}," ResizableElement",[3412,7687,3434],{"class":3433},[3412,7689,7690,7692,7694,7696,7698,7700,7703,7705,7707,7709,7712,7714,7716],{"class":3414,"line":4095},[3412,7691,3440],{"class":3425},[3412,7693,3865],{"class":3443},[3412,7695,3447],{"class":3433},[3412,7697,3451],{"class":3450},[3412,7699,3454],{"class":3433},[3412,7701,7702],{"class":3450},"min_size",[3412,7704,3877],{"class":3433},[3412,7706,3890],{"class":3429},[3412,7708,3921],{"class":3433},[3412,7710,7711],{"class":3924},"10",[3412,7713,7534],{"class":3433},[3412,7715,7537],{"class":3450},[3412,7717,3893],{"class":3433},[3412,7719,7720,7722,7724,7726,7729,7731,7733,7735,7737],{"class":3414,"line":5014},[3412,7721,4039],{"class":3443},[3412,7723,3447],{"class":3433},[3412,7725,3492],{"class":3425},[3412,7727,7728],{"class":3495},"\"  → ResizableElement.__init__(min_size=",[3412,7730,3499],{"class":3425},[3412,7732,7702],{"class":3433},[3412,7734,3504],{"class":3425},[3412,7736,7559],{"class":3495},[3412,7738,4069],{"class":3433},[3412,7740,7741,7743,7745,7747],{"class":3414,"line":5020},[3412,7742,4236],{"class":3429},[3412,7744,4239],{"class":3433},[3412,7746,4242],{"class":3443},[3412,7748,7749],{"class":3433},"(**kwargs)\n",[3412,7751,7752,7754],{"class":3414,"line":5026},[3412,7753,3898],{"class":3425},[3412,7755,7756],{"class":3433},".min_size = min_size\n",[3412,7758,7759],{"class":3414,"line":5035},[3412,7760,3554],{"emptyLinePlaceholder":3553},[3412,7762,7763],{"class":3414,"line":5046},[3412,7764,3554],{"emptyLinePlaceholder":3553},[3412,7766,7767],{"class":3414,"line":5057},[3412,7768,7769],{"class":3418},"# Button наслідує всіх трьох\n",[3412,7771,7772,7774,7777,7779,7782,7784,7787,7789,7792],{"class":3414,"line":5345},[3412,7773,3426],{"class":3425},[3412,7775,7776],{"class":3429}," Button",[3412,7778,3447],{"class":3433},[3412,7780,7781],{"class":3429},"GraphicElement",[3412,7783,3454],{"class":3433},[3412,7785,7786],{"class":3429},"ClickableElement",[3412,7788,3454],{"class":3433},[3412,7790,7791],{"class":3429},"ResizableElement",[3412,7793,3893],{"class":3433},[3412,7795,7796,7798,7800,7802,7804,7806,7809,7811,7813,7815,7817],{"class":3414,"line":5364},[3412,7797,3440],{"class":3425},[3412,7799,3865],{"class":3443},[3412,7801,3447],{"class":3433},[3412,7803,3451],{"class":3450},[3412,7805,3454],{"class":3433},[3412,7807,7808],{"class":3450},"label",[3412,7810,3877],{"class":3433},[3412,7812,3880],{"class":3429},[3412,7814,7534],{"class":3433},[3412,7816,7537],{"class":3450},[3412,7818,3893],{"class":3433},[3412,7820,7821,7823,7825,7827,7830,7832,7834,7836,7838],{"class":3414,"line":5370},[3412,7822,4039],{"class":3443},[3412,7824,3447],{"class":3433},[3412,7826,3492],{"class":3425},[3412,7828,7829],{"class":3495},"\"  → Button.__init__(label=",[3412,7831,3499],{"class":3425},[3412,7833,7808],{"class":3433},[3412,7835,4125],{"class":3425},[3412,7837,7559],{"class":3495},[3412,7839,4069],{"class":3433},[3412,7841,7842,7844,7846,7848],{"class":3414,"line":5376},[3412,7843,4236],{"class":3429},[3412,7845,4239],{"class":3433},[3412,7847,4242],{"class":3443},[3412,7849,7749],{"class":3433},[3412,7851,7852,7854],{"class":3414,"line":5382},[3412,7853,3898],{"class":3425},[3412,7855,7856],{"class":3433},".label = label\n",[3412,7858,7859],{"class":3414,"line":5388},[3412,7860,3554],{"emptyLinePlaceholder":3553},[3412,7862,7863],{"class":3414,"line":5393},[3412,7864,3554],{"emptyLinePlaceholder":3553},[3412,7866,7867,7869,7871,7874,7877,7879,7882,7885,7887,7890,7892],{"class":3414,"line":5399},[3412,7868,3487],{"class":3443},[3412,7870,3447],{"class":3433},[3412,7872,7873],{"class":3495},"\"MRO:\"",[3412,7875,7876],{"class":3433},", [c.",[3412,7878,4113],{"class":3450},[3412,7880,7881],{"class":3530}," for",[3412,7883,7884],{"class":3433}," c ",[3412,7886,4744],{"class":3530},[3412,7888,7889],{"class":3433}," Button.",[3412,7891,5340],{"class":3450},[3412,7893,7894],{"class":3433},"])\n",[3412,7896,7897],{"class":3414,"line":5422},[3412,7898,7899],{"class":3418},"# MRO: ['Button', 'GraphicElement', 'ClickableElement', 'ResizableElement', 'object']\n",[3412,7901,7902],{"class":3414,"line":5428},[3412,7903,3554],{"emptyLinePlaceholder":3553},[3412,7905,7906,7908,7910,7912,7914,7917],{"class":3414,"line":5434},[3412,7907,3487],{"class":3443},[3412,7909,3447],{"class":3433},[3412,7911,3507],{"class":3495},[3412,7913,5319],{"class":5318},[3412,7915,7916],{"class":3495},"Створення кнопки:\"",[3412,7918,4069],{"class":3433},[3412,7920,7921],{"class":3414,"line":5439},[3412,7922,7923],{"class":3433},"btn = Button(\n",[3412,7925,7926,7929,7931,7934],{"class":3414,"line":5456},[3412,7927,7928],{"class":3450},"    label",[3412,7930,4691],{"class":3433},[3412,7932,7933],{"class":3495},"\"Submit\"",[3412,7935,7936],{"class":3433},",\n",[3412,7938,7939,7942,7944,7947],{"class":3414,"line":5462},[3412,7940,7941],{"class":3450},"    color",[3412,7943,4691],{"class":3433},[3412,7945,7946],{"class":3495},"\"blue\"",[3412,7948,7936],{"class":3433},[3412,7950,7951,7954,7956,7959],{"class":3414,"line":5467},[3412,7952,7953],{"class":3450},"    on_click",[3412,7955,4691],{"class":3433},[3412,7957,7958],{"class":3495},"\"submit_form\"",[3412,7960,7936],{"class":3433},[3412,7962,7963,7966,7968,7971],{"class":3414,"line":5473},[3412,7964,7965],{"class":3450},"    min_size",[3412,7967,4691],{"class":3433},[3412,7969,7970],{"class":3924},"20",[3412,7972,7936],{"class":3433},[3412,7974,7975],{"class":3414,"line":5491},[3412,7976,4069],{"class":3433},[3412,7978,7979],{"class":3414,"line":5496},[3412,7980,3554],{"emptyLinePlaceholder":3553},[3412,7982,7983,7985,7987,7989,7991,7993,7996,7998,8001,8003,8006,8008,8011,8013],{"class":3414,"line":5502},[3412,7984,3487],{"class":3443},[3412,7986,3447],{"class":3433},[3412,7988,3492],{"class":3425},[3412,7990,3507],{"class":3495},[3412,7992,5319],{"class":5318},[3412,7994,7995],{"class":3495},"Атрибути: label=",[3412,7997,3499],{"class":3425},[3412,7999,8000],{"class":3433},"btn.label",[3412,8002,4125],{"class":3425},[3412,8004,8005],{"class":3495},", color=",[3412,8007,3499],{"class":3425},[3412,8009,8010],{"class":3433},"btn.color",[3412,8012,4125],{"class":3425},[3412,8014,8015],{"class":3495},", \"\n",[3412,8017,8018,8021,8024,8026,8029,8031,8034,8036,8039,8041,8043],{"class":3414,"line":5518},[3412,8019,8020],{"class":3425},"      f",[3412,8022,8023],{"class":3495},"\"on_click=",[3412,8025,3499],{"class":3425},[3412,8027,8028],{"class":3433},"btn.on_click",[3412,8030,4125],{"class":3425},[3412,8032,8033],{"class":3495},", min_size=",[3412,8035,3499],{"class":3425},[3412,8037,8038],{"class":3433},"btn.min_size",[3412,8040,3504],{"class":3425},[3412,8042,3507],{"class":3495},[3412,8044,4069],{"class":3433},[4757,8046,8048,8056,8064,8067,8071,8079,8086,8093,8100,8103],{"title":8047},"python cooperative_init.py",[4761,8049,8051,4769,8054],{"className":8050},[3414],[3412,8052,4768],{"className":8053},[4767],[3764,8055,8047],{},[4761,8057,8059,8060],{"className":8058},[3414],"MRO: ",[3412,8061,8063],{"className":8062},[4792],"['Button', 'GraphicElement', 'ClickableElement', 'ResizableElement', 'object']",[4761,8065],{"className":8066},[3414],[4761,8068,8070],{"className":8069},[3414],"Створення кнопки:",[4761,8072,8074,8075],{"className":8073},[3414],"  → ",[3412,8076,8078],{"className":8077},[4800],"Button.__init__(label='Submit')",[4761,8080,8074,8082],{"className":8081},[3414],[3412,8083,8085],{"className":8084},[4779],"GraphicElement.__init__(color='blue')",[4761,8087,8074,8089],{"className":8088},[3414],[3412,8090,8092],{"className":8091},[4779],"ClickableElement.__init__(on_click='submit_form')",[4761,8094,8074,8096],{"className":8095},[3414],[3412,8097,8099],{"className":8098},[4779],"ResizableElement.__init__(min_size=20)",[4761,8101],{"className":8102},[3414],[4761,8104,7995,8106,8005,8110,8114,8115,8033,8119],{"className":8105},[3414],[3412,8107,8109],{"className":8108},[4800],"'Submit'",[3412,8111,8113],{"className":8112},[4800],"'blue'",", on_click=",[3412,8116,8118],{"className":8117},[4800],"'submit_form'",[3412,8120,7970],{"className":8121},[4800],[5066,8123,8124,8134,8135,8138,8139,8142,8143,8146,8147,8149],{},[3764,8125,8126,8127,8129,8130,8133],{},"Якщо один з класів у ланцюжку НЕ передає ",[3390,8128,7484],{},"далі через",[3390,8131,8132],{},"super().**init**(**kwargs)",", і при цьому залишилися нерозпізнані аргументи"," — ",[3390,8136,8137],{},"object.__init__"," отримає їх і кине ",[3390,8140,8141],{},"TypeError",". Тому у всіх класах, що беруть участь у кооперативному наслідуванні, ",[3764,8144,8145],{},"обов'язково"," використовуйте ",[3390,8148,7484],{}," і передавайте їх далі.",[3796,8151],{},[3394,8153,8155],{"id":8154},"частина-iii-множинне-наслідування-та-проблема-діаманта","Частина III: Множинне наслідування та проблема «діаманта»",[3803,8157,8159],{"id":8158},"diamond-problem-як-це-виглядає","Diamond Problem: як це виглядає",[3399,8161,8162],{},"«Проблема діаманта» виникає, коли два батьківські класи мають спільного предка, і дочірній клас наслідує обох. Структура нагадує форму ромба (діаманта):",[6055,8164,8165],{},[3403,8166,8168],{"className":6059,"code":8167,"language":6061,"meta":3408,"style":3408},"@startuml\nskinparam style plain\nskinparam backgroundColor #ffffff\nskinparam ArrowColor #6366f1\nskinparam class {\n    BackgroundColor #f3f4f6\n    BorderColor #d1d5db\n    FontName \"Outfit\"\n}\n\nclass A {\n    + speak() : void\n}\n\nclass B extends A {\n    + speak() : void\n}\n\nclass C extends A {\n    + speak() : void\n}\n\nclass D extends B\nclass D extends C\n\nnote top of D\n  D наслідує від B і C,\n  обидва наслідують від A.\n  Який speak() викликати?\n  B.speak() чи C.speak()?\nend note\n\nnote right of A\n  Спільний предок —\n  джерело конфлікту\nend note\n@enduml\n",[3390,8169,8170,8174,8178,8182,8186,8191,8196,8201,8206,8210,8214,8219,8224,8228,8232,8237,8241,8245,8249,8254,8258,8262,8266,8271,8276,8280,8285,8290,8295,8300,8305,8309,8313,8318,8323,8328,8332],{"__ignoreMap":3408},[3412,8171,8172],{"class":3414,"line":3415},[3412,8173,6068],{},[3412,8175,8176],{"class":3414,"line":3422},[3412,8177,6073],{},[3412,8179,8180],{"class":3414,"line":3437},[3412,8181,6078],{},[3412,8183,8184],{"class":3414,"line":3468},[3412,8185,6083],{},[3412,8187,8188],{"class":3414,"line":3516},[3412,8189,8190],{},"skinparam class {\n",[3412,8192,8193],{"class":3414,"line":3550},[3412,8194,8195],{},"    BackgroundColor #f3f4f6\n",[3412,8197,8198],{"class":3414,"line":3557},[3412,8199,8200],{},"    BorderColor #d1d5db\n",[3412,8202,8203],{"class":3414,"line":3567},[3412,8204,8205],{},"    FontName \"Outfit\"\n",[3412,8207,8208],{"class":3414,"line":3588},[3412,8209,6108],{},[3412,8211,8212],{"class":3414,"line":3628},[3412,8213,3554],{"emptyLinePlaceholder":3553},[3412,8215,8216],{"class":3414,"line":3655},[3412,8217,8218],{},"class A {\n",[3412,8220,8221],{"class":3414,"line":3660},[3412,8222,8223],{},"    + speak() : void\n",[3412,8225,8226],{"class":3414,"line":3670},[3412,8227,6108],{},[3412,8229,8230],{"class":3414,"line":3691},[3412,8231,3554],{"emptyLinePlaceholder":3553},[3412,8233,8234],{"class":3414,"line":3731},[3412,8235,8236],{},"class B extends A {\n",[3412,8238,8239],{"class":3414,"line":4072},[3412,8240,8223],{},[3412,8242,8243],{"class":3414,"line":4077},[3412,8244,6108],{},[3412,8246,8247],{"class":3414,"line":4095},[3412,8248,3554],{"emptyLinePlaceholder":3553},[3412,8250,8251],{"class":3414,"line":5014},[3412,8252,8253],{},"class C extends A {\n",[3412,8255,8256],{"class":3414,"line":5020},[3412,8257,8223],{},[3412,8259,8260],{"class":3414,"line":5026},[3412,8261,6108],{},[3412,8263,8264],{"class":3414,"line":5035},[3412,8265,3554],{"emptyLinePlaceholder":3553},[3412,8267,8268],{"class":3414,"line":5046},[3412,8269,8270],{},"class D extends B\n",[3412,8272,8273],{"class":3414,"line":5057},[3412,8274,8275],{},"class D extends C\n",[3412,8277,8278],{"class":3414,"line":5345},[3412,8279,3554],{"emptyLinePlaceholder":3553},[3412,8281,8282],{"class":3414,"line":5364},[3412,8283,8284],{},"note top of D\n",[3412,8286,8287],{"class":3414,"line":5370},[3412,8288,8289],{},"  D наслідує від B і C,\n",[3412,8291,8292],{"class":3414,"line":5376},[3412,8293,8294],{},"  обидва наслідують від A.\n",[3412,8296,8297],{"class":3414,"line":5382},[3412,8298,8299],{},"  Який speak() викликати?\n",[3412,8301,8302],{"class":3414,"line":5388},[3412,8303,8304],{},"  B.speak() чи C.speak()?\n",[3412,8306,8307],{"class":3414,"line":5393},[3412,8308,6171],{},[3412,8310,8311],{"class":3414,"line":5399},[3412,8312,3554],{"emptyLinePlaceholder":3553},[3412,8314,8315],{"class":3414,"line":5422},[3412,8316,8317],{},"note right of A\n",[3412,8319,8320],{"class":3414,"line":5428},[3412,8321,8322],{},"  Спільний предок —\n",[3412,8324,8325],{"class":3414,"line":5434},[3412,8326,8327],{},"  джерело конфлікту\n",[3412,8329,8330],{"class":3414,"line":5439},[3412,8331,6171],{},[3412,8333,8334],{"class":3414,"line":5456},[3412,8335,6191],{},[3403,8337,8339],{"className":3405,"code":8338,"language":3407,"meta":3408,"style":3408},"# diamond.py\n\nclass A:\n    def speak(self) -> str:\n        return \"A.speak\"\n\nclass B(A):\n    def speak(self) -> str:\n        return f\"B.speak → {super().speak()}\"\n\nclass C(A):\n    def speak(self) -> str:\n        return f\"C.speak → {super().speak()}\"\n\nclass D(B, C):\n    def speak(self) -> str:\n        return f\"D.speak → {super().speak()}\"\n\nd = D()\nprint(d.speak())\n# D.speak → B.speak → C.speak → A.speak\n\nprint(D.mro())\n# [D, B, C, A, object]\n",[3390,8340,8341,8346,8350,8358,8375,8382,8386,8398,8414,8435,8439,8451,8467,8486,8490,8506,8522,8541,8545,8549,8556,8561,8565,8572],{"__ignoreMap":3408},[3412,8342,8343],{"class":3414,"line":3415},[3412,8344,8345],{"class":3418},"# diamond.py\n",[3412,8347,8348],{"class":3414,"line":3422},[3412,8349,3554],{"emptyLinePlaceholder":3553},[3412,8351,8352,8354,8356],{"class":3414,"line":3437},[3412,8353,3426],{"class":3425},[3412,8355,5814],{"class":3429},[3412,8357,3434],{"class":3433},[3412,8359,8360,8362,8365,8367,8369,8371,8373],{"class":3414,"line":3468},[3412,8361,3440],{"class":3425},[3412,8363,8364],{"class":3443}," speak",[3412,8366,3447],{"class":3433},[3412,8368,3451],{"class":3450},[3412,8370,3946],{"class":3433},[3412,8372,3880],{"class":3429},[3412,8374,3434],{"class":3433},[3412,8376,8377,8379],{"class":3414,"line":3516},[3412,8378,3955],{"class":3530},[3412,8380,8381],{"class":3495}," \"A.speak\"\n",[3412,8383,8384],{"class":3414,"line":3550},[3412,8385,3554],{"emptyLinePlaceholder":3553},[3412,8387,8388,8390,8392,8394,8396],{"class":3414,"line":3557},[3412,8389,3426],{"class":3425},[3412,8391,5861],{"class":3429},[3412,8393,3447],{"class":3433},[3412,8395,5866],{"class":3429},[3412,8397,3893],{"class":3433},[3412,8399,8400,8402,8404,8406,8408,8410,8412],{"class":3414,"line":3567},[3412,8401,3440],{"class":3425},[3412,8403,8364],{"class":3443},[3412,8405,3447],{"class":3433},[3412,8407,3451],{"class":3450},[3412,8409,3946],{"class":3433},[3412,8411,3880],{"class":3429},[3412,8413,3434],{"class":3433},[3412,8415,8416,8418,8420,8423,8425,8427,8430,8432],{"class":3414,"line":3588},[3412,8417,3955],{"class":3530},[3412,8419,3958],{"class":3425},[3412,8421,8422],{"class":3495},"\"B.speak → ",[3412,8424,3499],{"class":3425},[3412,8426,4285],{"class":3429},[3412,8428,8429],{"class":3433},"().speak()",[3412,8431,3504],{"class":3425},[3412,8433,8434],{"class":3495},"\"\n",[3412,8436,8437],{"class":3414,"line":3628},[3412,8438,3554],{"emptyLinePlaceholder":3553},[3412,8440,8441,8443,8445,8447,8449],{"class":3414,"line":3655},[3412,8442,3426],{"class":3425},[3412,8444,5911],{"class":3429},[3412,8446,3447],{"class":3433},[3412,8448,5866],{"class":3429},[3412,8450,3893],{"class":3433},[3412,8452,8453,8455,8457,8459,8461,8463,8465],{"class":3414,"line":3660},[3412,8454,3440],{"class":3425},[3412,8456,8364],{"class":3443},[3412,8458,3447],{"class":3433},[3412,8460,3451],{"class":3450},[3412,8462,3946],{"class":3433},[3412,8464,3880],{"class":3429},[3412,8466,3434],{"class":3433},[3412,8468,8469,8471,8473,8476,8478,8480,8482,8484],{"class":3414,"line":3670},[3412,8470,3955],{"class":3530},[3412,8472,3958],{"class":3425},[3412,8474,8475],{"class":3495},"\"C.speak → ",[3412,8477,3499],{"class":3425},[3412,8479,4285],{"class":3429},[3412,8481,8429],{"class":3433},[3412,8483,3504],{"class":3425},[3412,8485,8434],{"class":3495},[3412,8487,8488],{"class":3414,"line":3691},[3412,8489,3554],{"emptyLinePlaceholder":3553},[3412,8491,8492,8494,8496,8498,8500,8502,8504],{"class":3414,"line":3731},[3412,8493,3426],{"class":3425},[3412,8495,5960],{"class":3429},[3412,8497,3447],{"class":3433},[3412,8499,5965],{"class":3429},[3412,8501,3454],{"class":3433},[3412,8503,5970],{"class":3429},[3412,8505,3893],{"class":3433},[3412,8507,8508,8510,8512,8514,8516,8518,8520],{"class":3414,"line":4072},[3412,8509,3440],{"class":3425},[3412,8511,8364],{"class":3443},[3412,8513,3447],{"class":3433},[3412,8515,3451],{"class":3450},[3412,8517,3946],{"class":3433},[3412,8519,3880],{"class":3429},[3412,8521,3434],{"class":3433},[3412,8523,8524,8526,8528,8531,8533,8535,8537,8539],{"class":3414,"line":4077},[3412,8525,3955],{"class":3530},[3412,8527,3958],{"class":3425},[3412,8529,8530],{"class":3495},"\"D.speak → ",[3412,8532,3499],{"class":3425},[3412,8534,4285],{"class":3429},[3412,8536,8429],{"class":3433},[3412,8538,3504],{"class":3425},[3412,8540,8434],{"class":3495},[3412,8542,8543],{"class":3414,"line":4095},[3412,8544,3554],{"emptyLinePlaceholder":3553},[3412,8546,8547],{"class":3414,"line":5014},[3412,8548,6018],{"class":3433},[3412,8550,8551,8553],{"class":3414,"line":5020},[3412,8552,3487],{"class":3443},[3412,8554,8555],{"class":3433},"(d.speak())\n",[3412,8557,8558],{"class":3414,"line":5026},[3412,8559,8560],{"class":3418},"# D.speak → B.speak → C.speak → A.speak\n",[3412,8562,8563],{"class":3414,"line":5035},[3412,8564,3554],{"emptyLinePlaceholder":3553},[3412,8566,8567,8569],{"class":3414,"line":5046},[3412,8568,3487],{"class":3443},[3412,8570,8571],{"class":3433},"(D.mro())\n",[3412,8573,8574],{"class":3414,"line":5057},[3412,8575,8576],{"class":3418},"# [D, B, C, A, object]\n",[3803,8578,8580],{"id":8579},"стара-проблема-dfls-depth-first-left-to-right","Стара проблема: DFLS (Depth-First, Left-to-Right)",[3399,8582,8583,8584,8587,8588,8591],{},"До Python 2.2 пошук методів відбувався за ",[3764,8585,8586],{},"наївним алгоритмом DFLS",": спочатку вглиб по лівій гілці, потім по правій. Для класу ",[3390,8589,8590],{},"D(B, C)"," черга виглядала так:",[6055,8593,8594],{},[3403,8595,8597],{"className":6059,"code":8596,"language":6061,"meta":3408,"style":3408},"@startuml\nskinparam style plain\nskinparam backgroundColor #ffffff\nskinparam ArrowColor #e11d48\n\nrectangle \"Черга пошуку (DFLS)\" as Q #fee2e2 {\n    card \"D\" as D1 #fca5a5\n    card \"B\" as B1 #fca5a5\n    card \"A\" as A1 #fca5a5\n    card \"C\" as C1 #fca5a5\n    card \"A\" as A2 #fca5a5 {\n        note \"Дублікат!\\nОчищується\" as N1\n    }\n}\n\nD1 -right-> B1 : 1\nB1 -right-> A1 : 2 (вглиб)\nA1 -right-> C1 : 3 (вправо)\nC1 -right-> A2 : 4\n\nnote bottom of A1\n  ❌ Катастрофа!\n  A знайдено РАНІШЕ ніж C.\n  Перевизначений метод у C\n  повністю ігнорується.\nend note\n@enduml\n",[3390,8598,8599,8603,8607,8611,8616,8620,8625,8630,8635,8640,8645,8650,8655,8660,8664,8668,8673,8678,8683,8688,8692,8697,8702,8707,8712,8717,8721],{"__ignoreMap":3408},[3412,8600,8601],{"class":3414,"line":3415},[3412,8602,6068],{},[3412,8604,8605],{"class":3414,"line":3422},[3412,8606,6073],{},[3412,8608,8609],{"class":3414,"line":3437},[3412,8610,6078],{},[3412,8612,8613],{"class":3414,"line":3468},[3412,8614,8615],{},"skinparam ArrowColor #e11d48\n",[3412,8617,8618],{"class":3414,"line":3516},[3412,8619,3554],{"emptyLinePlaceholder":3553},[3412,8621,8622],{"class":3414,"line":3550},[3412,8623,8624],{},"rectangle \"Черга пошуку (DFLS)\" as Q #fee2e2 {\n",[3412,8626,8627],{"class":3414,"line":3557},[3412,8628,8629],{},"    card \"D\" as D1 #fca5a5\n",[3412,8631,8632],{"class":3414,"line":3567},[3412,8633,8634],{},"    card \"B\" as B1 #fca5a5\n",[3412,8636,8637],{"class":3414,"line":3588},[3412,8638,8639],{},"    card \"A\" as A1 #fca5a5\n",[3412,8641,8642],{"class":3414,"line":3628},[3412,8643,8644],{},"    card \"C\" as C1 #fca5a5\n",[3412,8646,8647],{"class":3414,"line":3655},[3412,8648,8649],{},"    card \"A\" as A2 #fca5a5 {\n",[3412,8651,8652],{"class":3414,"line":3660},[3412,8653,8654],{},"        note \"Дублікат!\\nОчищується\" as N1\n",[3412,8656,8657],{"class":3414,"line":3670},[3412,8658,8659],{},"    }\n",[3412,8661,8662],{"class":3414,"line":3691},[3412,8663,6108],{},[3412,8665,8666],{"class":3414,"line":3731},[3412,8667,3554],{"emptyLinePlaceholder":3553},[3412,8669,8670],{"class":3414,"line":4072},[3412,8671,8672],{},"D1 -right-> B1 : 1\n",[3412,8674,8675],{"class":3414,"line":4077},[3412,8676,8677],{},"B1 -right-> A1 : 2 (вглиб)\n",[3412,8679,8680],{"class":3414,"line":4095},[3412,8681,8682],{},"A1 -right-> C1 : 3 (вправо)\n",[3412,8684,8685],{"class":3414,"line":5014},[3412,8686,8687],{},"C1 -right-> A2 : 4\n",[3412,8689,8690],{"class":3414,"line":5020},[3412,8691,3554],{"emptyLinePlaceholder":3553},[3412,8693,8694],{"class":3414,"line":5026},[3412,8695,8696],{},"note bottom of A1\n",[3412,8698,8699],{"class":3414,"line":5035},[3412,8700,8701],{},"  ❌ Катастрофа!\n",[3412,8703,8704],{"class":3414,"line":5046},[3412,8705,8706],{},"  A знайдено РАНІШЕ ніж C.\n",[3412,8708,8709],{"class":3414,"line":5057},[3412,8710,8711],{},"  Перевизначений метод у C\n",[3412,8713,8714],{"class":3414,"line":5345},[3412,8715,8716],{},"  повністю ігнорується.\n",[3412,8718,8719],{"class":3414,"line":5364},[3412,8720,6171],{},[3412,8722,8723],{"class":3414,"line":5370},[3412,8724,6191],{},[3399,8726,8727,8730,8731,8733,8734,8737,8738,8741],{},[3764,8728,8729],{},"Чому DFLS руйнує логіку:"," якщо ",[3390,8732,5970],{}," перевизначає метод ",[3390,8735,8736],{},"speak()"," для специфічної поведінки, DFLS ніколи до нього не дістанеться — він знаходить ",[3390,8739,8740],{},"A.speak()"," раніше. Специфічні нащадки ігноруються на користь далеких предків. Це пряме порушення принципу Liskov Substitution.",[3399,8743,8744,8745,8748],{},"Саме ця проблема змусила розробників Python у версії 2.2 запровадити ",[3764,8746,8747],{},"C3-лінеаризацію"," та класи нового стилю.",[5739,8750,8751,8754,8755,8758,8759,8762,8763,8765,8766,8768,8769,3539],{},[3764,8752,8753],{},"Ще одна небезпека DFLS:"," при «ромбовій» ієрархії ",[3390,8756,8757],{},"A.__init__"," викликається ",[3764,8760,8761],{},"двічі",": один раз через ліву гілку ",[3390,8764,5965],{},", інший — через праву ",[3390,8767,5970],{},". Це призводить до повторної ініціалізації атрибутів батьківського класу — непередбачувана поведінка, що у сучасних Django\u002FSQLAlchemy-проектах могла б стати критичним багом. C3-лінеаризація гарантує, що кожен клас у MRO відвідується ",[3764,8770,8771],{},"рівно один раз",[3796,8773],{},[3394,8775,8777],{"id":8776},"частина-iv-глибокий-розбір-c3-лінеаризації","Частина IV: Глибокий розбір C3-лінеаризації",[3803,8779,8781],{"id":8780},"математична-основа-алгоритму","Математична основа алгоритму",[3399,8783,8784,8787],{},[3764,8785,8786],{},"C3-лінеаризація"," — алгоритм, запозичений з мови Dylan, що гарантує три властивості MRO:",[8789,8790,8791,8807,8825],"ol",{},[8792,8793,8794,8797,8798,8801,8802,8804,8805],"li",{},[3764,8795,8796],{},"Локальний порядок предків",": якщо ",[3390,8799,8800],{},"class D(B, C)",", то в MRO ",[3390,8803,5965],{}," завжди перед ",[3390,8806,5970],{},[8792,8808,8809,8797,8812,8815,8816,8819,8820,8815,8822,8824],{},[3764,8810,8811],{},"Монотонність",[3390,8813,8814],{},"X"," перед ",[3390,8817,8818],{},"Y"," в MRO будь-якого батька, то ",[3390,8821,8814],{},[3390,8823,8818],{}," і в MRO нащадка",[8792,8826,8827,8830],{},[3764,8828,8829],{},"Єдиний обхід",": кожен клас відвідується рівно один раз",[3399,8832,8833,8834,8838,8839,5790],{},"Формула лінеаризації класу ",[8835,8836,5970],"math-formula",{":inline":8837},"true",", що наслідує ",[8835,8840,8841],{":inline":8837},"B_1, B_2, \\dots, B_N",[8835,8843,8844,8845,3921,8848,8850,8851,8854,8855,8858,8859,3454,8862,6370],{},"L",[3412,8846,8847],{},"C(B_1, B_2, \\dots, B_N)",[3412,8849,5970],{}," + \\text{merge}(L",[3412,8852,8853],{},"B_1",", L",[3412,8856,8857],{},"B_2",", \\dots, L",[3412,8860,8861],{},"B_N",[3412,8863,8841],{},[3399,8865,8866,8867,8870],{},"Де ",[8835,8868,8869],{":inline":8837},"\\text{merge}"," — операція злиття кількох впорядкованих списків за правилом:",[8789,8872,8873,8884,8891,8894,8901],{},[8792,8874,8875,8876,8879,8880,8883],{},"Кожен список має ",[3764,8877,8878],{},"голову"," (перший елемент) та ",[3764,8881,8882],{},"хвіст"," (решта)",[8792,8885,8886,8887,8890],{},"Беремо голову першого списку — перевіряємо, чи немає її у ",[3764,8888,8889],{},"хвостах"," інших списків",[8792,8892,8893],{},"Якщо її немає у жодному хвості — вилучаємо з усіх списків, додаємо до MRO",[8792,8895,8896,8897,8900],{},"Якщо є хоча б в одному хвості — вона ",[3764,8898,8899],{},"заблокована",", переходимо до голови наступного списку",[8792,8902,8903,8904,6370],{},"Якщо всі голови заблоковані — MRO неможливо побудувати (",[3390,8905,8141],{},[3796,8907],{},[3803,8909,8911],{"id":8910},"покроковий-ручний-розрахунок-для-діаманта","Покроковий ручний розрахунок для «діаманта»",[3403,8913,8915],{"className":3405,"code":8914,"language":3407,"meta":3408,"style":3408},"class A(object): pass\nclass B(A): pass\nclass C(A): pass\nclass D(B, C): pass\n",[3390,8916,8917,8932,8946,8960],{"__ignoreMap":3408},[3412,8918,8919,8921,8923,8925,8927,8929],{"class":3414,"line":3415},[3412,8920,3426],{"class":3425},[3412,8922,5814],{"class":3429},[3412,8924,3447],{"class":3433},[3412,8926,3815],{"class":3429},[3412,8928,3484],{"class":3433},[3412,8930,8931],{"class":3530},"pass\n",[3412,8933,8934,8936,8938,8940,8942,8944],{"class":3414,"line":3422},[3412,8935,3426],{"class":3425},[3412,8937,5861],{"class":3429},[3412,8939,3447],{"class":3433},[3412,8941,5866],{"class":3429},[3412,8943,3484],{"class":3433},[3412,8945,8931],{"class":3530},[3412,8947,8948,8950,8952,8954,8956,8958],{"class":3414,"line":3437},[3412,8949,3426],{"class":3425},[3412,8951,5911],{"class":3429},[3412,8953,3447],{"class":3433},[3412,8955,5866],{"class":3429},[3412,8957,3484],{"class":3433},[3412,8959,8931],{"class":3530},[3412,8961,8962,8964,8966,8968,8970,8972,8974,8976],{"class":3414,"line":3468},[3412,8963,3426],{"class":3425},[3412,8965,5960],{"class":3429},[3412,8967,3447],{"class":3433},[3412,8969,5965],{"class":3429},[3412,8971,3454],{"class":3433},[3412,8973,5970],{"class":3429},[3412,8975,3484],{"class":3433},[3412,8977,8931],{"class":3530},[3399,8979,8980],{},[3764,8981,8982],{},"Відомі лінеаризації:",[8984,8985,8986,8995,9004,9013],"ul",{},[8792,8987,8988],{},[8835,8989,8844,8990,3921,8993],{":inline":8837},[3412,8991,8992],{},"\\text{object}",[3412,8994,8992],{},[8792,8996,8997],{},[8835,8998,8844,8999,3921,9001],{":inline":8837},[3412,9000,5866],{},[3412,9002,9003],{},"A, \\text{object}",[8792,9005,9006],{},[8835,9007,8844,9008,3921,9010],{":inline":8837},[3412,9009,5965],{},[3412,9011,9012],{},"B, A, \\text{object}",[8792,9014,9015],{},[8835,9016,8844,9017,3921,9019],{":inline":8837},[3412,9018,5970],{},[3412,9020,9021],{},"C, A, \\text{object}",[3399,9023,9024,4769,9027,5790],{},[3764,9025,9026],{},"Розрахунок",[8835,9028,8844,9029],{":inline":8837},[3412,9030,9031],{},"D",[8835,9033,8844,9034,3921,9036,9038,9039,9041,9042,9041,9044,6370],{},[3412,9035,9031],{},[3412,9037,9031],{}," + \\text{merge}(",[3412,9040,9012],{},",\\ ",[3412,9043,9021],{},[3412,9045,9046],{},"B, C",[9048,9049,9050,9056,9059,9094,9102,9115,9121,9123,9148,9158,9177,9183,9194,9200,9202,9220,9225,9236,9242,9248],"steps",{},[3803,9051,9053,9054],{"id":9052},"крок-1-беремо-голову-першого-списку-b","Крок 1: Беремо голову першого списку — ",[3390,9055,5965],{},[3399,9057,9058],{},"Перевіряємо хвости:",[8984,9060,9061,9074,9083],{},[8792,9062,9063,9064,9067,9068,3877,9071,9073],{},"Хвіст ",[3390,9065,9066],{},"[B, A, object]"," → ",[3390,9069,9070],{},"[A, object]",[3390,9072,5965],{}," відсутній ✅",[8792,9075,9063,9076,9067,9079,3877,9081,9073],{},[3390,9077,9078],{},"[C, A, object]",[3390,9080,9070],{},[3390,9082,5965],{},[8792,9084,9063,9085,9067,9088,3877,9091,9093],{},[3390,9086,9087],{},"[B, C]",[3390,9089,9090],{},"[C]",[3390,9092,5965],{}," відсутній ✅ (хвіст — це список БЕЗ голови)",[3399,9095,9096,9098,9099,9101],{},[3390,9097,5965],{}," не заблокований → додаємо до MRO. Видаляємо ",[3390,9100,5965],{}," звідусіль.",[8835,9103,8844,9104,3921,9106,9038,9109,9041,9111,9041,9113,6370],{},[3412,9105,9031],{},[3412,9107,9108],{},"D, B",[3412,9110,9003],{},[3412,9112,9021],{},[3412,9114,5970],{},[3803,9116,9118,9119],{"id":9117},"крок-2-беремо-голову-першого-списку-a","Крок 2: Беремо голову першого списку — ",[3390,9120,5866],{},[3399,9122,9058],{},[8984,9124,9125,9134],{},[8792,9126,9063,9127,9067,9129,3877,9132,9073],{},[3390,9128,9070],{},[3390,9130,9131],{},"[object]",[3390,9133,5866],{},[8792,9135,9063,9136,9067,9138,3877,9142,4769,9144,9147],{},[3390,9137,9078],{},[3764,9139,9140],{},[3390,9141,9070],{},[3390,9143,5866],{},[3764,9145,9146],{},"присутній"," ❌",[3399,9149,9150,9152,9153,9155,9156,3539],{},[3390,9151,5866],{}," заблокований → переходимо до голови ",[3764,9154,6469],{}," списку → ",[3390,9157,5970],{},[8984,9159,9160,9166],{},[8792,9161,9063,9162,3877,9164,9073],{},[3390,9163,9070],{},[3390,9165,5970],{},[8792,9167,9063,9168,9067,9170,9173,9174,9176],{},[3390,9169,9090],{},[3390,9171,9172],{},"[]"," (порожній): ",[3390,9175,5970],{}," відсутній ✅ (голова не є частиною свого хвоста)",[3399,9178,9179,9098,9181,9101],{},[3390,9180,5970],{},[3390,9182,5970],{},[8835,9184,8844,9185,3921,9187,9038,9190,9041,9192,6370],{},[3412,9186,9031],{},[3412,9188,9189],{},"D, B, C",[3412,9191,9003],{},[3412,9193,9003],{},[3803,9195,9197,9198],{"id":9196},"крок-3-беремо-голову-a","Крок 3: Беремо голову — ",[3390,9199,5866],{},[3399,9201,9058],{},[8984,9203,9204,9212],{},[8792,9205,9063,9206,9067,9208,3877,9210,9073],{},[3390,9207,9070],{},[3390,9209,9131],{},[3390,9211,5866],{},[8792,9213,9063,9214,9067,9216,3877,9218,9073],{},[3390,9215,9070],{},[3390,9217,9131],{},[3390,9219,5866],{},[3399,9221,9222,9224],{},[3390,9223,5866],{}," не заблокований → додаємо до MRO.",[8835,9226,8844,9227,3921,9229,9038,9232,9041,9234,6370],{},[3412,9228,9031],{},[3412,9230,9231],{},"D, B, C, A",[3412,9233,8992],{},[3412,9235,8992],{},[3803,9237,9239,9240],{"id":9238},"крок-4-залишається-object","Крок 4: Залишається ",[3390,9241,3815],{},[3399,9243,9244,9245,9247],{},"Обидва списки містять лише ",[3390,9246,3815],{}," як голову (хвости порожні). Додаємо.",[8835,9249,8844,9250,3921,9252],{},[3412,9251,9031],{},[3412,9253,9254],{},"D,\\ B,\\ C,\\ A,\\ \\text{object}",[3403,9256,9258],{"className":3405,"code":9257,"language":3407,"meta":3408,"style":3408},"# Верифікація розрахунку\nprint(D.mro())\n# [\u003Cclass 'D'>, \u003Cclass 'B'>, \u003Cclass 'C'>, \u003Cclass 'A'>, \u003Cclass 'object'>]\n",[3390,9259,9260,9265,9271],{"__ignoreMap":3408},[3412,9261,9262],{"class":3414,"line":3415},[3412,9263,9264],{"class":3418},"# Верифікація розрахунку\n",[3412,9266,9267,9269],{"class":3414,"line":3422},[3412,9268,3487],{"class":3443},[3412,9270,8571],{"class":3433},[3412,9272,9273],{"class":3414,"line":3437},[3412,9274,9275],{"class":3418},"# [\u003Cclass 'D'>, \u003Cclass 'B'>, \u003Cclass 'C'>, \u003Cclass 'A'>, \u003Cclass 'object'>]\n",[4757,9277,9279,9288,9312],{"title":9278},"Верифікація ручного розрахунку MRO",[4761,9280,9282,4769,9285],{"className":9281},[3414],[3412,9283,4768],{"className":9284},[4767],[3764,9286,9287],{},"python -c \"print(D.mro())\"",[4761,9289,9291,9292,3454,9296,3454,9300,3454,9304,3454,9308,9311],{"className":9290},[3414],"[",[3412,9293,9295],{"className":9294},[4800],"\u003Cclass 'D'>",[3412,9297,9299],{"className":9298},[4779],"\u003Cclass 'B'>",[3412,9301,9303],{"className":9302},[4779],"\u003Cclass 'C'>",[3412,9305,9307],{"className":9306},[4792],"\u003Cclass 'A'>",[3412,9309,5665],{"className":9310},[5673],"]",[4761,9313,9315],{"className":9314},[3414],[3412,9316,9318],{"className":9317},[4800],"✓ Збігається з ручним розрахунком!",[3399,9320,9321,9324,9325,9328,9329,9332,9333,9335,9336,9339,9340,9342],{},[3764,9322,9323],{},"Що це означає практично:"," при виклику ",[3390,9326,9327],{},"d.speak()"," Python шукає метод у порядку ",[3390,9330,9331],{},"D → B → C → A → object",". Перевизначений у ",[3390,9334,5970],{}," метод знаходиться ",[3764,9337,9338],{},"раніше"," ніж загальний у ",[3390,9341,5866],{}," — проблема DFLS вирішена.",[3796,9344],{},[3803,9346,9348],{"id":9347},"конфлікт-лінеаризації-коли-mro-неможливо-побудувати","Конфлікт лінеаризації: коли MRO неможливо побудувати",[3399,9350,9351,9352,9355,9356,5790],{},"Бувають ієрархії, де вимоги двох батьків суперечать одна одній. Python виявляє це ",[3764,9353,9354],{},"під час оголошення класу"," і кидає ",[3390,9357,8141],{},[3403,9359,9361],{"className":3405,"code":9360,"language":3407,"meta":3408,"style":3408},"# mro_conflict.py\n\nclass X: pass\nclass Y: pass\n\nclass A(X, Y): pass  # A вимагає: X перед Y\nclass B(Y, X): pass  # B вимагає: Y перед X (суперечить A!)\n\n# C не може задовольнити обидві вимоги одночасно\nclass C(A, B): pass  # ← TypeError при оголошенні!\n",[3390,9362,9363,9368,9372,9383,9394,9398,9420,9441,9445,9450],{"__ignoreMap":3408},[3412,9364,9365],{"class":3414,"line":3415},[3412,9366,9367],{"class":3418},"# mro_conflict.py\n",[3412,9369,9370],{"class":3414,"line":3422},[3412,9371,3554],{"emptyLinePlaceholder":3553},[3412,9373,9374,9376,9379,9381],{"class":3414,"line":3437},[3412,9375,3426],{"class":3425},[3412,9377,9378],{"class":3429}," X",[3412,9380,3877],{"class":3433},[3412,9382,8931],{"class":3530},[3412,9384,9385,9387,9390,9392],{"class":3414,"line":3468},[3412,9386,3426],{"class":3425},[3412,9388,9389],{"class":3429}," Y",[3412,9391,3877],{"class":3433},[3412,9393,8931],{"class":3530},[3412,9395,9396],{"class":3414,"line":3516},[3412,9397,3554],{"emptyLinePlaceholder":3553},[3412,9399,9400,9402,9404,9406,9408,9410,9412,9414,9417],{"class":3414,"line":3550},[3412,9401,3426],{"class":3425},[3412,9403,5814],{"class":3429},[3412,9405,3447],{"class":3433},[3412,9407,8814],{"class":3429},[3412,9409,3454],{"class":3433},[3412,9411,8818],{"class":3429},[3412,9413,3484],{"class":3433},[3412,9415,9416],{"class":3530},"pass",[3412,9418,9419],{"class":3418},"  # A вимагає: X перед Y\n",[3412,9421,9422,9424,9426,9428,9430,9432,9434,9436,9438],{"class":3414,"line":3557},[3412,9423,3426],{"class":3425},[3412,9425,5861],{"class":3429},[3412,9427,3447],{"class":3433},[3412,9429,8818],{"class":3429},[3412,9431,3454],{"class":3433},[3412,9433,8814],{"class":3429},[3412,9435,3484],{"class":3433},[3412,9437,9416],{"class":3530},[3412,9439,9440],{"class":3418},"  # B вимагає: Y перед X (суперечить A!)\n",[3412,9442,9443],{"class":3414,"line":3567},[3412,9444,3554],{"emptyLinePlaceholder":3553},[3412,9446,9447],{"class":3414,"line":3588},[3412,9448,9449],{"class":3418},"# C не може задовольнити обидві вимоги одночасно\n",[3412,9451,9452,9454,9456,9458,9460,9462,9464,9466,9468],{"class":3414,"line":3628},[3412,9453,3426],{"class":3425},[3412,9455,5911],{"class":3429},[3412,9457,3447],{"class":3433},[3412,9459,5866],{"class":3429},[3412,9461,3454],{"class":3433},[3412,9463,5965],{"class":3429},[3412,9465,3484],{"class":3433},[3412,9467,9416],{"class":3530},[3412,9469,9470],{"class":3418},"  # ← TypeError при оголошенні!\n",[4757,9472,9474,9482,9489,9493,9500,9503],{"title":9473},"python mro_conflict.py",[4761,9475,9477,4769,9480],{"className":9476},[3414],[3412,9478,4768],{"className":9479},[4767],[3764,9481,9473],{},[4761,9483,9485],{"className":9484},[3414],[3412,9486,9488],{"className":9487},[5732],"Traceback (most recent call last):",[4761,9490,9492],{"className":9491},[3414],"  File \"mro_conflict.py\", line 8, in \u003Cmodule>",[4761,9494,9496],{"className":9495},[3414],[3412,9497,9499],{"className":9498},[5732],"TypeError: Cannot create a consistent method resolution order (MRO) for bases X, Y",[4761,9501],{"className":9502},[3414],[4761,9504,9506],{"className":9505},[3414],[3412,9507,9509],{"className":9508},[5673],"# A вимагає X→Y, B вимагає Y→X. Обидві вимоги неможливо задовольнити.",[9511,9512,9513,9516,9517,9520],"important",{},[3764,9514,9515],{},"Python захищає вас на етапі оголошення класу",", а не під час виконання. Якщо ви бачите ",[3390,9518,9519],{},"TypeError: Cannot create a consistent MRO"," — це сигнал архітектурної проблеми. Рішення: перегляньте порядок батьків або виділіть спільну логіку у третій незалежний клас.",[3796,9522],{},[3803,9524,9526,9527,9529,9530],{"id":9525},"налагодження-mro-__mro__-vs-mro","Налагодження MRO: ",[3390,9528,5340],{}," vs ",[3390,9531,9532],{},"mro()",[3403,9534,9536],{"className":3405,"code":9535,"language":3407,"meta":3408,"style":3408},"# Два способи отримати MRO — різний тип результату\n\n# 1. Атрибут __mro__ — кортеж (tuple), незмінний\nprint(type(D.__mro__))   # \u003Cclass 'tuple'>\nprint(D.__mro__)         # (\u003Cclass 'D'>, \u003Cclass 'B'>, ..., \u003Cclass 'object'>)\n\n# 2. Метод mro() — список (list), зручніший для ітерації\nprint(type(D.mro()))     # \u003Cclass 'list'>\nprint(D.mro())           # [\u003Cclass 'D'>, \u003Cclass 'B'>, ..., \u003Cclass 'object'>]\n\n# Практичне використання: пошук класу, що надає метод\ndef find_method_owner(cls, method_name: str) -> type | None:\n    \"\"\"Знаходить, у якому класі фактично визначено метод.\"\"\"\n    for klass in cls.__mro__:\n        if method_name in klass.__dict__:  # __dict__ — лише власні атрибути!\n            return klass\n    return None\n\nprint(find_method_owner(D, \"speak\"))   # \u003Cclass 'D'>  — D.speak існує\nprint(find_method_owner(D, \"__init__\")) # \u003Cclass 'object'> — жоден не визначив\n",[3390,9537,9538,9543,9547,9552,9571,9585,9589,9594,9608,9618,9622,9627,9659,9664,9682,9703,9711,9719,9723,9738],{"__ignoreMap":3408},[3412,9539,9540],{"class":3414,"line":3415},[3412,9541,9542],{"class":3418},"# Два способи отримати MRO — різний тип результату\n",[3412,9544,9545],{"class":3414,"line":3422},[3412,9546,3554],{"emptyLinePlaceholder":3553},[3412,9548,9549],{"class":3414,"line":3437},[3412,9550,9551],{"class":3418},"# 1. Атрибут __mro__ — кортеж (tuple), незмінний\n",[3412,9553,9554,9556,9558,9560,9563,9565,9568],{"class":3414,"line":3468},[3412,9555,3487],{"class":3443},[3412,9557,3447],{"class":3433},[3412,9559,5110],{"class":3429},[3412,9561,9562],{"class":3433},"(D.",[3412,9564,5340],{"class":3450},[3412,9566,9567],{"class":3433},"))   ",[3412,9569,9570],{"class":3418},"# \u003Cclass 'tuple'>\n",[3412,9572,9573,9575,9577,9579,9582],{"class":3414,"line":3516},[3412,9574,3487],{"class":3443},[3412,9576,9562],{"class":3433},[3412,9578,5340],{"class":3450},[3412,9580,9581],{"class":3433},")         ",[3412,9583,9584],{"class":3418},"# (\u003Cclass 'D'>, \u003Cclass 'B'>, ..., \u003Cclass 'object'>)\n",[3412,9586,9587],{"class":3414,"line":3550},[3412,9588,3554],{"emptyLinePlaceholder":3553},[3412,9590,9591],{"class":3414,"line":3557},[3412,9592,9593],{"class":3418},"# 2. Метод mro() — список (list), зручніший для ітерації\n",[3412,9595,9596,9598,9600,9602,9605],{"class":3414,"line":3567},[3412,9597,3487],{"class":3443},[3412,9599,3447],{"class":3433},[3412,9601,5110],{"class":3429},[3412,9603,9604],{"class":3433},"(D.mro()))     ",[3412,9606,9607],{"class":3418},"# \u003Cclass 'list'>\n",[3412,9609,9610,9612,9615],{"class":3414,"line":3588},[3412,9611,3487],{"class":3443},[3412,9613,9614],{"class":3433},"(D.mro())           ",[3412,9616,9617],{"class":3418},"# [\u003Cclass 'D'>, \u003Cclass 'B'>, ..., \u003Cclass 'object'>]\n",[3412,9619,9620],{"class":3414,"line":3628},[3412,9621,3554],{"emptyLinePlaceholder":3553},[3412,9623,9624],{"class":3414,"line":3655},[3412,9625,9626],{"class":3418},"# Практичне використання: пошук класу, що надає метод\n",[3412,9628,9629,9632,9635,9637,9639,9641,9644,9646,9648,9650,9652,9655,9657],{"class":3414,"line":3660},[3412,9630,9631],{"class":3425},"def",[3412,9633,9634],{"class":3443}," find_method_owner",[3412,9636,3447],{"class":3433},[3412,9638,6497],{"class":3450},[3412,9640,3454],{"class":3433},[3412,9642,9643],{"class":3450},"method_name",[3412,9645,3877],{"class":3433},[3412,9647,3880],{"class":3429},[3412,9649,3946],{"class":3433},[3412,9651,5110],{"class":3429},[3412,9653,9654],{"class":3433}," | ",[3412,9656,4011],{"class":3425},[3412,9658,3434],{"class":3433},[3412,9660,9661],{"class":3414,"line":3670},[3412,9662,9663],{"class":3495},"    \"\"\"Знаходить, у якому класі фактично визначено метод.\"\"\"\n",[3412,9665,9666,9669,9672,9674,9676,9678,9680],{"class":3414,"line":3691},[3412,9667,9668],{"class":3530},"    for",[3412,9670,9671],{"class":3433}," klass ",[3412,9673,4744],{"class":3530},[3412,9675,5331],{"class":3425},[3412,9677,3539],{"class":3433},[3412,9679,5340],{"class":3450},[3412,9681,3434],{"class":3433},[3412,9683,9684,9687,9690,9692,9695,9697,9700],{"class":3414,"line":3731},[3412,9685,9686],{"class":3530},"        if",[3412,9688,9689],{"class":3433}," method_name ",[3412,9691,4744],{"class":3425},[3412,9693,9694],{"class":3433}," klass.",[3412,9696,3542],{"class":3450},[3412,9698,9699],{"class":3433},":  ",[3412,9701,9702],{"class":3418},"# __dict__ — лише власні атрибути!\n",[3412,9704,9705,9708],{"class":3414,"line":4072},[3412,9706,9707],{"class":3530},"            return",[3412,9709,9710],{"class":3433}," klass\n",[3412,9712,9713,9716],{"class":3414,"line":4077},[3412,9714,9715],{"class":3530},"    return",[3412,9717,9718],{"class":3425}," None\n",[3412,9720,9721],{"class":3414,"line":4095},[3412,9722,3554],{"emptyLinePlaceholder":3553},[3412,9724,9725,9727,9730,9733,9735],{"class":3414,"line":5014},[3412,9726,3487],{"class":3443},[3412,9728,9729],{"class":3433},"(find_method_owner(D, ",[3412,9731,9732],{"class":3495},"\"speak\"",[3412,9734,9567],{"class":3433},[3412,9736,9737],{"class":3418},"# \u003Cclass 'D'>  — D.speak існує\n",[3412,9739,9740,9742,9744,9747,9750],{"class":3414,"line":5020},[3412,9741,3487],{"class":3443},[3412,9743,9729],{"class":3433},[3412,9745,9746],{"class":3495},"\"__init__\"",[3412,9748,9749],{"class":3433},")) ",[3412,9751,9752],{"class":3418},"# \u003Cclass 'object'> — жоден не визначив\n",[4757,9754,9756,9765,9773,9781,9788],{"title":9755},"Порівняння **mro** та mro()",[4761,9757,9759,4769,9762],{"className":9758},[3414],[3412,9760,4768],{"className":9761},[4767],[3764,9763,9764],{},"python mro_inspect.py",[4761,9766,9768,9769],{"className":9767},[3414],"type(D.__mro__):  ",[3412,9770,9772],{"className":9771},[4792],"\u003Cclass 'tuple'>",[4761,9774,9776,9777],{"className":9775},[3414],"type(D.mro()):    ",[3412,9778,9780],{"className":9779},[4792],"\u003Cclass 'list'>",[4761,9782,9784,9785],{"className":9783},[3414],"find_method_owner(D, \"speak\"):    ",[3412,9786,9295],{"className":9787},[4800],[4761,9789,9791,9792],{"className":9790},[3414],"find_method_owner(D, \"__init__\"): ",[3412,9793,5665],{"className":9794},[5673],[3796,9796],{},[3394,9798,9800],{"id":9799},"частина-v-cpython-internals-як-mro-зберігається-у-памяті","Частина V: CPython Internals — як MRO зберігається у пам'яті",[3803,9802,9804,4819,9807],{"id":9803},"pytypeobject-та-tp_mro",[3390,9805,9806],{},"PyTypeObject",[3390,9808,9809],{},"tp_mro",[3399,9811,9812,9813,9815,9816,9818],{},"На рівні CPython (реалізація Python на C) кожен клас Python є екземпляром структури ",[3390,9814,9806],{},". MRO зберігається у полі ",[3390,9817,9809],{}," цієї структури як звичайний кортеж Python-об'єктів.",[3403,9820,9824],{"className":9821,"code":9822,"language":9823,"meta":3408,"style":3408},"language-c shiki shiki-themes light-plus dark-plus dark-plus","\u002F* Спрощено з CPython\u002FInclude\u002Fcpython\u002Fobject.h *\u002F\ntypedef struct _typeobject {\n    PyObject_VAR_HEAD\n    const char *tp_name;        \u002F* ім'я типу *\u002F\n    Py_ssize_t  tp_basicsize;   \u002F* розмір екземпляра *\u002F\n    \u002F* ... інші поля ... *\u002F\n    PyObject   *tp_mro;         \u002F* кортеж MRO — обчислюється C3 при type.__new__ *\u002F\n    PyObject   *tp_bases;       \u002F* кортеж батьківських класів (__bases__) *\u002F\n    PyObject   *tp_base;        \u002F* перший батьківський клас (__base__) *\u002F\n    \u002F* ... *\u002F\n} PyTypeObject;\n","c",[3390,9825,9826,9831,9842,9847,9861,9872,9877,9885,9893,9901,9906],{"__ignoreMap":3408},[3412,9827,9828],{"class":3414,"line":3415},[3412,9829,9830],{"class":3418},"\u002F* Спрощено з CPython\u002FInclude\u002Fcpython\u002Fobject.h *\u002F\n",[3412,9832,9833,9836,9839],{"class":3414,"line":3422},[3412,9834,9835],{"class":3425},"typedef",[3412,9837,9838],{"class":3425}," struct",[3412,9840,9841],{"class":3433}," _typeobject {\n",[3412,9843,9844],{"class":3414,"line":3437},[3412,9845,9846],{"class":3433},"    PyObject_VAR_HEAD\n",[3412,9848,9849,9852,9855,9858],{"class":3414,"line":3468},[3412,9850,9851],{"class":3425},"    const",[3412,9853,9854],{"class":3425}," char",[3412,9856,9857],{"class":3433}," *tp_name;",[3412,9859,9860],{"class":3418},"        \u002F* ім'я типу *\u002F\n",[3412,9862,9863,9866,9869],{"class":3414,"line":3516},[3412,9864,9865],{"class":3429},"    Py_ssize_t",[3412,9867,9868],{"class":3433},"  tp_basicsize;",[3412,9870,9871],{"class":3418},"   \u002F* розмір екземпляра *\u002F\n",[3412,9873,9874],{"class":3414,"line":3550},[3412,9875,9876],{"class":3418},"    \u002F* ... інші поля ... *\u002F\n",[3412,9878,9879,9882],{"class":3414,"line":3557},[3412,9880,9881],{"class":3433},"    PyObject   *tp_mro;",[3412,9883,9884],{"class":3418},"         \u002F* кортеж MRO — обчислюється C3 при type.__new__ *\u002F\n",[3412,9886,9887,9890],{"class":3414,"line":3567},[3412,9888,9889],{"class":3433},"    PyObject   *tp_bases;",[3412,9891,9892],{"class":3418},"       \u002F* кортеж батьківських класів (__bases__) *\u002F\n",[3412,9894,9895,9898],{"class":3414,"line":3588},[3412,9896,9897],{"class":3433},"    PyObject   *tp_base;",[3412,9899,9900],{"class":3418},"        \u002F* перший батьківський клас (__base__) *\u002F\n",[3412,9902,9903],{"class":3414,"line":3628},[3412,9904,9905],{"class":3418},"    \u002F* ... *\u002F\n",[3412,9907,9908],{"class":3414,"line":3655},[3412,9909,9910],{"class":3433},"} PyTypeObject;\n",[3399,9912,9913],{},[3764,9914,9915,9916,5790],{},"Ключові факти про ",[3390,9917,9809],{},[8984,9919,9920,9931,9940,9951],{},[8792,9921,9922,9923,9926,9927,9930],{},"MRO обчислюється ",[3764,9924,9925],{},"один раз"," — під час виконання ",[3390,9928,9929],{},"type.__new__()"," при оголошенні класу",[8792,9932,9933,9934,6034,9937,9939],{},"Результат ",[3764,9935,9936],{},"кешується",[3390,9938,9809],{}," назавжди (клас незмінний після створення)",[8792,9941,9942,9943,9946,9947,9950],{},"Кожен виклик ",[3390,9944,9945],{},"obj.method()"," використовує цей кеш — пошук методів є ",[3764,9948,9949],{},"O(n)"," по довжині MRO",[8792,9952,9953,9954,9957,9958],{},"Атрибут ",[3390,9955,9956],{},"cls.__mro__"," — це Python-обгортка навколо ",[3390,9959,9809],{},[3803,9961,9963,9964,4819,9967],{"id":9962},"як-python-шукає-метод-load_attr-та-type_getattro","Як Python шукає метод: ",[3390,9965,9966],{},"LOAD_ATTR",[3390,9968,9969],{},"type_getattro",[3399,9971,9972,9973,9975],{},"Коли Python виконує ",[3390,9974,9945],{},", відбуваються такі кроки на рівні CPython:",[3403,9977,9979],{"className":3405,"code":9978,"language":3407,"meta":3408,"style":3408},"# Ілюстрація Python-еквівалента алгоритму type_getattro\ndef type_getattro(obj, name: str):\n    \"\"\"\n    Спрощений Python-еквівалент C-функції type_getattro з CPython.\n    Саме цей алгоритм виконується при кожному доступі до атрибута.\n    \"\"\"\n    obj_type = type(obj)\n\n    # 1. Шукаємо дескриптор у MRO (перевіряємо __dict__ кожного класу)\n    meta_attribute = None\n    for base in obj_type.__mro__:\n        if name in base.__dict__:\n            meta_attribute = base.__dict__[name]\n            break\n\n    # 2. Якщо знайдений атрибут — дескриптор даних (має __set__) — він має пріоритет\n    if meta_attribute is not None and hasattr(meta_attribute, '__set__'):\n        return meta_attribute.__get__(obj, obj_type)  # виклик дескриптора\n\n    # 3. Перевіряємо власний __dict__ екземпляра\n    if name in obj.__dict__:\n        return obj.__dict__[name]\n\n    # 4. Повертаємо non-data дескриптор або просто атрибут класу\n    if meta_attribute is not None:\n        if hasattr(meta_attribute, '__get__'):\n            return meta_attribute.__get__(obj, obj_type)  # наприклад, метод\n        return meta_attribute\n\n    raise AttributeError(f\"'{obj_type.__name__}' object has no attribute '{name}'\")\n",[3390,9980,9981,9986,10008,10013,10018,10023,10027,10037,10041,10046,10054,10070,10086,10096,10101,10105,10110,10141,10157,10161,10166,10181,10191,10195,10200,10214,10227,10240,10247,10251],{"__ignoreMap":3408},[3412,9982,9983],{"class":3414,"line":3415},[3412,9984,9985],{"class":3418},"# Ілюстрація Python-еквівалента алгоритму type_getattro\n",[3412,9987,9988,9990,9993,9995,9998,10000,10002,10004,10006],{"class":3414,"line":3422},[3412,9989,9631],{"class":3425},[3412,9991,9992],{"class":3443}," type_getattro",[3412,9994,3447],{"class":3433},[3412,9996,9997],{"class":3450},"obj",[3412,9999,3454],{"class":3433},[3412,10001,6542],{"class":3450},[3412,10003,3877],{"class":3433},[3412,10005,3880],{"class":3429},[3412,10007,3893],{"class":3433},[3412,10009,10010],{"class":3414,"line":3437},[3412,10011,10012],{"class":3495},"    \"\"\"\n",[3412,10014,10015],{"class":3414,"line":3468},[3412,10016,10017],{"class":3495},"    Спрощений Python-еквівалент C-функції type_getattro з CPython.\n",[3412,10019,10020],{"class":3414,"line":3516},[3412,10021,10022],{"class":3495},"    Саме цей алгоритм виконується при кожному доступі до атрибута.\n",[3412,10024,10025],{"class":3414,"line":3550},[3412,10026,10012],{"class":3495},[3412,10028,10029,10032,10034],{"class":3414,"line":3557},[3412,10030,10031],{"class":3433},"    obj_type = ",[3412,10033,5110],{"class":3429},[3412,10035,10036],{"class":3433},"(obj)\n",[3412,10038,10039],{"class":3414,"line":3567},[3412,10040,3554],{"emptyLinePlaceholder":3553},[3412,10042,10043],{"class":3414,"line":3588},[3412,10044,10045],{"class":3418},"    # 1. Шукаємо дескриптор у MRO (перевіряємо __dict__ кожного класу)\n",[3412,10047,10048,10051],{"class":3414,"line":3628},[3412,10049,10050],{"class":3433},"    meta_attribute = ",[3412,10052,10053],{"class":3425},"None\n",[3412,10055,10056,10058,10061,10063,10066,10068],{"class":3414,"line":3655},[3412,10057,9668],{"class":3530},[3412,10059,10060],{"class":3433}," base ",[3412,10062,4744],{"class":3530},[3412,10064,10065],{"class":3433}," obj_type.",[3412,10067,5340],{"class":3450},[3412,10069,3434],{"class":3433},[3412,10071,10072,10074,10077,10079,10082,10084],{"class":3414,"line":3660},[3412,10073,9686],{"class":3530},[3412,10075,10076],{"class":3433}," name ",[3412,10078,4744],{"class":3425},[3412,10080,10081],{"class":3433}," base.",[3412,10083,3542],{"class":3450},[3412,10085,3434],{"class":3433},[3412,10087,10088,10091,10093],{"class":3414,"line":3670},[3412,10089,10090],{"class":3433},"            meta_attribute = base.",[3412,10092,3542],{"class":3450},[3412,10094,10095],{"class":3433},"[name]\n",[3412,10097,10098],{"class":3414,"line":3691},[3412,10099,10100],{"class":3530},"            break\n",[3412,10102,10103],{"class":3414,"line":3731},[3412,10104,3554],{"emptyLinePlaceholder":3553},[3412,10106,10107],{"class":3414,"line":4072},[3412,10108,10109],{"class":3418},"    # 2. Якщо знайдений атрибут — дескриптор даних (має __set__) — він має пріоритет\n",[3412,10111,10112,10115,10118,10121,10124,10127,10130,10133,10136,10139],{"class":3414,"line":4077},[3412,10113,10114],{"class":3530},"    if",[3412,10116,10117],{"class":3433}," meta_attribute ",[3412,10119,10120],{"class":3425},"is",[3412,10122,10123],{"class":3425}," not",[3412,10125,10126],{"class":3425}," None",[3412,10128,10129],{"class":3425}," and",[3412,10131,10132],{"class":3443}," hasattr",[3412,10134,10135],{"class":3433},"(meta_attribute, ",[3412,10137,10138],{"class":3495},"'__set__'",[3412,10140,3893],{"class":3433},[3412,10142,10143,10145,10148,10151,10154],{"class":3414,"line":4095},[3412,10144,3955],{"class":3530},[3412,10146,10147],{"class":3433}," meta_attribute.",[3412,10149,10150],{"class":3443},"__get__",[3412,10152,10153],{"class":3433},"(obj, obj_type)  ",[3412,10155,10156],{"class":3418},"# виклик дескриптора\n",[3412,10158,10159],{"class":3414,"line":5014},[3412,10160,3554],{"emptyLinePlaceholder":3553},[3412,10162,10163],{"class":3414,"line":5020},[3412,10164,10165],{"class":3418},"    # 3. Перевіряємо власний __dict__ екземпляра\n",[3412,10167,10168,10170,10172,10174,10177,10179],{"class":3414,"line":5026},[3412,10169,10114],{"class":3530},[3412,10171,10076],{"class":3433},[3412,10173,4744],{"class":3425},[3412,10175,10176],{"class":3433}," obj.",[3412,10178,3542],{"class":3450},[3412,10180,3434],{"class":3433},[3412,10182,10183,10185,10187,10189],{"class":3414,"line":5035},[3412,10184,3955],{"class":3530},[3412,10186,10176],{"class":3433},[3412,10188,3542],{"class":3450},[3412,10190,10095],{"class":3433},[3412,10192,10193],{"class":3414,"line":5046},[3412,10194,3554],{"emptyLinePlaceholder":3553},[3412,10196,10197],{"class":3414,"line":5057},[3412,10198,10199],{"class":3418},"    # 4. Повертаємо non-data дескриптор або просто атрибут класу\n",[3412,10201,10202,10204,10206,10208,10210,10212],{"class":3414,"line":5345},[3412,10203,10114],{"class":3530},[3412,10205,10117],{"class":3433},[3412,10207,10120],{"class":3425},[3412,10209,10123],{"class":3425},[3412,10211,10126],{"class":3425},[3412,10213,3434],{"class":3433},[3412,10215,10216,10218,10220,10222,10225],{"class":3414,"line":5364},[3412,10217,9686],{"class":3530},[3412,10219,10132],{"class":3443},[3412,10221,10135],{"class":3433},[3412,10223,10224],{"class":3495},"'__get__'",[3412,10226,3893],{"class":3433},[3412,10228,10229,10231,10233,10235,10237],{"class":3414,"line":5370},[3412,10230,9707],{"class":3530},[3412,10232,10147],{"class":3433},[3412,10234,10150],{"class":3443},[3412,10236,10153],{"class":3433},[3412,10238,10239],{"class":3418},"# наприклад, метод\n",[3412,10241,10242,10244],{"class":3414,"line":5376},[3412,10243,3955],{"class":3530},[3412,10245,10246],{"class":3433}," meta_attribute\n",[3412,10248,10249],{"class":3414,"line":5382},[3412,10250,3554],{"emptyLinePlaceholder":3553},[3412,10252,10253,10256,10259,10261,10263,10266,10268,10271,10273,10275,10278,10280,10282,10284,10287],{"class":3414,"line":5388},[3412,10254,10255],{"class":3530},"    raise",[3412,10257,10258],{"class":3429}," AttributeError",[3412,10260,3447],{"class":3433},[3412,10262,3492],{"class":3425},[3412,10264,10265],{"class":3495},"\"'",[3412,10267,3499],{"class":3425},[3412,10269,10270],{"class":3433},"obj_type.",[3412,10272,4113],{"class":3450},[3412,10274,3504],{"class":3425},[3412,10276,10277],{"class":3495},"' object has no attribute '",[3412,10279,3499],{"class":3425},[3412,10281,6542],{"class":3433},[3412,10283,3504],{"class":3425},[3412,10285,10286],{"class":3495},"'\"",[3412,10288,4069],{"class":3433},[5739,10290,10291,10292,10295,10296,10299,10300,10302,10303,10306,10307,10310,10311,10314,10315,3539],{},"Це пояснює, чому ",[3764,10293,10294],{},"функції класу доступні через екземпляр",": функція є ",[3764,10297,10298],{},"non-data дескриптором"," (має ",[3390,10301,10150],{},", але не ",[3390,10304,10305],{},"__set__","). При доступі через екземпляр викликається ",[3390,10308,10309],{},"function.__get__(obj, type)",", що повертає ",[3764,10312,10313],{},"зв'язаний метод (bound method)"," з вже підставленим ",[3390,10316,3451],{},[3803,10318,10320],{"id":10319},"дескрипторний-протокол-data-vs-non-data","Дескрипторний протокол: data vs non-data",[3399,10322,10323,10324,10326,10327,10330,10331,3454,10333,10335,10336,10339],{},"Алгоритм ",[3390,10325,9969],{}," вище згадує ",[3764,10328,10329],{},"дескриптори"," — об'єкти, що реалізують спеціальні методи ",[3390,10332,10150],{},[3390,10334,10305],{}," і\u002Fабо ",[3390,10337,10338],{},"__delete__",". Розуміння різниці між двома типами дескрипторів є ключовим для правильного трактування пріоритету атрибутів.",[5105,10341,10342,10367],{},[5108,10343,10346,10347,10350,10351,10353,10354,10357,10358,8758,10361,10364,10365,3539],{"name":10344,"type":10345},"Data Descriptor (дескриптор даних)","реалізує __get__ + __set__ (або __delete__)","Має ",[3764,10348,10349],{},"вищий пріоритет"," за атрибути екземпляра (",[3390,10352,4845],{},"). Типовий приклад — ",[3390,10355,10356],{},"property",". При записі ",[3390,10359,10360],{},"obj.attr = value",[3390,10362,10363],{},"descriptor.__set__(obj, value)",", а не безпосередній запис у ",[3390,10366,4845],{},[5108,10368,10346,10371,10374,10375,10377,10378,10381],{"name":10369,"type":10370},"Non-data Descriptor (дескриптор без запису)","реалізує лише __get__",[3764,10372,10373],{},"нижчий пріоритет"," за атрибути екземпляра. Типовий приклад — звичайна функція (метод). Якщо в ",[3390,10376,4845],{}," є атрибут з таким самим ім'ям, він ",[3764,10379,10380],{},"тіньує"," non-data дескриптор.",[3403,10383,10385],{"className":3405,"code":10384,"language":3407,"meta":3408,"style":3408},"# Ілюстрація пріоритетів дескрипторів\n\nclass DataDesc:\n    \"\"\"Data descriptor: має __get__ та __set__.\"\"\"\n    def __get__(self, obj, objtype=None):\n        return obj.__dict__.get('_data_value', 'default')\n    def __set__(self, obj, value):\n        print(f\"DataDesc.__set__({value!r})\")\n        obj.__dict__['_data_value'] = value\n\nclass NonDataDesc:\n    \"\"\"Non-data descriptor: лише __get__.\"\"\"\n    def __get__(self, obj, objtype=None):\n        return 'non_data_result'\n\nclass MyClass:\n    data_attr    = DataDesc()      # data descriptor\n    nondata_attr = NonDataDesc()   # non-data descriptor\n\nobj = MyClass()\n\n# Data descriptor: __set__ перехоплює присвоєння\nobj.data_attr = \"hello\"           # → DataDesc.__set__('hello')\nprint(obj.data_attr)              # → DataDesc.__get__ → 'hello'\n\n# Non-data descriptor: атрибут екземпляра ПЕРЕМАГАЄ\nobj.__dict__['nondata_attr'] = 'instance_value'\nprint(obj.nondata_attr)           # → 'instance_value' (не NonDataDesc!)\n\n# property — це data descriptor:\nclass Circle:\n    def __init__(self, radius):\n        self._radius = radius\n\n    @property  # data descriptor: __get__ + __set__ + __delete__\n    def radius(self):\n        return self._radius\n\n    @radius.setter\n    def radius(self, value):\n        if value \u003C 0:\n            raise ValueError(\"Радіус не може бути від'ємним\")\n        self._radius = value\n\nc = Circle(5)\nc.radius = 10     # → property.__set__ → перевірка → c._radius = 10\nprint(c.radius)   # → property.__get__ → 10\n# c.__dict__ не містить 'radius' — лише '_radius'\nprint(c.__dict__) # {'_radius': 10}\n",[3390,10386,10387,10392,10396,10405,10410,10436,10457,10479,10500,10514,10518,10527,10532,10556,10563,10567,10575,10583,10591,10595,10600,10604,10609,10620,10630,10634,10639,10657,10667,10671,10676,10685,10702,10709,10713,10722,10735,10745,10749,10754,10770,10782,10797,10804,10808,10818,10828,10838,10843],{"__ignoreMap":3408},[3412,10388,10389],{"class":3414,"line":3415},[3412,10390,10391],{"class":3418},"# Ілюстрація пріоритетів дескрипторів\n",[3412,10393,10394],{"class":3414,"line":3422},[3412,10395,3554],{"emptyLinePlaceholder":3553},[3412,10397,10398,10400,10403],{"class":3414,"line":3437},[3412,10399,3426],{"class":3425},[3412,10401,10402],{"class":3429}," DataDesc",[3412,10404,3434],{"class":3433},[3412,10406,10407],{"class":3414,"line":3468},[3412,10408,10409],{"class":3495},"    \"\"\"Data descriptor: має __get__ та __set__.\"\"\"\n",[3412,10411,10412,10414,10417,10419,10421,10423,10425,10427,10430,10432,10434],{"class":3414,"line":3516},[3412,10413,3440],{"class":3425},[3412,10415,10416],{"class":3443}," __get__",[3412,10418,3447],{"class":3433},[3412,10420,3451],{"class":3450},[3412,10422,3454],{"class":3433},[3412,10424,9997],{"class":3450},[3412,10426,3454],{"class":3433},[3412,10428,10429],{"class":3450},"objtype",[3412,10431,4691],{"class":3433},[3412,10433,4011],{"class":3425},[3412,10435,3893],{"class":3433},[3412,10437,10438,10440,10442,10444,10447,10450,10452,10455],{"class":3414,"line":3550},[3412,10439,3955],{"class":3530},[3412,10441,10176],{"class":3433},[3412,10443,3542],{"class":3450},[3412,10445,10446],{"class":3433},".get(",[3412,10448,10449],{"class":3495},"'_data_value'",[3412,10451,3454],{"class":3433},[3412,10453,10454],{"class":3495},"'default'",[3412,10456,4069],{"class":3433},[3412,10458,10459,10461,10464,10466,10468,10470,10472,10474,10477],{"class":3414,"line":3557},[3412,10460,3440],{"class":3425},[3412,10462,10463],{"class":3443}," __set__",[3412,10465,3447],{"class":3433},[3412,10467,3451],{"class":3450},[3412,10469,3454],{"class":3433},[3412,10471,9997],{"class":3450},[3412,10473,3454],{"class":3433},[3412,10475,10476],{"class":3450},"value",[3412,10478,3893],{"class":3433},[3412,10480,10481,10483,10485,10487,10490,10492,10494,10496,10498],{"class":3414,"line":3567},[3412,10482,4039],{"class":3443},[3412,10484,3447],{"class":3433},[3412,10486,3492],{"class":3425},[3412,10488,10489],{"class":3495},"\"DataDesc.__set__(",[3412,10491,3499],{"class":3425},[3412,10493,10476],{"class":3433},[3412,10495,4125],{"class":3425},[3412,10497,7559],{"class":3495},[3412,10499,4069],{"class":3433},[3412,10501,10502,10505,10507,10509,10511],{"class":3414,"line":3588},[3412,10503,10504],{"class":3433},"        obj.",[3412,10506,3542],{"class":3450},[3412,10508,9291],{"class":3433},[3412,10510,10449],{"class":3495},[3412,10512,10513],{"class":3433},"] = value\n",[3412,10515,10516],{"class":3414,"line":3628},[3412,10517,3554],{"emptyLinePlaceholder":3553},[3412,10519,10520,10522,10525],{"class":3414,"line":3655},[3412,10521,3426],{"class":3425},[3412,10523,10524],{"class":3429}," NonDataDesc",[3412,10526,3434],{"class":3433},[3412,10528,10529],{"class":3414,"line":3660},[3412,10530,10531],{"class":3495},"    \"\"\"Non-data descriptor: лише __get__.\"\"\"\n",[3412,10533,10534,10536,10538,10540,10542,10544,10546,10548,10550,10552,10554],{"class":3414,"line":3670},[3412,10535,3440],{"class":3425},[3412,10537,10416],{"class":3443},[3412,10539,3447],{"class":3433},[3412,10541,3451],{"class":3450},[3412,10543,3454],{"class":3433},[3412,10545,9997],{"class":3450},[3412,10547,3454],{"class":3433},[3412,10549,10429],{"class":3450},[3412,10551,4691],{"class":3433},[3412,10553,4011],{"class":3425},[3412,10555,3893],{"class":3433},[3412,10557,10558,10560],{"class":3414,"line":3691},[3412,10559,3955],{"class":3530},[3412,10561,10562],{"class":3495}," 'non_data_result'\n",[3412,10564,10565],{"class":3414,"line":3731},[3412,10566,3554],{"emptyLinePlaceholder":3553},[3412,10568,10569,10571,10573],{"class":3414,"line":4072},[3412,10570,3426],{"class":3425},[3412,10572,6229],{"class":3429},[3412,10574,3434],{"class":3433},[3412,10576,10577,10580],{"class":3414,"line":4077},[3412,10578,10579],{"class":3433},"    data_attr    = DataDesc()      ",[3412,10581,10582],{"class":3418},"# data descriptor\n",[3412,10584,10585,10588],{"class":3414,"line":4095},[3412,10586,10587],{"class":3433},"    nondata_attr = NonDataDesc()   ",[3412,10589,10590],{"class":3418},"# non-data descriptor\n",[3412,10592,10593],{"class":3414,"line":5014},[3412,10594,3554],{"emptyLinePlaceholder":3553},[3412,10596,10597],{"class":3414,"line":5020},[3412,10598,10599],{"class":3433},"obj = MyClass()\n",[3412,10601,10602],{"class":3414,"line":5026},[3412,10603,3554],{"emptyLinePlaceholder":3553},[3412,10605,10606],{"class":3414,"line":5035},[3412,10607,10608],{"class":3418},"# Data descriptor: __set__ перехоплює присвоєння\n",[3412,10610,10611,10614,10617],{"class":3414,"line":5046},[3412,10612,10613],{"class":3433},"obj.data_attr = ",[3412,10615,10616],{"class":3495},"\"hello\"",[3412,10618,10619],{"class":3418},"           # → DataDesc.__set__('hello')\n",[3412,10621,10622,10624,10627],{"class":3414,"line":5057},[3412,10623,3487],{"class":3443},[3412,10625,10626],{"class":3433},"(obj.data_attr)              ",[3412,10628,10629],{"class":3418},"# → DataDesc.__get__ → 'hello'\n",[3412,10631,10632],{"class":3414,"line":5345},[3412,10633,3554],{"emptyLinePlaceholder":3553},[3412,10635,10636],{"class":3414,"line":5364},[3412,10637,10638],{"class":3418},"# Non-data descriptor: атрибут екземпляра ПЕРЕМАГАЄ\n",[3412,10640,10641,10644,10646,10648,10651,10654],{"class":3414,"line":5370},[3412,10642,10643],{"class":3433},"obj.",[3412,10645,3542],{"class":3450},[3412,10647,9291],{"class":3433},[3412,10649,10650],{"class":3495},"'nondata_attr'",[3412,10652,10653],{"class":3433},"] = ",[3412,10655,10656],{"class":3495},"'instance_value'\n",[3412,10658,10659,10661,10664],{"class":3414,"line":5376},[3412,10660,3487],{"class":3443},[3412,10662,10663],{"class":3433},"(obj.nondata_attr)           ",[3412,10665,10666],{"class":3418},"# → 'instance_value' (не NonDataDesc!)\n",[3412,10668,10669],{"class":3414,"line":5382},[3412,10670,3554],{"emptyLinePlaceholder":3553},[3412,10672,10673],{"class":3414,"line":5388},[3412,10674,10675],{"class":3418},"# property — це data descriptor:\n",[3412,10677,10678,10680,10683],{"class":3414,"line":5393},[3412,10679,3426],{"class":3425},[3412,10681,10682],{"class":3429}," Circle",[3412,10684,3434],{"class":3433},[3412,10686,10687,10689,10691,10693,10695,10697,10700],{"class":3414,"line":5399},[3412,10688,3440],{"class":3425},[3412,10690,3865],{"class":3443},[3412,10692,3447],{"class":3433},[3412,10694,3451],{"class":3450},[3412,10696,3454],{"class":3433},[3412,10698,10699],{"class":3450},"radius",[3412,10701,3893],{"class":3433},[3412,10703,10704,10706],{"class":3414,"line":5422},[3412,10705,3898],{"class":3425},[3412,10707,10708],{"class":3433},"._radius = radius\n",[3412,10710,10711],{"class":3414,"line":5428},[3412,10712,3554],{"emptyLinePlaceholder":3553},[3412,10714,10715,10717,10719],{"class":3414,"line":5434},[3412,10716,6844],{"class":3443},[3412,10718,10356],{"class":3429},[3412,10720,10721],{"class":3418},"  # data descriptor: __get__ + __set__ + __delete__\n",[3412,10723,10724,10726,10729,10731,10733],{"class":3414,"line":5439},[3412,10725,3440],{"class":3425},[3412,10727,10728],{"class":3443}," radius",[3412,10730,3447],{"class":3433},[3412,10732,3451],{"class":3450},[3412,10734,3893],{"class":3433},[3412,10736,10737,10739,10742],{"class":3414,"line":5456},[3412,10738,3955],{"class":3530},[3412,10740,10741],{"class":3425}," self",[3412,10743,10744],{"class":3433},"._radius\n",[3412,10746,10747],{"class":3414,"line":5462},[3412,10748,3554],{"emptyLinePlaceholder":3553},[3412,10750,10751],{"class":3414,"line":5467},[3412,10752,10753],{"class":3443},"    @radius.setter\n",[3412,10755,10756,10758,10760,10762,10764,10766,10768],{"class":3414,"line":5473},[3412,10757,3440],{"class":3425},[3412,10759,10728],{"class":3443},[3412,10761,3447],{"class":3433},[3412,10763,3451],{"class":3450},[3412,10765,3454],{"class":3433},[3412,10767,10476],{"class":3450},[3412,10769,3893],{"class":3433},[3412,10771,10772,10774,10777,10780],{"class":3414,"line":5491},[3412,10773,9686],{"class":3530},[3412,10775,10776],{"class":3433}," value \u003C ",[3412,10778,10779],{"class":3924},"0",[3412,10781,3434],{"class":3433},[3412,10783,10784,10787,10790,10792,10795],{"class":3414,"line":5496},[3412,10785,10786],{"class":3530},"            raise",[3412,10788,10789],{"class":3429}," ValueError",[3412,10791,3447],{"class":3433},[3412,10793,10794],{"class":3495},"\"Радіус не може бути від'ємним\"",[3412,10796,4069],{"class":3433},[3412,10798,10799,10801],{"class":3414,"line":5502},[3412,10800,3898],{"class":3425},[3412,10802,10803],{"class":3433},"._radius = value\n",[3412,10805,10806],{"class":3414,"line":5518},[3412,10807,3554],{"emptyLinePlaceholder":3553},[3412,10809,10810,10813,10816],{"class":3414,"line":5533},[3412,10811,10812],{"class":3433},"c = Circle(",[3412,10814,10815],{"class":3924},"5",[3412,10817,4069],{"class":3433},[3412,10819,10820,10823,10825],{"class":3414,"line":5548},[3412,10821,10822],{"class":3433},"c.radius = ",[3412,10824,7711],{"class":3924},[3412,10826,10827],{"class":3418},"     # → property.__set__ → перевірка → c._radius = 10\n",[3412,10829,10830,10832,10835],{"class":3414,"line":5568},[3412,10831,3487],{"class":3443},[3412,10833,10834],{"class":3433},"(c.radius)   ",[3412,10836,10837],{"class":3418},"# → property.__get__ → 10\n",[3412,10839,10840],{"class":3414,"line":5573},[3412,10841,10842],{"class":3418},"# c.__dict__ не містить 'radius' — лише '_radius'\n",[3412,10844,10845,10847,10850,10852,10855],{"class":3414,"line":5579},[3412,10846,3487],{"class":3443},[3412,10848,10849],{"class":3433},"(c.",[3412,10851,3542],{"class":3450},[3412,10853,10854],{"class":3433},") ",[3412,10856,10857],{"class":3418},"# {'_radius': 10}\n",[9511,10859,10860,10863,10864,7459,10867,10870,10871,10874,10875,10877,10878,10881,10882,3539],{},[3764,10861,10862],{},"Практичне значення:"," коли ви використовуєте ",[3390,10865,10866],{},"@property",[3390,10868,10869],{},"@cached_property",", ці дескриптори ",[3764,10872,10873],{},"завжди перехоплюють"," доступ до атрибута навіть якщо в ",[3390,10876,4845],{}," є запис з тим самим ім'ям (у разі data descriptor). Саме тому присвоєння ",[3390,10879,10880],{},"obj.radius = -1"," викликає сеттер, а не записує у ",[3390,10883,3542],{},[3803,10885,10887],{"id":10886},"вимірювання-вартість-пошуку-по-mro","Вимірювання: вартість пошуку по MRO",[3403,10889,10891],{"className":3405,"code":10890,"language":3407,"meta":3408,"style":3408},"# mro_performance.py\nimport timeit\n\nclass A: pass\nclass B(A): pass\nclass C(B): pass\nclass D(C): pass\nclass E(D): pass\n\n# Метод визначено тільки в A (кінець MRO)\nclass A:\n    def method(self): return 42\n\n# Наскільки повільніше шукати метод через 4 рівні vs через 0?\ne = E()\na = A() if hasattr(A, 'method') else E()\n\n# Benchmark\nt_short = timeit.timeit(lambda: a.method(), number=1_000_000)\nt_long  = timeit.timeit(lambda: e.method(), number=1_000_000)\n\nprint(f\"Виклик через 1 рівень MRO: {t_short:.3f}с\")\nprint(f\"Виклик через 5 рівнів MRO: {t_long:.3f}с\")\nprint(f\"Різниця: {(t_long\u002Ft_short - 1)*100:.1f}%\")\n",[3390,10892,10893,10898,10905,10909,10919,10933,10947,10961,10976,10980,10985,10993,11010,11014,11019,11024,11048,11052,11057,11078,11096,11100,11124,11146],{"__ignoreMap":3408},[3412,10894,10895],{"class":3414,"line":3415},[3412,10896,10897],{"class":3418},"# mro_performance.py\n",[3412,10899,10900,10902],{"class":3414,"line":3422},[3412,10901,4154],{"class":3530},[3412,10903,10904],{"class":3433}," timeit\n",[3412,10906,10907],{"class":3414,"line":3437},[3412,10908,3554],{"emptyLinePlaceholder":3553},[3412,10910,10911,10913,10915,10917],{"class":3414,"line":3468},[3412,10912,3426],{"class":3425},[3412,10914,5814],{"class":3429},[3412,10916,3877],{"class":3433},[3412,10918,8931],{"class":3530},[3412,10920,10921,10923,10925,10927,10929,10931],{"class":3414,"line":3516},[3412,10922,3426],{"class":3425},[3412,10924,5861],{"class":3429},[3412,10926,3447],{"class":3433},[3412,10928,5866],{"class":3429},[3412,10930,3484],{"class":3433},[3412,10932,8931],{"class":3530},[3412,10934,10935,10937,10939,10941,10943,10945],{"class":3414,"line":3550},[3412,10936,3426],{"class":3425},[3412,10938,5911],{"class":3429},[3412,10940,3447],{"class":3433},[3412,10942,5965],{"class":3429},[3412,10944,3484],{"class":3433},[3412,10946,8931],{"class":3530},[3412,10948,10949,10951,10953,10955,10957,10959],{"class":3414,"line":3557},[3412,10950,3426],{"class":3425},[3412,10952,5960],{"class":3429},[3412,10954,3447],{"class":3433},[3412,10956,5970],{"class":3429},[3412,10958,3484],{"class":3433},[3412,10960,8931],{"class":3530},[3412,10962,10963,10965,10968,10970,10972,10974],{"class":3414,"line":3567},[3412,10964,3426],{"class":3425},[3412,10966,10967],{"class":3429}," E",[3412,10969,3447],{"class":3433},[3412,10971,9031],{"class":3429},[3412,10973,3484],{"class":3433},[3412,10975,8931],{"class":3530},[3412,10977,10978],{"class":3414,"line":3588},[3412,10979,3554],{"emptyLinePlaceholder":3553},[3412,10981,10982],{"class":3414,"line":3628},[3412,10983,10984],{"class":3418},"# Метод визначено тільки в A (кінець MRO)\n",[3412,10986,10987,10989,10991],{"class":3414,"line":3655},[3412,10988,3426],{"class":3425},[3412,10990,5814],{"class":3429},[3412,10992,3434],{"class":3433},[3412,10994,10995,10997,10999,11001,11003,11005,11007],{"class":3414,"line":3660},[3412,10996,3440],{"class":3425},[3412,10998,6678],{"class":3443},[3412,11000,3447],{"class":3433},[3412,11002,3451],{"class":3450},[3412,11004,3484],{"class":3433},[3412,11006,3531],{"class":3530},[3412,11008,11009],{"class":3924}," 42\n",[3412,11011,11012],{"class":3414,"line":3670},[3412,11013,3554],{"emptyLinePlaceholder":3553},[3412,11015,11016],{"class":3414,"line":3691},[3412,11017,11018],{"class":3418},"# Наскільки повільніше шукати метод через 4 рівні vs через 0?\n",[3412,11020,11021],{"class":3414,"line":3731},[3412,11022,11023],{"class":3433},"e = E()\n",[3412,11025,11026,11029,11032,11034,11037,11040,11042,11045],{"class":3414,"line":4072},[3412,11027,11028],{"class":3433},"a = A() ",[3412,11030,11031],{"class":3530},"if",[3412,11033,10132],{"class":3443},[3412,11035,11036],{"class":3433},"(A, ",[3412,11038,11039],{"class":3495},"'method'",[3412,11041,10854],{"class":3433},[3412,11043,11044],{"class":3530},"else",[3412,11046,11047],{"class":3433}," E()\n",[3412,11049,11050],{"class":3414,"line":4077},[3412,11051,3554],{"emptyLinePlaceholder":3553},[3412,11053,11054],{"class":3414,"line":4095},[3412,11055,11056],{"class":3418},"# Benchmark\n",[3412,11058,11059,11062,11065,11068,11071,11073,11076],{"class":3414,"line":5014},[3412,11060,11061],{"class":3433},"t_short = timeit.timeit(",[3412,11063,11064],{"class":3425},"lambda",[3412,11066,11067],{"class":3433},": a.method(), ",[3412,11069,11070],{"class":3450},"number",[3412,11072,4691],{"class":3433},[3412,11074,11075],{"class":3924},"1_000_000",[3412,11077,4069],{"class":3433},[3412,11079,11080,11083,11085,11088,11090,11092,11094],{"class":3414,"line":5020},[3412,11081,11082],{"class":3433},"t_long  = timeit.timeit(",[3412,11084,11064],{"class":3425},[3412,11086,11087],{"class":3433},": e.method(), ",[3412,11089,11070],{"class":3450},[3412,11091,4691],{"class":3433},[3412,11093,11075],{"class":3924},[3412,11095,4069],{"class":3433},[3412,11097,11098],{"class":3414,"line":5026},[3412,11099,3554],{"emptyLinePlaceholder":3553},[3412,11101,11102,11104,11106,11108,11111,11113,11116,11119,11122],{"class":3414,"line":5035},[3412,11103,3487],{"class":3443},[3412,11105,3447],{"class":3433},[3412,11107,3492],{"class":3425},[3412,11109,11110],{"class":3495},"\"Виклик через 1 рівень MRO: ",[3412,11112,3499],{"class":3425},[3412,11114,11115],{"class":3433},"t_short",[3412,11117,11118],{"class":3425},":.3f}",[3412,11120,11121],{"class":3495},"с\"",[3412,11123,4069],{"class":3433},[3412,11125,11126,11128,11130,11132,11135,11137,11140,11142,11144],{"class":3414,"line":5046},[3412,11127,3487],{"class":3443},[3412,11129,3447],{"class":3433},[3412,11131,3492],{"class":3425},[3412,11133,11134],{"class":3495},"\"Виклик через 5 рівнів MRO: ",[3412,11136,3499],{"class":3425},[3412,11138,11139],{"class":3433},"t_long",[3412,11141,11118],{"class":3425},[3412,11143,11121],{"class":3495},[3412,11145,4069],{"class":3433},[3412,11147,11148,11150,11152,11154,11157,11159,11162,11164,11167,11169,11171,11173],{"class":3414,"line":5057},[3412,11149,3487],{"class":3443},[3412,11151,3447],{"class":3433},[3412,11153,3492],{"class":3425},[3412,11155,11156],{"class":3495},"\"Різниця: ",[3412,11158,3499],{"class":3425},[3412,11160,11161],{"class":3433},"(t_long\u002Ft_short - ",[3412,11163,7331],{"class":3924},[3412,11165,11166],{"class":3433},")*",[3412,11168,4576],{"class":3924},[3412,11170,4063],{"class":3425},[3412,11172,4066],{"class":3495},[3412,11174,4069],{"class":3433},[4757,11176,11178,11186,11194,11202],{"title":11177},"python mro_performance.py",[4761,11179,11181,4769,11184],{"className":11180},[3414],[3412,11182,4768],{"className":11183},[4767],[3764,11185,11177],{},[4761,11187,11189,11190],{"className":11188},[3414],"Виклик через 1 рівень MRO: ",[3412,11191,11193],{"className":11192},[4800],"0.041с",[4761,11195,11197,11198],{"className":11196},[3414],"Виклик через 5 рівнів MRO: ",[3412,11199,11201],{"className":11200},[4779],"0.048с",[4761,11203,11205,11206,5684,11210],{"className":11204},[3414],"Різниця: ",[3412,11207,11209],{"className":11208},[5673],"~17%",[3412,11211,11213],{"className":11212},[5673],"# для 1 млн викликів — незначно",[3399,11215,11216,11217,11220],{},"Накладні витрати пошуку по MRO є незначними для більшості застосунків. CPython додатково використовує ",[3764,11218,11219],{},"inline cache"," (починаючи з Python 3.11) для кешування результату пошуку методу безпосередньо у байткоді — повторні виклики того самого методу фактично безкоштовні.",[3796,11222],{},[3394,11224,11226],{"id":11225},"частина-vi-liskov-substitution-principle-та-правильне-наслідування","Частина VI: Liskov Substitution Principle та правильне наслідування",[3803,11228,11230],{"id":11229},"lsp-математична-основа-правильного-is-a","LSP: математична основа правильного IS-A",[3399,11232,11233,11236,11237,11240,11241,11244,11245,11247,11248,11250],{},[3764,11234,11235],{},"Принцип підстановки Барбари Лісков (LSP)"," стверджує: якщо ",[3390,11238,11239],{},"S"," є підтипом ",[3390,11242,11243],{},"T",", то об'єкти типу ",[3390,11246,11243],{}," можна замінити об'єктами типу ",[3390,11249,11239],{}," без зміни коректності програми.",[3399,11252,11253,11254,11257],{},"Простіше: підклас повинен ",[3764,11255,11256],{},"повністю виконувати контракт"," батьківського класу.",[3403,11259,11261],{"className":3405,"code":11260,"language":3407,"meta":3408,"style":3408},"# ❌ Порушення LSP: підклас змінює очікувану поведінку\n\nclass Rectangle:\n    def __init__(self, width: float, height: float):\n        self.width = width\n        self.height = height\n\n    def area(self) -> float:\n        return self.width * self.height\n\n    def set_width(self, w: float) -> None:\n        self.width = w\n\n    def set_height(self, h: float) -> None:\n        self.height = h\n\n\nclass Square(Rectangle):\n    \"\"\"❌ НЕПРАВИЛЬНО: квадрат порушує LSP при наслідуванні від прямокутника.\"\"\"\n\n    def set_width(self, w: float) -> None:\n        # Квадрат вимушений змінювати обидва розміри одночасно —\n        # це порушує контракт Rectangle\n        self.width = w\n        self.height = w\n\n    def set_height(self, h: float) -> None:\n        self.width = h\n        self.height = h\n\n\ndef test_rectangle_contract(rect: Rectangle) -> None:\n    \"\"\"Тест, що повинен проходити для будь-якого Rectangle та його підкласів.\"\"\"\n    rect.set_width(5)\n    rect.set_height(10)\n    expected_area = 5 * 10  # 50\n    actual_area = rect.area()\n    assert actual_area == expected_area, \\\n        f\"LSP ПОРУШЕНО: очікувалось {expected_area}, отримано {actual_area}\"\n    print(f\"Контракт виконано: {actual_area}\")\n\nr = Rectangle(3, 4)\ntest_rectangle_contract(r)   # ✅ Площа: 50\n\ns = Square(3, 3)\ntest_rectangle_contract(s)   # ❌ AssertionError: LSP порушено!\n# Після set_height(10) — width теж стало 10 → area = 100, а не 50\n",[3390,11262,11263,11268,11272,11281,11311,11318,11325,11329,11346,11360,11364,11390,11397,11401,11427,11434,11438,11442,11456,11461,11465,11489,11494,11499,11505,11512,11516,11540,11547,11553,11557,11561,11580,11585,11594,11603,11618,11623,11631,11658,11679,11683,11697,11705,11709,11722,11730],{"__ignoreMap":3408},[3412,11264,11265],{"class":3414,"line":3415},[3412,11266,11267],{"class":3418},"# ❌ Порушення LSP: підклас змінює очікувану поведінку\n",[3412,11269,11270],{"class":3414,"line":3422},[3412,11271,3554],{"emptyLinePlaceholder":3553},[3412,11273,11274,11276,11279],{"class":3414,"line":3437},[3412,11275,3426],{"class":3425},[3412,11277,11278],{"class":3429}," Rectangle",[3412,11280,3434],{"class":3433},[3412,11282,11283,11285,11287,11289,11291,11293,11296,11298,11300,11302,11305,11307,11309],{"class":3414,"line":3468},[3412,11284,3440],{"class":3425},[3412,11286,3865],{"class":3443},[3412,11288,3447],{"class":3433},[3412,11290,3451],{"class":3450},[3412,11292,3454],{"class":3433},[3412,11294,11295],{"class":3450},"width",[3412,11297,3877],{"class":3433},[3412,11299,3918],{"class":3429},[3412,11301,3454],{"class":3433},[3412,11303,11304],{"class":3450},"height",[3412,11306,3877],{"class":3433},[3412,11308,3918],{"class":3429},[3412,11310,3893],{"class":3433},[3412,11312,11313,11315],{"class":3414,"line":3516},[3412,11314,3898],{"class":3425},[3412,11316,11317],{"class":3433},".width = width\n",[3412,11319,11320,11322],{"class":3414,"line":3550},[3412,11321,3898],{"class":3425},[3412,11323,11324],{"class":3433},".height = height\n",[3412,11326,11327],{"class":3414,"line":3557},[3412,11328,3554],{"emptyLinePlaceholder":3553},[3412,11330,11331,11333,11336,11338,11340,11342,11344],{"class":3414,"line":3567},[3412,11332,3440],{"class":3425},[3412,11334,11335],{"class":3443}," area",[3412,11337,3447],{"class":3433},[3412,11339,3451],{"class":3450},[3412,11341,3946],{"class":3433},[3412,11343,3918],{"class":3429},[3412,11345,3434],{"class":3433},[3412,11347,11348,11350,11352,11355,11357],{"class":3414,"line":3588},[3412,11349,3955],{"class":3530},[3412,11351,10741],{"class":3425},[3412,11353,11354],{"class":3433},".width * ",[3412,11356,3451],{"class":3425},[3412,11358,11359],{"class":3433},".height\n",[3412,11361,11362],{"class":3414,"line":3628},[3412,11363,3554],{"emptyLinePlaceholder":3553},[3412,11365,11366,11368,11371,11373,11375,11377,11380,11382,11384,11386,11388],{"class":3414,"line":3655},[3412,11367,3440],{"class":3425},[3412,11369,11370],{"class":3443}," set_width",[3412,11372,3447],{"class":3433},[3412,11374,3451],{"class":3450},[3412,11376,3454],{"class":3433},[3412,11378,11379],{"class":3450},"w",[3412,11381,3877],{"class":3433},[3412,11383,3918],{"class":3429},[3412,11385,3946],{"class":3433},[3412,11387,4011],{"class":3425},[3412,11389,3434],{"class":3433},[3412,11391,11392,11394],{"class":3414,"line":3660},[3412,11393,3898],{"class":3425},[3412,11395,11396],{"class":3433},".width = w\n",[3412,11398,11399],{"class":3414,"line":3670},[3412,11400,3554],{"emptyLinePlaceholder":3553},[3412,11402,11403,11405,11408,11410,11412,11414,11417,11419,11421,11423,11425],{"class":3414,"line":3691},[3412,11404,3440],{"class":3425},[3412,11406,11407],{"class":3443}," set_height",[3412,11409,3447],{"class":3433},[3412,11411,3451],{"class":3450},[3412,11413,3454],{"class":3433},[3412,11415,11416],{"class":3450},"h",[3412,11418,3877],{"class":3433},[3412,11420,3918],{"class":3429},[3412,11422,3946],{"class":3433},[3412,11424,4011],{"class":3425},[3412,11426,3434],{"class":3433},[3412,11428,11429,11431],{"class":3414,"line":3731},[3412,11430,3898],{"class":3425},[3412,11432,11433],{"class":3433},".height = h\n",[3412,11435,11436],{"class":3414,"line":4072},[3412,11437,3554],{"emptyLinePlaceholder":3553},[3412,11439,11440],{"class":3414,"line":4077},[3412,11441,3554],{"emptyLinePlaceholder":3553},[3412,11443,11444,11446,11449,11451,11454],{"class":3414,"line":4095},[3412,11445,3426],{"class":3425},[3412,11447,11448],{"class":3429}," Square",[3412,11450,3447],{"class":3433},[3412,11452,11453],{"class":3429},"Rectangle",[3412,11455,3893],{"class":3433},[3412,11457,11458],{"class":3414,"line":5014},[3412,11459,11460],{"class":3495},"    \"\"\"❌ НЕПРАВИЛЬНО: квадрат порушує LSP при наслідуванні від прямокутника.\"\"\"\n",[3412,11462,11463],{"class":3414,"line":5020},[3412,11464,3554],{"emptyLinePlaceholder":3553},[3412,11466,11467,11469,11471,11473,11475,11477,11479,11481,11483,11485,11487],{"class":3414,"line":5026},[3412,11468,3440],{"class":3425},[3412,11470,11370],{"class":3443},[3412,11472,3447],{"class":3433},[3412,11474,3451],{"class":3450},[3412,11476,3454],{"class":3433},[3412,11478,11379],{"class":3450},[3412,11480,3877],{"class":3433},[3412,11482,3918],{"class":3429},[3412,11484,3946],{"class":3433},[3412,11486,4011],{"class":3425},[3412,11488,3434],{"class":3433},[3412,11490,11491],{"class":3414,"line":5035},[3412,11492,11493],{"class":3418},"        # Квадрат вимушений змінювати обидва розміри одночасно —\n",[3412,11495,11496],{"class":3414,"line":5046},[3412,11497,11498],{"class":3418},"        # це порушує контракт Rectangle\n",[3412,11500,11501,11503],{"class":3414,"line":5057},[3412,11502,3898],{"class":3425},[3412,11504,11396],{"class":3433},[3412,11506,11507,11509],{"class":3414,"line":5345},[3412,11508,3898],{"class":3425},[3412,11510,11511],{"class":3433},".height = w\n",[3412,11513,11514],{"class":3414,"line":5364},[3412,11515,3554],{"emptyLinePlaceholder":3553},[3412,11517,11518,11520,11522,11524,11526,11528,11530,11532,11534,11536,11538],{"class":3414,"line":5370},[3412,11519,3440],{"class":3425},[3412,11521,11407],{"class":3443},[3412,11523,3447],{"class":3433},[3412,11525,3451],{"class":3450},[3412,11527,3454],{"class":3433},[3412,11529,11416],{"class":3450},[3412,11531,3877],{"class":3433},[3412,11533,3918],{"class":3429},[3412,11535,3946],{"class":3433},[3412,11537,4011],{"class":3425},[3412,11539,3434],{"class":3433},[3412,11541,11542,11544],{"class":3414,"line":5376},[3412,11543,3898],{"class":3425},[3412,11545,11546],{"class":3433},".width = h\n",[3412,11548,11549,11551],{"class":3414,"line":5382},[3412,11550,3898],{"class":3425},[3412,11552,11433],{"class":3433},[3412,11554,11555],{"class":3414,"line":5388},[3412,11556,3554],{"emptyLinePlaceholder":3553},[3412,11558,11559],{"class":3414,"line":5393},[3412,11560,3554],{"emptyLinePlaceholder":3553},[3412,11562,11563,11565,11568,11570,11573,11576,11578],{"class":3414,"line":5399},[3412,11564,9631],{"class":3425},[3412,11566,11567],{"class":3443}," test_rectangle_contract",[3412,11569,3447],{"class":3433},[3412,11571,11572],{"class":3450},"rect",[3412,11574,11575],{"class":3433},": Rectangle) -> ",[3412,11577,4011],{"class":3425},[3412,11579,3434],{"class":3433},[3412,11581,11582],{"class":3414,"line":5422},[3412,11583,11584],{"class":3495},"    \"\"\"Тест, що повинен проходити для будь-якого Rectangle та його підкласів.\"\"\"\n",[3412,11586,11587,11590,11592],{"class":3414,"line":5428},[3412,11588,11589],{"class":3433},"    rect.set_width(",[3412,11591,10815],{"class":3924},[3412,11593,4069],{"class":3433},[3412,11595,11596,11599,11601],{"class":3414,"line":5434},[3412,11597,11598],{"class":3433},"    rect.set_height(",[3412,11600,7711],{"class":3924},[3412,11602,4069],{"class":3433},[3412,11604,11605,11608,11610,11613,11615],{"class":3414,"line":5439},[3412,11606,11607],{"class":3433},"    expected_area = ",[3412,11609,10815],{"class":3924},[3412,11611,11612],{"class":3433}," * ",[3412,11614,7711],{"class":3924},[3412,11616,11617],{"class":3418},"  # 50\n",[3412,11619,11620],{"class":3414,"line":5456},[3412,11621,11622],{"class":3433},"    actual_area = rect.area()\n",[3412,11624,11625,11628],{"class":3414,"line":5462},[3412,11626,11627],{"class":3530},"    assert",[3412,11629,11630],{"class":3433}," actual_area == expected_area, \\\n",[3412,11632,11633,11636,11639,11641,11644,11646,11649,11651,11654,11656],{"class":3414,"line":5467},[3412,11634,11635],{"class":3425},"        f",[3412,11637,11638],{"class":3495},"\"LSP ПОРУШЕНО: очікувалось ",[3412,11640,3499],{"class":3425},[3412,11642,11643],{"class":3433},"expected_area",[3412,11645,3504],{"class":3425},[3412,11647,11648],{"class":3495},", отримано ",[3412,11650,3499],{"class":3425},[3412,11652,11653],{"class":3433},"actual_area",[3412,11655,3504],{"class":3425},[3412,11657,8434],{"class":3495},[3412,11659,11660,11662,11664,11666,11669,11671,11673,11675,11677],{"class":3414,"line":5473},[3412,11661,4752],{"class":3443},[3412,11663,3447],{"class":3433},[3412,11665,3492],{"class":3425},[3412,11667,11668],{"class":3495},"\"Контракт виконано: ",[3412,11670,3499],{"class":3425},[3412,11672,11653],{"class":3433},[3412,11674,3504],{"class":3425},[3412,11676,3507],{"class":3495},[3412,11678,4069],{"class":3433},[3412,11680,11681],{"class":3414,"line":5491},[3412,11682,3554],{"emptyLinePlaceholder":3553},[3412,11684,11685,11688,11691,11693,11695],{"class":3414,"line":5496},[3412,11686,11687],{"class":3433},"r = Rectangle(",[3412,11689,11690],{"class":3924},"3",[3412,11692,3454],{"class":3433},[3412,11694,4224],{"class":3924},[3412,11696,4069],{"class":3433},[3412,11698,11699,11702],{"class":3414,"line":5502},[3412,11700,11701],{"class":3433},"test_rectangle_contract(r)   ",[3412,11703,11704],{"class":3418},"# ✅ Площа: 50\n",[3412,11706,11707],{"class":3414,"line":5518},[3412,11708,3554],{"emptyLinePlaceholder":3553},[3412,11710,11711,11714,11716,11718,11720],{"class":3414,"line":5533},[3412,11712,11713],{"class":3433},"s = Square(",[3412,11715,11690],{"class":3924},[3412,11717,3454],{"class":3433},[3412,11719,11690],{"class":3924},[3412,11721,4069],{"class":3433},[3412,11723,11724,11727],{"class":3414,"line":5548},[3412,11725,11726],{"class":3433},"test_rectangle_contract(s)   ",[3412,11728,11729],{"class":3418},"# ❌ AssertionError: LSP порушено!\n",[3412,11731,11732],{"class":3414,"line":5568},[3412,11733,11734],{"class":3418},"# Після set_height(10) — width теж стало 10 → area = 100, а не 50\n",[4757,11736,11738,11747,11755],{"title":11737},"Демонстрація порушення LSP",[4761,11739,11741,4769,11744],{"className":11740},[3414],[3412,11742,4768],{"className":11743},[4767],[3764,11745,11746],{},"python lsp_demo.py",[4761,11748,11750,11751],{"className":11749},[3414],"Контракт виконано: ",[3412,11752,11754],{"className":11753},[4800],"50",[4761,11756,11758],{"className":11757},[3414],[3412,11759,11761],{"className":11760},[5732],"AssertionError: LSP ПОРУШЕНО: очікувалось 50, отримано 100",[3399,11763,11764,8730,11767,11770,11771,11773,11774,11777],{},[3764,11765,11766],{},"Правильне вирішення:",[3390,11768,11769],{},"Square"," не може повністю виконати контракт ",[3390,11772,11453],{},", вони не повинні бути у відношенні IS-A. Обидва можуть наслідувати від спільного абстрактного класу ",[3390,11775,11776],{},"Shape",", який не нав'язує незалежні сеттери розмірів.",[3403,11779,11781],{"className":3405,"code":11780,"language":3407,"meta":3408,"style":3408},"# ✅ Правильна архітектура: спільний предок без порушення контракту\nimport math\nfrom abc import ABC, abstractmethod\n\nclass Shape(ABC):\n    \"\"\"Абстрактна фігура: спільний предок Rectangle та Square.\"\"\"\n\n    @abstractmethod\n    def area(self) -> float: ...\n\n    @abstractmethod\n    def perimeter(self) -> float: ...\n\n\nclass Rectangle(Shape):\n    \"\"\"Прямокутник: незалежні ширина та висота.\"\"\"\n\n    def __init__(self, width: float, height: float):\n        self.width  = width\n        self.height = height\n\n    def area(self)      -> float: return self.width * self.height\n    def perimeter(self) -> float: return 2 * (self.width + self.height)\n\n\nclass Square(Shape):\n    \"\"\"\n    Квадрат: єдина сторона.\n    НЕ наслідує Rectangle — вони обидва Shape, але не пов'язані IS-A.\n    \"\"\"\n\n    def __init__(self, side: float):\n        self.side = side\n\n    def area(self)      -> float: return self.side ** 2\n    def perimeter(self) -> float: return 4 * self.side\n\n\n# Тепер LSP виконується для обох:\ndef print_shape_info(shape: Shape) -> None:\n    print(f\"{type(shape).__name__}: area={shape.area():.2f}, perimeter={shape.perimeter():.2f}\")\n\nfor s in [Rectangle(4, 6), Square(5)]:\n    print_shape_info(s)   # ✅ Будь-який Shape працює коректно\n",[3390,11782,11783,11788,11795,11807,11811,11821,11826,11830,11835,11852,11856,11860,11877,11881,11885,11897,11902,11906,11934,11941,11947,11951,11978,12012,12016,12020,12032,12036,12041,12046,12050,12054,12075,12082,12086,12111,12139,12143,12147,12152,12171,12217,12221,12248],{"__ignoreMap":3408},[3412,11784,11785],{"class":3414,"line":3415},[3412,11786,11787],{"class":3418},"# ✅ Правильна архітектура: спільний предок без порушення контракту\n",[3412,11789,11790,11792],{"class":3414,"line":3422},[3412,11791,4154],{"class":3530},[3412,11793,11794],{"class":3433}," math\n",[3412,11796,11797,11799,11802,11804],{"class":3414,"line":3437},[3412,11798,4148],{"class":3530},[3412,11800,11801],{"class":3433}," abc ",[3412,11803,4154],{"class":3530},[3412,11805,11806],{"class":3433}," ABC, abstractmethod\n",[3412,11808,11809],{"class":3414,"line":3468},[3412,11810,3554],{"emptyLinePlaceholder":3553},[3412,11812,11813,11815,11818],{"class":3414,"line":3516},[3412,11814,3426],{"class":3425},[3412,11816,11817],{"class":3429}," Shape",[3412,11819,11820],{"class":3433},"(ABC):\n",[3412,11822,11823],{"class":3414,"line":3550},[3412,11824,11825],{"class":3495},"    \"\"\"Абстрактна фігура: спільний предок Rectangle та Square.\"\"\"\n",[3412,11827,11828],{"class":3414,"line":3557},[3412,11829,3554],{"emptyLinePlaceholder":3553},[3412,11831,11832],{"class":3414,"line":3567},[3412,11833,11834],{"class":3443},"    @abstractmethod\n",[3412,11836,11837,11839,11841,11843,11845,11847,11849],{"class":3414,"line":3588},[3412,11838,3440],{"class":3425},[3412,11840,11335],{"class":3443},[3412,11842,3447],{"class":3433},[3412,11844,3451],{"class":3450},[3412,11846,3946],{"class":3433},[3412,11848,3918],{"class":3429},[3412,11850,11851],{"class":3433},": ...\n",[3412,11853,11854],{"class":3414,"line":3628},[3412,11855,3554],{"emptyLinePlaceholder":3553},[3412,11857,11858],{"class":3414,"line":3655},[3412,11859,11834],{"class":3443},[3412,11861,11862,11864,11867,11869,11871,11873,11875],{"class":3414,"line":3660},[3412,11863,3440],{"class":3425},[3412,11865,11866],{"class":3443}," perimeter",[3412,11868,3447],{"class":3433},[3412,11870,3451],{"class":3450},[3412,11872,3946],{"class":3433},[3412,11874,3918],{"class":3429},[3412,11876,11851],{"class":3433},[3412,11878,11879],{"class":3414,"line":3670},[3412,11880,3554],{"emptyLinePlaceholder":3553},[3412,11882,11883],{"class":3414,"line":3691},[3412,11884,3554],{"emptyLinePlaceholder":3553},[3412,11886,11887,11889,11891,11893,11895],{"class":3414,"line":3731},[3412,11888,3426],{"class":3425},[3412,11890,11278],{"class":3429},[3412,11892,3447],{"class":3433},[3412,11894,11776],{"class":3429},[3412,11896,3893],{"class":3433},[3412,11898,11899],{"class":3414,"line":4072},[3412,11900,11901],{"class":3495},"    \"\"\"Прямокутник: незалежні ширина та висота.\"\"\"\n",[3412,11903,11904],{"class":3414,"line":4077},[3412,11905,3554],{"emptyLinePlaceholder":3553},[3412,11907,11908,11910,11912,11914,11916,11918,11920,11922,11924,11926,11928,11930,11932],{"class":3414,"line":4095},[3412,11909,3440],{"class":3425},[3412,11911,3865],{"class":3443},[3412,11913,3447],{"class":3433},[3412,11915,3451],{"class":3450},[3412,11917,3454],{"class":3433},[3412,11919,11295],{"class":3450},[3412,11921,3877],{"class":3433},[3412,11923,3918],{"class":3429},[3412,11925,3454],{"class":3433},[3412,11927,11304],{"class":3450},[3412,11929,3877],{"class":3433},[3412,11931,3918],{"class":3429},[3412,11933,3893],{"class":3433},[3412,11935,11936,11938],{"class":3414,"line":5014},[3412,11937,3898],{"class":3425},[3412,11939,11940],{"class":3433},".width  = width\n",[3412,11942,11943,11945],{"class":3414,"line":5020},[3412,11944,3898],{"class":3425},[3412,11946,11324],{"class":3433},[3412,11948,11949],{"class":3414,"line":5026},[3412,11950,3554],{"emptyLinePlaceholder":3553},[3412,11952,11953,11955,11957,11959,11961,11964,11966,11968,11970,11972,11974,11976],{"class":3414,"line":5035},[3412,11954,3440],{"class":3425},[3412,11956,11335],{"class":3443},[3412,11958,3447],{"class":3433},[3412,11960,3451],{"class":3450},[3412,11962,11963],{"class":3433},")      -> ",[3412,11965,3918],{"class":3429},[3412,11967,3877],{"class":3433},[3412,11969,3531],{"class":3530},[3412,11971,10741],{"class":3425},[3412,11973,11354],{"class":3433},[3412,11975,3451],{"class":3425},[3412,11977,11359],{"class":3433},[3412,11979,11980,11982,11984,11986,11988,11990,11992,11994,11996,11999,12002,12004,12007,12009],{"class":3414,"line":5046},[3412,11981,3440],{"class":3425},[3412,11983,11866],{"class":3443},[3412,11985,3447],{"class":3433},[3412,11987,3451],{"class":3450},[3412,11989,3946],{"class":3433},[3412,11991,3918],{"class":3429},[3412,11993,3877],{"class":3433},[3412,11995,3531],{"class":3530},[3412,11997,11998],{"class":3924}," 2",[3412,12000,12001],{"class":3433}," * (",[3412,12003,3451],{"class":3425},[3412,12005,12006],{"class":3433},".width + ",[3412,12008,3451],{"class":3425},[3412,12010,12011],{"class":3433},".height)\n",[3412,12013,12014],{"class":3414,"line":5057},[3412,12015,3554],{"emptyLinePlaceholder":3553},[3412,12017,12018],{"class":3414,"line":5345},[3412,12019,3554],{"emptyLinePlaceholder":3553},[3412,12021,12022,12024,12026,12028,12030],{"class":3414,"line":5364},[3412,12023,3426],{"class":3425},[3412,12025,11448],{"class":3429},[3412,12027,3447],{"class":3433},[3412,12029,11776],{"class":3429},[3412,12031,3893],{"class":3433},[3412,12033,12034],{"class":3414,"line":5370},[3412,12035,10012],{"class":3495},[3412,12037,12038],{"class":3414,"line":5376},[3412,12039,12040],{"class":3495},"    Квадрат: єдина сторона.\n",[3412,12042,12043],{"class":3414,"line":5382},[3412,12044,12045],{"class":3495},"    НЕ наслідує Rectangle — вони обидва Shape, але не пов'язані IS-A.\n",[3412,12047,12048],{"class":3414,"line":5388},[3412,12049,10012],{"class":3495},[3412,12051,12052],{"class":3414,"line":5393},[3412,12053,3554],{"emptyLinePlaceholder":3553},[3412,12055,12056,12058,12060,12062,12064,12066,12069,12071,12073],{"class":3414,"line":5399},[3412,12057,3440],{"class":3425},[3412,12059,3865],{"class":3443},[3412,12061,3447],{"class":3433},[3412,12063,3451],{"class":3450},[3412,12065,3454],{"class":3433},[3412,12067,12068],{"class":3450},"side",[3412,12070,3877],{"class":3433},[3412,12072,3918],{"class":3429},[3412,12074,3893],{"class":3433},[3412,12076,12077,12079],{"class":3414,"line":5422},[3412,12078,3898],{"class":3425},[3412,12080,12081],{"class":3433},".side = side\n",[3412,12083,12084],{"class":3414,"line":5428},[3412,12085,3554],{"emptyLinePlaceholder":3553},[3412,12087,12088,12090,12092,12094,12096,12098,12100,12102,12104,12106,12109],{"class":3414,"line":5434},[3412,12089,3440],{"class":3425},[3412,12091,11335],{"class":3443},[3412,12093,3447],{"class":3433},[3412,12095,3451],{"class":3450},[3412,12097,11963],{"class":3433},[3412,12099,3918],{"class":3429},[3412,12101,3877],{"class":3433},[3412,12103,3531],{"class":3530},[3412,12105,10741],{"class":3425},[3412,12107,12108],{"class":3433},".side ** ",[3412,12110,5032],{"class":3924},[3412,12112,12113,12115,12117,12119,12121,12123,12125,12127,12129,12132,12134,12136],{"class":3414,"line":5439},[3412,12114,3440],{"class":3425},[3412,12116,11866],{"class":3443},[3412,12118,3447],{"class":3433},[3412,12120,3451],{"class":3450},[3412,12122,3946],{"class":3433},[3412,12124,3918],{"class":3429},[3412,12126,3877],{"class":3433},[3412,12128,3531],{"class":3530},[3412,12130,12131],{"class":3924}," 4",[3412,12133,11612],{"class":3433},[3412,12135,3451],{"class":3425},[3412,12137,12138],{"class":3433},".side\n",[3412,12140,12141],{"class":3414,"line":5456},[3412,12142,3554],{"emptyLinePlaceholder":3553},[3412,12144,12145],{"class":3414,"line":5462},[3412,12146,3554],{"emptyLinePlaceholder":3553},[3412,12148,12149],{"class":3414,"line":5467},[3412,12150,12151],{"class":3418},"# Тепер LSP виконується для обох:\n",[3412,12153,12154,12156,12159,12161,12164,12167,12169],{"class":3414,"line":5473},[3412,12155,9631],{"class":3425},[3412,12157,12158],{"class":3443}," print_shape_info",[3412,12160,3447],{"class":3433},[3412,12162,12163],{"class":3450},"shape",[3412,12165,12166],{"class":3433},": Shape) -> ",[3412,12168,4011],{"class":3425},[3412,12170,3434],{"class":3433},[3412,12172,12173,12175,12177,12179,12181,12183,12185,12188,12190,12192,12195,12197,12200,12203,12206,12208,12211,12213,12215],{"class":3414,"line":5491},[3412,12174,4752],{"class":3443},[3412,12176,3447],{"class":3433},[3412,12178,3492],{"class":3425},[3412,12180,3507],{"class":3495},[3412,12182,3499],{"class":3425},[3412,12184,5110],{"class":3429},[3412,12186,12187],{"class":3433},"(shape).",[3412,12189,4113],{"class":3450},[3412,12191,3504],{"class":3425},[3412,12193,12194],{"class":3495},": area=",[3412,12196,3499],{"class":3425},[3412,12198,12199],{"class":3433},"shape.area()",[3412,12201,12202],{"class":3425},":.2f}",[3412,12204,12205],{"class":3495},", perimeter=",[3412,12207,3499],{"class":3425},[3412,12209,12210],{"class":3433},"shape.perimeter()",[3412,12212,12202],{"class":3425},[3412,12214,3507],{"class":3495},[3412,12216,4069],{"class":3433},[3412,12218,12219],{"class":3414,"line":5496},[3412,12220,3554],{"emptyLinePlaceholder":3553},[3412,12222,12223,12225,12228,12230,12233,12235,12237,12240,12243,12245],{"class":3414,"line":5502},[3412,12224,4738],{"class":3530},[3412,12226,12227],{"class":3433}," s ",[3412,12229,4744],{"class":3530},[3412,12231,12232],{"class":3433}," [Rectangle(",[3412,12234,4224],{"class":3924},[3412,12236,3454],{"class":3433},[3412,12238,12239],{"class":3924},"6",[3412,12241,12242],{"class":3433},"), Square(",[3412,12244,10815],{"class":3924},[3412,12246,12247],{"class":3433},")]:\n",[3412,12249,12250,12253],{"class":3414,"line":5518},[3412,12251,12252],{"class":3433},"    print_shape_info(s)   ",[3412,12254,12255],{"class":3418},"# ✅ Будь-який Shape працює коректно\n",[5739,12257,12258,12261,12262,12265],{},[3764,12259,12260],{},"Спільний патерн вирішення LSP-конфліктів:"," якщо підклас вимушений звужувати (обмежувати) поведінку батька або змінювати семантику його методів — винесіть спільну поведінку у ",[3764,12263,12264],{},"нейтральний абстрактний клас",", від якого обидва класи наслідують незалежно. Це зберігає поліморфізм без порушення контракту.",[12267,12268,12269,12272,12273,12276,12277,12280,12281,12283],"caution",{},[3764,12270,12271],{},"LSP — це ваш критерій правильності наслідування."," Перед тим як написати ",[3390,12274,12275],{},"class Child(Parent)",", запитайте: «Чи можу я замінити кожне використання ",[3390,12278,12279],{},"Parent"," на ",[3390,12282,6669],{}," без зміни коректності програми?» Якщо ні — переходьте до композиції або перегляньте ієрархію абстракцій.",[3796,12285],{},[3394,12287,12289],{"id":12288},"частина-vii-патерн-mixins-класи-домішки","Частина VII: Патерн Mixins (Класи-домішки)",[3803,12291,12293],{"id":12292},"концепція-та-правила-проектування","Концепція та правила проектування",[12295,12296,12297],"blockquote",{},[3399,12298,12299,12300,12302,12303,12306],{},"Принципове розмежування: ",[3764,12301,3766],{}," моделює онтологічні відносини предметної галузі (IS-A), тоді як ",[3764,12304,12305],{},"Mixins"," є технічним механізмом горизонтального перевикористання коду, що не несе семантичного навантаження.",[3399,12308,12309,12312,12313,12316],{},[3764,12310,12311],{},"Mixin (домішка)"," — це клас, що призначений виключно для ",[3764,12314,12315],{},"горизонтального"," розподілу поведінки між непов'язаними класами. Він не моделює сутність (не є окремою концепцією предметної галузі), а надає набір функцій.",[6055,12318,12319],{},[3403,12320,12322],{"className":6059,"code":12321,"language":6061,"meta":3408,"style":3408},"@startuml\nskinparam style plain\nskinparam backgroundColor #ffffff\nskinparam ArrowColor #6366f1\nskinparam class {\n    BackgroundColor #f3f4f6\n    BorderColor #d1d5db\n}\n\nclass JSONSerializableMixin #d1fae5 {\n    + to_json() : str\n    + from_json(s: str) : Self\n}\n\nclass LoggingMixin #fef3c7 {\n    + log(msg: str) : void\n    + log_error(err: Exception) : void\n}\n\nclass ValidationMixin #dbeafe {\n    + validate() : bool\n    + assert_valid() : void\n}\n\nclass Product #f3f4f6 {\n    + name : str\n    + price : float\n}\n\nclass User #f3f4f6 {\n    + username : str\n    + email : str\n}\n\nclass Order #f3f4f6 {\n    + order_id : str\n    + items : list\n}\n\nProduct --|> JSONSerializableMixin\nProduct --|> LoggingMixin\nProduct --|> ValidationMixin\n\nUser --|> JSONSerializableMixin\nUser --|> LoggingMixin\n\nOrder --|> JSONSerializableMixin\nOrder --|> ValidationMixin\n\nnote bottom\n  Mixins — горизонтальне розширення.\n  Один Mixin використовується у\n  непов'язаних класах без успадкування\n  реального IS-A відношення.\nend note\n@enduml\n",[3390,12323,12324,12328,12332,12336,12340,12344,12348,12352,12356,12360,12365,12370,12375,12379,12383,12388,12393,12398,12402,12406,12411,12416,12421,12425,12429,12434,12439,12444,12448,12452,12457,12462,12467,12471,12475,12480,12485,12490,12494,12498,12503,12508,12513,12517,12522,12527,12531,12536,12541,12545,12550,12555,12560,12565,12570,12574],{"__ignoreMap":3408},[3412,12325,12326],{"class":3414,"line":3415},[3412,12327,6068],{},[3412,12329,12330],{"class":3414,"line":3422},[3412,12331,6073],{},[3412,12333,12334],{"class":3414,"line":3437},[3412,12335,6078],{},[3412,12337,12338],{"class":3414,"line":3468},[3412,12339,6083],{},[3412,12341,12342],{"class":3414,"line":3516},[3412,12343,8190],{},[3412,12345,12346],{"class":3414,"line":3550},[3412,12347,8195],{},[3412,12349,12350],{"class":3414,"line":3557},[3412,12351,8200],{},[3412,12353,12354],{"class":3414,"line":3567},[3412,12355,6108],{},[3412,12357,12358],{"class":3414,"line":3588},[3412,12359,3554],{"emptyLinePlaceholder":3553},[3412,12361,12362],{"class":3414,"line":3628},[3412,12363,12364],{},"class JSONSerializableMixin #d1fae5 {\n",[3412,12366,12367],{"class":3414,"line":3655},[3412,12368,12369],{},"    + to_json() : str\n",[3412,12371,12372],{"class":3414,"line":3660},[3412,12373,12374],{},"    + from_json(s: str) : Self\n",[3412,12376,12377],{"class":3414,"line":3670},[3412,12378,6108],{},[3412,12380,12381],{"class":3414,"line":3691},[3412,12382,3554],{"emptyLinePlaceholder":3553},[3412,12384,12385],{"class":3414,"line":3731},[3412,12386,12387],{},"class LoggingMixin #fef3c7 {\n",[3412,12389,12390],{"class":3414,"line":4072},[3412,12391,12392],{},"    + log(msg: str) : void\n",[3412,12394,12395],{"class":3414,"line":4077},[3412,12396,12397],{},"    + log_error(err: Exception) : void\n",[3412,12399,12400],{"class":3414,"line":4095},[3412,12401,6108],{},[3412,12403,12404],{"class":3414,"line":5014},[3412,12405,3554],{"emptyLinePlaceholder":3553},[3412,12407,12408],{"class":3414,"line":5020},[3412,12409,12410],{},"class ValidationMixin #dbeafe {\n",[3412,12412,12413],{"class":3414,"line":5026},[3412,12414,12415],{},"    + validate() : bool\n",[3412,12417,12418],{"class":3414,"line":5035},[3412,12419,12420],{},"    + assert_valid() : void\n",[3412,12422,12423],{"class":3414,"line":5046},[3412,12424,6108],{},[3412,12426,12427],{"class":3414,"line":5057},[3412,12428,3554],{"emptyLinePlaceholder":3553},[3412,12430,12431],{"class":3414,"line":5345},[3412,12432,12433],{},"class Product #f3f4f6 {\n",[3412,12435,12436],{"class":3414,"line":5364},[3412,12437,12438],{},"    + name : str\n",[3412,12440,12441],{"class":3414,"line":5370},[3412,12442,12443],{},"    + price : float\n",[3412,12445,12446],{"class":3414,"line":5376},[3412,12447,6108],{},[3412,12449,12450],{"class":3414,"line":5382},[3412,12451,3554],{"emptyLinePlaceholder":3553},[3412,12453,12454],{"class":3414,"line":5388},[3412,12455,12456],{},"class User #f3f4f6 {\n",[3412,12458,12459],{"class":3414,"line":5393},[3412,12460,12461],{},"    + username : str\n",[3412,12463,12464],{"class":3414,"line":5399},[3412,12465,12466],{},"    + email : str\n",[3412,12468,12469],{"class":3414,"line":5422},[3412,12470,6108],{},[3412,12472,12473],{"class":3414,"line":5428},[3412,12474,3554],{"emptyLinePlaceholder":3553},[3412,12476,12477],{"class":3414,"line":5434},[3412,12478,12479],{},"class Order #f3f4f6 {\n",[3412,12481,12482],{"class":3414,"line":5439},[3412,12483,12484],{},"    + order_id : str\n",[3412,12486,12487],{"class":3414,"line":5456},[3412,12488,12489],{},"    + items : list\n",[3412,12491,12492],{"class":3414,"line":5462},[3412,12493,6108],{},[3412,12495,12496],{"class":3414,"line":5467},[3412,12497,3554],{"emptyLinePlaceholder":3553},[3412,12499,12500],{"class":3414,"line":5473},[3412,12501,12502],{},"Product --|> JSONSerializableMixin\n",[3412,12504,12505],{"class":3414,"line":5491},[3412,12506,12507],{},"Product --|> LoggingMixin\n",[3412,12509,12510],{"class":3414,"line":5496},[3412,12511,12512],{},"Product --|> ValidationMixin\n",[3412,12514,12515],{"class":3414,"line":5502},[3412,12516,3554],{"emptyLinePlaceholder":3553},[3412,12518,12519],{"class":3414,"line":5518},[3412,12520,12521],{},"User --|> JSONSerializableMixin\n",[3412,12523,12524],{"class":3414,"line":5533},[3412,12525,12526],{},"User --|> LoggingMixin\n",[3412,12528,12529],{"class":3414,"line":5548},[3412,12530,3554],{"emptyLinePlaceholder":3553},[3412,12532,12533],{"class":3414,"line":5568},[3412,12534,12535],{},"Order --|> JSONSerializableMixin\n",[3412,12537,12538],{"class":3414,"line":5573},[3412,12539,12540],{},"Order --|> ValidationMixin\n",[3412,12542,12543],{"class":3414,"line":5579},[3412,12544,3554],{"emptyLinePlaceholder":3553},[3412,12546,12547],{"class":3414,"line":5595},[3412,12548,12549],{},"note bottom\n",[3412,12551,12552],{"class":3414,"line":5609},[3412,12553,12554],{},"  Mixins — горизонтальне розширення.\n",[3412,12556,12557],{"class":3414,"line":7232},[3412,12558,12559],{},"  Один Mixin використовується у\n",[3412,12561,12562],{"class":3414,"line":7261},[3412,12563,12564],{},"  непов'язаних класах без успадкування\n",[3412,12566,12567],{"class":3414,"line":7266},[3412,12568,12569],{},"  реального IS-A відношення.\n",[3412,12571,12572],{"class":3414,"line":7271},[3412,12573,6171],{},[3412,12575,12576],{"class":3414,"line":7277},[3412,12577,6191],{},[3769,12579,12580,12598,12603,12612],{},[3772,12581,12584,12585,12587,12588,12590,12591,12593,12594,12597],{"icon":12582,"title":12583},"i-heroicons-circle-stack","Без власного стану","Домішка не повинна зберігати стан у власному ",[3390,12586,4242],{},". Якщо ",[3390,12589,4242],{}," потрібен — він ",[3764,12592,8145],{}," має викликати ",[3390,12595,12596],{},"super().__init__(*args, **kwargs)",", щоб не обривати ланцюжок MRO.",[3772,12599,12602],{"icon":12600,"title":12601},"i-heroicons-check-badge","Single Responsibility","Кожна домішка вирішує одну задачу: серіалізація, логування, валідація, авторизація. Одна домішка — одна відповідальність.",[3772,12604,12607,12608,12611],{"icon":12605,"title":12606},"i-heroicons-arrows-right-left","Порядок у наслідуванні","Домішки вказуються ",[3764,12609,12610],{},"ліворуч"," від основного базового класу. Це забезпечує їм вищий пріоритет у MRO — їхні методи перехоплюють виклики першими.",[3772,12613,12616,12617,12620],{"icon":12614,"title":12615},"i-heroicons-tag","Суфікс 'Mixin'","Назва домішки має закінчуватися на ",[3390,12618,12619],{},"Mixin",". Це конвенція, що сигналізує читачу коду: «цей клас не призначений для самостійного використання».",[3803,12622,12624],{"id":12623},"практична-реалізація-повноцінний-набір-домішок","Практична реалізація: повноцінний набір домішок",[3403,12626,12628],{"className":3405,"code":12627,"language":3407,"meta":3408,"style":3408},"# mixins.py\nimport json\nimport hashlib\nfrom datetime import datetime\nfrom typing import Any\n\n\nclass JSONSerializableMixin:\n    \"\"\"\n    Домішка для автоматичної JSON-серіалізації та десеріалізації.\n    Ігнорує атрибути з підкресленням (_protected, __private).\n    \"\"\"\n\n    def to_json(self) -> str:\n        \"\"\"Серіалізує публічні атрибути об'єкта у JSON-рядок.\"\"\"\n        public_data = {\n            key: value\n            for key, value in self.__dict__.items()\n            if not key.startswith('_')\n        }\n        return json.dumps(public_data, ensure_ascii=False, default=str)\n\n    @classmethod\n    def from_json(cls, json_str: str) -> \"JSONSerializableMixin\":\n        \"\"\"Десеріалізує JSON-рядок у новий об'єкт класу.\"\"\"\n        data = json.loads(json_str)\n        return cls(**data)\n\n    def to_dict(self) -> dict[str, Any]:\n        \"\"\"Повертає публічні атрибути як словник.\"\"\"\n        return {\n            key: value\n            for key, value in self.__dict__.items()\n            if not key.startswith('_')\n        }\n\n\nclass LoggingMixin:\n    \"\"\"\n    Домішка для структурованого логування операцій.\n    Автоматично підставляє ім'я реального класу в лог.\n    \"\"\"\n\n    def log(self, message: str, level: str = \"INFO\") -> None:\n        timestamp = datetime.now().strftime(\"%H:%M:%S.%f\")[:-3]\n        class_name = self.__class__.__name__\n        print(f\"[{timestamp}] [{level:5}] [{class_name}] {message}\")\n\n    def log_error(self, error: Exception) -> None:\n        self.log(f\"Помилка: {type(error).__name__}: {error}\", level=\"ERROR\")\n\n\nclass ValidationMixin:\n    \"\"\"\n    Домішка для декларативної валідації даних.\n    Підкласи можуть перевизначити `_validation_rules` — список функцій-перевірок.\n    \"\"\"\n\n    _validation_rules: list = []  # клас-рівень: перевизначте у підкласі\n\n    def validate(self) -> list[str]:\n        \"\"\"Запускає всі правила валідації. Повертає список помилок.\"\"\"\n        errors = []\n        for rule in self._validation_rules:\n            error = rule(self)\n            if error:\n                errors.append(error)\n        return errors\n\n    def assert_valid(self) -> None:\n        \"\"\"Перевіряє валідність або кидає ValueError зі списком помилок.\"\"\"\n        errors = self.validate()\n        if errors:\n            raise ValueError(f\"Помилки валідації: {'; '.join(errors)}\")\n\n\nclass HashableMixin:\n    \"\"\"\n    Домішка для генерації хешу об'єкта на основі його JSON-представлення.\n    Вимагає JSONSerializableMixin.\n    \"\"\"\n\n    @property\n    def content_hash(self) -> str:\n        \"\"\"SHA-256 хеш вмісту об'єкта. Ідентичні дані → ідентичний хеш.\"\"\"\n        if not hasattr(self, 'to_json'):\n            raise TypeError(\"HashableMixin вимагає JSONSerializableMixin\")\n        content = self.to_json().encode('utf-8')\n        return hashlib.sha256(content).hexdigest()[:16]\n",[3390,12629,12630,12635,12641,12648,12658,12670,12674,12678,12687,12691,12696,12701,12705,12709,12725,12730,12735,12740,12759,12774,12779,12804,12808,12814,12839,12844,12849,12858,12862,12881,12886,12893,12897,12913,12925,12929,12933,12937,12946,12950,12955,12960,12964,12968,13007,13027,13043,13093,13097,13124,13168,13172,13176,13185,13189,13194,13199,13203,13207,13221,13225,13244,13249,13254,13269,13278,13286,13292,13300,13305,13323,13329,13340,13348,13376,13381,13386,13396,13401,13407,13413,13418,13423,13431,13449,13455,13475,13490,13506],{"__ignoreMap":3408},[3412,12631,12632],{"class":3414,"line":3415},[3412,12633,12634],{"class":3418},"# mixins.py\n",[3412,12636,12637,12639],{"class":3414,"line":3422},[3412,12638,4154],{"class":3530},[3412,12640,6757],{"class":3433},[3412,12642,12643,12645],{"class":3414,"line":3437},[3412,12644,4154],{"class":3530},[3412,12646,12647],{"class":3433}," hashlib\n",[3412,12649,12650,12652,12654,12656],{"class":3414,"line":3468},[3412,12651,4148],{"class":3530},[3412,12653,6764],{"class":3433},[3412,12655,4154],{"class":3530},[3412,12657,6769],{"class":3433},[3412,12659,12660,12662,12665,12667],{"class":3414,"line":3516},[3412,12661,4148],{"class":3530},[3412,12663,12664],{"class":3433}," typing ",[3412,12666,4154],{"class":3530},[3412,12668,12669],{"class":3433}," Any\n",[3412,12671,12672],{"class":3414,"line":3550},[3412,12673,3554],{"emptyLinePlaceholder":3553},[3412,12675,12676],{"class":3414,"line":3557},[3412,12677,3554],{"emptyLinePlaceholder":3553},[3412,12679,12680,12682,12685],{"class":3414,"line":3567},[3412,12681,3426],{"class":3425},[3412,12683,12684],{"class":3429}," JSONSerializableMixin",[3412,12686,3434],{"class":3433},[3412,12688,12689],{"class":3414,"line":3588},[3412,12690,10012],{"class":3495},[3412,12692,12693],{"class":3414,"line":3628},[3412,12694,12695],{"class":3495},"    Домішка для автоматичної JSON-серіалізації та десеріалізації.\n",[3412,12697,12698],{"class":3414,"line":3655},[3412,12699,12700],{"class":3495},"    Ігнорує атрибути з підкресленням (_protected, __private).\n",[3412,12702,12703],{"class":3414,"line":3660},[3412,12704,10012],{"class":3495},[3412,12706,12707],{"class":3414,"line":3670},[3412,12708,3554],{"emptyLinePlaceholder":3553},[3412,12710,12711,12713,12715,12717,12719,12721,12723],{"class":3414,"line":3691},[3412,12712,3440],{"class":3425},[3412,12714,3521],{"class":3443},[3412,12716,3447],{"class":3433},[3412,12718,3451],{"class":3450},[3412,12720,3946],{"class":3433},[3412,12722,3880],{"class":3429},[3412,12724,3434],{"class":3433},[3412,12726,12727],{"class":3414,"line":3731},[3412,12728,12729],{"class":3495},"        \"\"\"Серіалізує публічні атрибути об'єкта у JSON-рядок.\"\"\"\n",[3412,12731,12732],{"class":3414,"line":4072},[3412,12733,12734],{"class":3433},"        public_data = {\n",[3412,12736,12737],{"class":3414,"line":4077},[3412,12738,12739],{"class":3433},"            key: value\n",[3412,12741,12742,12745,12748,12750,12752,12754,12756],{"class":3414,"line":4095},[3412,12743,12744],{"class":3530},"            for",[3412,12746,12747],{"class":3433}," key, value ",[3412,12749,4744],{"class":3530},[3412,12751,10741],{"class":3425},[3412,12753,3539],{"class":3433},[3412,12755,3542],{"class":3450},[3412,12757,12758],{"class":3433},".items()\n",[3412,12760,12761,12764,12766,12769,12772],{"class":3414,"line":5014},[3412,12762,12763],{"class":3530},"            if",[3412,12765,10123],{"class":3425},[3412,12767,12768],{"class":3433}," key.startswith(",[3412,12770,12771],{"class":3495},"'_'",[3412,12773,4069],{"class":3433},[3412,12775,12776],{"class":3414,"line":5020},[3412,12777,12778],{"class":3433},"        }\n",[3412,12780,12781,12783,12786,12789,12791,12793,12795,12798,12800,12802],{"class":3414,"line":5026},[3412,12782,3955],{"class":3530},[3412,12784,12785],{"class":3433}," json.dumps(public_data, ",[3412,12787,12788],{"class":3450},"ensure_ascii",[3412,12790,4691],{"class":3433},[3412,12792,5733],{"class":3425},[3412,12794,3454],{"class":3433},[3412,12796,12797],{"class":3450},"default",[3412,12799,4691],{"class":3433},[3412,12801,3880],{"class":3429},[3412,12803,4069],{"class":3433},[3412,12805,12806],{"class":3414,"line":5035},[3412,12807,3554],{"emptyLinePlaceholder":3553},[3412,12809,12810,12812],{"class":3414,"line":5046},[3412,12811,6844],{"class":3443},[3412,12813,6847],{"class":3429},[3412,12815,12816,12818,12820,12822,12824,12826,12828,12830,12832,12834,12837],{"class":3414,"line":5057},[3412,12817,3440],{"class":3425},[3412,12819,6958],{"class":3443},[3412,12821,3447],{"class":3433},[3412,12823,6497],{"class":3450},[3412,12825,3454],{"class":3433},[3412,12827,6967],{"class":3450},[3412,12829,3877],{"class":3433},[3412,12831,3880],{"class":3429},[3412,12833,3946],{"class":3433},[3412,12835,12836],{"class":3495},"\"JSONSerializableMixin\"",[3412,12838,3434],{"class":3433},[3412,12840,12841],{"class":3414,"line":5345},[3412,12842,12843],{"class":3495},"        \"\"\"Десеріалізує JSON-рядок у новий об'єкт класу.\"\"\"\n",[3412,12845,12846],{"class":3414,"line":5364},[3412,12847,12848],{"class":3433},"        data = json.loads(json_str)\n",[3412,12850,12851,12853,12855],{"class":3414,"line":5370},[3412,12852,3955],{"class":3530},[3412,12854,5331],{"class":3425},[3412,12856,12857],{"class":3433},"(**data)\n",[3412,12859,12860],{"class":3414,"line":5376},[3412,12861,3554],{"emptyLinePlaceholder":3553},[3412,12863,12864,12866,12869,12871,12873,12876,12878],{"class":3414,"line":5382},[3412,12865,3440],{"class":3425},[3412,12867,12868],{"class":3443}," to_dict",[3412,12870,3447],{"class":3433},[3412,12872,3451],{"class":3450},[3412,12874,12875],{"class":3433},") -> dict[",[3412,12877,3880],{"class":3429},[3412,12879,12880],{"class":3433},", Any]:\n",[3412,12882,12883],{"class":3414,"line":5388},[3412,12884,12885],{"class":3495},"        \"\"\"Повертає публічні атрибути як словник.\"\"\"\n",[3412,12887,12888,12890],{"class":3414,"line":5393},[3412,12889,3955],{"class":3530},[3412,12891,12892],{"class":3433}," {\n",[3412,12894,12895],{"class":3414,"line":5399},[3412,12896,12739],{"class":3433},[3412,12898,12899,12901,12903,12905,12907,12909,12911],{"class":3414,"line":5422},[3412,12900,12744],{"class":3530},[3412,12902,12747],{"class":3433},[3412,12904,4744],{"class":3530},[3412,12906,10741],{"class":3425},[3412,12908,3539],{"class":3433},[3412,12910,3542],{"class":3450},[3412,12912,12758],{"class":3433},[3412,12914,12915,12917,12919,12921,12923],{"class":3414,"line":5428},[3412,12916,12763],{"class":3530},[3412,12918,10123],{"class":3425},[3412,12920,12768],{"class":3433},[3412,12922,12771],{"class":3495},[3412,12924,4069],{"class":3433},[3412,12926,12927],{"class":3414,"line":5434},[3412,12928,12778],{"class":3433},[3412,12930,12931],{"class":3414,"line":5439},[3412,12932,3554],{"emptyLinePlaceholder":3553},[3412,12934,12935],{"class":3414,"line":5456},[3412,12936,3554],{"emptyLinePlaceholder":3553},[3412,12938,12939,12941,12944],{"class":3414,"line":5462},[3412,12940,3426],{"class":3425},[3412,12942,12943],{"class":3429}," LoggingMixin",[3412,12945,3434],{"class":3433},[3412,12947,12948],{"class":3414,"line":5467},[3412,12949,10012],{"class":3495},[3412,12951,12952],{"class":3414,"line":5473},[3412,12953,12954],{"class":3495},"    Домішка для структурованого логування операцій.\n",[3412,12956,12957],{"class":3414,"line":5491},[3412,12958,12959],{"class":3495},"    Автоматично підставляє ім'я реального класу в лог.\n",[3412,12961,12962],{"class":3414,"line":5496},[3412,12963,10012],{"class":3495},[3412,12965,12966],{"class":3414,"line":5502},[3412,12967,3554],{"emptyLinePlaceholder":3553},[3412,12969,12970,12972,12974,12976,12978,12980,12983,12985,12987,12989,12992,12994,12996,12998,13001,13003,13005],{"class":3414,"line":5518},[3412,12971,3440],{"class":3425},[3412,12973,3473],{"class":3443},[3412,12975,3447],{"class":3433},[3412,12977,3451],{"class":3450},[3412,12979,3454],{"class":3433},[3412,12981,12982],{"class":3450},"message",[3412,12984,3877],{"class":3433},[3412,12986,3880],{"class":3429},[3412,12988,3454],{"class":3433},[3412,12990,12991],{"class":3450},"level",[3412,12993,3877],{"class":3433},[3412,12995,3880],{"class":3429},[3412,12997,3921],{"class":3433},[3412,12999,13000],{"class":3495},"\"INFO\"",[3412,13002,3946],{"class":3433},[3412,13004,4011],{"class":3425},[3412,13006,3434],{"class":3433},[3412,13008,13009,13012,13015,13018,13020,13023,13025],{"class":3414,"line":5533},[3412,13010,13011],{"class":3433},"        timestamp = datetime.now().strftime(",[3412,13013,13014],{"class":3495},"\"%H:%M:%S.",[3412,13016,13017],{"class":3425},"%f",[3412,13019,3507],{"class":3495},[3412,13021,13022],{"class":3433},")[:-",[3412,13024,11690],{"class":3924},[3412,13026,4724],{"class":3433},[3412,13028,13029,13032,13034,13036,13038,13040],{"class":3414,"line":5548},[3412,13030,13031],{"class":3433},"        class_name = ",[3412,13033,3451],{"class":3425},[3412,13035,3539],{"class":3433},[3412,13037,4108],{"class":3450},[3412,13039,3539],{"class":3433},[3412,13041,13042],{"class":3450},"__name__\n",[3412,13044,13045,13047,13049,13051,13054,13056,13059,13061,13064,13066,13068,13071,13073,13075,13078,13080,13083,13085,13087,13089,13091],{"class":3414,"line":5568},[3412,13046,4039],{"class":3443},[3412,13048,3447],{"class":3433},[3412,13050,3492],{"class":3425},[3412,13052,13053],{"class":3495},"\"[",[3412,13055,3499],{"class":3425},[3412,13057,13058],{"class":3433},"timestamp",[3412,13060,3504],{"class":3425},[3412,13062,13063],{"class":3495},"] [",[3412,13065,3499],{"class":3425},[3412,13067,12991],{"class":3433},[3412,13069,13070],{"class":3425},":5}",[3412,13072,13063],{"class":3495},[3412,13074,3499],{"class":3425},[3412,13076,13077],{"class":3433},"class_name",[3412,13079,3504],{"class":3425},[3412,13081,13082],{"class":3495},"] ",[3412,13084,3499],{"class":3425},[3412,13086,12982],{"class":3433},[3412,13088,3504],{"class":3425},[3412,13090,3507],{"class":3495},[3412,13092,4069],{"class":3433},[3412,13094,13095],{"class":3414,"line":5573},[3412,13096,3554],{"emptyLinePlaceholder":3553},[3412,13098,13099,13101,13104,13106,13108,13110,13113,13115,13118,13120,13122],{"class":3414,"line":5579},[3412,13100,3440],{"class":3425},[3412,13102,13103],{"class":3443}," log_error",[3412,13105,3447],{"class":3433},[3412,13107,3451],{"class":3450},[3412,13109,3454],{"class":3433},[3412,13111,13112],{"class":3450},"error",[3412,13114,3877],{"class":3433},[3412,13116,13117],{"class":3429},"Exception",[3412,13119,3946],{"class":3433},[3412,13121,4011],{"class":3425},[3412,13123,3434],{"class":3433},[3412,13125,13126,13128,13131,13133,13136,13138,13140,13143,13145,13147,13149,13151,13153,13155,13157,13159,13161,13163,13166],{"class":3414,"line":5595},[3412,13127,3898],{"class":3425},[3412,13129,13130],{"class":3433},".log(",[3412,13132,3492],{"class":3425},[3412,13134,13135],{"class":3495},"\"Помилка: ",[3412,13137,3499],{"class":3425},[3412,13139,5110],{"class":3429},[3412,13141,13142],{"class":3433},"(error).",[3412,13144,4113],{"class":3450},[3412,13146,3504],{"class":3425},[3412,13148,3877],{"class":3495},[3412,13150,3499],{"class":3425},[3412,13152,13112],{"class":3433},[3412,13154,3504],{"class":3425},[3412,13156,3507],{"class":3495},[3412,13158,3454],{"class":3433},[3412,13160,12991],{"class":3450},[3412,13162,4691],{"class":3433},[3412,13164,13165],{"class":3495},"\"ERROR\"",[3412,13167,4069],{"class":3433},[3412,13169,13170],{"class":3414,"line":5609},[3412,13171,3554],{"emptyLinePlaceholder":3553},[3412,13173,13174],{"class":3414,"line":7232},[3412,13175,3554],{"emptyLinePlaceholder":3553},[3412,13177,13178,13180,13183],{"class":3414,"line":7261},[3412,13179,3426],{"class":3425},[3412,13181,13182],{"class":3429}," ValidationMixin",[3412,13184,3434],{"class":3433},[3412,13186,13187],{"class":3414,"line":7266},[3412,13188,10012],{"class":3495},[3412,13190,13191],{"class":3414,"line":7271},[3412,13192,13193],{"class":3495},"    Домішка для декларативної валідації даних.\n",[3412,13195,13196],{"class":3414,"line":7277},[3412,13197,13198],{"class":3495},"    Підкласи можуть перевизначити `_validation_rules` — список функцій-перевірок.\n",[3412,13200,13201],{"class":3414,"line":7310},[3412,13202,10012],{"class":3495},[3412,13204,13205],{"class":3414,"line":7315},[3412,13206,3554],{"emptyLinePlaceholder":3553},[3412,13208,13209,13212,13215,13218],{"class":3414,"line":7321},[3412,13210,13211],{"class":3433},"    _validation_rules: ",[3412,13213,13214],{"class":3429},"list",[3412,13216,13217],{"class":3433}," = []  ",[3412,13219,13220],{"class":3418},"# клас-рівень: перевизначте у підкласі\n",[3412,13222,13223],{"class":3414,"line":7346},[3412,13224,3554],{"emptyLinePlaceholder":3553},[3412,13226,13227,13229,13232,13234,13236,13239,13241],{"class":3414,"line":7361},[3412,13228,3440],{"class":3425},[3412,13230,13231],{"class":3443}," validate",[3412,13233,3447],{"class":3433},[3412,13235,3451],{"class":3450},[3412,13237,13238],{"class":3433},") -> list[",[3412,13240,3880],{"class":3429},[3412,13242,13243],{"class":3433},"]:\n",[3412,13245,13246],{"class":3414,"line":7366},[3412,13247,13248],{"class":3495},"        \"\"\"Запускає всі правила валідації. Повертає список помилок.\"\"\"\n",[3412,13250,13251],{"class":3414,"line":7372},[3412,13252,13253],{"class":3433},"        errors = []\n",[3412,13255,13256,13259,13262,13264,13266],{"class":3414,"line":7378},[3412,13257,13258],{"class":3530},"        for",[3412,13260,13261],{"class":3433}," rule ",[3412,13263,4744],{"class":3530},[3412,13265,10741],{"class":3425},[3412,13267,13268],{"class":3433},"._validation_rules:\n",[3412,13270,13271,13274,13276],{"class":3414,"line":7393},[3412,13272,13273],{"class":3433},"            error = rule(",[3412,13275,3451],{"class":3425},[3412,13277,4069],{"class":3433},[3412,13279,13281,13283],{"class":3414,"line":13280},66,[3412,13282,12763],{"class":3530},[3412,13284,13285],{"class":3433}," error:\n",[3412,13287,13289],{"class":3414,"line":13288},67,[3412,13290,13291],{"class":3433},"                errors.append(error)\n",[3412,13293,13295,13297],{"class":3414,"line":13294},68,[3412,13296,3955],{"class":3530},[3412,13298,13299],{"class":3433}," errors\n",[3412,13301,13303],{"class":3414,"line":13302},69,[3412,13304,3554],{"emptyLinePlaceholder":3553},[3412,13306,13308,13310,13313,13315,13317,13319,13321],{"class":3414,"line":13307},70,[3412,13309,3440],{"class":3425},[3412,13311,13312],{"class":3443}," assert_valid",[3412,13314,3447],{"class":3433},[3412,13316,3451],{"class":3450},[3412,13318,3946],{"class":3433},[3412,13320,4011],{"class":3425},[3412,13322,3434],{"class":3433},[3412,13324,13326],{"class":3414,"line":13325},71,[3412,13327,13328],{"class":3495},"        \"\"\"Перевіряє валідність або кидає ValueError зі списком помилок.\"\"\"\n",[3412,13330,13332,13335,13337],{"class":3414,"line":13331},72,[3412,13333,13334],{"class":3433},"        errors = ",[3412,13336,3451],{"class":3425},[3412,13338,13339],{"class":3433},".validate()\n",[3412,13341,13343,13345],{"class":3414,"line":13342},73,[3412,13344,9686],{"class":3530},[3412,13346,13347],{"class":3433}," errors:\n",[3412,13349,13351,13353,13355,13357,13359,13362,13364,13367,13370,13372,13374],{"class":3414,"line":13350},74,[3412,13352,10786],{"class":3530},[3412,13354,10789],{"class":3429},[3412,13356,3447],{"class":3433},[3412,13358,3492],{"class":3425},[3412,13360,13361],{"class":3495},"\"Помилки валідації: ",[3412,13363,3499],{"class":3425},[3412,13365,13366],{"class":3495},"'; '",[3412,13368,13369],{"class":3433},".join(errors)",[3412,13371,3504],{"class":3425},[3412,13373,3507],{"class":3495},[3412,13375,4069],{"class":3433},[3412,13377,13379],{"class":3414,"line":13378},75,[3412,13380,3554],{"emptyLinePlaceholder":3553},[3412,13382,13384],{"class":3414,"line":13383},76,[3412,13385,3554],{"emptyLinePlaceholder":3553},[3412,13387,13389,13391,13394],{"class":3414,"line":13388},77,[3412,13390,3426],{"class":3425},[3412,13392,13393],{"class":3429}," HashableMixin",[3412,13395,3434],{"class":3433},[3412,13397,13399],{"class":3414,"line":13398},78,[3412,13400,10012],{"class":3495},[3412,13402,13404],{"class":3414,"line":13403},79,[3412,13405,13406],{"class":3495},"    Домішка для генерації хешу об'єкта на основі його JSON-представлення.\n",[3412,13408,13410],{"class":3414,"line":13409},80,[3412,13411,13412],{"class":3495},"    Вимагає JSONSerializableMixin.\n",[3412,13414,13416],{"class":3414,"line":13415},81,[3412,13417,10012],{"class":3495},[3412,13419,13421],{"class":3414,"line":13420},82,[3412,13422,3554],{"emptyLinePlaceholder":3553},[3412,13424,13426,13428],{"class":3414,"line":13425},83,[3412,13427,6844],{"class":3443},[3412,13429,13430],{"class":3429},"property\n",[3412,13432,13434,13436,13439,13441,13443,13445,13447],{"class":3414,"line":13433},84,[3412,13435,3440],{"class":3425},[3412,13437,13438],{"class":3443}," content_hash",[3412,13440,3447],{"class":3433},[3412,13442,3451],{"class":3450},[3412,13444,3946],{"class":3433},[3412,13446,3880],{"class":3429},[3412,13448,3434],{"class":3433},[3412,13450,13452],{"class":3414,"line":13451},85,[3412,13453,13454],{"class":3495},"        \"\"\"SHA-256 хеш вмісту об'єкта. Ідентичні дані → ідентичний хеш.\"\"\"\n",[3412,13456,13458,13460,13462,13464,13466,13468,13470,13473],{"class":3414,"line":13457},86,[3412,13459,9686],{"class":3530},[3412,13461,10123],{"class":3425},[3412,13463,10132],{"class":3443},[3412,13465,3447],{"class":3433},[3412,13467,3451],{"class":3425},[3412,13469,3454],{"class":3433},[3412,13471,13472],{"class":3495},"'to_json'",[3412,13474,3893],{"class":3433},[3412,13476,13478,13480,13483,13485,13488],{"class":3414,"line":13477},87,[3412,13479,10786],{"class":3530},[3412,13481,13482],{"class":3429}," TypeError",[3412,13484,3447],{"class":3433},[3412,13486,13487],{"class":3495},"\"HashableMixin вимагає JSONSerializableMixin\"",[3412,13489,4069],{"class":3433},[3412,13491,13493,13496,13498,13501,13504],{"class":3414,"line":13492},88,[3412,13494,13495],{"class":3433},"        content = ",[3412,13497,3451],{"class":3425},[3412,13499,13500],{"class":3433},".to_json().encode(",[3412,13502,13503],{"class":3495},"'utf-8'",[3412,13505,4069],{"class":3433},[3412,13507,13509,13511,13514,13517],{"class":3414,"line":13508},89,[3412,13510,3955],{"class":3530},[3412,13512,13513],{"class":3433}," hashlib.sha256(content).hexdigest()[:",[3412,13515,13516],{"class":3924},"16",[3412,13518,4724],{"class":3433},[3403,13520,13522],{"className":3405,"code":13521,"language":3407,"meta":3408,"style":3408},"# business_entities.py\nfrom mixins import JSONSerializableMixin, LoggingMixin, ValidationMixin, HashableMixin\n\n\nclass Product(JSONSerializableMixin, LoggingMixin, ValidationMixin, HashableMixin):\n    \"\"\"Продукт: поєднує всі чотири домішки.\"\"\"\n\n    _validation_rules = [\n        lambda self: \"Ціна має бути > 0\" if self.price \u003C= 0 else None,\n        lambda self: \"Назва не може бути порожньою\" if not self.name.strip() else None,\n        lambda self: \"Кількість не може бути від'ємною\" if self.stock \u003C 0 else None,\n    ]\n\n    def __init__(self, name: str, price: float, stock: int = 0):\n        self.name = name\n        self.price = price\n        self.stock = stock\n        self.log(f\"Створено продукт '{self.name}' (ціна: {self.price} грн)\")\n\n    def __repr__(self) -> str:\n        return f\"Product(name={self.name!r}, price={self.price})\"\n\n\nclass User(JSONSerializableMixin, LoggingMixin, ValidationMixin):\n    \"\"\"Користувач: серіалізація, логування, валідація (без хешування).\"\"\"\n\n    _validation_rules = [\n        lambda self: \"Username занадто короткий\" if len(self.username) \u003C 3 else None,\n        lambda self: \"Email повинен містити '@'\" if '@' not in self.email else None,\n    ]\n\n    def __init__(self, username: str, email: str):\n        self.username = username\n        self.email = email\n        self._password_hash = \"...\"  # захищений — не потрапить у to_json()\n        self.log(f\"Зареєстровано користувача {self.username!r}\")\n\n    def __repr__(self) -> str:\n        return f\"User(username={self.username!r})\"\n",[3390,13523,13524,13529,13541,13545,13549,13578,13583,13587,13592,13621,13647,13673,13678,13682,13724,13731,13738,13745,13778,13782,13798,13824,13828,13832,13853,13858,13862,13866,13897,13928,13932,13936,13964,13970,13976,13989,14010,14014,14030],{"__ignoreMap":3408},[3412,13525,13526],{"class":3414,"line":3415},[3412,13527,13528],{"class":3418},"# business_entities.py\n",[3412,13530,13531,13533,13536,13538],{"class":3414,"line":3422},[3412,13532,4148],{"class":3530},[3412,13534,13535],{"class":3433}," mixins ",[3412,13537,4154],{"class":3530},[3412,13539,13540],{"class":3433}," JSONSerializableMixin, LoggingMixin, ValidationMixin, HashableMixin\n",[3412,13542,13543],{"class":3414,"line":3437},[3412,13544,3554],{"emptyLinePlaceholder":3553},[3412,13546,13547],{"class":3414,"line":3468},[3412,13548,3554],{"emptyLinePlaceholder":3553},[3412,13550,13551,13553,13556,13558,13561,13563,13566,13568,13571,13573,13576],{"class":3414,"line":3516},[3412,13552,3426],{"class":3425},[3412,13554,13555],{"class":3429}," Product",[3412,13557,3447],{"class":3433},[3412,13559,13560],{"class":3429},"JSONSerializableMixin",[3412,13562,3454],{"class":3433},[3412,13564,13565],{"class":3429},"LoggingMixin",[3412,13567,3454],{"class":3433},[3412,13569,13570],{"class":3429},"ValidationMixin",[3412,13572,3454],{"class":3433},[3412,13574,13575],{"class":3429},"HashableMixin",[3412,13577,3893],{"class":3433},[3412,13579,13580],{"class":3414,"line":3550},[3412,13581,13582],{"class":3495},"    \"\"\"Продукт: поєднує всі чотири домішки.\"\"\"\n",[3412,13584,13585],{"class":3414,"line":3557},[3412,13586,3554],{"emptyLinePlaceholder":3553},[3412,13588,13589],{"class":3414,"line":3567},[3412,13590,13591],{"class":3433},"    _validation_rules = [\n",[3412,13593,13594,13597,13599,13601,13604,13607,13609,13612,13614,13617,13619],{"class":3414,"line":3588},[3412,13595,13596],{"class":3425},"        lambda",[3412,13598,10741],{"class":3450},[3412,13600,3877],{"class":3433},[3412,13602,13603],{"class":3495},"\"Ціна має бути > 0\"",[3412,13605,13606],{"class":3530}," if",[3412,13608,10741],{"class":3425},[3412,13610,13611],{"class":3433},".price \u003C= ",[3412,13613,10779],{"class":3924},[3412,13615,13616],{"class":3530}," else",[3412,13618,10126],{"class":3425},[3412,13620,7936],{"class":3433},[3412,13622,13623,13625,13627,13629,13632,13634,13636,13638,13641,13643,13645],{"class":3414,"line":3628},[3412,13624,13596],{"class":3425},[3412,13626,10741],{"class":3450},[3412,13628,3877],{"class":3433},[3412,13630,13631],{"class":3495},"\"Назва не може бути порожньою\"",[3412,13633,13606],{"class":3530},[3412,13635,10123],{"class":3425},[3412,13637,10741],{"class":3425},[3412,13639,13640],{"class":3433},".name.strip() ",[3412,13642,11044],{"class":3530},[3412,13644,10126],{"class":3425},[3412,13646,7936],{"class":3433},[3412,13648,13649,13651,13653,13655,13658,13660,13662,13665,13667,13669,13671],{"class":3414,"line":3655},[3412,13650,13596],{"class":3425},[3412,13652,10741],{"class":3450},[3412,13654,3877],{"class":3433},[3412,13656,13657],{"class":3495},"\"Кількість не може бути від'ємною\"",[3412,13659,13606],{"class":3530},[3412,13661,10741],{"class":3425},[3412,13663,13664],{"class":3433},".stock \u003C ",[3412,13666,10779],{"class":3924},[3412,13668,13616],{"class":3530},[3412,13670,10126],{"class":3425},[3412,13672,7936],{"class":3433},[3412,13674,13675],{"class":3414,"line":3660},[3412,13676,13677],{"class":3433},"    ]\n",[3412,13679,13680],{"class":3414,"line":3670},[3412,13681,3554],{"emptyLinePlaceholder":3553},[3412,13683,13684,13686,13688,13690,13692,13694,13696,13698,13700,13702,13705,13707,13709,13711,13714,13716,13718,13720,13722],{"class":3414,"line":3691},[3412,13685,3440],{"class":3425},[3412,13687,3865],{"class":3443},[3412,13689,3447],{"class":3433},[3412,13691,3451],{"class":3450},[3412,13693,3454],{"class":3433},[3412,13695,6542],{"class":3450},[3412,13697,3877],{"class":3433},[3412,13699,3880],{"class":3429},[3412,13701,3454],{"class":3433},[3412,13703,13704],{"class":3450},"price",[3412,13706,3877],{"class":3433},[3412,13708,3918],{"class":3429},[3412,13710,3454],{"class":3433},[3412,13712,13713],{"class":3450},"stock",[3412,13715,3877],{"class":3433},[3412,13717,3890],{"class":3429},[3412,13719,3921],{"class":3433},[3412,13721,10779],{"class":3924},[3412,13723,3893],{"class":3433},[3412,13725,13726,13728],{"class":3414,"line":3731},[3412,13727,3898],{"class":3425},[3412,13729,13730],{"class":3433},".name = name\n",[3412,13732,13733,13735],{"class":3414,"line":4072},[3412,13734,3898],{"class":3425},[3412,13736,13737],{"class":3433},".price = price\n",[3412,13739,13740,13742],{"class":3414,"line":4077},[3412,13741,3898],{"class":3425},[3412,13743,13744],{"class":3433},".stock = stock\n",[3412,13746,13747,13749,13751,13753,13756,13758,13761,13763,13766,13768,13771,13773,13776],{"class":3414,"line":4095},[3412,13748,3898],{"class":3425},[3412,13750,13130],{"class":3433},[3412,13752,3492],{"class":3425},[3412,13754,13755],{"class":3495},"\"Створено продукт '",[3412,13757,3964],{"class":3425},[3412,13759,13760],{"class":3433},".name",[3412,13762,3504],{"class":3425},[3412,13764,13765],{"class":3495},"' (ціна: ",[3412,13767,3964],{"class":3425},[3412,13769,13770],{"class":3433},".price",[3412,13772,3504],{"class":3425},[3412,13774,13775],{"class":3495}," грн)\"",[3412,13777,4069],{"class":3433},[3412,13779,13780],{"class":3414,"line":5014},[3412,13781,3554],{"emptyLinePlaceholder":3553},[3412,13783,13784,13786,13788,13790,13792,13794,13796],{"class":3414,"line":5020},[3412,13785,3440],{"class":3425},[3412,13787,4082],{"class":3443},[3412,13789,3447],{"class":3433},[3412,13791,3451],{"class":3450},[3412,13793,3946],{"class":3433},[3412,13795,3880],{"class":3429},[3412,13797,3434],{"class":3433},[3412,13799,13800,13802,13804,13807,13809,13811,13813,13816,13818,13820,13822],{"class":3414,"line":5026},[3412,13801,3955],{"class":3530},[3412,13803,3958],{"class":3425},[3412,13805,13806],{"class":3495},"\"Product(name=",[3412,13808,3964],{"class":3425},[3412,13810,13760],{"class":3433},[3412,13812,4125],{"class":3425},[3412,13814,13815],{"class":3495},", price=",[3412,13817,3964],{"class":3425},[3412,13819,13770],{"class":3433},[3412,13821,3504],{"class":3425},[3412,13823,4137],{"class":3495},[3412,13825,13826],{"class":3414,"line":5035},[3412,13827,3554],{"emptyLinePlaceholder":3553},[3412,13829,13830],{"class":3414,"line":5046},[3412,13831,3554],{"emptyLinePlaceholder":3553},[3412,13833,13834,13836,13839,13841,13843,13845,13847,13849,13851],{"class":3414,"line":5057},[3412,13835,3426],{"class":3425},[3412,13837,13838],{"class":3429}," User",[3412,13840,3447],{"class":3433},[3412,13842,13560],{"class":3429},[3412,13844,3454],{"class":3433},[3412,13846,13565],{"class":3429},[3412,13848,3454],{"class":3433},[3412,13850,13570],{"class":3429},[3412,13852,3893],{"class":3433},[3412,13854,13855],{"class":3414,"line":5345},[3412,13856,13857],{"class":3495},"    \"\"\"Користувач: серіалізація, логування, валідація (без хешування).\"\"\"\n",[3412,13859,13860],{"class":3414,"line":5364},[3412,13861,3554],{"emptyLinePlaceholder":3553},[3412,13863,13864],{"class":3414,"line":5370},[3412,13865,13591],{"class":3433},[3412,13867,13868,13870,13872,13874,13877,13879,13882,13884,13886,13889,13891,13893,13895],{"class":3414,"line":5376},[3412,13869,13596],{"class":3425},[3412,13871,10741],{"class":3450},[3412,13873,3877],{"class":3433},[3412,13875,13876],{"class":3495},"\"Username занадто короткий\"",[3412,13878,13606],{"class":3530},[3412,13880,13881],{"class":3443}," len",[3412,13883,3447],{"class":3433},[3412,13885,3451],{"class":3425},[3412,13887,13888],{"class":3433},".username) \u003C ",[3412,13890,11690],{"class":3924},[3412,13892,13616],{"class":3530},[3412,13894,10126],{"class":3425},[3412,13896,7936],{"class":3433},[3412,13898,13899,13901,13903,13905,13908,13910,13913,13915,13917,13919,13922,13924,13926],{"class":3414,"line":5382},[3412,13900,13596],{"class":3425},[3412,13902,10741],{"class":3450},[3412,13904,3877],{"class":3433},[3412,13906,13907],{"class":3495},"\"Email повинен містити '@'\"",[3412,13909,13606],{"class":3530},[3412,13911,13912],{"class":3495}," '@'",[3412,13914,10123],{"class":3425},[3412,13916,5334],{"class":3425},[3412,13918,10741],{"class":3425},[3412,13920,13921],{"class":3433},".email ",[3412,13923,11044],{"class":3530},[3412,13925,10126],{"class":3425},[3412,13927,7936],{"class":3433},[3412,13929,13930],{"class":3414,"line":5388},[3412,13931,13677],{"class":3433},[3412,13933,13934],{"class":3414,"line":5393},[3412,13935,3554],{"emptyLinePlaceholder":3553},[3412,13937,13938,13940,13942,13944,13946,13948,13950,13952,13954,13956,13958,13960,13962],{"class":3414,"line":5399},[3412,13939,3440],{"class":3425},[3412,13941,3865],{"class":3443},[3412,13943,3447],{"class":3433},[3412,13945,3451],{"class":3450},[3412,13947,3454],{"class":3433},[3412,13949,7048],{"class":3450},[3412,13951,3877],{"class":3433},[3412,13953,3880],{"class":3429},[3412,13955,3454],{"class":3433},[3412,13957,7057],{"class":3450},[3412,13959,3877],{"class":3433},[3412,13961,3880],{"class":3429},[3412,13963,3893],{"class":3433},[3412,13965,13966,13968],{"class":3414,"line":5422},[3412,13967,3898],{"class":3425},[3412,13969,7081],{"class":3433},[3412,13971,13972,13974],{"class":3414,"line":5428},[3412,13973,3898],{"class":3425},[3412,13975,7088],{"class":3433},[3412,13977,13978,13980,13983,13986],{"class":3414,"line":5434},[3412,13979,3898],{"class":3425},[3412,13981,13982],{"class":3433},"._password_hash = ",[3412,13984,13985],{"class":3495},"\"...\"",[3412,13987,13988],{"class":3418},"  # захищений — не потрапить у to_json()\n",[3412,13990,13991,13993,13995,13997,14000,14002,14004,14006,14008],{"class":3414,"line":5439},[3412,13992,3898],{"class":3425},[3412,13994,13130],{"class":3433},[3412,13996,3492],{"class":3425},[3412,13998,13999],{"class":3495},"\"Зареєстровано користувача ",[3412,14001,3964],{"class":3425},[3412,14003,7254],{"class":3433},[3412,14005,4125],{"class":3425},[3412,14007,3507],{"class":3495},[3412,14009,4069],{"class":3433},[3412,14011,14012],{"class":3414,"line":5456},[3412,14013,3554],{"emptyLinePlaceholder":3553},[3412,14015,14016,14018,14020,14022,14024,14026,14028],{"class":3414,"line":5462},[3412,14017,3440],{"class":3425},[3412,14019,4082],{"class":3443},[3412,14021,3447],{"class":3433},[3412,14023,3451],{"class":3450},[3412,14025,3946],{"class":3433},[3412,14027,3880],{"class":3429},[3412,14029,3434],{"class":3433},[3412,14031,14032,14034,14036,14039,14041,14043,14045],{"class":3414,"line":5467},[3412,14033,3955],{"class":3530},[3412,14035,3958],{"class":3425},[3412,14037,14038],{"class":3495},"\"User(username=",[3412,14040,3964],{"class":3425},[3412,14042,7254],{"class":3433},[3412,14044,4125],{"class":3425},[3412,14046,4137],{"class":3495},[3403,14048,14050],{"className":3405,"code":14049,"language":3407,"meta":3408,"style":3408},"# demo.py\nfrom business_entities import Product, User\n\nprint(\"=== Демонстрація Mixins ===\\n\")\n\n# ── Product ────────────────────────────────────────────────────────────────────\nprod = Product(\"MacBook Pro\", 89999.0, stock=5)\nprint(f\"JSON: {prod.to_json()}\")\nprint(f\"Hash: {prod.content_hash}\")\n\n# Валідація\nerrors = prod.validate()\nprint(f\"Помилки валідації: {errors or 'немає'}\")\n\n# Невалідний продукт\nbad = Product(\"\", -100.0, stock=-1)\nerrors = bad.validate()\nprint(f\"Помилки bad-продукту: {errors}\")\n\nprint()\n\n# ── User ───────────────────────────────────────────────────────────────────────\nuser = User(\"arakviel\", \"arakviel@example.com\")\nprint(f\"JSON: {user.to_json()}\")\n# Зверніть увагу: _password_hash відсутній!\n\n# MRO\nprint(f\"\\nMRO Product: {[c.__name__ for c in Product.__mro__]}\")\nprint(f\"MRO User:    {[c.__name__ for c in User.__mro__]}\")\n",[3390,14051,14052,14057,14069,14073,14088,14092,14097,14120,14142,14164,14168,14173,14178,14205,14209,14214,14238,14243,14265,14269,14276,14280,14285,14298,14319,14324,14328,14333,14374],{"__ignoreMap":3408},[3412,14053,14054],{"class":3414,"line":3415},[3412,14055,14056],{"class":3418},"# demo.py\n",[3412,14058,14059,14061,14064,14066],{"class":3414,"line":3422},[3412,14060,4148],{"class":3530},[3412,14062,14063],{"class":3433}," business_entities ",[3412,14065,4154],{"class":3530},[3412,14067,14068],{"class":3433}," Product, User\n",[3412,14070,14071],{"class":3414,"line":3437},[3412,14072,3554],{"emptyLinePlaceholder":3553},[3412,14074,14075,14077,14079,14082,14084,14086],{"class":3414,"line":3468},[3412,14076,3487],{"class":3443},[3412,14078,3447],{"class":3433},[3412,14080,14081],{"class":3495},"\"=== Демонстрація Mixins ===",[3412,14083,5319],{"class":5318},[3412,14085,3507],{"class":3495},[3412,14087,4069],{"class":3433},[3412,14089,14090],{"class":3414,"line":3516},[3412,14091,3554],{"emptyLinePlaceholder":3553},[3412,14093,14094],{"class":3414,"line":3550},[3412,14095,14096],{"class":3418},"# ── Product ────────────────────────────────────────────────────────────────────\n",[3412,14098,14099,14102,14105,14107,14110,14112,14114,14116,14118],{"class":3414,"line":3557},[3412,14100,14101],{"class":3433},"prod = Product(",[3412,14103,14104],{"class":3495},"\"MacBook Pro\"",[3412,14106,3454],{"class":3433},[3412,14108,14109],{"class":3924},"89999.0",[3412,14111,3454],{"class":3433},[3412,14113,13713],{"class":3450},[3412,14115,4691],{"class":3433},[3412,14117,10815],{"class":3924},[3412,14119,4069],{"class":3433},[3412,14121,14122,14124,14126,14128,14131,14133,14136,14138,14140],{"class":3414,"line":3567},[3412,14123,3487],{"class":3443},[3412,14125,3447],{"class":3433},[3412,14127,3492],{"class":3425},[3412,14129,14130],{"class":3495},"\"JSON: ",[3412,14132,3499],{"class":3425},[3412,14134,14135],{"class":3433},"prod.to_json()",[3412,14137,3504],{"class":3425},[3412,14139,3507],{"class":3495},[3412,14141,4069],{"class":3433},[3412,14143,14144,14146,14148,14150,14153,14155,14158,14160,14162],{"class":3414,"line":3588},[3412,14145,3487],{"class":3443},[3412,14147,3447],{"class":3433},[3412,14149,3492],{"class":3425},[3412,14151,14152],{"class":3495},"\"Hash: ",[3412,14154,3499],{"class":3425},[3412,14156,14157],{"class":3433},"prod.content_hash",[3412,14159,3504],{"class":3425},[3412,14161,3507],{"class":3495},[3412,14163,4069],{"class":3433},[3412,14165,14166],{"class":3414,"line":3628},[3412,14167,3554],{"emptyLinePlaceholder":3553},[3412,14169,14170],{"class":3414,"line":3655},[3412,14171,14172],{"class":3418},"# Валідація\n",[3412,14174,14175],{"class":3414,"line":3660},[3412,14176,14177],{"class":3433},"errors = prod.validate()\n",[3412,14179,14180,14182,14184,14186,14188,14190,14193,14196,14199,14201,14203],{"class":3414,"line":3670},[3412,14181,3487],{"class":3443},[3412,14183,3447],{"class":3433},[3412,14185,3492],{"class":3425},[3412,14187,13361],{"class":3495},[3412,14189,3499],{"class":3425},[3412,14191,14192],{"class":3433},"errors ",[3412,14194,14195],{"class":3425},"or",[3412,14197,14198],{"class":3495}," 'немає'",[3412,14200,3504],{"class":3425},[3412,14202,3507],{"class":3495},[3412,14204,4069],{"class":3433},[3412,14206,14207],{"class":3414,"line":3691},[3412,14208,3554],{"emptyLinePlaceholder":3553},[3412,14210,14211],{"class":3414,"line":3731},[3412,14212,14213],{"class":3418},"# Невалідний продукт\n",[3412,14215,14216,14219,14222,14225,14227,14229,14231,14234,14236],{"class":3414,"line":4072},[3412,14217,14218],{"class":3433},"bad = Product(",[3412,14220,14221],{"class":3495},"\"\"",[3412,14223,14224],{"class":3433},", -",[3412,14226,3925],{"class":3924},[3412,14228,3454],{"class":3433},[3412,14230,13713],{"class":3450},[3412,14232,14233],{"class":3433},"=-",[3412,14235,7331],{"class":3924},[3412,14237,4069],{"class":3433},[3412,14239,14240],{"class":3414,"line":4077},[3412,14241,14242],{"class":3433},"errors = bad.validate()\n",[3412,14244,14245,14247,14249,14251,14254,14256,14259,14261,14263],{"class":3414,"line":4095},[3412,14246,3487],{"class":3443},[3412,14248,3447],{"class":3433},[3412,14250,3492],{"class":3425},[3412,14252,14253],{"class":3495},"\"Помилки bad-продукту: ",[3412,14255,3499],{"class":3425},[3412,14257,14258],{"class":3433},"errors",[3412,14260,3504],{"class":3425},[3412,14262,3507],{"class":3495},[3412,14264,4069],{"class":3433},[3412,14266,14267],{"class":3414,"line":5014},[3412,14268,3554],{"emptyLinePlaceholder":3553},[3412,14270,14271,14273],{"class":3414,"line":5020},[3412,14272,3487],{"class":3443},[3412,14274,14275],{"class":3433},"()\n",[3412,14277,14278],{"class":3414,"line":5026},[3412,14279,3554],{"emptyLinePlaceholder":3553},[3412,14281,14282],{"class":3414,"line":5035},[3412,14283,14284],{"class":3418},"# ── User ───────────────────────────────────────────────────────────────────────\n",[3412,14286,14287,14290,14292,14294,14296],{"class":3414,"line":5046},[3412,14288,14289],{"class":3433},"user = User(",[3412,14291,7296],{"class":3495},[3412,14293,3454],{"class":3433},[3412,14295,7305],{"class":3495},[3412,14297,4069],{"class":3433},[3412,14299,14300,14302,14304,14306,14308,14310,14313,14315,14317],{"class":3414,"line":5057},[3412,14301,3487],{"class":3443},[3412,14303,3447],{"class":3433},[3412,14305,3492],{"class":3425},[3412,14307,14130],{"class":3495},[3412,14309,3499],{"class":3425},[3412,14311,14312],{"class":3433},"user.to_json()",[3412,14314,3504],{"class":3425},[3412,14316,3507],{"class":3495},[3412,14318,4069],{"class":3433},[3412,14320,14321],{"class":3414,"line":5345},[3412,14322,14323],{"class":3418},"# Зверніть увагу: _password_hash відсутній!\n",[3412,14325,14326],{"class":3414,"line":5364},[3412,14327,3554],{"emptyLinePlaceholder":3553},[3412,14329,14330],{"class":3414,"line":5370},[3412,14331,14332],{"class":3418},"# MRO\n",[3412,14334,14335,14337,14339,14341,14343,14345,14348,14350,14353,14355,14357,14359,14361,14364,14366,14368,14370,14372],{"class":3414,"line":5376},[3412,14336,3487],{"class":3443},[3412,14338,3447],{"class":3433},[3412,14340,3492],{"class":3425},[3412,14342,3507],{"class":3495},[3412,14344,5319],{"class":5318},[3412,14346,14347],{"class":3495},"MRO Product: ",[3412,14349,3499],{"class":3425},[3412,14351,14352],{"class":3433},"[c.",[3412,14354,4113],{"class":3450},[3412,14356,7881],{"class":3530},[3412,14358,7884],{"class":3433},[3412,14360,4744],{"class":3530},[3412,14362,14363],{"class":3433}," Product.",[3412,14365,5340],{"class":3450},[3412,14367,9311],{"class":3433},[3412,14369,3504],{"class":3425},[3412,14371,3507],{"class":3495},[3412,14373,4069],{"class":3433},[3412,14375,14376,14378,14380,14382,14385,14387,14389,14391,14393,14395,14397,14400,14402,14404,14406,14408],{"class":3414,"line":5382},[3412,14377,3487],{"class":3443},[3412,14379,3447],{"class":3433},[3412,14381,3492],{"class":3425},[3412,14383,14384],{"class":3495},"\"MRO User:    ",[3412,14386,3499],{"class":3425},[3412,14388,14352],{"class":3433},[3412,14390,4113],{"class":3450},[3412,14392,7881],{"class":3530},[3412,14394,7884],{"class":3433},[3412,14396,4744],{"class":3530},[3412,14398,14399],{"class":3433}," User.",[3412,14401,5340],{"class":3450},[3412,14403,9311],{"class":3433},[3412,14405,3504],{"class":3425},[3412,14407,3507],{"class":3495},[3412,14409,4069],{"class":3433},[4757,14411,14413,14421,14425,14428,14440,14448,14456,14464,14467,14478,14486,14489,14500,14507,14514,14517,14524],{"title":14412},"python demo.py",[4761,14414,14416,4769,14419],{"className":14415},[3414],[3412,14417,4768],{"className":14418},[4767],[3764,14420,14412],{},[4761,14422,14424],{"className":14423},[3414],"=== Демонстрація Mixins ===",[4761,14426],{"className":14427},[3414],[4761,14429,9291,14431,13063,14435,14439],{"className":14430},[3414],[3412,14432,14434],{"className":14433},[5673],"14:22:01.341",[3412,14436,14438],{"className":14437},[4792],"INFO ","] [Product] Створено продукт 'MacBook Pro' (ціна: 89999.0 грн)",[4761,14441,14443,14444],{"className":14442},[3414],"JSON: ",[3412,14445,14447],{"className":14446},[4800],"{\"name\": \"MacBook Pro\", \"price\": 89999.0, \"stock\": 5}",[4761,14449,14451,14452],{"className":14450},[3414],"Hash: ",[3412,14453,14455],{"className":14454},[4779],"3f8a91bc2e7d4012",[4761,14457,14459,14460],{"className":14458},[3414],"Помилки валідації: ",[3412,14461,14463],{"className":14462},[4800],"немає",[4761,14465],{"className":14466},[3414],[4761,14468,9291,14470,13063,14474,14477],{"className":14469},[3414],[3412,14471,14473],{"className":14472},[5673],"14:22:01.342",[3412,14475,14438],{"className":14476},[4792],"] [Product] Створено продукт '' (ціна: -100.0 грн)",[4761,14479,14481,14482],{"className":14480},[3414],"Помилки bad-продукту: ",[3412,14483,14485],{"className":14484},[5732],"['Ціна має бути > 0', 'Назва не може бути порожньою', 'Кількість не може бути від'ємною']",[4761,14487],{"className":14488},[3414],[4761,14490,9291,14492,13063,14496,14499],{"className":14491},[3414],[3412,14493,14495],{"className":14494},[5673],"14:22:01.343",[3412,14497,14438],{"className":14498},[4792],"] [User] Зареєстровано користувача 'arakviel'",[4761,14501,14443,14503],{"className":14502},[3414],[3412,14504,14506],{"className":14505},[4800],"{\"username\": \"arakviel\", \"email\": \"arakviel@example.com\"}",[4761,14508,14510],{"className":14509},[3414],[3412,14511,14513],{"className":14512},[5673],"# _password_hash відфільтровано автоматично!",[4761,14515],{"className":14516},[3414],[4761,14518,14347,14520],{"className":14519},[3414],[3412,14521,14523],{"className":14522},[4792],"['Product', 'JSONSerializableMixin', 'LoggingMixin', 'ValidationMixin', 'HashableMixin', 'object']",[4761,14525,14527,14528],{"className":14526},[3414],"MRO User:    ",[3412,14529,14531],{"className":14530},[4792],"['User', 'JSONSerializableMixin', 'LoggingMixin', 'ValidationMixin', 'object']",[3796,14533],{},[3803,14535,14537],{"id":14536},"антипатерни-mixin-що-не-слід-робити","Антипатерни Mixin: що не слід робити",[3399,14539,14540],{},"Попри гнучкість, Mixins мають чіткі межі застосування. Порушення цих меж призводить до архітектурних проблем, що складно усунути.",[3769,14542,14543,14557,14579,14584],{},[3772,14544,14547,14548,14550,14551,14553,14554,14556],{"icon":14545,"title":14546},"i-heroicons-exclamation-circle","❌ Mixin зі станом","Mixin з власним ",[3390,14549,4242],{},", що зберігає стан без передачі ",[3390,14552,7484],{}," далі через ",[3390,14555,3392],{},", обриває ланцюжок ініціалізації MRO. Усі наступні класи у черзі залишаться неініціалізованими.",[3772,14558,14561,14562,14564,14565,14568,14569,14571,14572,14575,14576,3539],{"icon":14559,"title":14560},"i-heroicons-link","❌ Залежності між Mixin'ами","Якщо один Mixin неявно покладається на методи іншого (наприклад, ",[3390,14563,13575],{}," потребує ",[3390,14566,14567],{},"to_json"," з ",[3390,14570,13560],{},"), це ",[3764,14573,14574],{},"прихована залежність",", яка не видна зі сигнатури класу. Документуйте такі вимоги явно або перевіряйте через ",[3390,14577,14578],{},"hasattr",[3772,14580,14583],{"icon":14581,"title":14582},"i-heroicons-bars-3-bottom-right","❌ Занадто глибокі ланцюжки","Коли клас наслідує 6+ Mixin'ів — це сигнал про необхідність рефакторингу: можливо, частину функціональності слід перемістити у композицію (HAS-A), або виділити проміжний базовий клас.",[3772,14585,14588,14589,14591],{"icon":14586,"title":14587},"i-heroicons-x-circle","❌ Mixin перевизначає бізнес-метод","Мixin не повинен перевизначати методи, що несуть бізнес-логіку. Він має лише розширювати функціональність «навколо» основного методу (через ",[3390,14590,3392],{},"), а не замінювати саму логіку.",[3403,14593,14595],{"className":3405,"code":14594,"language":3407,"meta":3408,"style":3408},"# ❌ Антипатерн 1: Mixin зі станом без super().__init__(**kwargs)\nclass BadCacheMixin:\n    def __init__(self):\n        self._cache = {}  # ← ПОМИЛКА: kwargs не передаються!\n        # Якщо ця домішка у ланцюжку, наступні __init__ ніколи не викличуться\n\n\n# ✅ Правильно: завжди передавайте **kwargs\nclass GoodCacheMixin:\n    def __init__(self, **kwargs):\n        super().__init__(**kwargs)  # ← ланцюжок не обривається\n        self._cache: dict = {}\n\n    def get_cached(self, key: str):\n        return self._cache.get(key)\n\n    def set_cached(self, key: str, value) -> None:\n        self._cache[key] = value\n\n\n# ❌ Антипатерн 2: прихована залежність між Mixin'ами\nclass BadHashMixin:\n    @property\n    def content_hash(self) -> str:\n        # Неявна вимога: об'єкт повинен мати метод to_json()\n        # Якщо JSONSerializableMixin не підключений — RuntimeError\n        return hashlib.sha256(self.to_json().encode()).hexdigest()[:16]  # type: ignore\n\n\n# ✅ Правильно: явна перевірка з інформативним повідомленням\nclass SafeHashMixin:\n    @property\n    def content_hash(self) -> str:\n        if not hasattr(self, 'to_json'):\n            raise TypeError(\n                f\"{type(self).__name__} використовує SafeHashMixin, але не має методу \"\n                f\"to_json(). Додайте JSONSerializableMixin до переліку батьків.\"\n            )\n        return hashlib.sha256(self.to_json().encode()).hexdigest()[:16]\n",[3390,14596,14597,14602,14611,14623,14633,14638,14642,14646,14651,14660,14676,14689,14701,14705,14727,14736,14740,14769,14776,14780,14784,14789,14798,14804,14820,14825,14830,14850,14854,14858,14863,14872,14878,14894,14912,14920,14944,14951,14956],{"__ignoreMap":3408},[3412,14598,14599],{"class":3414,"line":3415},[3412,14600,14601],{"class":3418},"# ❌ Антипатерн 1: Mixin зі станом без super().__init__(**kwargs)\n",[3412,14603,14604,14606,14609],{"class":3414,"line":3422},[3412,14605,3426],{"class":3425},[3412,14607,14608],{"class":3429}," BadCacheMixin",[3412,14610,3434],{"class":3433},[3412,14612,14613,14615,14617,14619,14621],{"class":3414,"line":3437},[3412,14614,3440],{"class":3425},[3412,14616,3865],{"class":3443},[3412,14618,3447],{"class":3433},[3412,14620,3451],{"class":3450},[3412,14622,3893],{"class":3433},[3412,14624,14625,14627,14630],{"class":3414,"line":3468},[3412,14626,3898],{"class":3425},[3412,14628,14629],{"class":3433},"._cache = {}  ",[3412,14631,14632],{"class":3418},"# ← ПОМИЛКА: kwargs не передаються!\n",[3412,14634,14635],{"class":3414,"line":3516},[3412,14636,14637],{"class":3418},"        # Якщо ця домішка у ланцюжку, наступні __init__ ніколи не викличуться\n",[3412,14639,14640],{"class":3414,"line":3550},[3412,14641,3554],{"emptyLinePlaceholder":3553},[3412,14643,14644],{"class":3414,"line":3557},[3412,14645,3554],{"emptyLinePlaceholder":3553},[3412,14647,14648],{"class":3414,"line":3567},[3412,14649,14650],{"class":3418},"# ✅ Правильно: завжди передавайте **kwargs\n",[3412,14652,14653,14655,14658],{"class":3414,"line":3588},[3412,14654,3426],{"class":3425},[3412,14656,14657],{"class":3429}," GoodCacheMixin",[3412,14659,3434],{"class":3433},[3412,14661,14662,14664,14666,14668,14670,14672,14674],{"class":3414,"line":3628},[3412,14663,3440],{"class":3425},[3412,14665,3865],{"class":3443},[3412,14667,3447],{"class":3433},[3412,14669,3451],{"class":3450},[3412,14671,7534],{"class":3433},[3412,14673,7537],{"class":3450},[3412,14675,3893],{"class":3433},[3412,14677,14678,14680,14682,14684,14686],{"class":3414,"line":3655},[3412,14679,4236],{"class":3429},[3412,14681,4239],{"class":3433},[3412,14683,4242],{"class":3443},[3412,14685,7572],{"class":3433},[3412,14687,14688],{"class":3418},"# ← ланцюжок не обривається\n",[3412,14690,14691,14693,14696,14698],{"class":3414,"line":3660},[3412,14692,3898],{"class":3425},[3412,14694,14695],{"class":3433},"._cache: ",[3412,14697,6868],{"class":3429},[3412,14699,14700],{"class":3433}," = {}\n",[3412,14702,14703],{"class":3414,"line":3670},[3412,14704,3554],{"emptyLinePlaceholder":3553},[3412,14706,14707,14709,14712,14714,14716,14718,14721,14723,14725],{"class":3414,"line":3691},[3412,14708,3440],{"class":3425},[3412,14710,14711],{"class":3443}," get_cached",[3412,14713,3447],{"class":3433},[3412,14715,3451],{"class":3450},[3412,14717,3454],{"class":3433},[3412,14719,14720],{"class":3450},"key",[3412,14722,3877],{"class":3433},[3412,14724,3880],{"class":3429},[3412,14726,3893],{"class":3433},[3412,14728,14729,14731,14733],{"class":3414,"line":3731},[3412,14730,3955],{"class":3530},[3412,14732,10741],{"class":3425},[3412,14734,14735],{"class":3433},"._cache.get(key)\n",[3412,14737,14738],{"class":3414,"line":4072},[3412,14739,3554],{"emptyLinePlaceholder":3553},[3412,14741,14742,14744,14747,14749,14751,14753,14755,14757,14759,14761,14763,14765,14767],{"class":3414,"line":4077},[3412,14743,3440],{"class":3425},[3412,14745,14746],{"class":3443}," set_cached",[3412,14748,3447],{"class":3433},[3412,14750,3451],{"class":3450},[3412,14752,3454],{"class":3433},[3412,14754,14720],{"class":3450},[3412,14756,3877],{"class":3433},[3412,14758,3880],{"class":3429},[3412,14760,3454],{"class":3433},[3412,14762,10476],{"class":3450},[3412,14764,3946],{"class":3433},[3412,14766,4011],{"class":3425},[3412,14768,3434],{"class":3433},[3412,14770,14771,14773],{"class":3414,"line":4095},[3412,14772,3898],{"class":3425},[3412,14774,14775],{"class":3433},"._cache[key] = value\n",[3412,14777,14778],{"class":3414,"line":5014},[3412,14779,3554],{"emptyLinePlaceholder":3553},[3412,14781,14782],{"class":3414,"line":5020},[3412,14783,3554],{"emptyLinePlaceholder":3553},[3412,14785,14786],{"class":3414,"line":5026},[3412,14787,14788],{"class":3418},"# ❌ Антипатерн 2: прихована залежність між Mixin'ами\n",[3412,14790,14791,14793,14796],{"class":3414,"line":5035},[3412,14792,3426],{"class":3425},[3412,14794,14795],{"class":3429}," BadHashMixin",[3412,14797,3434],{"class":3433},[3412,14799,14800,14802],{"class":3414,"line":5046},[3412,14801,6844],{"class":3443},[3412,14803,13430],{"class":3429},[3412,14805,14806,14808,14810,14812,14814,14816,14818],{"class":3414,"line":5057},[3412,14807,3440],{"class":3425},[3412,14809,13438],{"class":3443},[3412,14811,3447],{"class":3433},[3412,14813,3451],{"class":3450},[3412,14815,3946],{"class":3433},[3412,14817,3880],{"class":3429},[3412,14819,3434],{"class":3433},[3412,14821,14822],{"class":3414,"line":5345},[3412,14823,14824],{"class":3418},"        # Неявна вимога: об'єкт повинен мати метод to_json()\n",[3412,14826,14827],{"class":3414,"line":5364},[3412,14828,14829],{"class":3418},"        # Якщо JSONSerializableMixin не підключений — RuntimeError\n",[3412,14831,14832,14834,14837,14839,14842,14844,14847],{"class":3414,"line":5370},[3412,14833,3955],{"class":3530},[3412,14835,14836],{"class":3433}," hashlib.sha256(",[3412,14838,3451],{"class":3425},[3412,14840,14841],{"class":3433},".to_json().encode()).hexdigest()[:",[3412,14843,13516],{"class":3924},[3412,14845,14846],{"class":3433},"]  ",[3412,14848,14849],{"class":3418},"# type: ignore\n",[3412,14851,14852],{"class":3414,"line":5376},[3412,14853,3554],{"emptyLinePlaceholder":3553},[3412,14855,14856],{"class":3414,"line":5382},[3412,14857,3554],{"emptyLinePlaceholder":3553},[3412,14859,14860],{"class":3414,"line":5388},[3412,14861,14862],{"class":3418},"# ✅ Правильно: явна перевірка з інформативним повідомленням\n",[3412,14864,14865,14867,14870],{"class":3414,"line":5393},[3412,14866,3426],{"class":3425},[3412,14868,14869],{"class":3429}," SafeHashMixin",[3412,14871,3434],{"class":3433},[3412,14873,14874,14876],{"class":3414,"line":5399},[3412,14875,6844],{"class":3443},[3412,14877,13430],{"class":3429},[3412,14879,14880,14882,14884,14886,14888,14890,14892],{"class":3414,"line":5422},[3412,14881,3440],{"class":3425},[3412,14883,13438],{"class":3443},[3412,14885,3447],{"class":3433},[3412,14887,3451],{"class":3450},[3412,14889,3946],{"class":3433},[3412,14891,3880],{"class":3429},[3412,14893,3434],{"class":3433},[3412,14895,14896,14898,14900,14902,14904,14906,14908,14910],{"class":3414,"line":5428},[3412,14897,9686],{"class":3530},[3412,14899,10123],{"class":3425},[3412,14901,10132],{"class":3443},[3412,14903,3447],{"class":3433},[3412,14905,3451],{"class":3425},[3412,14907,3454],{"class":3433},[3412,14909,13472],{"class":3495},[3412,14911,3893],{"class":3433},[3412,14913,14914,14916,14918],{"class":3414,"line":5434},[3412,14915,10786],{"class":3530},[3412,14917,13482],{"class":3429},[3412,14919,6908],{"class":3433},[3412,14921,14922,14925,14927,14929,14931,14933,14935,14937,14939,14941],{"class":3414,"line":5439},[3412,14923,14924],{"class":3425},"                f",[3412,14926,3507],{"class":3495},[3412,14928,3499],{"class":3425},[3412,14930,5110],{"class":3429},[3412,14932,3447],{"class":3433},[3412,14934,3451],{"class":3425},[3412,14936,6479],{"class":3433},[3412,14938,4113],{"class":3450},[3412,14940,3504],{"class":3425},[3412,14942,14943],{"class":3495}," використовує SafeHashMixin, але не має методу \"\n",[3412,14945,14946,14948],{"class":3414,"line":5456},[3412,14947,14924],{"class":3425},[3412,14949,14950],{"class":3495},"\"to_json(). Додайте JSONSerializableMixin до переліку батьків.\"\n",[3412,14952,14953],{"class":3414,"line":5462},[3412,14954,14955],{"class":3433},"            )\n",[3412,14957,14958,14960,14962,14964,14966,14968],{"class":3414,"line":5467},[3412,14959,3955],{"class":3530},[3412,14961,14836],{"class":3433},[3412,14963,3451],{"class":3425},[3412,14965,14841],{"class":3433},[3412,14967,13516],{"class":3924},[3412,14969,4724],{"class":3433},[3796,14971],{},[3803,14973,14975,14978],{"id":14974},"__init_subclass__-сучасна-альтернатива-метакласам",[3390,14976,14977],{},"__init_subclass__",": сучасна альтернатива метакласам",[3399,14980,14981,14985,14986,14989],{},[3764,14982,14983],{},[3390,14984,14977],{}," (PEP 487, Python 3.6+) — це метод, що автоматично викликається Python щоразу, коли оголошується ",[3764,14987,14988],{},"новий підклас"," даного класу. Це дозволяє базовому класу реагувати на факт успадкування без використання метакласів.",[3403,14991,14993],{"className":3405,"code":14992,"language":3407,"meta":3408,"style":3408},"class Base:\n    def __init_subclass__(cls, \u002F, **kwargs):\n        \"\"\"\n        Викликається при оголошенні КОЖНОГО підкласу Base.\n        cls — це щойно створений підклас (не екземпляр!).\n        kwargs — ключові аргументи, передані в дужках при оголошенні.\n        \"\"\"\n        super().__init_subclass__(**kwargs)  # обов'язково для кооперативності\n        print(f\"Новий підклас: {cls.__name__}\")\n\nclass Child(Base):      # → друкує: «Новий підклас: Child»\n    pass\n\nclass GrandChild(Child): # → друкує: «Новий підклас: GrandChild»\n    pass\n",[3390,14994,14995,15004,15022,15026,15031,15036,15041,15045,15058,15082,15086,15103,15107,15111,15126],{"__ignoreMap":3408},[3412,14996,14997,14999,15002],{"class":3414,"line":3415},[3412,14998,3426],{"class":3425},[3412,15000,15001],{"class":3429}," Base",[3412,15003,3434],{"class":3433},[3412,15005,15006,15008,15011,15013,15015,15018,15020],{"class":3414,"line":3422},[3412,15007,3440],{"class":3425},[3412,15009,15010],{"class":3443}," __init_subclass__",[3412,15012,3447],{"class":3433},[3412,15014,6497],{"class":3450},[3412,15016,15017],{"class":3433},", \u002F, **",[3412,15019,7537],{"class":3450},[3412,15021,3893],{"class":3433},[3412,15023,15024],{"class":3414,"line":3437},[3412,15025,6880],{"class":3495},[3412,15027,15028],{"class":3414,"line":3468},[3412,15029,15030],{"class":3495},"        Викликається при оголошенні КОЖНОГО підкласу Base.\n",[3412,15032,15033],{"class":3414,"line":3516},[3412,15034,15035],{"class":3495},"        cls — це щойно створений підклас (не екземпляр!).\n",[3412,15037,15038],{"class":3414,"line":3550},[3412,15039,15040],{"class":3495},"        kwargs — ключові аргументи, передані в дужках при оголошенні.\n",[3412,15042,15043],{"class":3414,"line":3557},[3412,15044,6880],{"class":3495},[3412,15046,15047,15049,15051,15053,15055],{"class":3414,"line":3567},[3412,15048,4236],{"class":3429},[3412,15050,4239],{"class":3433},[3412,15052,14977],{"class":3443},[3412,15054,7572],{"class":3433},[3412,15056,15057],{"class":3418},"# обов'язково для кооперативності\n",[3412,15059,15060,15062,15064,15066,15069,15072,15074,15076,15078,15080],{"class":3414,"line":3588},[3412,15061,4039],{"class":3443},[3412,15063,3447],{"class":3433},[3412,15065,3492],{"class":3425},[3412,15067,15068],{"class":3495},"\"Новий підклас: ",[3412,15070,15071],{"class":3425},"{cls",[3412,15073,3539],{"class":3433},[3412,15075,4113],{"class":3450},[3412,15077,3504],{"class":3425},[3412,15079,3507],{"class":3495},[3412,15081,4069],{"class":3433},[3412,15083,15084],{"class":3414,"line":3628},[3412,15085,3554],{"emptyLinePlaceholder":3553},[3412,15087,15088,15090,15092,15094,15097,15100],{"class":3414,"line":3655},[3412,15089,3426],{"class":3425},[3412,15091,6387],{"class":3429},[3412,15093,3447],{"class":3433},[3412,15095,15096],{"class":3429},"Base",[3412,15098,15099],{"class":3433},"):      ",[3412,15101,15102],{"class":3418},"# → друкує: «Новий підклас: Child»\n",[3412,15104,15105],{"class":3414,"line":3660},[3412,15106,6399],{"class":3530},[3412,15108,15109],{"class":3414,"line":3670},[3412,15110,3554],{"emptyLinePlaceholder":3553},[3412,15112,15113,15115,15117,15119,15121,15123],{"class":3414,"line":3691},[3412,15114,3426],{"class":3425},[3412,15116,6664],{"class":3429},[3412,15118,3447],{"class":3433},[3412,15120,6669],{"class":3429},[3412,15122,3484],{"class":3433},[3412,15124,15125],{"class":3418},"# → друкує: «Новий підклас: GrandChild»\n",[3412,15127,15128],{"class":3414,"line":3731},[3412,15129,6399],{"class":3530},[3399,15131,15132],{},[3764,15133,15134],{},"Практичне застосування — автоматичний реєстр без метакласів:",[3403,15136,15138],{"className":3405,"code":15137,"language":3407,"meta":3408,"style":3408},"# auto_registry.py\nfrom abc import ABC, abstractmethod\n\nclass Serializer(ABC):\n    \"\"\"\n    Базовий клас для серіалізаторів з автоматичним реєстром форматів.\n    Підкласи оголошуються з аргументом format_name='...' у дужках.\n    \"\"\"\n    _registry: dict[str, type[\"Serializer\"]] = {}\n\n    def __init_subclass__(cls, format_name: str | None = None, **kwargs) -> None:\n        super().__init_subclass__(**kwargs)\n        if format_name is not None:\n            # Реєструємо підклас у момент його оголошення\n            Serializer._registry[format_name] = cls\n\n    @abstractmethod\n    def dumps(self, data: object) -> str: ...\n\n    @abstractmethod\n    def loads(self, raw: str) -> object: ...\n\n    @classmethod\n    def for_format(cls, fmt: str) -> \"Serializer\":\n        try:\n            return cls._registry[fmt]()\n        except KeyError:\n            raise ValueError(f\"Невідомий формат: {fmt!r}. Доступні: {list(cls._registry)}\")\n\n\n# Реєстрація відбувається АВТОМАТИЧНО при оголошенні класу:\nclass JsonSerializer(Serializer, format_name=\"json\"):\n    def dumps(self, data) -> str:\n        import json; return json.dumps(data, ensure_ascii=False)\n    def loads(self, raw):\n        import json; return json.loads(raw)\n\nclass YamlSerializer(Serializer, format_name=\"yaml\"):\n    def dumps(self, data) -> str:\n        return \"\\n\".join(f\"{k}: {v}\" for k, v in data.items())\n    def loads(self, raw):\n        return dict(line.split(\": \", 1) for line in raw.splitlines() if \": \" in line)\n\n\n# Використання фабрики — жодного явного імпорту конкретних класів:\nserializer = Serializer.for_format(\"json\")\nprint(serializer.dumps({\"name\": \"Alice\", \"age\": 30}))\n# {\"name\": \"Alice\", \"age\": 30}\n\nprint(Serializer._registry)\n# {'json': \u003Cclass 'JsonSerializer'>, 'yaml': \u003Cclass 'YamlSerializer'>}\n",[3390,15139,15140,15145,15155,15159,15168,15172,15177,15182,15186,15202,15206,15243,15253,15268,15273,15281,15285,15289,15314,15318,15322,15348,15352,15358,15384,15391,15400,15410,15449,15453,15457,15462,15485,15505,15526,15542,15553,15557,15579,15599,15645,15661,15700,15704,15708,15713,15722,15750,15755,15759,15766],{"__ignoreMap":3408},[3412,15141,15142],{"class":3414,"line":3415},[3412,15143,15144],{"class":3418},"# auto_registry.py\n",[3412,15146,15147,15149,15151,15153],{"class":3414,"line":3422},[3412,15148,4148],{"class":3530},[3412,15150,11801],{"class":3433},[3412,15152,4154],{"class":3530},[3412,15154,11806],{"class":3433},[3412,15156,15157],{"class":3414,"line":3437},[3412,15158,3554],{"emptyLinePlaceholder":3553},[3412,15160,15161,15163,15166],{"class":3414,"line":3468},[3412,15162,3426],{"class":3425},[3412,15164,15165],{"class":3429}," Serializer",[3412,15167,11820],{"class":3433},[3412,15169,15170],{"class":3414,"line":3516},[3412,15171,10012],{"class":3495},[3412,15173,15174],{"class":3414,"line":3550},[3412,15175,15176],{"class":3495},"    Базовий клас для серіалізаторів з автоматичним реєстром форматів.\n",[3412,15178,15179],{"class":3414,"line":3557},[3412,15180,15181],{"class":3495},"    Підкласи оголошуються з аргументом format_name='...' у дужках.\n",[3412,15183,15184],{"class":3414,"line":3567},[3412,15185,10012],{"class":3495},[3412,15187,15188,15191,15193,15196,15199],{"class":3414,"line":3588},[3412,15189,15190],{"class":3433},"    _registry: dict[",[3412,15192,3880],{"class":3429},[3412,15194,15195],{"class":3433},", type[",[3412,15197,15198],{"class":3495},"\"Serializer\"",[3412,15200,15201],{"class":3433},"]] = {}\n",[3412,15203,15204],{"class":3414,"line":3628},[3412,15205,3554],{"emptyLinePlaceholder":3553},[3412,15207,15208,15210,15212,15214,15216,15218,15221,15223,15225,15227,15229,15231,15233,15235,15237,15239,15241],{"class":3414,"line":3655},[3412,15209,3440],{"class":3425},[3412,15211,15010],{"class":3443},[3412,15213,3447],{"class":3433},[3412,15215,6497],{"class":3450},[3412,15217,3454],{"class":3433},[3412,15219,15220],{"class":3450},"format_name",[3412,15222,3877],{"class":3433},[3412,15224,3880],{"class":3429},[3412,15226,9654],{"class":3433},[3412,15228,4011],{"class":3425},[3412,15230,3921],{"class":3433},[3412,15232,4011],{"class":3425},[3412,15234,7534],{"class":3433},[3412,15236,7537],{"class":3450},[3412,15238,3946],{"class":3433},[3412,15240,4011],{"class":3425},[3412,15242,3434],{"class":3433},[3412,15244,15245,15247,15249,15251],{"class":3414,"line":3660},[3412,15246,4236],{"class":3429},[3412,15248,4239],{"class":3433},[3412,15250,14977],{"class":3443},[3412,15252,7749],{"class":3433},[3412,15254,15255,15257,15260,15262,15264,15266],{"class":3414,"line":3670},[3412,15256,9686],{"class":3530},[3412,15258,15259],{"class":3433}," format_name ",[3412,15261,10120],{"class":3425},[3412,15263,10123],{"class":3425},[3412,15265,10126],{"class":3425},[3412,15267,3434],{"class":3433},[3412,15269,15270],{"class":3414,"line":3691},[3412,15271,15272],{"class":3418},"            # Реєструємо підклас у момент його оголошення\n",[3412,15274,15275,15278],{"class":3414,"line":3731},[3412,15276,15277],{"class":3433},"            Serializer._registry[format_name] = ",[3412,15279,15280],{"class":3425},"cls\n",[3412,15282,15283],{"class":3414,"line":4072},[3412,15284,3554],{"emptyLinePlaceholder":3553},[3412,15286,15287],{"class":3414,"line":4077},[3412,15288,11834],{"class":3443},[3412,15290,15291,15293,15296,15298,15300,15302,15304,15306,15308,15310,15312],{"class":3414,"line":4095},[3412,15292,3440],{"class":3425},[3412,15294,15295],{"class":3443}," dumps",[3412,15297,3447],{"class":3433},[3412,15299,3451],{"class":3450},[3412,15301,3454],{"class":3433},[3412,15303,6863],{"class":3450},[3412,15305,3877],{"class":3433},[3412,15307,3815],{"class":3429},[3412,15309,3946],{"class":3433},[3412,15311,3880],{"class":3429},[3412,15313,11851],{"class":3433},[3412,15315,15316],{"class":3414,"line":5014},[3412,15317,3554],{"emptyLinePlaceholder":3553},[3412,15319,15320],{"class":3414,"line":5020},[3412,15321,11834],{"class":3443},[3412,15323,15324,15326,15329,15331,15333,15335,15338,15340,15342,15344,15346],{"class":3414,"line":5026},[3412,15325,3440],{"class":3425},[3412,15327,15328],{"class":3443}," loads",[3412,15330,3447],{"class":3433},[3412,15332,3451],{"class":3450},[3412,15334,3454],{"class":3433},[3412,15336,15337],{"class":3450},"raw",[3412,15339,3877],{"class":3433},[3412,15341,3880],{"class":3429},[3412,15343,3946],{"class":3433},[3412,15345,3815],{"class":3429},[3412,15347,11851],{"class":3433},[3412,15349,15350],{"class":3414,"line":5035},[3412,15351,3554],{"emptyLinePlaceholder":3553},[3412,15353,15354,15356],{"class":3414,"line":5046},[3412,15355,6844],{"class":3443},[3412,15357,6847],{"class":3429},[3412,15359,15360,15362,15365,15367,15369,15371,15374,15376,15378,15380,15382],{"class":3414,"line":5057},[3412,15361,3440],{"class":3425},[3412,15363,15364],{"class":3443}," for_format",[3412,15366,3447],{"class":3433},[3412,15368,6497],{"class":3450},[3412,15370,3454],{"class":3433},[3412,15372,15373],{"class":3450},"fmt",[3412,15375,3877],{"class":3433},[3412,15377,3880],{"class":3429},[3412,15379,3946],{"class":3433},[3412,15381,15198],{"class":3495},[3412,15383,3434],{"class":3433},[3412,15385,15386,15389],{"class":3414,"line":5345},[3412,15387,15388],{"class":3530},"        try",[3412,15390,3434],{"class":3433},[3412,15392,15393,15395,15397],{"class":3414,"line":5364},[3412,15394,9707],{"class":3530},[3412,15396,5331],{"class":3425},[3412,15398,15399],{"class":3433},"._registry[fmt]()\n",[3412,15401,15402,15405,15408],{"class":3414,"line":5370},[3412,15403,15404],{"class":3530},"        except",[3412,15406,15407],{"class":3429}," KeyError",[3412,15409,3434],{"class":3433},[3412,15411,15412,15414,15416,15418,15420,15423,15425,15427,15429,15432,15434,15436,15438,15440,15443,15445,15447],{"class":3414,"line":5376},[3412,15413,10786],{"class":3530},[3412,15415,10789],{"class":3429},[3412,15417,3447],{"class":3433},[3412,15419,3492],{"class":3425},[3412,15421,15422],{"class":3495},"\"Невідомий формат: ",[3412,15424,3499],{"class":3425},[3412,15426,15373],{"class":3433},[3412,15428,4125],{"class":3425},[3412,15430,15431],{"class":3495},". Доступні: ",[3412,15433,3499],{"class":3425},[3412,15435,13214],{"class":3429},[3412,15437,3447],{"class":3433},[3412,15439,6497],{"class":3425},[3412,15441,15442],{"class":3433},"._registry)",[3412,15444,3504],{"class":3425},[3412,15446,3507],{"class":3495},[3412,15448,4069],{"class":3433},[3412,15450,15451],{"class":3414,"line":5382},[3412,15452,3554],{"emptyLinePlaceholder":3553},[3412,15454,15455],{"class":3414,"line":5388},[3412,15456,3554],{"emptyLinePlaceholder":3553},[3412,15458,15459],{"class":3414,"line":5393},[3412,15460,15461],{"class":3418},"# Реєстрація відбувається АВТОМАТИЧНО при оголошенні класу:\n",[3412,15463,15464,15466,15469,15471,15474,15476,15478,15480,15483],{"class":3414,"line":5399},[3412,15465,3426],{"class":3425},[3412,15467,15468],{"class":3429}," JsonSerializer",[3412,15470,3447],{"class":3433},[3412,15472,15473],{"class":3429},"Serializer",[3412,15475,3454],{"class":3433},[3412,15477,15220],{"class":3450},[3412,15479,4691],{"class":3433},[3412,15481,15482],{"class":3495},"\"json\"",[3412,15484,3893],{"class":3433},[3412,15486,15487,15489,15491,15493,15495,15497,15499,15501,15503],{"class":3414,"line":5422},[3412,15488,3440],{"class":3425},[3412,15490,15295],{"class":3443},[3412,15492,3447],{"class":3433},[3412,15494,3451],{"class":3450},[3412,15496,3454],{"class":3433},[3412,15498,6863],{"class":3450},[3412,15500,3946],{"class":3433},[3412,15502,3880],{"class":3429},[3412,15504,3434],{"class":3433},[3412,15506,15507,15510,15513,15515,15518,15520,15522,15524],{"class":3414,"line":5428},[3412,15508,15509],{"class":3530},"        import",[3412,15511,15512],{"class":3433}," json; ",[3412,15514,3531],{"class":3530},[3412,15516,15517],{"class":3433}," json.dumps(data, ",[3412,15519,12788],{"class":3450},[3412,15521,4691],{"class":3433},[3412,15523,5733],{"class":3425},[3412,15525,4069],{"class":3433},[3412,15527,15528,15530,15532,15534,15536,15538,15540],{"class":3414,"line":5434},[3412,15529,3440],{"class":3425},[3412,15531,15328],{"class":3443},[3412,15533,3447],{"class":3433},[3412,15535,3451],{"class":3450},[3412,15537,3454],{"class":3433},[3412,15539,15337],{"class":3450},[3412,15541,3893],{"class":3433},[3412,15543,15544,15546,15548,15550],{"class":3414,"line":5439},[3412,15545,15509],{"class":3530},[3412,15547,15512],{"class":3433},[3412,15549,3531],{"class":3530},[3412,15551,15552],{"class":3433}," json.loads(raw)\n",[3412,15554,15555],{"class":3414,"line":5456},[3412,15556,3554],{"emptyLinePlaceholder":3553},[3412,15558,15559,15561,15564,15566,15568,15570,15572,15574,15577],{"class":3414,"line":5462},[3412,15560,3426],{"class":3425},[3412,15562,15563],{"class":3429}," YamlSerializer",[3412,15565,3447],{"class":3433},[3412,15567,15473],{"class":3429},[3412,15569,3454],{"class":3433},[3412,15571,15220],{"class":3450},[3412,15573,4691],{"class":3433},[3412,15575,15576],{"class":3495},"\"yaml\"",[3412,15578,3893],{"class":3433},[3412,15580,15581,15583,15585,15587,15589,15591,15593,15595,15597],{"class":3414,"line":5467},[3412,15582,3440],{"class":3425},[3412,15584,15295],{"class":3443},[3412,15586,3447],{"class":3433},[3412,15588,3451],{"class":3450},[3412,15590,3454],{"class":3433},[3412,15592,6863],{"class":3450},[3412,15594,3946],{"class":3433},[3412,15596,3880],{"class":3429},[3412,15598,3434],{"class":3433},[3412,15600,15601,15603,15606,15608,15610,15613,15615,15617,15619,15622,15624,15626,15628,15631,15633,15635,15637,15640,15642],{"class":3414,"line":5473},[3412,15602,3955],{"class":3530},[3412,15604,15605],{"class":3495}," \"",[3412,15607,5319],{"class":5318},[3412,15609,3507],{"class":3495},[3412,15611,15612],{"class":3433},".join(",[3412,15614,3492],{"class":3425},[3412,15616,3507],{"class":3495},[3412,15618,3499],{"class":3425},[3412,15620,15621],{"class":3433},"k",[3412,15623,3504],{"class":3425},[3412,15625,3877],{"class":3495},[3412,15627,3499],{"class":3425},[3412,15629,15630],{"class":3433},"v",[3412,15632,3504],{"class":3425},[3412,15634,3507],{"class":3495},[3412,15636,7881],{"class":3530},[3412,15638,15639],{"class":3433}," k, v ",[3412,15641,4744],{"class":3530},[3412,15643,15644],{"class":3433}," data.items())\n",[3412,15646,15647,15649,15651,15653,15655,15657,15659],{"class":3414,"line":5491},[3412,15648,3440],{"class":3425},[3412,15650,15328],{"class":3443},[3412,15652,3447],{"class":3433},[3412,15654,3451],{"class":3450},[3412,15656,3454],{"class":3433},[3412,15658,15337],{"class":3450},[3412,15660,3893],{"class":3433},[3412,15662,15663,15665,15668,15671,15674,15676,15678,15680,15682,15685,15687,15690,15692,15695,15697],{"class":3414,"line":5496},[3412,15664,3955],{"class":3530},[3412,15666,15667],{"class":3429}," dict",[3412,15669,15670],{"class":3433},"(line.split(",[3412,15672,15673],{"class":3495},"\": \"",[3412,15675,3454],{"class":3433},[3412,15677,7331],{"class":3924},[3412,15679,10854],{"class":3433},[3412,15681,4738],{"class":3530},[3412,15683,15684],{"class":3433}," line ",[3412,15686,4744],{"class":3530},[3412,15688,15689],{"class":3433}," raw.splitlines() ",[3412,15691,11031],{"class":3530},[3412,15693,15694],{"class":3495}," \": \"",[3412,15696,5334],{"class":3530},[3412,15698,15699],{"class":3433}," line)\n",[3412,15701,15702],{"class":3414,"line":5502},[3412,15703,3554],{"emptyLinePlaceholder":3553},[3412,15705,15706],{"class":3414,"line":5518},[3412,15707,3554],{"emptyLinePlaceholder":3553},[3412,15709,15710],{"class":3414,"line":5533},[3412,15711,15712],{"class":3418},"# Використання фабрики — жодного явного імпорту конкретних класів:\n",[3412,15714,15715,15718,15720],{"class":3414,"line":5548},[3412,15716,15717],{"class":3433},"serializer = Serializer.for_format(",[3412,15719,15482],{"class":3495},[3412,15721,4069],{"class":3433},[3412,15723,15724,15726,15729,15732,15734,15737,15739,15742,15744,15747],{"class":3414,"line":5568},[3412,15725,3487],{"class":3443},[3412,15727,15728],{"class":3433},"(serializer.dumps({",[3412,15730,15731],{"class":3495},"\"name\"",[3412,15733,3877],{"class":3433},[3412,15735,15736],{"class":3495},"\"Alice\"",[3412,15738,3454],{"class":3433},[3412,15740,15741],{"class":3495},"\"age\"",[3412,15743,3877],{"class":3433},[3412,15745,15746],{"class":3924},"30",[3412,15748,15749],{"class":3433},"}))\n",[3412,15751,15752],{"class":3414,"line":5573},[3412,15753,15754],{"class":3418},"# {\"name\": \"Alice\", \"age\": 30}\n",[3412,15756,15757],{"class":3414,"line":5579},[3412,15758,3554],{"emptyLinePlaceholder":3553},[3412,15760,15761,15763],{"class":3414,"line":5595},[3412,15762,3487],{"class":3443},[3412,15764,15765],{"class":3433},"(Serializer._registry)\n",[3412,15767,15768],{"class":3414,"line":5609},[3412,15769,15770],{"class":3418},"# {'json': \u003Cclass 'JsonSerializer'>, 'yaml': \u003Cclass 'YamlSerializer'>}\n",[9511,15772,15773,15779,15780,15782,15783,3539],{},[3764,15774,15775,15776,15778],{},"Ключова перевага ",[3390,15777,14977],{}," над метакласами:"," метакласи змінюють сам процес створення класу і можуть вступати в конфлікт між собою (неможливо мати два різні метакласи в одній ієрархії без спеціального об'єднання). ",[3390,15781,14977],{}," — це звичайний метод, що бере участь у MRO за стандартними правилами кооперативного наслідування через ",[3390,15784,15785],{},"super().__init_subclass__(**kwargs)",[3796,15787],{},[3394,15789,15791],{"id":15790},"частина-viii-практичні-завдання","Частина VIII: Практичні завдання",[3803,15793,15795],{"id":15794},"рівень-1-базовий-ієрархія-геометричних-фігур","Рівень 1 (Базовий): Ієрархія геометричних фігур",[3399,15797,15798,15799,15801],{},"Закріпіть синтаксис одиночного наслідування, ",[3390,15800,3392],{}," та перевизначення методів.",[3399,15803,15804,15807,15808,15811,15812,15815,15816,3539],{},[3764,15805,15806],{},"Завдання:"," Реалізуйте ієрархію ",[3390,15809,15810],{},"Shape → Polygon → Triangle\u002FRectangle → Square",". Кожен клас повинен коректно передавати аргументи через ",[3390,15813,15814],{},"super().__init__()"," та перевизначати метод ",[3390,15817,15818],{},"area()",[3403,15820,15822],{"className":3405,"code":15821,"language":3407,"meta":3408,"style":3408},"# shapes.py\nimport math\n\n\nclass Shape:\n    \"\"\"Абстрактний базовий клас для фігур.\"\"\"\n\n    def __init__(self, color: str = \"black\"):\n        self.color = color\n\n    def area(self) -> float:\n        raise NotImplementedError(f\"{type(self).__name__} має реалізувати area()\")\n\n    def perimeter(self) -> float:\n        raise NotImplementedError(f\"{type(self).__name__} має реалізувати perimeter()\")\n\n    def describe(self) -> str:\n        return (\n            f\"{self.__class__.__name__} [{self.color}]: \"\n            f\"площа={self.area():.2f}, периметр={self.perimeter():.2f}\"\n        )\n\n\nclass Polygon(Shape):\n    \"\"\"Багатокутник: знає кількість сторін.\"\"\"\n\n    def __init__(self, sides: int, color: str = \"black\"):\n        super().__init__(color)\n        self.sides = sides\n\n    def describe(self) -> str:\n        base = super().describe()\n        return f\"{base}, сторін={self.sides}\"\n\n\nclass Rectangle(Polygon):\n    def __init__(self, width: float, height: float, color: str = \"black\"):\n        super().__init__(sides=4, color=color)\n        self.width = width\n        self.height = height\n\n    def area(self) -> float:\n        return self.width * self.height\n\n    def perimeter(self) -> float:\n        return 2 * (self.width + self.height)\n\n\nclass Square(Rectangle):\n    def __init__(self, side: float, color: str = \"black\"):\n        super().__init__(width=side, height=side, color=color)\n        self.side = side\n\n    # Не перевизначаємо area() та perimeter() — Rectangle вже правильно рахує\n\n\nclass Triangle(Polygon):\n    def __init__(self, a: float, b: float, c: float, color: str = \"black\"):\n        if a + b \u003C= c or a + c \u003C= b or b + c \u003C= a:\n            raise ValueError(f\"Недопустимі сторони: {a}, {b}, {c}\")\n        super().__init__(sides=3, color=color)\n        self.a, self.b, self.c = a, b, c\n\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    def perimeter(self) -> float:\n        return self.a + self.b + self.c\n\n\n# Тест\nshapes: list[Shape] = [\n    Rectangle(4, 6, \"blue\"),\n    Square(5, \"red\"),\n    Triangle(3, 4, 5, \"green\"),\n]\n\nfor shape in shapes:\n    print(shape.describe())\n    print(f\"  isinstance(shape, Shape):   {isinstance(shape, Shape)}\")\n    print(f\"  isinstance(shape, Polygon): {isinstance(shape, Polygon)}\")\n    print()\n\n# Перевірка MRO\nprint(\"Square MRO:\", [c.__name__ for c in Square.__mro__])\n",[3390,15823,15824,15829,15835,15839,15843,15851,15856,15860,15884,15890,15894,15910,15943,15947,15963,15994,15998,16015,16022,16054,16080,16084,16088,16092,16105,16110,16114,16147,16158,16165,16169,16185,16194,16220,16224,16228,16241,16281,16304,16310,16316,16320,16336,16348,16352,16368,16384,16388,16392,16404,16436,16459,16465,16469,16474,16478,16482,16495,16545,16562,16601,16623,16640,16644,16663,16675,16697,16701,16717,16736,16740,16744,16748,16753,16770,16784,16806,16810,16814,16826,16833,16857,16881,16887,16891,16896],{"__ignoreMap":3408},[3412,15825,15826],{"class":3414,"line":3415},[3412,15827,15828],{"class":3418},"# shapes.py\n",[3412,15830,15831,15833],{"class":3414,"line":3422},[3412,15832,4154],{"class":3530},[3412,15834,11794],{"class":3433},[3412,15836,15837],{"class":3414,"line":3437},[3412,15838,3554],{"emptyLinePlaceholder":3553},[3412,15840,15841],{"class":3414,"line":3468},[3412,15842,3554],{"emptyLinePlaceholder":3553},[3412,15844,15845,15847,15849],{"class":3414,"line":3516},[3412,15846,3426],{"class":3425},[3412,15848,11817],{"class":3429},[3412,15850,3434],{"class":3433},[3412,15852,15853],{"class":3414,"line":3550},[3412,15854,15855],{"class":3495},"    \"\"\"Абстрактний базовий клас для фігур.\"\"\"\n",[3412,15857,15858],{"class":3414,"line":3557},[3412,15859,3554],{"emptyLinePlaceholder":3553},[3412,15861,15862,15864,15866,15868,15870,15872,15874,15876,15878,15880,15882],{"class":3414,"line":3567},[3412,15863,3440],{"class":3425},[3412,15865,3865],{"class":3443},[3412,15867,3447],{"class":3433},[3412,15869,3451],{"class":3450},[3412,15871,3454],{"class":3433},[3412,15873,7522],{"class":3450},[3412,15875,3877],{"class":3433},[3412,15877,3880],{"class":3429},[3412,15879,3921],{"class":3433},[3412,15881,7531],{"class":3495},[3412,15883,3893],{"class":3433},[3412,15885,15886,15888],{"class":3414,"line":3588},[3412,15887,3898],{"class":3425},[3412,15889,7582],{"class":3433},[3412,15891,15892],{"class":3414,"line":3628},[3412,15893,3554],{"emptyLinePlaceholder":3553},[3412,15895,15896,15898,15900,15902,15904,15906,15908],{"class":3414,"line":3655},[3412,15897,3440],{"class":3425},[3412,15899,11335],{"class":3443},[3412,15901,3447],{"class":3433},[3412,15903,3451],{"class":3450},[3412,15905,3946],{"class":3433},[3412,15907,3918],{"class":3429},[3412,15909,3434],{"class":3433},[3412,15911,15912,15915,15918,15920,15922,15924,15926,15928,15930,15932,15934,15936,15938,15941],{"class":3414,"line":3660},[3412,15913,15914],{"class":3530},"        raise",[3412,15916,15917],{"class":3429}," NotImplementedError",[3412,15919,3447],{"class":3433},[3412,15921,3492],{"class":3425},[3412,15923,3507],{"class":3495},[3412,15925,3499],{"class":3425},[3412,15927,5110],{"class":3429},[3412,15929,3447],{"class":3433},[3412,15931,3451],{"class":3425},[3412,15933,6479],{"class":3433},[3412,15935,4113],{"class":3450},[3412,15937,3504],{"class":3425},[3412,15939,15940],{"class":3495}," має реалізувати area()\"",[3412,15942,4069],{"class":3433},[3412,15944,15945],{"class":3414,"line":3670},[3412,15946,3554],{"emptyLinePlaceholder":3553},[3412,15948,15949,15951,15953,15955,15957,15959,15961],{"class":3414,"line":3691},[3412,15950,3440],{"class":3425},[3412,15952,11866],{"class":3443},[3412,15954,3447],{"class":3433},[3412,15956,3451],{"class":3450},[3412,15958,3946],{"class":3433},[3412,15960,3918],{"class":3429},[3412,15962,3434],{"class":3433},[3412,15964,15965,15967,15969,15971,15973,15975,15977,15979,15981,15983,15985,15987,15989,15992],{"class":3414,"line":3731},[3412,15966,15914],{"class":3530},[3412,15968,15917],{"class":3429},[3412,15970,3447],{"class":3433},[3412,15972,3492],{"class":3425},[3412,15974,3507],{"class":3495},[3412,15976,3499],{"class":3425},[3412,15978,5110],{"class":3429},[3412,15980,3447],{"class":3433},[3412,15982,3451],{"class":3425},[3412,15984,6479],{"class":3433},[3412,15986,4113],{"class":3450},[3412,15988,3504],{"class":3425},[3412,15990,15991],{"class":3495}," має реалізувати perimeter()\"",[3412,15993,4069],{"class":3433},[3412,15995,15996],{"class":3414,"line":4072},[3412,15997,3554],{"emptyLinePlaceholder":3553},[3412,15999,16000,16002,16005,16007,16009,16011,16013],{"class":3414,"line":4077},[3412,16001,3440],{"class":3425},[3412,16003,16004],{"class":3443}," describe",[3412,16006,3447],{"class":3433},[3412,16008,3451],{"class":3450},[3412,16010,3946],{"class":3433},[3412,16012,3880],{"class":3429},[3412,16014,3434],{"class":3433},[3412,16016,16017,16019],{"class":3414,"line":4095},[3412,16018,3955],{"class":3530},[3412,16020,16021],{"class":3433}," (\n",[3412,16023,16024,16027,16029,16031,16033,16035,16037,16039,16041,16044,16046,16049,16051],{"class":3414,"line":5014},[3412,16025,16026],{"class":3425},"            f",[3412,16028,3507],{"class":3495},[3412,16030,3964],{"class":3425},[3412,16032,3539],{"class":3433},[3412,16034,4108],{"class":3450},[3412,16036,3539],{"class":3433},[3412,16038,4113],{"class":3450},[3412,16040,3504],{"class":3425},[3412,16042,16043],{"class":3495}," [",[3412,16045,3964],{"class":3425},[3412,16047,16048],{"class":3433},".color",[3412,16050,3504],{"class":3425},[3412,16052,16053],{"class":3495},"]: \"\n",[3412,16055,16056,16058,16061,16063,16066,16068,16071,16073,16076,16078],{"class":3414,"line":5020},[3412,16057,16026],{"class":3425},[3412,16059,16060],{"class":3495},"\"площа=",[3412,16062,3964],{"class":3425},[3412,16064,16065],{"class":3433},".area()",[3412,16067,12202],{"class":3425},[3412,16069,16070],{"class":3495},", периметр=",[3412,16072,3964],{"class":3425},[3412,16074,16075],{"class":3433},".perimeter()",[3412,16077,12202],{"class":3425},[3412,16079,8434],{"class":3495},[3412,16081,16082],{"class":3414,"line":5026},[3412,16083,6941],{"class":3433},[3412,16085,16086],{"class":3414,"line":5035},[3412,16087,3554],{"emptyLinePlaceholder":3553},[3412,16089,16090],{"class":3414,"line":5046},[3412,16091,3554],{"emptyLinePlaceholder":3553},[3412,16093,16094,16096,16099,16101,16103],{"class":3414,"line":5057},[3412,16095,3426],{"class":3425},[3412,16097,16098],{"class":3429}," Polygon",[3412,16100,3447],{"class":3433},[3412,16102,11776],{"class":3429},[3412,16104,3893],{"class":3433},[3412,16106,16107],{"class":3414,"line":5345},[3412,16108,16109],{"class":3495},"    \"\"\"Багатокутник: знає кількість сторін.\"\"\"\n",[3412,16111,16112],{"class":3414,"line":5364},[3412,16113,3554],{"emptyLinePlaceholder":3553},[3412,16115,16116,16118,16120,16122,16124,16126,16129,16131,16133,16135,16137,16139,16141,16143,16145],{"class":3414,"line":5370},[3412,16117,3440],{"class":3425},[3412,16119,3865],{"class":3443},[3412,16121,3447],{"class":3433},[3412,16123,3451],{"class":3450},[3412,16125,3454],{"class":3433},[3412,16127,16128],{"class":3450},"sides",[3412,16130,3877],{"class":3433},[3412,16132,3890],{"class":3429},[3412,16134,3454],{"class":3433},[3412,16136,7522],{"class":3450},[3412,16138,3877],{"class":3433},[3412,16140,3880],{"class":3429},[3412,16142,3921],{"class":3433},[3412,16144,7531],{"class":3495},[3412,16146,3893],{"class":3433},[3412,16148,16149,16151,16153,16155],{"class":3414,"line":5376},[3412,16150,4236],{"class":3429},[3412,16152,4239],{"class":3433},[3412,16154,4242],{"class":3443},[3412,16156,16157],{"class":3433},"(color)\n",[3412,16159,16160,16162],{"class":3414,"line":5382},[3412,16161,3898],{"class":3425},[3412,16163,16164],{"class":3433},".sides = sides\n",[3412,16166,16167],{"class":3414,"line":5388},[3412,16168,3554],{"emptyLinePlaceholder":3553},[3412,16170,16171,16173,16175,16177,16179,16181,16183],{"class":3414,"line":5393},[3412,16172,3440],{"class":3425},[3412,16174,16004],{"class":3443},[3412,16176,3447],{"class":3433},[3412,16178,3451],{"class":3450},[3412,16180,3946],{"class":3433},[3412,16182,3880],{"class":3429},[3412,16184,3434],{"class":3433},[3412,16186,16187,16189,16191],{"class":3414,"line":5399},[3412,16188,4282],{"class":3433},[3412,16190,4285],{"class":3429},[3412,16192,16193],{"class":3433},"().describe()\n",[3412,16195,16196,16198,16200,16202,16204,16206,16208,16211,16213,16216,16218],{"class":3414,"line":5422},[3412,16197,3955],{"class":3530},[3412,16199,3958],{"class":3425},[3412,16201,3507],{"class":3495},[3412,16203,3499],{"class":3425},[3412,16205,4301],{"class":3433},[3412,16207,3504],{"class":3425},[3412,16209,16210],{"class":3495},", сторін=",[3412,16212,3964],{"class":3425},[3412,16214,16215],{"class":3433},".sides",[3412,16217,3504],{"class":3425},[3412,16219,8434],{"class":3495},[3412,16221,16222],{"class":3414,"line":5428},[3412,16223,3554],{"emptyLinePlaceholder":3553},[3412,16225,16226],{"class":3414,"line":5434},[3412,16227,3554],{"emptyLinePlaceholder":3553},[3412,16229,16230,16232,16234,16236,16239],{"class":3414,"line":5439},[3412,16231,3426],{"class":3425},[3412,16233,11278],{"class":3429},[3412,16235,3447],{"class":3433},[3412,16237,16238],{"class":3429},"Polygon",[3412,16240,3893],{"class":3433},[3412,16242,16243,16245,16247,16249,16251,16253,16255,16257,16259,16261,16263,16265,16267,16269,16271,16273,16275,16277,16279],{"class":3414,"line":5456},[3412,16244,3440],{"class":3425},[3412,16246,3865],{"class":3443},[3412,16248,3447],{"class":3433},[3412,16250,3451],{"class":3450},[3412,16252,3454],{"class":3433},[3412,16254,11295],{"class":3450},[3412,16256,3877],{"class":3433},[3412,16258,3918],{"class":3429},[3412,16260,3454],{"class":3433},[3412,16262,11304],{"class":3450},[3412,16264,3877],{"class":3433},[3412,16266,3918],{"class":3429},[3412,16268,3454],{"class":3433},[3412,16270,7522],{"class":3450},[3412,16272,3877],{"class":3433},[3412,16274,3880],{"class":3429},[3412,16276,3921],{"class":3433},[3412,16278,7531],{"class":3495},[3412,16280,3893],{"class":3433},[3412,16282,16283,16285,16287,16289,16291,16293,16295,16297,16299,16301],{"class":3414,"line":5462},[3412,16284,4236],{"class":3429},[3412,16286,4239],{"class":3433},[3412,16288,4242],{"class":3443},[3412,16290,3447],{"class":3433},[3412,16292,16128],{"class":3450},[3412,16294,4691],{"class":3433},[3412,16296,4224],{"class":3924},[3412,16298,3454],{"class":3433},[3412,16300,7522],{"class":3450},[3412,16302,16303],{"class":3433},"=color)\n",[3412,16305,16306,16308],{"class":3414,"line":5467},[3412,16307,3898],{"class":3425},[3412,16309,11317],{"class":3433},[3412,16311,16312,16314],{"class":3414,"line":5473},[3412,16313,3898],{"class":3425},[3412,16315,11324],{"class":3433},[3412,16317,16318],{"class":3414,"line":5491},[3412,16319,3554],{"emptyLinePlaceholder":3553},[3412,16321,16322,16324,16326,16328,16330,16332,16334],{"class":3414,"line":5496},[3412,16323,3440],{"class":3425},[3412,16325,11335],{"class":3443},[3412,16327,3447],{"class":3433},[3412,16329,3451],{"class":3450},[3412,16331,3946],{"class":3433},[3412,16333,3918],{"class":3429},[3412,16335,3434],{"class":3433},[3412,16337,16338,16340,16342,16344,16346],{"class":3414,"line":5502},[3412,16339,3955],{"class":3530},[3412,16341,10741],{"class":3425},[3412,16343,11354],{"class":3433},[3412,16345,3451],{"class":3425},[3412,16347,11359],{"class":3433},[3412,16349,16350],{"class":3414,"line":5518},[3412,16351,3554],{"emptyLinePlaceholder":3553},[3412,16353,16354,16356,16358,16360,16362,16364,16366],{"class":3414,"line":5533},[3412,16355,3440],{"class":3425},[3412,16357,11866],{"class":3443},[3412,16359,3447],{"class":3433},[3412,16361,3451],{"class":3450},[3412,16363,3946],{"class":3433},[3412,16365,3918],{"class":3429},[3412,16367,3434],{"class":3433},[3412,16369,16370,16372,16374,16376,16378,16380,16382],{"class":3414,"line":5548},[3412,16371,3955],{"class":3530},[3412,16373,11998],{"class":3924},[3412,16375,12001],{"class":3433},[3412,16377,3451],{"class":3425},[3412,16379,12006],{"class":3433},[3412,16381,3451],{"class":3425},[3412,16383,12011],{"class":3433},[3412,16385,16386],{"class":3414,"line":5568},[3412,16387,3554],{"emptyLinePlaceholder":3553},[3412,16389,16390],{"class":3414,"line":5573},[3412,16391,3554],{"emptyLinePlaceholder":3553},[3412,16393,16394,16396,16398,16400,16402],{"class":3414,"line":5579},[3412,16395,3426],{"class":3425},[3412,16397,11448],{"class":3429},[3412,16399,3447],{"class":3433},[3412,16401,11453],{"class":3429},[3412,16403,3893],{"class":3433},[3412,16405,16406,16408,16410,16412,16414,16416,16418,16420,16422,16424,16426,16428,16430,16432,16434],{"class":3414,"line":5595},[3412,16407,3440],{"class":3425},[3412,16409,3865],{"class":3443},[3412,16411,3447],{"class":3433},[3412,16413,3451],{"class":3450},[3412,16415,3454],{"class":3433},[3412,16417,12068],{"class":3450},[3412,16419,3877],{"class":3433},[3412,16421,3918],{"class":3429},[3412,16423,3454],{"class":3433},[3412,16425,7522],{"class":3450},[3412,16427,3877],{"class":3433},[3412,16429,3880],{"class":3429},[3412,16431,3921],{"class":3433},[3412,16433,7531],{"class":3495},[3412,16435,3893],{"class":3433},[3412,16437,16438,16440,16442,16444,16446,16448,16451,16453,16455,16457],{"class":3414,"line":5609},[3412,16439,4236],{"class":3429},[3412,16441,4239],{"class":3433},[3412,16443,4242],{"class":3443},[3412,16445,3447],{"class":3433},[3412,16447,11295],{"class":3450},[3412,16449,16450],{"class":3433},"=side, ",[3412,16452,11304],{"class":3450},[3412,16454,16450],{"class":3433},[3412,16456,7522],{"class":3450},[3412,16458,16303],{"class":3433},[3412,16460,16461,16463],{"class":3414,"line":7232},[3412,16462,3898],{"class":3425},[3412,16464,12081],{"class":3433},[3412,16466,16467],{"class":3414,"line":7261},[3412,16468,3554],{"emptyLinePlaceholder":3553},[3412,16470,16471],{"class":3414,"line":7266},[3412,16472,16473],{"class":3418},"    # Не перевизначаємо area() та perimeter() — Rectangle вже правильно рахує\n",[3412,16475,16476],{"class":3414,"line":7271},[3412,16477,3554],{"emptyLinePlaceholder":3553},[3412,16479,16480],{"class":3414,"line":7277},[3412,16481,3554],{"emptyLinePlaceholder":3553},[3412,16483,16484,16486,16489,16491,16493],{"class":3414,"line":7310},[3412,16485,3426],{"class":3425},[3412,16487,16488],{"class":3429}," Triangle",[3412,16490,3447],{"class":3433},[3412,16492,16238],{"class":3429},[3412,16494,3893],{"class":3433},[3412,16496,16497,16499,16501,16503,16505,16507,16510,16512,16514,16516,16519,16521,16523,16525,16527,16529,16531,16533,16535,16537,16539,16541,16543],{"class":3414,"line":7315},[3412,16498,3440],{"class":3425},[3412,16500,3865],{"class":3443},[3412,16502,3447],{"class":3433},[3412,16504,3451],{"class":3450},[3412,16506,3454],{"class":3433},[3412,16508,16509],{"class":3450},"a",[3412,16511,3877],{"class":3433},[3412,16513,3918],{"class":3429},[3412,16515,3454],{"class":3433},[3412,16517,16518],{"class":3450},"b",[3412,16520,3877],{"class":3433},[3412,16522,3918],{"class":3429},[3412,16524,3454],{"class":3433},[3412,16526,9823],{"class":3450},[3412,16528,3877],{"class":3433},[3412,16530,3918],{"class":3429},[3412,16532,3454],{"class":3433},[3412,16534,7522],{"class":3450},[3412,16536,3877],{"class":3433},[3412,16538,3880],{"class":3429},[3412,16540,3921],{"class":3433},[3412,16542,7531],{"class":3495},[3412,16544,3893],{"class":3433},[3412,16546,16547,16549,16552,16554,16557,16559],{"class":3414,"line":7321},[3412,16548,9686],{"class":3530},[3412,16550,16551],{"class":3433}," a + b \u003C= c ",[3412,16553,14195],{"class":3425},[3412,16555,16556],{"class":3433}," a + c \u003C= b ",[3412,16558,14195],{"class":3425},[3412,16560,16561],{"class":3433}," b + c \u003C= a:\n",[3412,16563,16564,16566,16568,16570,16572,16575,16577,16579,16581,16583,16585,16587,16589,16591,16593,16595,16597,16599],{"class":3414,"line":7346},[3412,16565,10786],{"class":3530},[3412,16567,10789],{"class":3429},[3412,16569,3447],{"class":3433},[3412,16571,3492],{"class":3425},[3412,16573,16574],{"class":3495},"\"Недопустимі сторони: ",[3412,16576,3499],{"class":3425},[3412,16578,16509],{"class":3433},[3412,16580,3504],{"class":3425},[3412,16582,3454],{"class":3495},[3412,16584,3499],{"class":3425},[3412,16586,16518],{"class":3433},[3412,16588,3504],{"class":3425},[3412,16590,3454],{"class":3495},[3412,16592,3499],{"class":3425},[3412,16594,9823],{"class":3433},[3412,16596,3504],{"class":3425},[3412,16598,3507],{"class":3495},[3412,16600,4069],{"class":3433},[3412,16602,16603,16605,16607,16609,16611,16613,16615,16617,16619,16621],{"class":3414,"line":7361},[3412,16604,4236],{"class":3429},[3412,16606,4239],{"class":3433},[3412,16608,4242],{"class":3443},[3412,16610,3447],{"class":3433},[3412,16612,16128],{"class":3450},[3412,16614,4691],{"class":3433},[3412,16616,11690],{"class":3924},[3412,16618,3454],{"class":3433},[3412,16620,7522],{"class":3450},[3412,16622,16303],{"class":3433},[3412,16624,16625,16627,16630,16632,16635,16637],{"class":3414,"line":7366},[3412,16626,3898],{"class":3425},[3412,16628,16629],{"class":3433},".a, ",[3412,16631,3451],{"class":3425},[3412,16633,16634],{"class":3433},".b, ",[3412,16636,3451],{"class":3425},[3412,16638,16639],{"class":3433},".c = a, b, c\n",[3412,16641,16642],{"class":3414,"line":7372},[3412,16643,3554],{"emptyLinePlaceholder":3553},[3412,16645,16646,16648,16650,16652,16654,16656,16658,16660],{"class":3414,"line":7378},[3412,16647,3440],{"class":3425},[3412,16649,11335],{"class":3443},[3412,16651,3447],{"class":3433},[3412,16653,3451],{"class":3450},[3412,16655,3946],{"class":3433},[3412,16657,3918],{"class":3429},[3412,16659,9699],{"class":3433},[3412,16661,16662],{"class":3418},"# Формула Герона\n",[3412,16664,16665,16668,16670,16673],{"class":3414,"line":7393},[3412,16666,16667],{"class":3433},"        s = ",[3412,16669,3451],{"class":3425},[3412,16671,16672],{"class":3433},".perimeter() \u002F ",[3412,16674,5032],{"class":3924},[3412,16676,16677,16679,16682,16684,16687,16689,16692,16694],{"class":3414,"line":13280},[3412,16678,3955],{"class":3530},[3412,16680,16681],{"class":3433}," math.sqrt(s * (s - ",[3412,16683,3451],{"class":3425},[3412,16685,16686],{"class":3433},".a) * (s - ",[3412,16688,3451],{"class":3425},[3412,16690,16691],{"class":3433},".b) * (s - ",[3412,16693,3451],{"class":3425},[3412,16695,16696],{"class":3433},".c))\n",[3412,16698,16699],{"class":3414,"line":13288},[3412,16700,3554],{"emptyLinePlaceholder":3553},[3412,16702,16703,16705,16707,16709,16711,16713,16715],{"class":3414,"line":13294},[3412,16704,3440],{"class":3425},[3412,16706,11866],{"class":3443},[3412,16708,3447],{"class":3433},[3412,16710,3451],{"class":3450},[3412,16712,3946],{"class":3433},[3412,16714,3918],{"class":3429},[3412,16716,3434],{"class":3433},[3412,16718,16719,16721,16723,16726,16728,16731,16733],{"class":3414,"line":13302},[3412,16720,3955],{"class":3530},[3412,16722,10741],{"class":3425},[3412,16724,16725],{"class":3433},".a + ",[3412,16727,3451],{"class":3425},[3412,16729,16730],{"class":3433},".b + ",[3412,16732,3451],{"class":3425},[3412,16734,16735],{"class":3433},".c\n",[3412,16737,16738],{"class":3414,"line":13307},[3412,16739,3554],{"emptyLinePlaceholder":3553},[3412,16741,16742],{"class":3414,"line":13325},[3412,16743,3554],{"emptyLinePlaceholder":3553},[3412,16745,16746],{"class":3414,"line":13331},[3412,16747,7274],{"class":3418},[3412,16749,16750],{"class":3414,"line":13342},[3412,16751,16752],{"class":3433},"shapes: list[Shape] = [\n",[3412,16754,16755,16758,16760,16762,16764,16766,16768],{"class":3414,"line":13350},[3412,16756,16757],{"class":3433},"    Rectangle(",[3412,16759,4224],{"class":3924},[3412,16761,3454],{"class":3433},[3412,16763,12239],{"class":3924},[3412,16765,3454],{"class":3433},[3412,16767,7946],{"class":3495},[3412,16769,4671],{"class":3433},[3412,16771,16772,16775,16777,16779,16782],{"class":3414,"line":13378},[3412,16773,16774],{"class":3433},"    Square(",[3412,16776,10815],{"class":3924},[3412,16778,3454],{"class":3433},[3412,16780,16781],{"class":3495},"\"red\"",[3412,16783,4671],{"class":3433},[3412,16785,16786,16789,16791,16793,16795,16797,16799,16801,16804],{"class":3414,"line":13383},[3412,16787,16788],{"class":3433},"    Triangle(",[3412,16790,11690],{"class":3924},[3412,16792,3454],{"class":3433},[3412,16794,4224],{"class":3924},[3412,16796,3454],{"class":3433},[3412,16798,10815],{"class":3924},[3412,16800,3454],{"class":3433},[3412,16802,16803],{"class":3495},"\"green\"",[3412,16805,4671],{"class":3433},[3412,16807,16808],{"class":3414,"line":13388},[3412,16809,4724],{"class":3433},[3412,16811,16812],{"class":3414,"line":13398},[3412,16813,3554],{"emptyLinePlaceholder":3553},[3412,16815,16816,16818,16821,16823],{"class":3414,"line":13403},[3412,16817,4738],{"class":3530},[3412,16819,16820],{"class":3433}," shape ",[3412,16822,4744],{"class":3530},[3412,16824,16825],{"class":3433}," shapes:\n",[3412,16827,16828,16830],{"class":3414,"line":13409},[3412,16829,4752],{"class":3443},[3412,16831,16832],{"class":3433},"(shape.describe())\n",[3412,16834,16835,16837,16839,16841,16844,16846,16848,16851,16853,16855],{"class":3414,"line":13415},[3412,16836,4752],{"class":3443},[3412,16838,3447],{"class":3433},[3412,16840,3492],{"class":3425},[3412,16842,16843],{"class":3495},"\"  isinstance(shape, Shape):   ",[3412,16845,3499],{"class":3425},[3412,16847,5509],{"class":3443},[3412,16849,16850],{"class":3433},"(shape, Shape)",[3412,16852,3504],{"class":3425},[3412,16854,3507],{"class":3495},[3412,16856,4069],{"class":3433},[3412,16858,16859,16861,16863,16865,16868,16870,16872,16875,16877,16879],{"class":3414,"line":13420},[3412,16860,4752],{"class":3443},[3412,16862,3447],{"class":3433},[3412,16864,3492],{"class":3425},[3412,16866,16867],{"class":3495},"\"  isinstance(shape, Polygon): ",[3412,16869,3499],{"class":3425},[3412,16871,5509],{"class":3443},[3412,16873,16874],{"class":3433},"(shape, Polygon)",[3412,16876,3504],{"class":3425},[3412,16878,3507],{"class":3495},[3412,16880,4069],{"class":3433},[3412,16882,16883,16885],{"class":3414,"line":13425},[3412,16884,4752],{"class":3443},[3412,16886,14275],{"class":3433},[3412,16888,16889],{"class":3414,"line":13433},[3412,16890,3554],{"emptyLinePlaceholder":3553},[3412,16892,16893],{"class":3414,"line":13451},[3412,16894,16895],{"class":3418},"# Перевірка MRO\n",[3412,16897,16898,16900,16902,16905,16907,16909,16911,16913,16915,16918,16920],{"class":3414,"line":13457},[3412,16899,3487],{"class":3443},[3412,16901,3447],{"class":3433},[3412,16903,16904],{"class":3495},"\"Square MRO:\"",[3412,16906,7876],{"class":3433},[3412,16908,4113],{"class":3450},[3412,16910,7881],{"class":3530},[3412,16912,7884],{"class":3433},[3412,16914,4744],{"class":3530},[3412,16916,16917],{"class":3433}," Square.",[3412,16919,5340],{"class":3450},[3412,16921,7894],{"class":3433},[4757,16923,16925,16933,16953,16960,16967,16970,16988,16994,17000,17003,17022,17028,17034,17037],{"title":16924},"python shapes.py",[4761,16926,16928,4769,16931],{"className":16927},[3414],[3412,16929,4768],{"className":16930},[4767],[3764,16932,16924],{},[4761,16934,16936,16937,16941,16942,16070,16946,16210,16950],{"className":16935},[3414],"Rectangle [",[3412,16938,16940],{"className":16939},[4792],"blue","]: площа=",[3412,16943,16945],{"className":16944},[4800],"24.00",[3412,16947,16949],{"className":16948},[4800],"20.00",[3412,16951,4224],{"className":16952},[4779],[4761,16954,16956,16957],{"className":16955},[3414],"  isinstance(shape, Shape):   ",[3412,16958,5719],{"className":16959},[4800],[4761,16961,16963,16964],{"className":16962},[3414],"  isinstance(shape, Polygon): ",[3412,16965,5719],{"className":16966},[4800],[4761,16968],{"className":16969},[3414],[4761,16971,16973,16974,16941,16978,16070,16982,16210,16985],{"className":16972},[3414],"Square [",[3412,16975,16977],{"className":16976},[5732],"red",[3412,16979,16981],{"className":16980},[4800],"25.00",[3412,16983,16949],{"className":16984},[4800],[3412,16986,4224],{"className":16987},[4779],[4761,16989,16956,16991],{"className":16990},[3414],[3412,16992,5719],{"className":16993},[4800],[4761,16995,16963,16997],{"className":16996},[3414],[3412,16998,5719],{"className":16999},[4800],[4761,17001],{"className":17002},[3414],[4761,17004,17006,17007,16941,17011,16070,17015,16210,17019],{"className":17005},[3414],"Triangle [",[3412,17008,17010],{"className":17009},[4800],"green",[3412,17012,17014],{"className":17013},[4800],"6.00",[3412,17016,17018],{"className":17017},[4800],"12.00",[3412,17020,11690],{"className":17021},[4779],[4761,17023,16956,17025],{"className":17024},[3414],[3412,17026,5719],{"className":17027},[4800],[4761,17029,16963,17031],{"className":17030},[3414],[3412,17032,5719],{"className":17033},[4800],[4761,17035],{"className":17036},[3414],[4761,17038,17040,17041],{"className":17039},[3414],"Square MRO: ",[3412,17042,17044],{"className":17043},[4792],"['Square', 'Rectangle', 'Polygon', 'Shape', 'object']",[3796,17046],{},[3803,17048,17050],{"id":17049},"рівень-2-середній-система-банківських-транзакцій","Рівень 2 (Середній): Система банківських транзакцій",[3399,17052,17053],{},"Реальний production-сценарій: комбінація лінійного наслідування та Mixins у системі фінансових транзакцій.",[3828,17055,17056,17982],{},[3403,17057,17060],{"className":3405,"code":17058,"filename":17059,"language":3407,"meta":3408,"style":3408},"import json\nimport uuid\nfrom datetime import datetime\n\n# ── Домішки ───────────────────────────────────────────────────────────────────\n\nclass AuditLogMixin:\n    \"\"\"Аудиторський лог для кожної транзакції.\"\"\"\n\n    def log_operation(self, status: str, details: str) -> None:\n        timestamp = datetime.now().isoformat(timespec='milliseconds')\n        class_name = self.__class__.__name__\n        print(f\"[{timestamp}] [AUDIT\u002F{class_name}] {status}: {details}\")\n\n\nclass JSONSerializableMixin:\n    \"\"\"Серіалізація транзакції у JSON для збереження в БД.\"\"\"\n\n    def to_json(self) -> str:\n        public = {k: v for k, v in self.__dict__.items() if not k.startswith('_')}\n        return json.dumps(public, ensure_ascii=False, default=str)\n\n\n# ── Базові класи транзакцій ───────────────────────────────────────────────────\n\nclass Transaction:\n    \"\"\"Базовий клас транзакції: ідентифікатор, сума, час.\"\"\"\n\n    def __init__(self, amount: float, **kwargs):\n        print(f\"  → Transaction.__init__(amount={amount})\")\n        super().__init__(**kwargs)\n        self.tx_id = str(uuid.uuid4())[:8]\n        self.amount = float(amount)\n        self.timestamp = datetime.now().strftime(\"%Y-%m-%d %H:%M:%S\")\n\n    def execute(self) -> None:\n        print(f\"[{self.tx_id}] Базова транзакція: ${self.amount:.2f}\")\n\n\nclass ValidatedTransaction(Transaction):\n    \"\"\"Транзакція з перевіркою лімітів.\"\"\"\n\n    def __init__(self, amount: float, max_limit: float = 100_000.0, **kwargs):\n        print(f\"  → ValidatedTransaction.__init__(max_limit={max_limit})\")\n        super().__init__(amount=amount, **kwargs)\n        self.max_limit = max_limit\n\n    def execute(self) -> None:\n        if self.amount \u003C 1.0:\n            raise ValueError(f\"Мінімальна сума транзакції: $1.00\")\n        if self.amount > self.max_limit:\n            raise ValueError(f\"Перевищено ліміт: ${self.max_limit:,.2f}\")\n        super().execute()\n\n\n# ── Фінальний клас: комбінує Mixins та ієрархію ───────────────────────────────\n\nclass SecureDepositTransaction(JSONSerializableMixin, AuditLogMixin, ValidatedTransaction):\n    \"\"\"\n    Захищена транзакція поповнення рахунку.\n    MRO: SecureDepositTransaction → JSONSerializableMixin → AuditLogMixin\n         → ValidatedTransaction → Transaction → object\n    \"\"\"\n\n    def __init__(self, amount: float, depositor_name: str, **kwargs):\n        print(f\"  → SecureDepositTransaction.__init__(depositor={depositor_name!r})\")\n        super().__init__(amount=amount, **kwargs)\n        self.depositor_name = depositor_name\n\n    def execute(self) -> None:\n        self.log_operation(\"START\", f\"Депозит для {self.depositor_name!r}\")\n        try:\n            super().execute()\n            self.log_operation(\"SUCCESS\", f\"Депозит ${self.amount:.2f} виконано\")\n        except ValueError as e:\n            self.log_operation(\"FAILED\", str(e))\n            raise\n","banking_system.py",[3390,17061,17062,17068,17075,17085,17089,17094,17098,17107,17112,17116,17151,17166,17180,17225,17229,17233,17241,17246,17250,17266,17298,17321,17325,17329,17334,17338,17347,17352,17356,17381,17402,17412,17429,17441,17459,17463,17480,17511,17515,17519,17533,17538,17542,17580,17601,17616,17623,17627,17643,17657,17672,17686,17711,17718,17722,17726,17731,17735,17758,17762,17767,17772,17777,17781,17785,17818,17839,17853,17860,17864,17880,17908,17914,17921,17949,17961,17977],{"__ignoreMap":3408},[3412,17063,17064,17066],{"class":3414,"line":3415},[3412,17065,4154],{"class":3530},[3412,17067,6757],{"class":3433},[3412,17069,17070,17072],{"class":3414,"line":3422},[3412,17071,4154],{"class":3530},[3412,17073,17074],{"class":3433}," uuid\n",[3412,17076,17077,17079,17081,17083],{"class":3414,"line":3437},[3412,17078,4148],{"class":3530},[3412,17080,6764],{"class":3433},[3412,17082,4154],{"class":3530},[3412,17084,6769],{"class":3433},[3412,17086,17087],{"class":3414,"line":3468},[3412,17088,3554],{"emptyLinePlaceholder":3553},[3412,17090,17091],{"class":3414,"line":3516},[3412,17092,17093],{"class":3418},"# ── Домішки ───────────────────────────────────────────────────────────────────\n",[3412,17095,17096],{"class":3414,"line":3550},[3412,17097,3554],{"emptyLinePlaceholder":3553},[3412,17099,17100,17102,17105],{"class":3414,"line":3557},[3412,17101,3426],{"class":3425},[3412,17103,17104],{"class":3429}," AuditLogMixin",[3412,17106,3434],{"class":3433},[3412,17108,17109],{"class":3414,"line":3567},[3412,17110,17111],{"class":3495},"    \"\"\"Аудиторський лог для кожної транзакції.\"\"\"\n",[3412,17113,17114],{"class":3414,"line":3588},[3412,17115,3554],{"emptyLinePlaceholder":3553},[3412,17117,17118,17120,17123,17125,17127,17129,17132,17134,17136,17138,17141,17143,17145,17147,17149],{"class":3414,"line":3628},[3412,17119,3440],{"class":3425},[3412,17121,17122],{"class":3443}," log_operation",[3412,17124,3447],{"class":3433},[3412,17126,3451],{"class":3450},[3412,17128,3454],{"class":3433},[3412,17130,17131],{"class":3450},"status",[3412,17133,3877],{"class":3433},[3412,17135,3880],{"class":3429},[3412,17137,3454],{"class":3433},[3412,17139,17140],{"class":3450},"details",[3412,17142,3877],{"class":3433},[3412,17144,3880],{"class":3429},[3412,17146,3946],{"class":3433},[3412,17148,4011],{"class":3425},[3412,17150,3434],{"class":3433},[3412,17152,17153,17156,17159,17161,17164],{"class":3414,"line":3655},[3412,17154,17155],{"class":3433},"        timestamp = datetime.now().isoformat(",[3412,17157,17158],{"class":3450},"timespec",[3412,17160,4691],{"class":3433},[3412,17162,17163],{"class":3495},"'milliseconds'",[3412,17165,4069],{"class":3433},[3412,17167,17168,17170,17172,17174,17176,17178],{"class":3414,"line":3660},[3412,17169,13031],{"class":3433},[3412,17171,3451],{"class":3425},[3412,17173,3539],{"class":3433},[3412,17175,4108],{"class":3450},[3412,17177,3539],{"class":3433},[3412,17179,13042],{"class":3450},[3412,17181,17182,17184,17186,17188,17190,17192,17194,17196,17199,17201,17203,17205,17207,17209,17211,17213,17215,17217,17219,17221,17223],{"class":3414,"line":3670},[3412,17183,4039],{"class":3443},[3412,17185,3447],{"class":3433},[3412,17187,3492],{"class":3425},[3412,17189,13053],{"class":3495},[3412,17191,3499],{"class":3425},[3412,17193,13058],{"class":3433},[3412,17195,3504],{"class":3425},[3412,17197,17198],{"class":3495},"] [AUDIT\u002F",[3412,17200,3499],{"class":3425},[3412,17202,13077],{"class":3433},[3412,17204,3504],{"class":3425},[3412,17206,13082],{"class":3495},[3412,17208,3499],{"class":3425},[3412,17210,17131],{"class":3433},[3412,17212,3504],{"class":3425},[3412,17214,3877],{"class":3495},[3412,17216,3499],{"class":3425},[3412,17218,17140],{"class":3433},[3412,17220,3504],{"class":3425},[3412,17222,3507],{"class":3495},[3412,17224,4069],{"class":3433},[3412,17226,17227],{"class":3414,"line":3691},[3412,17228,3554],{"emptyLinePlaceholder":3553},[3412,17230,17231],{"class":3414,"line":3731},[3412,17232,3554],{"emptyLinePlaceholder":3553},[3412,17234,17235,17237,17239],{"class":3414,"line":4072},[3412,17236,3426],{"class":3425},[3412,17238,12684],{"class":3429},[3412,17240,3434],{"class":3433},[3412,17242,17243],{"class":3414,"line":4077},[3412,17244,17245],{"class":3495},"    \"\"\"Серіалізація транзакції у JSON для збереження в БД.\"\"\"\n",[3412,17247,17248],{"class":3414,"line":4095},[3412,17249,3554],{"emptyLinePlaceholder":3553},[3412,17251,17252,17254,17256,17258,17260,17262,17264],{"class":3414,"line":5014},[3412,17253,3440],{"class":3425},[3412,17255,3521],{"class":3443},[3412,17257,3447],{"class":3433},[3412,17259,3451],{"class":3450},[3412,17261,3946],{"class":3433},[3412,17263,3880],{"class":3429},[3412,17265,3434],{"class":3433},[3412,17267,17268,17271,17273,17275,17277,17279,17281,17283,17286,17288,17290,17293,17295],{"class":3414,"line":5020},[3412,17269,17270],{"class":3433},"        public = {k: v ",[3412,17272,4738],{"class":3530},[3412,17274,15639],{"class":3433},[3412,17276,4744],{"class":3530},[3412,17278,10741],{"class":3425},[3412,17280,3539],{"class":3433},[3412,17282,3542],{"class":3450},[3412,17284,17285],{"class":3433},".items() ",[3412,17287,11031],{"class":3530},[3412,17289,10123],{"class":3425},[3412,17291,17292],{"class":3433}," k.startswith(",[3412,17294,12771],{"class":3495},[3412,17296,17297],{"class":3433},")}\n",[3412,17299,17300,17302,17305,17307,17309,17311,17313,17315,17317,17319],{"class":3414,"line":5026},[3412,17301,3955],{"class":3530},[3412,17303,17304],{"class":3433}," json.dumps(public, ",[3412,17306,12788],{"class":3450},[3412,17308,4691],{"class":3433},[3412,17310,5733],{"class":3425},[3412,17312,3454],{"class":3433},[3412,17314,12797],{"class":3450},[3412,17316,4691],{"class":3433},[3412,17318,3880],{"class":3429},[3412,17320,4069],{"class":3433},[3412,17322,17323],{"class":3414,"line":5035},[3412,17324,3554],{"emptyLinePlaceholder":3553},[3412,17326,17327],{"class":3414,"line":5046},[3412,17328,3554],{"emptyLinePlaceholder":3553},[3412,17330,17331],{"class":3414,"line":5057},[3412,17332,17333],{"class":3418},"# ── Базові класи транзакцій ───────────────────────────────────────────────────\n",[3412,17335,17336],{"class":3414,"line":5345},[3412,17337,3554],{"emptyLinePlaceholder":3553},[3412,17339,17340,17342,17345],{"class":3414,"line":5364},[3412,17341,3426],{"class":3425},[3412,17343,17344],{"class":3429}," Transaction",[3412,17346,3434],{"class":3433},[3412,17348,17349],{"class":3414,"line":5370},[3412,17350,17351],{"class":3495},"    \"\"\"Базовий клас транзакції: ідентифікатор, сума, час.\"\"\"\n",[3412,17353,17354],{"class":3414,"line":5376},[3412,17355,3554],{"emptyLinePlaceholder":3553},[3412,17357,17358,17360,17362,17364,17366,17368,17371,17373,17375,17377,17379],{"class":3414,"line":5382},[3412,17359,3440],{"class":3425},[3412,17361,3865],{"class":3443},[3412,17363,3447],{"class":3433},[3412,17365,3451],{"class":3450},[3412,17367,3454],{"class":3433},[3412,17369,17370],{"class":3450},"amount",[3412,17372,3877],{"class":3433},[3412,17374,3918],{"class":3429},[3412,17376,7534],{"class":3433},[3412,17378,7537],{"class":3450},[3412,17380,3893],{"class":3433},[3412,17382,17383,17385,17387,17389,17392,17394,17396,17398,17400],{"class":3414,"line":5388},[3412,17384,4039],{"class":3443},[3412,17386,3447],{"class":3433},[3412,17388,3492],{"class":3425},[3412,17390,17391],{"class":3495},"\"  → Transaction.__init__(amount=",[3412,17393,3499],{"class":3425},[3412,17395,17370],{"class":3433},[3412,17397,3504],{"class":3425},[3412,17399,7559],{"class":3495},[3412,17401,4069],{"class":3433},[3412,17403,17404,17406,17408,17410],{"class":3414,"line":5393},[3412,17405,4236],{"class":3429},[3412,17407,4239],{"class":3433},[3412,17409,4242],{"class":3443},[3412,17411,7749],{"class":3433},[3412,17413,17414,17416,17419,17421,17424,17427],{"class":3414,"line":5399},[3412,17415,3898],{"class":3425},[3412,17417,17418],{"class":3433},".tx_id = ",[3412,17420,3880],{"class":3429},[3412,17422,17423],{"class":3433},"(uuid.uuid4())[:",[3412,17425,17426],{"class":3924},"8",[3412,17428,4724],{"class":3433},[3412,17430,17431,17433,17436,17438],{"class":3414,"line":5422},[3412,17432,3898],{"class":3425},[3412,17434,17435],{"class":3433},".amount = ",[3412,17437,3918],{"class":3429},[3412,17439,17440],{"class":3433},"(amount)\n",[3412,17442,17443,17445,17448,17451,17454,17457],{"class":3414,"line":5428},[3412,17444,3898],{"class":3425},[3412,17446,17447],{"class":3433},".timestamp = datetime.now().strftime(",[3412,17449,17450],{"class":3495},"\"%Y-%m-",[3412,17452,17453],{"class":3425},"%d",[3412,17455,17456],{"class":3495}," %H:%M:%S\"",[3412,17458,4069],{"class":3433},[3412,17460,17461],{"class":3414,"line":5434},[3412,17462,3554],{"emptyLinePlaceholder":3553},[3412,17464,17465,17467,17470,17472,17474,17476,17478],{"class":3414,"line":5439},[3412,17466,3440],{"class":3425},[3412,17468,17469],{"class":3443}," execute",[3412,17471,3447],{"class":3433},[3412,17473,3451],{"class":3450},[3412,17475,3946],{"class":3433},[3412,17477,4011],{"class":3425},[3412,17479,3434],{"class":3433},[3412,17481,17482,17484,17486,17488,17490,17492,17495,17497,17500,17502,17505,17507,17509],{"class":3414,"line":5456},[3412,17483,4039],{"class":3443},[3412,17485,3447],{"class":3433},[3412,17487,3492],{"class":3425},[3412,17489,13053],{"class":3495},[3412,17491,3964],{"class":3425},[3412,17493,17494],{"class":3433},".tx_id",[3412,17496,3504],{"class":3425},[3412,17498,17499],{"class":3495},"] Базова транзакція: $",[3412,17501,3964],{"class":3425},[3412,17503,17504],{"class":3433},".amount",[3412,17506,12202],{"class":3425},[3412,17508,3507],{"class":3495},[3412,17510,4069],{"class":3433},[3412,17512,17513],{"class":3414,"line":5462},[3412,17514,3554],{"emptyLinePlaceholder":3553},[3412,17516,17517],{"class":3414,"line":5467},[3412,17518,3554],{"emptyLinePlaceholder":3553},[3412,17520,17521,17523,17526,17528,17531],{"class":3414,"line":5473},[3412,17522,3426],{"class":3425},[3412,17524,17525],{"class":3429}," ValidatedTransaction",[3412,17527,3447],{"class":3433},[3412,17529,17530],{"class":3429},"Transaction",[3412,17532,3893],{"class":3433},[3412,17534,17535],{"class":3414,"line":5491},[3412,17536,17537],{"class":3495},"    \"\"\"Транзакція з перевіркою лімітів.\"\"\"\n",[3412,17539,17540],{"class":3414,"line":5496},[3412,17541,3554],{"emptyLinePlaceholder":3553},[3412,17543,17544,17546,17548,17550,17552,17554,17556,17558,17560,17562,17565,17567,17569,17571,17574,17576,17578],{"class":3414,"line":5502},[3412,17545,3440],{"class":3425},[3412,17547,3865],{"class":3443},[3412,17549,3447],{"class":3433},[3412,17551,3451],{"class":3450},[3412,17553,3454],{"class":3433},[3412,17555,17370],{"class":3450},[3412,17557,3877],{"class":3433},[3412,17559,3918],{"class":3429},[3412,17561,3454],{"class":3433},[3412,17563,17564],{"class":3450},"max_limit",[3412,17566,3877],{"class":3433},[3412,17568,3918],{"class":3429},[3412,17570,3921],{"class":3433},[3412,17572,17573],{"class":3924},"100_000.0",[3412,17575,7534],{"class":3433},[3412,17577,7537],{"class":3450},[3412,17579,3893],{"class":3433},[3412,17581,17582,17584,17586,17588,17591,17593,17595,17597,17599],{"class":3414,"line":5518},[3412,17583,4039],{"class":3443},[3412,17585,3447],{"class":3433},[3412,17587,3492],{"class":3425},[3412,17589,17590],{"class":3495},"\"  → ValidatedTransaction.__init__(max_limit=",[3412,17592,3499],{"class":3425},[3412,17594,17564],{"class":3433},[3412,17596,3504],{"class":3425},[3412,17598,7559],{"class":3495},[3412,17600,4069],{"class":3433},[3412,17602,17603,17605,17607,17609,17611,17613],{"class":3414,"line":5533},[3412,17604,4236],{"class":3429},[3412,17606,4239],{"class":3433},[3412,17608,4242],{"class":3443},[3412,17610,3447],{"class":3433},[3412,17612,17370],{"class":3450},[3412,17614,17615],{"class":3433},"=amount, **kwargs)\n",[3412,17617,17618,17620],{"class":3414,"line":5548},[3412,17619,3898],{"class":3425},[3412,17621,17622],{"class":3433},".max_limit = max_limit\n",[3412,17624,17625],{"class":3414,"line":5568},[3412,17626,3554],{"emptyLinePlaceholder":3553},[3412,17628,17629,17631,17633,17635,17637,17639,17641],{"class":3414,"line":5573},[3412,17630,3440],{"class":3425},[3412,17632,17469],{"class":3443},[3412,17634,3447],{"class":3433},[3412,17636,3451],{"class":3450},[3412,17638,3946],{"class":3433},[3412,17640,4011],{"class":3425},[3412,17642,3434],{"class":3433},[3412,17644,17645,17647,17649,17652,17655],{"class":3414,"line":5579},[3412,17646,9686],{"class":3530},[3412,17648,10741],{"class":3425},[3412,17650,17651],{"class":3433},".amount \u003C ",[3412,17653,17654],{"class":3924},"1.0",[3412,17656,3434],{"class":3433},[3412,17658,17659,17661,17663,17665,17667,17670],{"class":3414,"line":5595},[3412,17660,10786],{"class":3530},[3412,17662,10789],{"class":3429},[3412,17664,3447],{"class":3433},[3412,17666,3492],{"class":3425},[3412,17668,17669],{"class":3495},"\"Мінімальна сума транзакції: $1.00\"",[3412,17671,4069],{"class":3433},[3412,17673,17674,17676,17678,17681,17683],{"class":3414,"line":5609},[3412,17675,9686],{"class":3530},[3412,17677,10741],{"class":3425},[3412,17679,17680],{"class":3433},".amount > ",[3412,17682,3451],{"class":3425},[3412,17684,17685],{"class":3433},".max_limit:\n",[3412,17687,17688,17690,17692,17694,17696,17699,17701,17704,17707,17709],{"class":3414,"line":7232},[3412,17689,10786],{"class":3530},[3412,17691,10789],{"class":3429},[3412,17693,3447],{"class":3433},[3412,17695,3492],{"class":3425},[3412,17697,17698],{"class":3495},"\"Перевищено ліміт: $",[3412,17700,3964],{"class":3425},[3412,17702,17703],{"class":3433},".max_limit",[3412,17705,17706],{"class":3425},":,.2f}",[3412,17708,3507],{"class":3495},[3412,17710,4069],{"class":3433},[3412,17712,17713,17715],{"class":3414,"line":7261},[3412,17714,4236],{"class":3429},[3412,17716,17717],{"class":3433},"().execute()\n",[3412,17719,17720],{"class":3414,"line":7266},[3412,17721,3554],{"emptyLinePlaceholder":3553},[3412,17723,17724],{"class":3414,"line":7271},[3412,17725,3554],{"emptyLinePlaceholder":3553},[3412,17727,17728],{"class":3414,"line":7277},[3412,17729,17730],{"class":3418},"# ── Фінальний клас: комбінує Mixins та ієрархію ───────────────────────────────\n",[3412,17732,17733],{"class":3414,"line":7310},[3412,17734,3554],{"emptyLinePlaceholder":3553},[3412,17736,17737,17739,17742,17744,17746,17748,17751,17753,17756],{"class":3414,"line":7315},[3412,17738,3426],{"class":3425},[3412,17740,17741],{"class":3429}," SecureDepositTransaction",[3412,17743,3447],{"class":3433},[3412,17745,13560],{"class":3429},[3412,17747,3454],{"class":3433},[3412,17749,17750],{"class":3429},"AuditLogMixin",[3412,17752,3454],{"class":3433},[3412,17754,17755],{"class":3429},"ValidatedTransaction",[3412,17757,3893],{"class":3433},[3412,17759,17760],{"class":3414,"line":7321},[3412,17761,10012],{"class":3495},[3412,17763,17764],{"class":3414,"line":7346},[3412,17765,17766],{"class":3495},"    Захищена транзакція поповнення рахунку.\n",[3412,17768,17769],{"class":3414,"line":7361},[3412,17770,17771],{"class":3495},"    MRO: SecureDepositTransaction → JSONSerializableMixin → AuditLogMixin\n",[3412,17773,17774],{"class":3414,"line":7366},[3412,17775,17776],{"class":3495},"         → ValidatedTransaction → Transaction → object\n",[3412,17778,17779],{"class":3414,"line":7372},[3412,17780,10012],{"class":3495},[3412,17782,17783],{"class":3414,"line":7378},[3412,17784,3554],{"emptyLinePlaceholder":3553},[3412,17786,17787,17789,17791,17793,17795,17797,17799,17801,17803,17805,17808,17810,17812,17814,17816],{"class":3414,"line":7393},[3412,17788,3440],{"class":3425},[3412,17790,3865],{"class":3443},[3412,17792,3447],{"class":3433},[3412,17794,3451],{"class":3450},[3412,17796,3454],{"class":3433},[3412,17798,17370],{"class":3450},[3412,17800,3877],{"class":3433},[3412,17802,3918],{"class":3429},[3412,17804,3454],{"class":3433},[3412,17806,17807],{"class":3450},"depositor_name",[3412,17809,3877],{"class":3433},[3412,17811,3880],{"class":3429},[3412,17813,7534],{"class":3433},[3412,17815,7537],{"class":3450},[3412,17817,3893],{"class":3433},[3412,17819,17820,17822,17824,17826,17829,17831,17833,17835,17837],{"class":3414,"line":13280},[3412,17821,4039],{"class":3443},[3412,17823,3447],{"class":3433},[3412,17825,3492],{"class":3425},[3412,17827,17828],{"class":3495},"\"  → SecureDepositTransaction.__init__(depositor=",[3412,17830,3499],{"class":3425},[3412,17832,17807],{"class":3433},[3412,17834,4125],{"class":3425},[3412,17836,7559],{"class":3495},[3412,17838,4069],{"class":3433},[3412,17840,17841,17843,17845,17847,17849,17851],{"class":3414,"line":13288},[3412,17842,4236],{"class":3429},[3412,17844,4239],{"class":3433},[3412,17846,4242],{"class":3443},[3412,17848,3447],{"class":3433},[3412,17850,17370],{"class":3450},[3412,17852,17615],{"class":3433},[3412,17854,17855,17857],{"class":3414,"line":13294},[3412,17856,3898],{"class":3425},[3412,17858,17859],{"class":3433},".depositor_name = depositor_name\n",[3412,17861,17862],{"class":3414,"line":13302},[3412,17863,3554],{"emptyLinePlaceholder":3553},[3412,17865,17866,17868,17870,17872,17874,17876,17878],{"class":3414,"line":13307},[3412,17867,3440],{"class":3425},[3412,17869,17469],{"class":3443},[3412,17871,3447],{"class":3433},[3412,17873,3451],{"class":3450},[3412,17875,3946],{"class":3433},[3412,17877,4011],{"class":3425},[3412,17879,3434],{"class":3433},[3412,17881,17882,17884,17887,17890,17892,17894,17897,17899,17902,17904,17906],{"class":3414,"line":13325},[3412,17883,3898],{"class":3425},[3412,17885,17886],{"class":3433},".log_operation(",[3412,17888,17889],{"class":3495},"\"START\"",[3412,17891,3454],{"class":3433},[3412,17893,3492],{"class":3425},[3412,17895,17896],{"class":3495},"\"Депозит для ",[3412,17898,3964],{"class":3425},[3412,17900,17901],{"class":3433},".depositor_name",[3412,17903,4125],{"class":3425},[3412,17905,3507],{"class":3495},[3412,17907,4069],{"class":3433},[3412,17909,17910,17912],{"class":3414,"line":13331},[3412,17911,15388],{"class":3530},[3412,17913,3434],{"class":3433},[3412,17915,17916,17919],{"class":3414,"line":13342},[3412,17917,17918],{"class":3429},"            super",[3412,17920,17717],{"class":3433},[3412,17922,17923,17926,17928,17931,17933,17935,17938,17940,17942,17944,17947],{"class":3414,"line":13350},[3412,17924,17925],{"class":3425},"            self",[3412,17927,17886],{"class":3433},[3412,17929,17930],{"class":3495},"\"SUCCESS\"",[3412,17932,3454],{"class":3433},[3412,17934,3492],{"class":3425},[3412,17936,17937],{"class":3495},"\"Депозит $",[3412,17939,3964],{"class":3425},[3412,17941,17504],{"class":3433},[3412,17943,12202],{"class":3425},[3412,17945,17946],{"class":3495}," виконано\"",[3412,17948,4069],{"class":3433},[3412,17950,17951,17953,17955,17958],{"class":3414,"line":13378},[3412,17952,15404],{"class":3530},[3412,17954,10789],{"class":3429},[3412,17956,17957],{"class":3530}," as",[3412,17959,17960],{"class":3433}," e:\n",[3412,17962,17963,17965,17967,17970,17972,17974],{"class":3414,"line":13383},[3412,17964,17925],{"class":3425},[3412,17966,17886],{"class":3433},[3412,17968,17969],{"class":3495},"\"FAILED\"",[3412,17971,3454],{"class":3433},[3412,17973,3880],{"class":3429},[3412,17975,17976],{"class":3433},"(e))\n",[3412,17978,17979],{"class":3414,"line":13388},[3412,17980,17981],{"class":3530},"            raise\n",[3403,17983,17985],{"className":3405,"code":17984,"filename":4612,"language":3407,"meta":3408,"style":3408},"from banking_system import SecureDepositTransaction\n\n# ── Перегляд MRO ──────────────────────────────────────────────────────────────\nprint(\"=== MRO SecureDepositTransaction ===\")\nfor cls in SecureDepositTransaction.mro():\n    print(f\"  {cls.__name__}\")\n\n# ── Успішна транзакція ────────────────────────────────────────────────────────\nprint(\"\\n=== Успішна транзакція ===\")\ntx1 = SecureDepositTransaction(\n    amount=5000.0,\n    depositor_name=\"Олексій Коваленко\",\n    max_limit=10_000.0,\n)\ntx1.execute()\nprint(f\"JSON: {tx1.to_json()}\")\n\n# ── Перевищення ліміту ────────────────────────────────────────────────────────\nprint(\"\\n=== Перевищення ліміту ===\")\ntry:\n    tx2 = SecureDepositTransaction(\n        amount=15_000.0,\n        depositor_name=\"Ірина Петренко\",\n        max_limit=10_000.0,\n    )\n    tx2.execute()\nexcept ValueError as e:\n    print(f\"Зловлено: {e}\")\n",[3390,17986,17987,17999,18003,18008,18019,18030,18052,18056,18061,18076,18081,18093,18105,18117,18121,18126,18147,18151,18156,18171,18178,18183,18195,18207,18218,18223,18228,18239],{"__ignoreMap":3408},[3412,17988,17989,17991,17994,17996],{"class":3414,"line":3415},[3412,17990,4148],{"class":3530},[3412,17992,17993],{"class":3433}," banking_system ",[3412,17995,4154],{"class":3530},[3412,17997,17998],{"class":3433}," SecureDepositTransaction\n",[3412,18000,18001],{"class":3414,"line":3422},[3412,18002,3554],{"emptyLinePlaceholder":3553},[3412,18004,18005],{"class":3414,"line":3437},[3412,18006,18007],{"class":3418},"# ── Перегляд MRO ──────────────────────────────────────────────────────────────\n",[3412,18009,18010,18012,18014,18017],{"class":3414,"line":3468},[3412,18011,3487],{"class":3443},[3412,18013,3447],{"class":3433},[3412,18015,18016],{"class":3495},"\"=== MRO SecureDepositTransaction ===\"",[3412,18018,4069],{"class":3433},[3412,18020,18021,18023,18025,18027],{"class":3414,"line":3516},[3412,18022,4738],{"class":3530},[3412,18024,5331],{"class":3425},[3412,18026,5334],{"class":3530},[3412,18028,18029],{"class":3433}," SecureDepositTransaction.mro():\n",[3412,18031,18032,18034,18036,18038,18040,18042,18044,18046,18048,18050],{"class":3414,"line":3550},[3412,18033,4752],{"class":3443},[3412,18035,3447],{"class":3433},[3412,18037,3492],{"class":3425},[3412,18039,5354],{"class":3495},[3412,18041,15071],{"class":3425},[3412,18043,3539],{"class":3433},[3412,18045,4113],{"class":3450},[3412,18047,3504],{"class":3425},[3412,18049,3507],{"class":3495},[3412,18051,4069],{"class":3433},[3412,18053,18054],{"class":3414,"line":3557},[3412,18055,3554],{"emptyLinePlaceholder":3553},[3412,18057,18058],{"class":3414,"line":3567},[3412,18059,18060],{"class":3418},"# ── Успішна транзакція ────────────────────────────────────────────────────────\n",[3412,18062,18063,18065,18067,18069,18071,18074],{"class":3414,"line":3588},[3412,18064,3487],{"class":3443},[3412,18066,3447],{"class":3433},[3412,18068,3507],{"class":3495},[3412,18070,5319],{"class":5318},[3412,18072,18073],{"class":3495},"=== Успішна транзакція ===\"",[3412,18075,4069],{"class":3433},[3412,18077,18078],{"class":3414,"line":3628},[3412,18079,18080],{"class":3433},"tx1 = SecureDepositTransaction(\n",[3412,18082,18083,18086,18088,18091],{"class":3414,"line":3655},[3412,18084,18085],{"class":3450},"    amount",[3412,18087,4691],{"class":3433},[3412,18089,18090],{"class":3924},"5000.0",[3412,18092,7936],{"class":3433},[3412,18094,18095,18098,18100,18103],{"class":3414,"line":3660},[3412,18096,18097],{"class":3450},"    depositor_name",[3412,18099,4691],{"class":3433},[3412,18101,18102],{"class":3495},"\"Олексій Коваленко\"",[3412,18104,7936],{"class":3433},[3412,18106,18107,18110,18112,18115],{"class":3414,"line":3670},[3412,18108,18109],{"class":3450},"    max_limit",[3412,18111,4691],{"class":3433},[3412,18113,18114],{"class":3924},"10_000.0",[3412,18116,7936],{"class":3433},[3412,18118,18119],{"class":3414,"line":3691},[3412,18120,4069],{"class":3433},[3412,18122,18123],{"class":3414,"line":3731},[3412,18124,18125],{"class":3433},"tx1.execute()\n",[3412,18127,18128,18130,18132,18134,18136,18138,18141,18143,18145],{"class":3414,"line":4072},[3412,18129,3487],{"class":3443},[3412,18131,3447],{"class":3433},[3412,18133,3492],{"class":3425},[3412,18135,14130],{"class":3495},[3412,18137,3499],{"class":3425},[3412,18139,18140],{"class":3433},"tx1.to_json()",[3412,18142,3504],{"class":3425},[3412,18144,3507],{"class":3495},[3412,18146,4069],{"class":3433},[3412,18148,18149],{"class":3414,"line":4077},[3412,18150,3554],{"emptyLinePlaceholder":3553},[3412,18152,18153],{"class":3414,"line":4095},[3412,18154,18155],{"class":3418},"# ── Перевищення ліміту ────────────────────────────────────────────────────────\n",[3412,18157,18158,18160,18162,18164,18166,18169],{"class":3414,"line":5014},[3412,18159,3487],{"class":3443},[3412,18161,3447],{"class":3433},[3412,18163,3507],{"class":3495},[3412,18165,5319],{"class":5318},[3412,18167,18168],{"class":3495},"=== Перевищення ліміту ===\"",[3412,18170,4069],{"class":3433},[3412,18172,18173,18176],{"class":3414,"line":5020},[3412,18174,18175],{"class":3530},"try",[3412,18177,3434],{"class":3433},[3412,18179,18180],{"class":3414,"line":5026},[3412,18181,18182],{"class":3433},"    tx2 = SecureDepositTransaction(\n",[3412,18184,18185,18188,18190,18193],{"class":3414,"line":5035},[3412,18186,18187],{"class":3450},"        amount",[3412,18189,4691],{"class":3433},[3412,18191,18192],{"class":3924},"15_000.0",[3412,18194,7936],{"class":3433},[3412,18196,18197,18200,18202,18205],{"class":3414,"line":5046},[3412,18198,18199],{"class":3450},"        depositor_name",[3412,18201,4691],{"class":3433},[3412,18203,18204],{"class":3495},"\"Ірина Петренко\"",[3412,18206,7936],{"class":3433},[3412,18208,18209,18212,18214,18216],{"class":3414,"line":5057},[3412,18210,18211],{"class":3450},"        max_limit",[3412,18213,4691],{"class":3433},[3412,18215,18114],{"class":3924},[3412,18217,7936],{"class":3433},[3412,18219,18220],{"class":3414,"line":5345},[3412,18221,18222],{"class":3433},"    )\n",[3412,18224,18225],{"class":3414,"line":5364},[3412,18226,18227],{"class":3433},"    tx2.execute()\n",[3412,18229,18230,18233,18235,18237],{"class":3414,"line":5370},[3412,18231,18232],{"class":3530},"except",[3412,18234,10789],{"class":3429},[3412,18236,17957],{"class":3530},[3412,18238,17960],{"class":3433},[3412,18240,18241,18243,18245,18247,18250,18252,18255,18257,18259],{"class":3414,"line":5376},[3412,18242,4752],{"class":3443},[3412,18244,3447],{"class":3433},[3412,18246,3492],{"class":3425},[3412,18248,18249],{"class":3495},"\"Зловлено: ",[3412,18251,3499],{"class":3425},[3412,18253,18254],{"class":3433},"e",[3412,18256,3504],{"class":3425},[3412,18258,3507],{"class":3495},[3412,18260,4069],{"class":3433},[4757,18262,18263,18271,18275,18282,18288,18294,18300,18306,18312,18315,18319,18323,18327,18331,18344,18356,18363,18366,18370,18374,18377,18381,18393],{"title":4759},[4761,18264,18266,4769,18269],{"className":18265},[3414],[3412,18267,4768],{"className":18268},[4767],[3764,18270,4759],{},[4761,18272,18274],{"className":18273},[3414],"=== MRO SecureDepositTransaction ===",[4761,18276,5684,18278],{"className":18277},[3414],[3412,18279,18281],{"className":18280},[4800],"SecureDepositTransaction",[4761,18283,5684,18285],{"className":18284},[3414],[3412,18286,13560],{"className":18287},[4779],[4761,18289,5684,18291],{"className":18290},[3414],[3412,18292,17750],{"className":18293},[4779],[4761,18295,5684,18297],{"className":18296},[3414],[3412,18298,17755],{"className":18299},[4792],[4761,18301,5684,18303],{"className":18302},[3414],[3412,18304,17530],{"className":18305},[4792],[4761,18307,5684,18309],{"className":18308},[3414],[3412,18310,3815],{"className":18311},[5673],[4761,18313],{"className":18314},[3414],[4761,18316,18318],{"className":18317},[3414],"=== Успішна транзакція ===",[4761,18320,18322],{"className":18321},[3414],"  → SecureDepositTransaction.__init__(depositor='Олексій Коваленко')",[4761,18324,18326],{"className":18325},[3414],"  → ValidatedTransaction.__init__(max_limit=10000.0)",[4761,18328,18330],{"className":18329},[3414],"  → Transaction.__init__(amount=5000.0)",[4761,18332,9291,18334,18338,18339,18343],{"className":18333},[3414],[3412,18335,18337],{"className":18336},[5673],"2026-06-15T14:22:01.341","] [AUDIT\u002FSecureDepositTransaction] ",[3412,18340,18342],{"className":18341},[4792],"START",": Депозит для 'Олексій Коваленко'",[4761,18345,9291,18347,18338,18351,18355],{"className":18346},[3414],[3412,18348,18350],{"className":18349},[5673],"2026-06-15T14:22:01.342",[3412,18352,18354],{"className":18353},[4800],"SUCCESS",": Депозит $5000.00 виконано",[4761,18357,14443,18359],{"className":18358},[3414],[3412,18360,18362],{"className":18361},[4800],"{\"tx_id\": \"f84e2a1b\", \"amount\": 5000.0, \"timestamp\": \"...\", \"max_limit\": 10000.0, \"depositor_name\": \"Олексій Коваленко\"}",[4761,18364],{"className":18365},[3414],[4761,18367,18369],{"className":18368},[3414],"=== Перевищення ліміту ===",[4761,18371,18373],{"className":18372},[3414],"  → SecureDepositTransaction.__init__(depositor='Ірина Петренко')",[4761,18375,18326],{"className":18376},[3414],[4761,18378,18380],{"className":18379},[3414],"  → Transaction.__init__(amount=15000.0)",[4761,18382,9291,18384,18338,18388,18392],{"className":18383},[3414],[3412,18385,18387],{"className":18386},[5673],"2026-06-15T14:22:01.343",[3412,18389,18391],{"className":18390},[5732],"FAILED",": Перевищено ліміт: $10,000.00",[4761,18394,18396,18397],{"className":18395},[3414],"Зловлено: ",[3412,18398,18400],{"className":18399},[5732],"Перевищено ліміт: $10,000.00",[3796,18402],{},[3803,18404,18406,18407],{"id":18405},"рівень-3-advanced-міні-фреймворк-реєстру-плагінів-через-__init_subclass__","Рівень 3 (Advanced): Міні-фреймворк реєстру плагінів через ",[3390,18408,14977],{},[3399,18410,18411],{},"Реалізуйте систему плагінів для обробки даних. Кожен плагін автоматично реєструється при оголошенні класу — без явних викликів реєстрації. Це сучасна альтернатива метакласам для Python 3.6+.",[3403,18413,18415],{"className":3405,"code":18414,"language":3407,"meta":3408,"style":3408},"# plugin_framework.py\nfrom abc import ABC, abstractmethod\nfrom typing import ClassVar\n\n\nclass DataProcessor(ABC):\n    \"\"\"\n    Базовий клас для плагінів обробки даних.\n    Автоматичний реєстр через __init_subclass__ (PEP 487).\n    \"\"\"\n\n    # Реєстр: {ім'я_формату: клас_плагіна}\n    _registry: ClassVar[dict[str, type[\"DataProcessor\"]]] = {}\n\n    def __init_subclass__(cls, format_name: str | None = None, **kwargs) -> None:\n        \"\"\"\n        Викликається Python АВТОМАТИЧНО при оголошенні кожного підкласу.\n        Це альтернатива метакласам — чиста та явна.\n        \"\"\"\n        super().__init_subclass__(**kwargs)\n        if format_name is not None:\n            DataProcessor._registry[format_name.lower()] = cls\n            print(f\"[Registry] Зареєстровано: '{format_name}' → {cls.__name__}\")\n\n    # ── Абстрактний інтерфейс ─────────────────────────────────────────────────\n\n    @abstractmethod\n    def parse(self, raw: str) -> list[dict]:\n        \"\"\"Розбирає вхідний рядок у список записів.\"\"\"\n        ...\n\n    @abstractmethod\n    def serialize(self, records: list[dict]) -> str:\n        \"\"\"Серіалізує список записів у вихідний формат.\"\"\"\n        ...\n\n    # ── Конкретні методи (шаблонний метод) ───────────────────────────────────\n\n    def process(self, raw: str, transform=None) -> str:\n        \"\"\"\n        Шаблонний метод: parse → transform → serialize.\n        Підкласи реалізують parse\u002Fserialize, а transform — зовнішня функція.\n        \"\"\"\n        records = self.parse(raw)\n        if transform is not None:\n            records = [transform(r) for r in records]\n        return self.serialize(records)\n\n    # ── Фабричний метод ───────────────────────────────────────────────────────\n\n    @classmethod\n    def for_format(cls, format_name: str) -> \"DataProcessor\":\n        \"\"\"Фабрика: повертає екземпляр плагіна для вказаного формату.\"\"\"\n        plugin_cls = cls._registry.get(format_name.lower())\n        if plugin_cls is None:\n            available = list(cls._registry.keys())\n            raise ValueError(\n                f\"Невідомий формат: {format_name!r}. \"\n                f\"Доступні: {available}\"\n            )\n        return plugin_cls()\n\n    @classmethod\n    def available_formats(cls) -> list[str]:\n        return sorted(cls._registry.keys())\n\n\n# ── Конкретні плагіни: автоматично реєструються при оголошенні ────────────────\n\nclass CsvProcessor(DataProcessor, format_name=\"csv\"):\n    \"\"\"CSV-плагін: format_name='csv' → автореєстрація.\"\"\"\n\n    def parse(self, raw: str) -> list[dict]:\n        lines = [l.strip() for l in raw.strip().splitlines() if l.strip()]\n        if not lines:\n            return []\n        headers = [h.strip() for h in lines[0].split(',')]\n        result = []\n        for line in lines[1:]:\n            values = [v.strip() for v in line.split(',')]\n            result.append(dict(zip(headers, values)))\n        return result\n\n    def serialize(self, records: list[dict]) -> str:\n        if not records:\n            return \"\"\n        headers = list(records[0].keys())\n        rows = [\",\".join(headers)]\n        for record in records:\n            rows.append(\",\".join(str(record.get(h, \"\")) for h in headers))\n        return \"\\n\".join(rows)\n\n\nclass JsonLinesProcessor(DataProcessor, format_name=\"jsonl\"):\n    \"\"\"JSONL-плагін (один JSON-об'єкт на рядок).\"\"\"\n    import json as _json\n\n    def parse(self, raw: str) -> list[dict]:\n        import json\n        return [json.loads(line) for line in raw.strip().splitlines() if line.strip()]\n\n    def serialize(self, records: list[dict]) -> str:\n        import json\n        return \"\\n\".join(json.dumps(r, ensure_ascii=False) for r in records)\n\n\nclass TsvProcessor(DataProcessor, format_name=\"tsv\"):\n    \"\"\"TSV-плагін (Tab-Separated Values).\"\"\"\n\n    def parse(self, raw: str) -> list[dict]:\n        lines = raw.strip().splitlines()\n        if not lines:\n            return []\n        headers = lines[0].split('\\t')\n        return [dict(zip(headers, line.split('\\t'))) for line in lines[1:]]\n\n    def serialize(self, records: list[dict]) -> str:\n        if not records:\n            return \"\"\n        headers = list(records[0].keys())\n        rows = [\"\\t\".join(headers)]\n        for r in records:\n            rows.append(\"\\t\".join(str(r.get(h, \"\")) for r in [r] for h in headers))\n        return \"\\n\".join(rows)\n\n\n# ── Демонстрація ──────────────────────────────────────────────────────────────\n\ndef main() -> None:\n    print(f\"\\nДоступні формати: {DataProcessor.available_formats()}\")\n    print(f\"Реєстр: {list(DataProcessor._registry.keys())}\")\n\n    # CSV → обробка → JSONL (конвертація формату)\n    csv_data = \"\"\"name,age,city\nОлена,28,Київ\nІван,35,Харків\nМарія,22,Львів\"\"\"\n\n    print(\"\\n=== CSV → parse → JSONL serialize ===\")\n    csv_proc = DataProcessor.for_format(\"csv\")\n    records = csv_proc.parse(csv_data)\n    print(f\"Розібрано записів: {len(records)}\")\n\n    jsonl_proc = DataProcessor.for_format(\"jsonl\")\n    output = jsonl_proc.serialize(records)\n    print(\"JSONL:\")\n    print(output)\n\n    # Шаблонний метод з трансформацією\n    print(\"\\n=== CSV → process → uppercase names ===\")\n    def uppercase_name(r: dict) -> dict:\n        return {**r, \"name\": r[\"name\"].upper()}\n\n    result = csv_proc.process(csv_data, transform=uppercase_name)\n    print(result)\n\n    # Невідомий формат\n    try:\n        DataProcessor.for_format(\"xml\")\n    except ValueError as e:\n        print(f\"\\nОчікувана помилка: {e}\")\n\n\nif __name__ == \"__main__\":\n    main()\n",[3390,18416,18417,18422,18432,18443,18447,18451,18460,18464,18469,18474,18478,18482,18487,18502,18506,18542,18546,18551,18556,18560,18570,18584,18591,18624,18628,18633,18637,18641,18666,18671,18676,18680,18684,18712,18717,18721,18725,18730,18734,18768,18772,18777,18782,18786,18796,18811,18826,18835,18839,18844,18848,18854,18878,18883,18893,18906,18920,18928,18943,18959,18963,18970,18974,18980,18997,19010,19014,19018,19023,19027,19050,19055,19059,19083,19103,19112,19119,19145,19150,19165,19183,19198,19205,19209,19233,19242,19249,19264,19275,19286,19314,19328,19333,19338,19361,19367,19382,19387,19412,19419,19440,19445,19470,19477,19508,19513,19518,19541,19547,19552,19577,19583,19592,19599,19619,19657,19662,19687,19696,19703,19716,19729,19740,19779,19792,19797,19802,19808,19813,19828,19855,19880,19885,19891,19900,19906,19912,19918,19923,19939,19949,19955,19981,19986,19996,20002,20014,20022,20027,20033,20049,20072,20090,20095,20106,20114,20119,20125,20133,20144,20156,20182,20187,20192,20208],{"__ignoreMap":3408},[3412,18418,18419],{"class":3414,"line":3415},[3412,18420,18421],{"class":3418},"# plugin_framework.py\n",[3412,18423,18424,18426,18428,18430],{"class":3414,"line":3422},[3412,18425,4148],{"class":3530},[3412,18427,11801],{"class":3433},[3412,18429,4154],{"class":3530},[3412,18431,11806],{"class":3433},[3412,18433,18434,18436,18438,18440],{"class":3414,"line":3437},[3412,18435,4148],{"class":3530},[3412,18437,12664],{"class":3433},[3412,18439,4154],{"class":3530},[3412,18441,18442],{"class":3433}," ClassVar\n",[3412,18444,18445],{"class":3414,"line":3468},[3412,18446,3554],{"emptyLinePlaceholder":3553},[3412,18448,18449],{"class":3414,"line":3516},[3412,18450,3554],{"emptyLinePlaceholder":3553},[3412,18452,18453,18455,18458],{"class":3414,"line":3550},[3412,18454,3426],{"class":3425},[3412,18456,18457],{"class":3429}," DataProcessor",[3412,18459,11820],{"class":3433},[3412,18461,18462],{"class":3414,"line":3557},[3412,18463,10012],{"class":3495},[3412,18465,18466],{"class":3414,"line":3567},[3412,18467,18468],{"class":3495},"    Базовий клас для плагінів обробки даних.\n",[3412,18470,18471],{"class":3414,"line":3588},[3412,18472,18473],{"class":3495},"    Автоматичний реєстр через __init_subclass__ (PEP 487).\n",[3412,18475,18476],{"class":3414,"line":3628},[3412,18477,10012],{"class":3495},[3412,18479,18480],{"class":3414,"line":3655},[3412,18481,3554],{"emptyLinePlaceholder":3553},[3412,18483,18484],{"class":3414,"line":3660},[3412,18485,18486],{"class":3418},"    # Реєстр: {ім'я_формату: клас_плагіна}\n",[3412,18488,18489,18492,18494,18496,18499],{"class":3414,"line":3670},[3412,18490,18491],{"class":3433},"    _registry: ClassVar[dict[",[3412,18493,3880],{"class":3429},[3412,18495,15195],{"class":3433},[3412,18497,18498],{"class":3495},"\"DataProcessor\"",[3412,18500,18501],{"class":3433},"]]] = {}\n",[3412,18503,18504],{"class":3414,"line":3691},[3412,18505,3554],{"emptyLinePlaceholder":3553},[3412,18507,18508,18510,18512,18514,18516,18518,18520,18522,18524,18526,18528,18530,18532,18534,18536,18538,18540],{"class":3414,"line":3731},[3412,18509,3440],{"class":3425},[3412,18511,15010],{"class":3443},[3412,18513,3447],{"class":3433},[3412,18515,6497],{"class":3450},[3412,18517,3454],{"class":3433},[3412,18519,15220],{"class":3450},[3412,18521,3877],{"class":3433},[3412,18523,3880],{"class":3429},[3412,18525,9654],{"class":3433},[3412,18527,4011],{"class":3425},[3412,18529,3921],{"class":3433},[3412,18531,4011],{"class":3425},[3412,18533,7534],{"class":3433},[3412,18535,7537],{"class":3450},[3412,18537,3946],{"class":3433},[3412,18539,4011],{"class":3425},[3412,18541,3434],{"class":3433},[3412,18543,18544],{"class":3414,"line":4072},[3412,18545,6880],{"class":3495},[3412,18547,18548],{"class":3414,"line":4077},[3412,18549,18550],{"class":3495},"        Викликається Python АВТОМАТИЧНО при оголошенні кожного підкласу.\n",[3412,18552,18553],{"class":3414,"line":4095},[3412,18554,18555],{"class":3495},"        Це альтернатива метакласам — чиста та явна.\n",[3412,18557,18558],{"class":3414,"line":5014},[3412,18559,6880],{"class":3495},[3412,18561,18562,18564,18566,18568],{"class":3414,"line":5020},[3412,18563,4236],{"class":3429},[3412,18565,4239],{"class":3433},[3412,18567,14977],{"class":3443},[3412,18569,7749],{"class":3433},[3412,18571,18572,18574,18576,18578,18580,18582],{"class":3414,"line":5026},[3412,18573,9686],{"class":3530},[3412,18575,15259],{"class":3433},[3412,18577,10120],{"class":3425},[3412,18579,10123],{"class":3425},[3412,18581,10126],{"class":3425},[3412,18583,3434],{"class":3433},[3412,18585,18586,18589],{"class":3414,"line":5035},[3412,18587,18588],{"class":3433},"            DataProcessor._registry[format_name.lower()] = ",[3412,18590,15280],{"class":3425},[3412,18592,18593,18596,18598,18600,18603,18605,18607,18609,18612,18614,18616,18618,18620,18622],{"class":3414,"line":5046},[3412,18594,18595],{"class":3443},"            print",[3412,18597,3447],{"class":3433},[3412,18599,3492],{"class":3425},[3412,18601,18602],{"class":3495},"\"[Registry] Зареєстровано: '",[3412,18604,3499],{"class":3425},[3412,18606,15220],{"class":3433},[3412,18608,3504],{"class":3425},[3412,18610,18611],{"class":3495},"' → ",[3412,18613,15071],{"class":3425},[3412,18615,3539],{"class":3433},[3412,18617,4113],{"class":3450},[3412,18619,3504],{"class":3425},[3412,18621,3507],{"class":3495},[3412,18623,4069],{"class":3433},[3412,18625,18626],{"class":3414,"line":5057},[3412,18627,3554],{"emptyLinePlaceholder":3553},[3412,18629,18630],{"class":3414,"line":5345},[3412,18631,18632],{"class":3418},"    # ── Абстрактний інтерфейс ─────────────────────────────────────────────────\n",[3412,18634,18635],{"class":3414,"line":5364},[3412,18636,3554],{"emptyLinePlaceholder":3553},[3412,18638,18639],{"class":3414,"line":5370},[3412,18640,11834],{"class":3443},[3412,18642,18643,18645,18648,18650,18652,18654,18656,18658,18660,18662,18664],{"class":3414,"line":5376},[3412,18644,3440],{"class":3425},[3412,18646,18647],{"class":3443}," parse",[3412,18649,3447],{"class":3433},[3412,18651,3451],{"class":3450},[3412,18653,3454],{"class":3433},[3412,18655,15337],{"class":3450},[3412,18657,3877],{"class":3433},[3412,18659,3880],{"class":3429},[3412,18661,13238],{"class":3433},[3412,18663,6868],{"class":3429},[3412,18665,13243],{"class":3433},[3412,18667,18668],{"class":3414,"line":5382},[3412,18669,18670],{"class":3495},"        \"\"\"Розбирає вхідний рядок у список записів.\"\"\"\n",[3412,18672,18673],{"class":3414,"line":5388},[3412,18674,18675],{"class":3433},"        ...\n",[3412,18677,18678],{"class":3414,"line":5393},[3412,18679,3554],{"emptyLinePlaceholder":3553},[3412,18681,18682],{"class":3414,"line":5399},[3412,18683,11834],{"class":3443},[3412,18685,18686,18688,18691,18693,18695,18697,18700,18703,18705,18708,18710],{"class":3414,"line":5422},[3412,18687,3440],{"class":3425},[3412,18689,18690],{"class":3443}," serialize",[3412,18692,3447],{"class":3433},[3412,18694,3451],{"class":3450},[3412,18696,3454],{"class":3433},[3412,18698,18699],{"class":3450},"records",[3412,18701,18702],{"class":3433},": list[",[3412,18704,6868],{"class":3429},[3412,18706,18707],{"class":3433},"]) -> ",[3412,18709,3880],{"class":3429},[3412,18711,3434],{"class":3433},[3412,18713,18714],{"class":3414,"line":5428},[3412,18715,18716],{"class":3495},"        \"\"\"Серіалізує список записів у вихідний формат.\"\"\"\n",[3412,18718,18719],{"class":3414,"line":5434},[3412,18720,18675],{"class":3433},[3412,18722,18723],{"class":3414,"line":5439},[3412,18724,3554],{"emptyLinePlaceholder":3553},[3412,18726,18727],{"class":3414,"line":5456},[3412,18728,18729],{"class":3418},"    # ── Конкретні методи (шаблонний метод) ───────────────────────────────────\n",[3412,18731,18732],{"class":3414,"line":5462},[3412,18733,3554],{"emptyLinePlaceholder":3553},[3412,18735,18736,18738,18741,18743,18745,18747,18749,18751,18753,18755,18758,18760,18762,18764,18766],{"class":3414,"line":5467},[3412,18737,3440],{"class":3425},[3412,18739,18740],{"class":3443}," process",[3412,18742,3447],{"class":3433},[3412,18744,3451],{"class":3450},[3412,18746,3454],{"class":3433},[3412,18748,15337],{"class":3450},[3412,18750,3877],{"class":3433},[3412,18752,3880],{"class":3429},[3412,18754,3454],{"class":3433},[3412,18756,18757],{"class":3450},"transform",[3412,18759,4691],{"class":3433},[3412,18761,4011],{"class":3425},[3412,18763,3946],{"class":3433},[3412,18765,3880],{"class":3429},[3412,18767,3434],{"class":3433},[3412,18769,18770],{"class":3414,"line":5473},[3412,18771,6880],{"class":3495},[3412,18773,18774],{"class":3414,"line":5491},[3412,18775,18776],{"class":3495},"        Шаблонний метод: parse → transform → serialize.\n",[3412,18778,18779],{"class":3414,"line":5496},[3412,18780,18781],{"class":3495},"        Підкласи реалізують parse\u002Fserialize, а transform — зовнішня функція.\n",[3412,18783,18784],{"class":3414,"line":5502},[3412,18785,6880],{"class":3495},[3412,18787,18788,18791,18793],{"class":3414,"line":5518},[3412,18789,18790],{"class":3433},"        records = ",[3412,18792,3451],{"class":3425},[3412,18794,18795],{"class":3433},".parse(raw)\n",[3412,18797,18798,18800,18803,18805,18807,18809],{"class":3414,"line":5533},[3412,18799,9686],{"class":3530},[3412,18801,18802],{"class":3433}," transform ",[3412,18804,10120],{"class":3425},[3412,18806,10123],{"class":3425},[3412,18808,10126],{"class":3425},[3412,18810,3434],{"class":3433},[3412,18812,18813,18816,18818,18821,18823],{"class":3414,"line":5548},[3412,18814,18815],{"class":3433},"            records = [transform(r) ",[3412,18817,4738],{"class":3530},[3412,18819,18820],{"class":3433}," r ",[3412,18822,4744],{"class":3530},[3412,18824,18825],{"class":3433}," records]\n",[3412,18827,18828,18830,18832],{"class":3414,"line":5568},[3412,18829,3955],{"class":3530},[3412,18831,10741],{"class":3425},[3412,18833,18834],{"class":3433},".serialize(records)\n",[3412,18836,18837],{"class":3414,"line":5573},[3412,18838,3554],{"emptyLinePlaceholder":3553},[3412,18840,18841],{"class":3414,"line":5579},[3412,18842,18843],{"class":3418},"    # ── Фабричний метод ───────────────────────────────────────────────────────\n",[3412,18845,18846],{"class":3414,"line":5595},[3412,18847,3554],{"emptyLinePlaceholder":3553},[3412,18849,18850,18852],{"class":3414,"line":5609},[3412,18851,6844],{"class":3443},[3412,18853,6847],{"class":3429},[3412,18855,18856,18858,18860,18862,18864,18866,18868,18870,18872,18874,18876],{"class":3414,"line":7232},[3412,18857,3440],{"class":3425},[3412,18859,15364],{"class":3443},[3412,18861,3447],{"class":3433},[3412,18863,6497],{"class":3450},[3412,18865,3454],{"class":3433},[3412,18867,15220],{"class":3450},[3412,18869,3877],{"class":3433},[3412,18871,3880],{"class":3429},[3412,18873,3946],{"class":3433},[3412,18875,18498],{"class":3495},[3412,18877,3434],{"class":3433},[3412,18879,18880],{"class":3414,"line":7261},[3412,18881,18882],{"class":3495},"        \"\"\"Фабрика: повертає екземпляр плагіна для вказаного формату.\"\"\"\n",[3412,18884,18885,18888,18890],{"class":3414,"line":7266},[3412,18886,18887],{"class":3433},"        plugin_cls = ",[3412,18889,6497],{"class":3425},[3412,18891,18892],{"class":3433},"._registry.get(format_name.lower())\n",[3412,18894,18895,18897,18900,18902,18904],{"class":3414,"line":7271},[3412,18896,9686],{"class":3530},[3412,18898,18899],{"class":3433}," plugin_cls ",[3412,18901,10120],{"class":3425},[3412,18903,10126],{"class":3425},[3412,18905,3434],{"class":3433},[3412,18907,18908,18911,18913,18915,18917],{"class":3414,"line":7277},[3412,18909,18910],{"class":3433},"            available = ",[3412,18912,13214],{"class":3429},[3412,18914,3447],{"class":3433},[3412,18916,6497],{"class":3425},[3412,18918,18919],{"class":3433},"._registry.keys())\n",[3412,18921,18922,18924,18926],{"class":3414,"line":7310},[3412,18923,10786],{"class":3530},[3412,18925,10789],{"class":3429},[3412,18927,6908],{"class":3433},[3412,18929,18930,18932,18934,18936,18938,18940],{"class":3414,"line":7315},[3412,18931,14924],{"class":3425},[3412,18933,15422],{"class":3495},[3412,18935,3499],{"class":3425},[3412,18937,15220],{"class":3433},[3412,18939,4125],{"class":3425},[3412,18941,18942],{"class":3495},". \"\n",[3412,18944,18945,18947,18950,18952,18955,18957],{"class":3414,"line":7321},[3412,18946,14924],{"class":3425},[3412,18948,18949],{"class":3495},"\"Доступні: ",[3412,18951,3499],{"class":3425},[3412,18953,18954],{"class":3433},"available",[3412,18956,3504],{"class":3425},[3412,18958,8434],{"class":3495},[3412,18960,18961],{"class":3414,"line":7346},[3412,18962,14955],{"class":3433},[3412,18964,18965,18967],{"class":3414,"line":7361},[3412,18966,3955],{"class":3530},[3412,18968,18969],{"class":3433}," plugin_cls()\n",[3412,18971,18972],{"class":3414,"line":7366},[3412,18973,3554],{"emptyLinePlaceholder":3553},[3412,18975,18976,18978],{"class":3414,"line":7372},[3412,18977,6844],{"class":3443},[3412,18979,6847],{"class":3429},[3412,18981,18982,18984,18987,18989,18991,18993,18995],{"class":3414,"line":7378},[3412,18983,3440],{"class":3425},[3412,18985,18986],{"class":3443}," available_formats",[3412,18988,3447],{"class":3433},[3412,18990,6497],{"class":3450},[3412,18992,13238],{"class":3433},[3412,18994,3880],{"class":3429},[3412,18996,13243],{"class":3433},[3412,18998,18999,19001,19004,19006,19008],{"class":3414,"line":7393},[3412,19000,3955],{"class":3530},[3412,19002,19003],{"class":3443}," sorted",[3412,19005,3447],{"class":3433},[3412,19007,6497],{"class":3425},[3412,19009,18919],{"class":3433},[3412,19011,19012],{"class":3414,"line":13280},[3412,19013,3554],{"emptyLinePlaceholder":3553},[3412,19015,19016],{"class":3414,"line":13288},[3412,19017,3554],{"emptyLinePlaceholder":3553},[3412,19019,19020],{"class":3414,"line":13294},[3412,19021,19022],{"class":3418},"# ── Конкретні плагіни: автоматично реєструються при оголошенні ────────────────\n",[3412,19024,19025],{"class":3414,"line":13302},[3412,19026,3554],{"emptyLinePlaceholder":3553},[3412,19028,19029,19031,19034,19036,19039,19041,19043,19045,19048],{"class":3414,"line":13307},[3412,19030,3426],{"class":3425},[3412,19032,19033],{"class":3429}," CsvProcessor",[3412,19035,3447],{"class":3433},[3412,19037,19038],{"class":3429},"DataProcessor",[3412,19040,3454],{"class":3433},[3412,19042,15220],{"class":3450},[3412,19044,4691],{"class":3433},[3412,19046,19047],{"class":3495},"\"csv\"",[3412,19049,3893],{"class":3433},[3412,19051,19052],{"class":3414,"line":13325},[3412,19053,19054],{"class":3495},"    \"\"\"CSV-плагін: format_name='csv' → автореєстрація.\"\"\"\n",[3412,19056,19057],{"class":3414,"line":13331},[3412,19058,3554],{"emptyLinePlaceholder":3553},[3412,19060,19061,19063,19065,19067,19069,19071,19073,19075,19077,19079,19081],{"class":3414,"line":13342},[3412,19062,3440],{"class":3425},[3412,19064,18647],{"class":3443},[3412,19066,3447],{"class":3433},[3412,19068,3451],{"class":3450},[3412,19070,3454],{"class":3433},[3412,19072,15337],{"class":3450},[3412,19074,3877],{"class":3433},[3412,19076,3880],{"class":3429},[3412,19078,13238],{"class":3433},[3412,19080,6868],{"class":3429},[3412,19082,13243],{"class":3433},[3412,19084,19085,19088,19090,19093,19095,19098,19100],{"class":3414,"line":13350},[3412,19086,19087],{"class":3433},"        lines = [l.strip() ",[3412,19089,4738],{"class":3530},[3412,19091,19092],{"class":3433}," l ",[3412,19094,4744],{"class":3530},[3412,19096,19097],{"class":3433}," raw.strip().splitlines() ",[3412,19099,11031],{"class":3530},[3412,19101,19102],{"class":3433}," l.strip()]\n",[3412,19104,19105,19107,19109],{"class":3414,"line":13378},[3412,19106,9686],{"class":3530},[3412,19108,10123],{"class":3425},[3412,19110,19111],{"class":3433}," lines:\n",[3412,19113,19114,19116],{"class":3414,"line":13383},[3412,19115,9707],{"class":3530},[3412,19117,19118],{"class":3433}," []\n",[3412,19120,19121,19124,19126,19129,19131,19134,19136,19139,19142],{"class":3414,"line":13388},[3412,19122,19123],{"class":3433},"        headers = [h.strip() ",[3412,19125,4738],{"class":3530},[3412,19127,19128],{"class":3433}," h ",[3412,19130,4744],{"class":3530},[3412,19132,19133],{"class":3433}," lines[",[3412,19135,10779],{"class":3924},[3412,19137,19138],{"class":3433},"].split(",[3412,19140,19141],{"class":3495},"','",[3412,19143,19144],{"class":3433},")]\n",[3412,19146,19147],{"class":3414,"line":13398},[3412,19148,19149],{"class":3433},"        result = []\n",[3412,19151,19152,19154,19156,19158,19160,19162],{"class":3414,"line":13403},[3412,19153,13258],{"class":3530},[3412,19155,15684],{"class":3433},[3412,19157,4744],{"class":3530},[3412,19159,19133],{"class":3433},[3412,19161,7331],{"class":3924},[3412,19163,19164],{"class":3433},":]:\n",[3412,19166,19167,19170,19172,19174,19176,19179,19181],{"class":3414,"line":13409},[3412,19168,19169],{"class":3433},"            values = [v.strip() ",[3412,19171,4738],{"class":3530},[3412,19173,4741],{"class":3433},[3412,19175,4744],{"class":3530},[3412,19177,19178],{"class":3433}," line.split(",[3412,19180,19141],{"class":3495},[3412,19182,19144],{"class":3433},[3412,19184,19185,19188,19190,19192,19195],{"class":3414,"line":13415},[3412,19186,19187],{"class":3433},"            result.append(",[3412,19189,6868],{"class":3429},[3412,19191,3447],{"class":3433},[3412,19193,19194],{"class":3443},"zip",[3412,19196,19197],{"class":3433},"(headers, values)))\n",[3412,19199,19200,19202],{"class":3414,"line":13420},[3412,19201,3955],{"class":3530},[3412,19203,19204],{"class":3433}," result\n",[3412,19206,19207],{"class":3414,"line":13425},[3412,19208,3554],{"emptyLinePlaceholder":3553},[3412,19210,19211,19213,19215,19217,19219,19221,19223,19225,19227,19229,19231],{"class":3414,"line":13433},[3412,19212,3440],{"class":3425},[3412,19214,18690],{"class":3443},[3412,19216,3447],{"class":3433},[3412,19218,3451],{"class":3450},[3412,19220,3454],{"class":3433},[3412,19222,18699],{"class":3450},[3412,19224,18702],{"class":3433},[3412,19226,6868],{"class":3429},[3412,19228,18707],{"class":3433},[3412,19230,3880],{"class":3429},[3412,19232,3434],{"class":3433},[3412,19234,19235,19237,19239],{"class":3414,"line":13451},[3412,19236,9686],{"class":3530},[3412,19238,10123],{"class":3425},[3412,19240,19241],{"class":3433}," records:\n",[3412,19243,19244,19246],{"class":3414,"line":13457},[3412,19245,9707],{"class":3530},[3412,19247,19248],{"class":3495}," \"\"\n",[3412,19250,19251,19254,19256,19259,19261],{"class":3414,"line":13477},[3412,19252,19253],{"class":3433},"        headers = ",[3412,19255,13214],{"class":3429},[3412,19257,19258],{"class":3433},"(records[",[3412,19260,10779],{"class":3924},[3412,19262,19263],{"class":3433},"].keys())\n",[3412,19265,19266,19269,19272],{"class":3414,"line":13492},[3412,19267,19268],{"class":3433},"        rows = [",[3412,19270,19271],{"class":3495},"\",\"",[3412,19273,19274],{"class":3433},".join(headers)]\n",[3412,19276,19277,19279,19282,19284],{"class":3414,"line":13508},[3412,19278,13258],{"class":3530},[3412,19280,19281],{"class":3433}," record ",[3412,19283,4744],{"class":3530},[3412,19285,19241],{"class":3433},[3412,19287,19289,19292,19294,19296,19298,19301,19303,19305,19307,19309,19311],{"class":3414,"line":19288},90,[3412,19290,19291],{"class":3433},"            rows.append(",[3412,19293,19271],{"class":3495},[3412,19295,15612],{"class":3433},[3412,19297,3880],{"class":3429},[3412,19299,19300],{"class":3433},"(record.get(h, ",[3412,19302,14221],{"class":3495},[3412,19304,9749],{"class":3433},[3412,19306,4738],{"class":3530},[3412,19308,19128],{"class":3433},[3412,19310,4744],{"class":3530},[3412,19312,19313],{"class":3433}," headers))\n",[3412,19315,19317,19319,19321,19323,19325],{"class":3414,"line":19316},91,[3412,19318,3955],{"class":3530},[3412,19320,15605],{"class":3495},[3412,19322,5319],{"class":5318},[3412,19324,3507],{"class":3495},[3412,19326,19327],{"class":3433},".join(rows)\n",[3412,19329,19331],{"class":3414,"line":19330},92,[3412,19332,3554],{"emptyLinePlaceholder":3553},[3412,19334,19336],{"class":3414,"line":19335},93,[3412,19337,3554],{"emptyLinePlaceholder":3553},[3412,19339,19341,19343,19346,19348,19350,19352,19354,19356,19359],{"class":3414,"line":19340},94,[3412,19342,3426],{"class":3425},[3412,19344,19345],{"class":3429}," JsonLinesProcessor",[3412,19347,3447],{"class":3433},[3412,19349,19038],{"class":3429},[3412,19351,3454],{"class":3433},[3412,19353,15220],{"class":3450},[3412,19355,4691],{"class":3433},[3412,19357,19358],{"class":3495},"\"jsonl\"",[3412,19360,3893],{"class":3433},[3412,19362,19364],{"class":3414,"line":19363},95,[3412,19365,19366],{"class":3495},"    \"\"\"JSONL-плагін (один JSON-об'єкт на рядок).\"\"\"\n",[3412,19368,19370,19373,19376,19379],{"class":3414,"line":19369},96,[3412,19371,19372],{"class":3530},"    import",[3412,19374,19375],{"class":3433}," json ",[3412,19377,19378],{"class":3530},"as",[3412,19380,19381],{"class":3433}," _json\n",[3412,19383,19385],{"class":3414,"line":19384},97,[3412,19386,3554],{"emptyLinePlaceholder":3553},[3412,19388,19390,19392,19394,19396,19398,19400,19402,19404,19406,19408,19410],{"class":3414,"line":19389},98,[3412,19391,3440],{"class":3425},[3412,19393,18647],{"class":3443},[3412,19395,3447],{"class":3433},[3412,19397,3451],{"class":3450},[3412,19399,3454],{"class":3433},[3412,19401,15337],{"class":3450},[3412,19403,3877],{"class":3433},[3412,19405,3880],{"class":3429},[3412,19407,13238],{"class":3433},[3412,19409,6868],{"class":3429},[3412,19411,13243],{"class":3433},[3412,19413,19415,19417],{"class":3414,"line":19414},99,[3412,19416,15509],{"class":3530},[3412,19418,6757],{"class":3433},[3412,19420,19422,19424,19427,19429,19431,19433,19435,19437],{"class":3414,"line":19421},100,[3412,19423,3955],{"class":3530},[3412,19425,19426],{"class":3433}," [json.loads(line) ",[3412,19428,4738],{"class":3530},[3412,19430,15684],{"class":3433},[3412,19432,4744],{"class":3530},[3412,19434,19097],{"class":3433},[3412,19436,11031],{"class":3530},[3412,19438,19439],{"class":3433}," line.strip()]\n",[3412,19441,19443],{"class":3414,"line":19442},101,[3412,19444,3554],{"emptyLinePlaceholder":3553},[3412,19446,19448,19450,19452,19454,19456,19458,19460,19462,19464,19466,19468],{"class":3414,"line":19447},102,[3412,19449,3440],{"class":3425},[3412,19451,18690],{"class":3443},[3412,19453,3447],{"class":3433},[3412,19455,3451],{"class":3450},[3412,19457,3454],{"class":3433},[3412,19459,18699],{"class":3450},[3412,19461,18702],{"class":3433},[3412,19463,6868],{"class":3429},[3412,19465,18707],{"class":3433},[3412,19467,3880],{"class":3429},[3412,19469,3434],{"class":3433},[3412,19471,19473,19475],{"class":3414,"line":19472},103,[3412,19474,15509],{"class":3530},[3412,19476,6757],{"class":3433},[3412,19478,19480,19482,19484,19486,19488,19491,19493,19495,19497,19499,19501,19503,19505],{"class":3414,"line":19479},104,[3412,19481,3955],{"class":3530},[3412,19483,15605],{"class":3495},[3412,19485,5319],{"class":5318},[3412,19487,3507],{"class":3495},[3412,19489,19490],{"class":3433},".join(json.dumps(r, ",[3412,19492,12788],{"class":3450},[3412,19494,4691],{"class":3433},[3412,19496,5733],{"class":3425},[3412,19498,10854],{"class":3433},[3412,19500,4738],{"class":3530},[3412,19502,18820],{"class":3433},[3412,19504,4744],{"class":3530},[3412,19506,19507],{"class":3433}," records)\n",[3412,19509,19511],{"class":3414,"line":19510},105,[3412,19512,3554],{"emptyLinePlaceholder":3553},[3412,19514,19516],{"class":3414,"line":19515},106,[3412,19517,3554],{"emptyLinePlaceholder":3553},[3412,19519,19521,19523,19526,19528,19530,19532,19534,19536,19539],{"class":3414,"line":19520},107,[3412,19522,3426],{"class":3425},[3412,19524,19525],{"class":3429}," TsvProcessor",[3412,19527,3447],{"class":3433},[3412,19529,19038],{"class":3429},[3412,19531,3454],{"class":3433},[3412,19533,15220],{"class":3450},[3412,19535,4691],{"class":3433},[3412,19537,19538],{"class":3495},"\"tsv\"",[3412,19540,3893],{"class":3433},[3412,19542,19544],{"class":3414,"line":19543},108,[3412,19545,19546],{"class":3495},"    \"\"\"TSV-плагін (Tab-Separated Values).\"\"\"\n",[3412,19548,19550],{"class":3414,"line":19549},109,[3412,19551,3554],{"emptyLinePlaceholder":3553},[3412,19553,19555,19557,19559,19561,19563,19565,19567,19569,19571,19573,19575],{"class":3414,"line":19554},110,[3412,19556,3440],{"class":3425},[3412,19558,18647],{"class":3443},[3412,19560,3447],{"class":3433},[3412,19562,3451],{"class":3450},[3412,19564,3454],{"class":3433},[3412,19566,15337],{"class":3450},[3412,19568,3877],{"class":3433},[3412,19570,3880],{"class":3429},[3412,19572,13238],{"class":3433},[3412,19574,6868],{"class":3429},[3412,19576,13243],{"class":3433},[3412,19578,19580],{"class":3414,"line":19579},111,[3412,19581,19582],{"class":3433},"        lines = raw.strip().splitlines()\n",[3412,19584,19586,19588,19590],{"class":3414,"line":19585},112,[3412,19587,9686],{"class":3530},[3412,19589,10123],{"class":3425},[3412,19591,19111],{"class":3433},[3412,19593,19595,19597],{"class":3414,"line":19594},113,[3412,19596,9707],{"class":3530},[3412,19598,19118],{"class":3433},[3412,19600,19602,19605,19607,19609,19612,19615,19617],{"class":3414,"line":19601},114,[3412,19603,19604],{"class":3433},"        headers = lines[",[3412,19606,10779],{"class":3924},[3412,19608,19138],{"class":3433},[3412,19610,19611],{"class":3495},"'",[3412,19613,19614],{"class":5318},"\\t",[3412,19616,19611],{"class":3495},[3412,19618,4069],{"class":3433},[3412,19620,19622,19624,19626,19628,19630,19632,19635,19637,19639,19641,19644,19646,19648,19650,19652,19654],{"class":3414,"line":19621},115,[3412,19623,3955],{"class":3530},[3412,19625,16043],{"class":3433},[3412,19627,6868],{"class":3429},[3412,19629,3447],{"class":3433},[3412,19631,19194],{"class":3443},[3412,19633,19634],{"class":3433},"(headers, line.split(",[3412,19636,19611],{"class":3495},[3412,19638,19614],{"class":5318},[3412,19640,19611],{"class":3495},[3412,19642,19643],{"class":3433},"))) ",[3412,19645,4738],{"class":3530},[3412,19647,15684],{"class":3433},[3412,19649,4744],{"class":3530},[3412,19651,19133],{"class":3433},[3412,19653,7331],{"class":3924},[3412,19655,19656],{"class":3433},":]]\n",[3412,19658,19660],{"class":3414,"line":19659},116,[3412,19661,3554],{"emptyLinePlaceholder":3553},[3412,19663,19665,19667,19669,19671,19673,19675,19677,19679,19681,19683,19685],{"class":3414,"line":19664},117,[3412,19666,3440],{"class":3425},[3412,19668,18690],{"class":3443},[3412,19670,3447],{"class":3433},[3412,19672,3451],{"class":3450},[3412,19674,3454],{"class":3433},[3412,19676,18699],{"class":3450},[3412,19678,18702],{"class":3433},[3412,19680,6868],{"class":3429},[3412,19682,18707],{"class":3433},[3412,19684,3880],{"class":3429},[3412,19686,3434],{"class":3433},[3412,19688,19690,19692,19694],{"class":3414,"line":19689},118,[3412,19691,9686],{"class":3530},[3412,19693,10123],{"class":3425},[3412,19695,19241],{"class":3433},[3412,19697,19699,19701],{"class":3414,"line":19698},119,[3412,19700,9707],{"class":3530},[3412,19702,19248],{"class":3495},[3412,19704,19706,19708,19710,19712,19714],{"class":3414,"line":19705},120,[3412,19707,19253],{"class":3433},[3412,19709,13214],{"class":3429},[3412,19711,19258],{"class":3433},[3412,19713,10779],{"class":3924},[3412,19715,19263],{"class":3433},[3412,19717,19719,19721,19723,19725,19727],{"class":3414,"line":19718},121,[3412,19720,19268],{"class":3433},[3412,19722,3507],{"class":3495},[3412,19724,19614],{"class":5318},[3412,19726,3507],{"class":3495},[3412,19728,19274],{"class":3433},[3412,19730,19732,19734,19736,19738],{"class":3414,"line":19731},122,[3412,19733,13258],{"class":3530},[3412,19735,18820],{"class":3433},[3412,19737,4744],{"class":3530},[3412,19739,19241],{"class":3433},[3412,19741,19743,19745,19747,19749,19751,19753,19755,19758,19760,19762,19764,19766,19768,19771,19773,19775,19777],{"class":3414,"line":19742},123,[3412,19744,19291],{"class":3433},[3412,19746,3507],{"class":3495},[3412,19748,19614],{"class":5318},[3412,19750,3507],{"class":3495},[3412,19752,15612],{"class":3433},[3412,19754,3880],{"class":3429},[3412,19756,19757],{"class":3433},"(r.get(h, ",[3412,19759,14221],{"class":3495},[3412,19761,9749],{"class":3433},[3412,19763,4738],{"class":3530},[3412,19765,18820],{"class":3433},[3412,19767,4744],{"class":3530},[3412,19769,19770],{"class":3433}," [r] ",[3412,19772,4738],{"class":3530},[3412,19774,19128],{"class":3433},[3412,19776,4744],{"class":3530},[3412,19778,19313],{"class":3433},[3412,19780,19782,19784,19786,19788,19790],{"class":3414,"line":19781},124,[3412,19783,3955],{"class":3530},[3412,19785,15605],{"class":3495},[3412,19787,5319],{"class":5318},[3412,19789,3507],{"class":3495},[3412,19791,19327],{"class":3433},[3412,19793,19795],{"class":3414,"line":19794},125,[3412,19796,3554],{"emptyLinePlaceholder":3553},[3412,19798,19800],{"class":3414,"line":19799},126,[3412,19801,3554],{"emptyLinePlaceholder":3553},[3412,19803,19805],{"class":3414,"line":19804},127,[3412,19806,19807],{"class":3418},"# ── Демонстрація ──────────────────────────────────────────────────────────────\n",[3412,19809,19811],{"class":3414,"line":19810},128,[3412,19812,3554],{"emptyLinePlaceholder":3553},[3412,19814,19816,19818,19821,19824,19826],{"class":3414,"line":19815},129,[3412,19817,9631],{"class":3425},[3412,19819,19820],{"class":3443}," main",[3412,19822,19823],{"class":3433},"() -> ",[3412,19825,4011],{"class":3425},[3412,19827,3434],{"class":3433},[3412,19829,19831,19833,19835,19837,19839,19841,19844,19846,19849,19851,19853],{"class":3414,"line":19830},130,[3412,19832,4752],{"class":3443},[3412,19834,3447],{"class":3433},[3412,19836,3492],{"class":3425},[3412,19838,3507],{"class":3495},[3412,19840,5319],{"class":5318},[3412,19842,19843],{"class":3495},"Доступні формати: ",[3412,19845,3499],{"class":3425},[3412,19847,19848],{"class":3433},"DataProcessor.available_formats()",[3412,19850,3504],{"class":3425},[3412,19852,3507],{"class":3495},[3412,19854,4069],{"class":3433},[3412,19856,19858,19860,19862,19864,19867,19869,19871,19874,19876,19878],{"class":3414,"line":19857},131,[3412,19859,4752],{"class":3443},[3412,19861,3447],{"class":3433},[3412,19863,3492],{"class":3425},[3412,19865,19866],{"class":3495},"\"Реєстр: ",[3412,19868,3499],{"class":3425},[3412,19870,13214],{"class":3429},[3412,19872,19873],{"class":3433},"(DataProcessor._registry.keys())",[3412,19875,3504],{"class":3425},[3412,19877,3507],{"class":3495},[3412,19879,4069],{"class":3433},[3412,19881,19883],{"class":3414,"line":19882},132,[3412,19884,3554],{"emptyLinePlaceholder":3553},[3412,19886,19888],{"class":3414,"line":19887},133,[3412,19889,19890],{"class":3418},"    # CSV → обробка → JSONL (конвертація формату)\n",[3412,19892,19894,19897],{"class":3414,"line":19893},134,[3412,19895,19896],{"class":3433},"    csv_data = ",[3412,19898,19899],{"class":3495},"\"\"\"name,age,city\n",[3412,19901,19903],{"class":3414,"line":19902},135,[3412,19904,19905],{"class":3495},"Олена,28,Київ\n",[3412,19907,19909],{"class":3414,"line":19908},136,[3412,19910,19911],{"class":3495},"Іван,35,Харків\n",[3412,19913,19915],{"class":3414,"line":19914},137,[3412,19916,19917],{"class":3495},"Марія,22,Львів\"\"\"\n",[3412,19919,19921],{"class":3414,"line":19920},138,[3412,19922,3554],{"emptyLinePlaceholder":3553},[3412,19924,19926,19928,19930,19932,19934,19937],{"class":3414,"line":19925},139,[3412,19927,4752],{"class":3443},[3412,19929,3447],{"class":3433},[3412,19931,3507],{"class":3495},[3412,19933,5319],{"class":5318},[3412,19935,19936],{"class":3495},"=== CSV → parse → JSONL serialize ===\"",[3412,19938,4069],{"class":3433},[3412,19940,19942,19945,19947],{"class":3414,"line":19941},140,[3412,19943,19944],{"class":3433},"    csv_proc = DataProcessor.for_format(",[3412,19946,19047],{"class":3495},[3412,19948,4069],{"class":3433},[3412,19950,19952],{"class":3414,"line":19951},141,[3412,19953,19954],{"class":3433},"    records = csv_proc.parse(csv_data)\n",[3412,19956,19958,19960,19962,19964,19967,19969,19972,19975,19977,19979],{"class":3414,"line":19957},142,[3412,19959,4752],{"class":3443},[3412,19961,3447],{"class":3433},[3412,19963,3492],{"class":3425},[3412,19965,19966],{"class":3495},"\"Розібрано записів: ",[3412,19968,3499],{"class":3425},[3412,19970,19971],{"class":3443},"len",[3412,19973,19974],{"class":3433},"(records)",[3412,19976,3504],{"class":3425},[3412,19978,3507],{"class":3495},[3412,19980,4069],{"class":3433},[3412,19982,19984],{"class":3414,"line":19983},143,[3412,19985,3554],{"emptyLinePlaceholder":3553},[3412,19987,19989,19992,19994],{"class":3414,"line":19988},144,[3412,19990,19991],{"class":3433},"    jsonl_proc = DataProcessor.for_format(",[3412,19993,19358],{"class":3495},[3412,19995,4069],{"class":3433},[3412,19997,19999],{"class":3414,"line":19998},145,[3412,20000,20001],{"class":3433},"    output = jsonl_proc.serialize(records)\n",[3412,20003,20005,20007,20009,20012],{"class":3414,"line":20004},146,[3412,20006,4752],{"class":3443},[3412,20008,3447],{"class":3433},[3412,20010,20011],{"class":3495},"\"JSONL:\"",[3412,20013,4069],{"class":3433},[3412,20015,20017,20019],{"class":3414,"line":20016},147,[3412,20018,4752],{"class":3443},[3412,20020,20021],{"class":3433},"(output)\n",[3412,20023,20025],{"class":3414,"line":20024},148,[3412,20026,3554],{"emptyLinePlaceholder":3553},[3412,20028,20030],{"class":3414,"line":20029},149,[3412,20031,20032],{"class":3418},"    # Шаблонний метод з трансформацією\n",[3412,20034,20036,20038,20040,20042,20044,20047],{"class":3414,"line":20035},150,[3412,20037,4752],{"class":3443},[3412,20039,3447],{"class":3433},[3412,20041,3507],{"class":3495},[3412,20043,5319],{"class":5318},[3412,20045,20046],{"class":3495},"=== CSV → process → uppercase names ===\"",[3412,20048,4069],{"class":3433},[3412,20050,20052,20054,20057,20059,20062,20064,20066,20068,20070],{"class":3414,"line":20051},151,[3412,20053,3440],{"class":3425},[3412,20055,20056],{"class":3443}," uppercase_name",[3412,20058,3447],{"class":3433},[3412,20060,20061],{"class":3450},"r",[3412,20063,3877],{"class":3433},[3412,20065,6868],{"class":3429},[3412,20067,3946],{"class":3433},[3412,20069,6868],{"class":3429},[3412,20071,3434],{"class":3433},[3412,20073,20075,20077,20080,20082,20085,20087],{"class":3414,"line":20074},152,[3412,20076,3955],{"class":3530},[3412,20078,20079],{"class":3433}," {**r, ",[3412,20081,15731],{"class":3495},[3412,20083,20084],{"class":3433},": r[",[3412,20086,15731],{"class":3495},[3412,20088,20089],{"class":3433},"].upper()}\n",[3412,20091,20093],{"class":3414,"line":20092},153,[3412,20094,3554],{"emptyLinePlaceholder":3553},[3412,20096,20098,20101,20103],{"class":3414,"line":20097},154,[3412,20099,20100],{"class":3433},"    result = csv_proc.process(csv_data, ",[3412,20102,18757],{"class":3450},[3412,20104,20105],{"class":3433},"=uppercase_name)\n",[3412,20107,20109,20111],{"class":3414,"line":20108},155,[3412,20110,4752],{"class":3443},[3412,20112,20113],{"class":3433},"(result)\n",[3412,20115,20117],{"class":3414,"line":20116},156,[3412,20118,3554],{"emptyLinePlaceholder":3553},[3412,20120,20122],{"class":3414,"line":20121},157,[3412,20123,20124],{"class":3418},"    # Невідомий формат\n",[3412,20126,20128,20131],{"class":3414,"line":20127},158,[3412,20129,20130],{"class":3530},"    try",[3412,20132,3434],{"class":3433},[3412,20134,20136,20139,20142],{"class":3414,"line":20135},159,[3412,20137,20138],{"class":3433},"        DataProcessor.for_format(",[3412,20140,20141],{"class":3495},"\"xml\"",[3412,20143,4069],{"class":3433},[3412,20145,20147,20150,20152,20154],{"class":3414,"line":20146},160,[3412,20148,20149],{"class":3530},"    except",[3412,20151,10789],{"class":3429},[3412,20153,17957],{"class":3530},[3412,20155,17960],{"class":3433},[3412,20157,20159,20161,20163,20165,20167,20169,20172,20174,20176,20178,20180],{"class":3414,"line":20158},161,[3412,20160,4039],{"class":3443},[3412,20162,3447],{"class":3433},[3412,20164,3492],{"class":3425},[3412,20166,3507],{"class":3495},[3412,20168,5319],{"class":5318},[3412,20170,20171],{"class":3495},"Очікувана помилка: ",[3412,20173,3499],{"class":3425},[3412,20175,18254],{"class":3433},[3412,20177,3504],{"class":3425},[3412,20179,3507],{"class":3495},[3412,20181,4069],{"class":3433},[3412,20183,20185],{"class":3414,"line":20184},162,[3412,20186,3554],{"emptyLinePlaceholder":3553},[3412,20188,20190],{"class":3414,"line":20189},163,[3412,20191,3554],{"emptyLinePlaceholder":3553},[3412,20193,20195,20197,20200,20203,20206],{"class":3414,"line":20194},164,[3412,20196,11031],{"class":3530},[3412,20198,20199],{"class":3450}," __name__",[3412,20201,20202],{"class":3433}," == ",[3412,20204,20205],{"class":3495},"\"__main__\"",[3412,20207,3434],{"class":3433},[3412,20209,20211],{"class":3414,"line":20210},165,[3412,20212,20213],{"class":3433},"    main()\n",[4757,20215,20217,20225,20234,20242,20250,20253,20260,20263,20267,20274,20278,20285,20292,20299,20302,20306,20310,20318,20326,20334,20337],{"title":20216},"python plugin_framework.py",[4761,20218,20220,4769,20223],{"className":20219},[3414],[3412,20221,4768],{"className":20222},[4767],[3764,20224,20216],{},[4761,20226,20228,20229,20233],{"className":20227},[3414],"[Registry] Зареєстровано: ",[3412,20230,20232],{"className":20231},[4792],"'csv'"," → CsvProcessor",[4761,20235,20228,20237,20241],{"className":20236},[3414],[3412,20238,20240],{"className":20239},[4792],"'jsonl'"," → JsonLinesProcessor",[4761,20243,20228,20245,20249],{"className":20244},[3414],[3412,20246,20248],{"className":20247},[4792],"'tsv'"," → TsvProcessor",[4761,20251],{"className":20252},[3414],[4761,20254,19843,20256],{"className":20255},[3414],[3412,20257,20259],{"className":20258},[4800],"['csv', 'jsonl', 'tsv']",[4761,20261],{"className":20262},[3414],[4761,20264,20266],{"className":20265},[3414],"=== CSV → parse → JSONL serialize ===",[4761,20268,20270,20271],{"className":20269},[3414],"Розібрано записів: ",[3412,20272,11690],{"className":20273},[4779],[4761,20275,20277],{"className":20276},[3414],"JSONL:",[4761,20279,20281],{"className":20280},[3414],[3412,20282,20284],{"className":20283},[4800],"{\"name\": \"Олена\", \"age\": \"28\", \"city\": \"Київ\"}",[4761,20286,20288],{"className":20287},[3414],[3412,20289,20291],{"className":20290},[4800],"{\"name\": \"Іван\", \"age\": \"35\", \"city\": \"Харків\"}",[4761,20293,20295],{"className":20294},[3414],[3412,20296,20298],{"className":20297},[4800],"{\"name\": \"Марія\", \"age\": \"22\", \"city\": \"Львів\"}",[4761,20300],{"className":20301},[3414],[4761,20303,20305],{"className":20304},[3414],"=== CSV → process → uppercase names ===",[4761,20307,20309],{"className":20308},[3414],"name,age,city",[4761,20311,20313,20317],{"className":20312},[3414],[3412,20314,20316],{"className":20315},[4792],"ОЛЕНА",",28,Київ",[4761,20319,20321,20325],{"className":20320},[3414],[3412,20322,20324],{"className":20323},[4792],"ІВАН",",35,Харків",[4761,20327,20329,20333],{"className":20328},[3414],[3412,20330,20332],{"className":20331},[4792],"МАРІЯ",",22,Львів",[4761,20335],{"className":20336},[3414],[4761,20338,20171,20340],{"className":20339},[3414],[3412,20341,20343],{"className":20342},[5732],"Невідомий формат: 'xml'. Доступні: ['csv', 'jsonl', 'tsv']",[3796,20345],{},[3394,20347,20349],{"id":20348},"практична-лабораторія-http-middleware-pipeline-від-а-до-я","Практична лабораторія: HTTP Middleware Pipeline від А до Я",[3399,20351,20352,20353,20356],{},"Реалізуємо мінімалістичний фреймворк для обробки HTTP-запитів, що демонструє ",[3764,20354,20355],{},"всі концепції статті"," в єдиній системі:",[20358,20359,20360,20373],"table",{},[20361,20362,20363],"thead",{},[20364,20365,20366,20370],"tr",{},[20367,20368,20369],"th",{},"Концепція",[20367,20371,20372],{},"Де застосовано",[20374,20375,20376,20390,20401,20411,20428,20444,20456,20465],"tbody",{},[20364,20377,20378,20381],{},[20379,20380,3811],"td",{},[20379,20382,20383,3454,20386,20389],{},[3390,20384,20385],{},"Request",[3390,20387,20388],{},"Response"," — базові типи даних",[20364,20391,20392,20398],{},[20379,20393,20394,20395],{},"Кооперативний ",[3390,20396,20397],{},"super().handle()",[20379,20399,20400],{},"Кожен Middleware передає запит далі по MRO",[20364,20402,20403,20406],{},[20379,20404,20405],{},"Множинне наслідування + diamond",[20379,20407,20408],{},[3390,20409,20410],{},"SecureLoggingHandler(LoggingMixin, AuthMixin, BaseHandler)",[20364,20412,20413,20415],{},[20379,20414,12305],{},[20379,20416,20417,3454,20419,3454,20422,3454,20425],{},[3390,20418,13565],{},[3390,20420,20421],{},"AuthMixin",[3390,20423,20424],{},"RateLimitMixin",[3390,20426,20427],{},"CORSMixin",[20364,20429,20430,20438],{},[20379,20431,20432,6034,20434,14568,20436],{},[3390,20433,3392],{},[3390,20435,4242],{},[3390,20437,7484],{},[20379,20439,20440,20441,20443],{},"Всі Mixin-конструктори передають ",[3390,20442,7484],{}," далі",[20364,20445,20446,20453],{},[20379,20447,20448,20450,20451],{},[3390,20449,5509],{}," \u002F ",[3390,20452,5586],{},[20379,20454,20455],{},"Типізована маршрутизація запитів",[20364,20457,20458,20462],{},[20379,20459,20460],{},[3390,20461,14977],{},[20379,20463,20464],{},"Автоматична реєстрація обробників маршрутів",[20364,20466,20467,20470],{},[20379,20468,20469],{},"LSP",[20379,20471,20472,20475,20476],{},[3390,20473,20474],{},"AuthMixin.handle()"," повністю виконує контракт ",[3390,20477,20478],{},"BaseHandler",[3803,20480,20482],{"id":20481},"архітектура-системи","Архітектура системи",[6055,20484,20485],{},[3403,20486,20488],{"className":6059,"code":20487,"language":6061,"meta":3408,"style":3408},"@startuml\nskinparam style plain\nskinparam backgroundColor #ffffff\nskinparam ArrowColor #6366f1\nskinparam class {\n    BackgroundColor #f3f4f6\n    BorderColor #d1d5db\n}\n\nclass Request {\n    + method : str\n    + path : str\n    + headers : dict\n    + body : str\n    + user : str | None\n}\n\nclass Response {\n    + status : int\n    + body : str\n    + headers : dict\n    + ok : bool \u003C\u003Cproperty>>\n}\n\nclass BaseHandler {\n    + handle(request) : Response\n    # _handle(request) : Response \u003C\u003Cabstract>>\n}\n\nclass LoggingMixin {\n    + handle(request) : Response\n    # _log(msg, level)\n}\n\nclass AuthMixin {\n    + __init__(required_roles, **kwargs)\n    + handle(request) : Response\n}\n\nclass RateLimitMixin {\n    + __init__(max_rps, **kwargs)\n    + handle(request) : Response\n    - __request_log : dict\n}\n\nclass CORSMixin {\n    + __init__(allowed_origins, **kwargs)\n    + handle(request) : Response\n}\n\nclass SecureLoggingHandler {\n    MRO: SecureLoggingHandler\\n→ LoggingMixin → AuthMixin\\n→ RateLimitMixin → CORSMixin\\n→ BaseHandler\n}\n\nclass RouteRegistry {\n    + register(path, handler_cls)\n    + dispatch(request) : Response\n}\n\nBaseHandler \u003C|-- SecureLoggingHandler\nLoggingMixin \u003C|-- SecureLoggingHandler\nAuthMixin \u003C|-- SecureLoggingHandler\nRateLimitMixin \u003C|-- SecureLoggingHandler\nCORSMixin \u003C|-- SecureLoggingHandler\n\nRouteRegistry --> BaseHandler : dispatches to\nRequest --> BaseHandler : processed by\nBaseHandler --> Response : produces\n@enduml\n",[3390,20489,20490,20494,20498,20502,20506,20510,20514,20518,20522,20526,20531,20536,20541,20546,20551,20556,20560,20564,20569,20574,20578,20582,20587,20591,20595,20600,20605,20610,20614,20618,20623,20627,20632,20636,20640,20645,20650,20654,20658,20662,20667,20672,20676,20681,20685,20689,20694,20699,20703,20707,20711,20716,20721,20725,20729,20734,20739,20744,20748,20752,20757,20762,20767,20772,20777,20781,20786,20791,20796],{"__ignoreMap":3408},[3412,20491,20492],{"class":3414,"line":3415},[3412,20493,6068],{},[3412,20495,20496],{"class":3414,"line":3422},[3412,20497,6073],{},[3412,20499,20500],{"class":3414,"line":3437},[3412,20501,6078],{},[3412,20503,20504],{"class":3414,"line":3468},[3412,20505,6083],{},[3412,20507,20508],{"class":3414,"line":3516},[3412,20509,8190],{},[3412,20511,20512],{"class":3414,"line":3550},[3412,20513,8195],{},[3412,20515,20516],{"class":3414,"line":3557},[3412,20517,8200],{},[3412,20519,20520],{"class":3414,"line":3567},[3412,20521,6108],{},[3412,20523,20524],{"class":3414,"line":3588},[3412,20525,3554],{"emptyLinePlaceholder":3553},[3412,20527,20528],{"class":3414,"line":3628},[3412,20529,20530],{},"class Request {\n",[3412,20532,20533],{"class":3414,"line":3655},[3412,20534,20535],{},"    + method : str\n",[3412,20537,20538],{"class":3414,"line":3660},[3412,20539,20540],{},"    + path : str\n",[3412,20542,20543],{"class":3414,"line":3670},[3412,20544,20545],{},"    + headers : dict\n",[3412,20547,20548],{"class":3414,"line":3691},[3412,20549,20550],{},"    + body : str\n",[3412,20552,20553],{"class":3414,"line":3731},[3412,20554,20555],{},"    + user : str | None\n",[3412,20557,20558],{"class":3414,"line":4072},[3412,20559,6108],{},[3412,20561,20562],{"class":3414,"line":4077},[3412,20563,3554],{"emptyLinePlaceholder":3553},[3412,20565,20566],{"class":3414,"line":4095},[3412,20567,20568],{},"class Response {\n",[3412,20570,20571],{"class":3414,"line":5014},[3412,20572,20573],{},"    + status : int\n",[3412,20575,20576],{"class":3414,"line":5020},[3412,20577,20550],{},[3412,20579,20580],{"class":3414,"line":5026},[3412,20581,20545],{},[3412,20583,20584],{"class":3414,"line":5035},[3412,20585,20586],{},"    + ok : bool \u003C\u003Cproperty>>\n",[3412,20588,20589],{"class":3414,"line":5046},[3412,20590,6108],{},[3412,20592,20593],{"class":3414,"line":5057},[3412,20594,3554],{"emptyLinePlaceholder":3553},[3412,20596,20597],{"class":3414,"line":5345},[3412,20598,20599],{},"class BaseHandler {\n",[3412,20601,20602],{"class":3414,"line":5364},[3412,20603,20604],{},"    + handle(request) : Response\n",[3412,20606,20607],{"class":3414,"line":5370},[3412,20608,20609],{},"    # _handle(request) : Response \u003C\u003Cabstract>>\n",[3412,20611,20612],{"class":3414,"line":5376},[3412,20613,6108],{},[3412,20615,20616],{"class":3414,"line":5382},[3412,20617,3554],{"emptyLinePlaceholder":3553},[3412,20619,20620],{"class":3414,"line":5388},[3412,20621,20622],{},"class LoggingMixin {\n",[3412,20624,20625],{"class":3414,"line":5393},[3412,20626,20604],{},[3412,20628,20629],{"class":3414,"line":5399},[3412,20630,20631],{},"    # _log(msg, level)\n",[3412,20633,20634],{"class":3414,"line":5422},[3412,20635,6108],{},[3412,20637,20638],{"class":3414,"line":5428},[3412,20639,3554],{"emptyLinePlaceholder":3553},[3412,20641,20642],{"class":3414,"line":5434},[3412,20643,20644],{},"class AuthMixin {\n",[3412,20646,20647],{"class":3414,"line":5439},[3412,20648,20649],{},"    + __init__(required_roles, **kwargs)\n",[3412,20651,20652],{"class":3414,"line":5456},[3412,20653,20604],{},[3412,20655,20656],{"class":3414,"line":5462},[3412,20657,6108],{},[3412,20659,20660],{"class":3414,"line":5467},[3412,20661,3554],{"emptyLinePlaceholder":3553},[3412,20663,20664],{"class":3414,"line":5473},[3412,20665,20666],{},"class RateLimitMixin {\n",[3412,20668,20669],{"class":3414,"line":5491},[3412,20670,20671],{},"    + __init__(max_rps, **kwargs)\n",[3412,20673,20674],{"class":3414,"line":5496},[3412,20675,20604],{},[3412,20677,20678],{"class":3414,"line":5502},[3412,20679,20680],{},"    - __request_log : dict\n",[3412,20682,20683],{"class":3414,"line":5518},[3412,20684,6108],{},[3412,20686,20687],{"class":3414,"line":5533},[3412,20688,3554],{"emptyLinePlaceholder":3553},[3412,20690,20691],{"class":3414,"line":5548},[3412,20692,20693],{},"class CORSMixin {\n",[3412,20695,20696],{"class":3414,"line":5568},[3412,20697,20698],{},"    + __init__(allowed_origins, **kwargs)\n",[3412,20700,20701],{"class":3414,"line":5573},[3412,20702,20604],{},[3412,20704,20705],{"class":3414,"line":5579},[3412,20706,6108],{},[3412,20708,20709],{"class":3414,"line":5595},[3412,20710,3554],{"emptyLinePlaceholder":3553},[3412,20712,20713],{"class":3414,"line":5609},[3412,20714,20715],{},"class SecureLoggingHandler {\n",[3412,20717,20718],{"class":3414,"line":7232},[3412,20719,20720],{},"    MRO: SecureLoggingHandler\\n→ LoggingMixin → AuthMixin\\n→ RateLimitMixin → CORSMixin\\n→ BaseHandler\n",[3412,20722,20723],{"class":3414,"line":7261},[3412,20724,6108],{},[3412,20726,20727],{"class":3414,"line":7266},[3412,20728,3554],{"emptyLinePlaceholder":3553},[3412,20730,20731],{"class":3414,"line":7271},[3412,20732,20733],{},"class RouteRegistry {\n",[3412,20735,20736],{"class":3414,"line":7277},[3412,20737,20738],{},"    + register(path, handler_cls)\n",[3412,20740,20741],{"class":3414,"line":7310},[3412,20742,20743],{},"    + dispatch(request) : Response\n",[3412,20745,20746],{"class":3414,"line":7315},[3412,20747,6108],{},[3412,20749,20750],{"class":3414,"line":7321},[3412,20751,3554],{"emptyLinePlaceholder":3553},[3412,20753,20754],{"class":3414,"line":7346},[3412,20755,20756],{},"BaseHandler \u003C|-- SecureLoggingHandler\n",[3412,20758,20759],{"class":3414,"line":7361},[3412,20760,20761],{},"LoggingMixin \u003C|-- SecureLoggingHandler\n",[3412,20763,20764],{"class":3414,"line":7366},[3412,20765,20766],{},"AuthMixin \u003C|-- SecureLoggingHandler\n",[3412,20768,20769],{"class":3414,"line":7372},[3412,20770,20771],{},"RateLimitMixin \u003C|-- SecureLoggingHandler\n",[3412,20773,20774],{"class":3414,"line":7378},[3412,20775,20776],{},"CORSMixin \u003C|-- SecureLoggingHandler\n",[3412,20778,20779],{"class":3414,"line":7393},[3412,20780,3554],{"emptyLinePlaceholder":3553},[3412,20782,20783],{"class":3414,"line":13280},[3412,20784,20785],{},"RouteRegistry --> BaseHandler : dispatches to\n",[3412,20787,20788],{"class":3414,"line":13288},[3412,20789,20790],{},"Request --> BaseHandler : processed by\n",[3412,20792,20793],{"class":3414,"line":13294},[3412,20794,20795],{},"BaseHandler --> Response : produces\n",[3412,20797,20798],{"class":3414,"line":13302},[3412,20799,6191],{},[3803,20801,20803],{"id":20802},"реалізація","Реалізація",[3828,20805,20806,21451,23584,24044],{},[3403,20807,20810],{"className":3405,"code":20808,"filename":20809,"language":3407,"meta":3408,"style":3408},"# Базові типи даних системи\n\nfrom dataclasses import dataclass, field\n\n\n@dataclass\nclass Request:\n    \"\"\"HTTP-запит. Базовий незмінний тип даних.\"\"\"\n    method:  str\n    path:    str\n    headers: dict = field(default_factory=dict)\n    body:    str  = \"\"\n    user:    str | None = None    # заповнюється AuthMixin\n\n    def __post_init__(self):\n        self.method = self.method.upper()\n\n    def __repr__(self) -> str:\n        user_str = f\" user={self.user!r}\" if self.user else \"\"\n        return f\"Request({self.method} {self.path}{user_str})\"\n\n\n@dataclass\nclass Response:\n    \"\"\"HTTP-відповідь.\"\"\"\n    status:  int\n    body:    str  = \"\"\n    headers: dict = field(default_factory=dict)\n\n    # HTTP статус-коди для зручності\n    OK              = 200\n    CREATED         = 201\n    UNAUTHORIZED    = 401\n    FORBIDDEN       = 403\n    NOT_FOUND       = 404\n    TOO_MANY        = 429\n    SERVER_ERROR    = 500\n\n    STATUS_PHRASES = {\n        200: \"OK\", 201: \"Created\",\n        401: \"Unauthorized\", 403: \"Forbidden\",\n        404: \"Not Found\", 429: \"Too Many Requests\",\n        500: \"Internal Server Error\",\n    }\n\n    @property\n    def ok(self) -> bool:\n        return 200 \u003C= self.status \u003C 300\n\n    @property\n    def status_line(self) -> str:\n        phrase = self.STATUS_PHRASES.get(self.status, \"Unknown\")\n        return f\"HTTP\u002F1.1 {self.status} {phrase}\"\n\n    def __repr__(self) -> str:\n        icon = \"✅\" if self.ok else \"❌\"\n        return f\"{icon} Response({self.status}: {self.body[:40]!r})\"\n","models.py",[3390,20811,20812,20817,20821,20833,20837,20841,20846,20855,20860,20868,20875,20894,20907,20925,20929,20942,20954,20958,20974,21004,21036,21040,21044,21048,21057,21062,21070,21080,21096,21100,21105,21113,21121,21129,21137,21145,21153,21161,21165,21170,21192,21214,21236,21248,21252,21256,21262,21280,21298,21302,21308,21325,21345,21371,21375,21391,21411],{"__ignoreMap":3408},[3412,20813,20814],{"class":3414,"line":3415},[3412,20815,20816],{"class":3418},"# Базові типи даних системи\n",[3412,20818,20819],{"class":3414,"line":3422},[3412,20820,3554],{"emptyLinePlaceholder":3553},[3412,20822,20823,20825,20828,20830],{"class":3414,"line":3437},[3412,20824,4148],{"class":3530},[3412,20826,20827],{"class":3433}," dataclasses ",[3412,20829,4154],{"class":3530},[3412,20831,20832],{"class":3433}," dataclass, field\n",[3412,20834,20835],{"class":3414,"line":3468},[3412,20836,3554],{"emptyLinePlaceholder":3553},[3412,20838,20839],{"class":3414,"line":3516},[3412,20840,3554],{"emptyLinePlaceholder":3553},[3412,20842,20843],{"class":3414,"line":3550},[3412,20844,20845],{"class":3443},"@dataclass\n",[3412,20847,20848,20850,20853],{"class":3414,"line":3557},[3412,20849,3426],{"class":3425},[3412,20851,20852],{"class":3429}," Request",[3412,20854,3434],{"class":3433},[3412,20856,20857],{"class":3414,"line":3567},[3412,20858,20859],{"class":3495},"    \"\"\"HTTP-запит. Базовий незмінний тип даних.\"\"\"\n",[3412,20861,20862,20865],{"class":3414,"line":3588},[3412,20863,20864],{"class":3433},"    method:  ",[3412,20866,20867],{"class":3429},"str\n",[3412,20869,20870,20873],{"class":3414,"line":3628},[3412,20871,20872],{"class":3433},"    path:    ",[3412,20874,20867],{"class":3429},[3412,20876,20877,20880,20882,20885,20888,20890,20892],{"class":3414,"line":3655},[3412,20878,20879],{"class":3433},"    headers: ",[3412,20881,6868],{"class":3429},[3412,20883,20884],{"class":3433}," = field(",[3412,20886,20887],{"class":3450},"default_factory",[3412,20889,4691],{"class":3433},[3412,20891,6868],{"class":3429},[3412,20893,4069],{"class":3433},[3412,20895,20896,20899,20901,20904],{"class":3414,"line":3660},[3412,20897,20898],{"class":3433},"    body:    ",[3412,20900,3880],{"class":3429},[3412,20902,20903],{"class":3433},"  = ",[3412,20905,20906],{"class":3495},"\"\"\n",[3412,20908,20909,20912,20914,20916,20918,20920,20922],{"class":3414,"line":3670},[3412,20910,20911],{"class":3433},"    user:    ",[3412,20913,3880],{"class":3429},[3412,20915,9654],{"class":3433},[3412,20917,4011],{"class":3425},[3412,20919,3921],{"class":3433},[3412,20921,4011],{"class":3425},[3412,20923,20924],{"class":3418},"    # заповнюється AuthMixin\n",[3412,20926,20927],{"class":3414,"line":3691},[3412,20928,3554],{"emptyLinePlaceholder":3553},[3412,20930,20931,20933,20936,20938,20940],{"class":3414,"line":3731},[3412,20932,3440],{"class":3425},[3412,20934,20935],{"class":3450}," __post_init__",[3412,20937,3447],{"class":3433},[3412,20939,3451],{"class":3450},[3412,20941,3893],{"class":3433},[3412,20943,20944,20946,20949,20951],{"class":3414,"line":4072},[3412,20945,3898],{"class":3425},[3412,20947,20948],{"class":3433},".method = ",[3412,20950,3451],{"class":3425},[3412,20952,20953],{"class":3433},".method.upper()\n",[3412,20955,20956],{"class":3414,"line":4077},[3412,20957,3554],{"emptyLinePlaceholder":3553},[3412,20959,20960,20962,20964,20966,20968,20970,20972],{"class":3414,"line":4095},[3412,20961,3440],{"class":3425},[3412,20963,4082],{"class":3443},[3412,20965,3447],{"class":3433},[3412,20967,3451],{"class":3450},[3412,20969,3946],{"class":3433},[3412,20971,3880],{"class":3429},[3412,20973,3434],{"class":3433},[3412,20975,20976,20979,20981,20984,20986,20989,20991,20993,20995,20997,21000,21002],{"class":3414,"line":5014},[3412,20977,20978],{"class":3433},"        user_str = ",[3412,20980,3492],{"class":3425},[3412,20982,20983],{"class":3495},"\" user=",[3412,20985,3964],{"class":3425},[3412,20987,20988],{"class":3433},".user",[3412,20990,4125],{"class":3425},[3412,20992,3507],{"class":3495},[3412,20994,13606],{"class":3530},[3412,20996,10741],{"class":3425},[3412,20998,20999],{"class":3433},".user ",[3412,21001,11044],{"class":3530},[3412,21003,19248],{"class":3495},[3412,21005,21006,21008,21010,21013,21015,21018,21020,21023,21026,21029,21032,21034],{"class":3414,"line":5020},[3412,21007,3955],{"class":3530},[3412,21009,3958],{"class":3425},[3412,21011,21012],{"class":3495},"\"Request(",[3412,21014,3964],{"class":3425},[3412,21016,21017],{"class":3433},".method",[3412,21019,3504],{"class":3425},[3412,21021,21022],{"class":3425}," {self",[3412,21024,21025],{"class":3433},".path",[3412,21027,21028],{"class":3425},"}{",[3412,21030,21031],{"class":3433},"user_str",[3412,21033,3504],{"class":3425},[3412,21035,4137],{"class":3495},[3412,21037,21038],{"class":3414,"line":5026},[3412,21039,3554],{"emptyLinePlaceholder":3553},[3412,21041,21042],{"class":3414,"line":5035},[3412,21043,3554],{"emptyLinePlaceholder":3553},[3412,21045,21046],{"class":3414,"line":5046},[3412,21047,20845],{"class":3443},[3412,21049,21050,21052,21055],{"class":3414,"line":5057},[3412,21051,3426],{"class":3425},[3412,21053,21054],{"class":3429}," Response",[3412,21056,3434],{"class":3433},[3412,21058,21059],{"class":3414,"line":5345},[3412,21060,21061],{"class":3495},"    \"\"\"HTTP-відповідь.\"\"\"\n",[3412,21063,21064,21067],{"class":3414,"line":5364},[3412,21065,21066],{"class":3433},"    status:  ",[3412,21068,21069],{"class":3429},"int\n",[3412,21071,21072,21074,21076,21078],{"class":3414,"line":5370},[3412,21073,20898],{"class":3433},[3412,21075,3880],{"class":3429},[3412,21077,20903],{"class":3433},[3412,21079,20906],{"class":3495},[3412,21081,21082,21084,21086,21088,21090,21092,21094],{"class":3414,"line":5376},[3412,21083,20879],{"class":3433},[3412,21085,6868],{"class":3429},[3412,21087,20884],{"class":3433},[3412,21089,20887],{"class":3450},[3412,21091,4691],{"class":3433},[3412,21093,6868],{"class":3429},[3412,21095,4069],{"class":3433},[3412,21097,21098],{"class":3414,"line":5382},[3412,21099,3554],{"emptyLinePlaceholder":3553},[3412,21101,21102],{"class":3414,"line":5388},[3412,21103,21104],{"class":3418},"    # HTTP статус-коди для зручності\n",[3412,21106,21107,21110],{"class":3414,"line":5393},[3412,21108,21109],{"class":3433},"    OK              = ",[3412,21111,21112],{"class":3924},"200\n",[3412,21114,21115,21118],{"class":3414,"line":5399},[3412,21116,21117],{"class":3433},"    CREATED         = ",[3412,21119,21120],{"class":3924},"201\n",[3412,21122,21123,21126],{"class":3414,"line":5422},[3412,21124,21125],{"class":3433},"    UNAUTHORIZED    = ",[3412,21127,21128],{"class":3924},"401\n",[3412,21130,21131,21134],{"class":3414,"line":5428},[3412,21132,21133],{"class":3433},"    FORBIDDEN       = ",[3412,21135,21136],{"class":3924},"403\n",[3412,21138,21139,21142],{"class":3414,"line":5434},[3412,21140,21141],{"class":3433},"    NOT_FOUND       = ",[3412,21143,21144],{"class":3924},"404\n",[3412,21146,21147,21150],{"class":3414,"line":5439},[3412,21148,21149],{"class":3433},"    TOO_MANY        = ",[3412,21151,21152],{"class":3924},"429\n",[3412,21154,21155,21158],{"class":3414,"line":5456},[3412,21156,21157],{"class":3433},"    SERVER_ERROR    = ",[3412,21159,21160],{"class":3924},"500\n",[3412,21162,21163],{"class":3414,"line":5462},[3412,21164,3554],{"emptyLinePlaceholder":3553},[3412,21166,21167],{"class":3414,"line":5467},[3412,21168,21169],{"class":3433},"    STATUS_PHRASES = {\n",[3412,21171,21172,21175,21177,21180,21182,21185,21187,21190],{"class":3414,"line":5473},[3412,21173,21174],{"class":3924},"        200",[3412,21176,3877],{"class":3433},[3412,21178,21179],{"class":3495},"\"OK\"",[3412,21181,3454],{"class":3433},[3412,21183,21184],{"class":3924},"201",[3412,21186,3877],{"class":3433},[3412,21188,21189],{"class":3495},"\"Created\"",[3412,21191,7936],{"class":3433},[3412,21193,21194,21197,21199,21202,21204,21207,21209,21212],{"class":3414,"line":5491},[3412,21195,21196],{"class":3924},"        401",[3412,21198,3877],{"class":3433},[3412,21200,21201],{"class":3495},"\"Unauthorized\"",[3412,21203,3454],{"class":3433},[3412,21205,21206],{"class":3924},"403",[3412,21208,3877],{"class":3433},[3412,21210,21211],{"class":3495},"\"Forbidden\"",[3412,21213,7936],{"class":3433},[3412,21215,21216,21219,21221,21224,21226,21229,21231,21234],{"class":3414,"line":5496},[3412,21217,21218],{"class":3924},"        404",[3412,21220,3877],{"class":3433},[3412,21222,21223],{"class":3495},"\"Not Found\"",[3412,21225,3454],{"class":3433},[3412,21227,21228],{"class":3924},"429",[3412,21230,3877],{"class":3433},[3412,21232,21233],{"class":3495},"\"Too Many Requests\"",[3412,21235,7936],{"class":3433},[3412,21237,21238,21241,21243,21246],{"class":3414,"line":5502},[3412,21239,21240],{"class":3924},"        500",[3412,21242,3877],{"class":3433},[3412,21244,21245],{"class":3495},"\"Internal Server Error\"",[3412,21247,7936],{"class":3433},[3412,21249,21250],{"class":3414,"line":5518},[3412,21251,8659],{"class":3433},[3412,21253,21254],{"class":3414,"line":5533},[3412,21255,3554],{"emptyLinePlaceholder":3553},[3412,21257,21258,21260],{"class":3414,"line":5548},[3412,21259,6844],{"class":3443},[3412,21261,13430],{"class":3429},[3412,21263,21264,21266,21269,21271,21273,21275,21278],{"class":3414,"line":5568},[3412,21265,3440],{"class":3425},[3412,21267,21268],{"class":3443}," ok",[3412,21270,3447],{"class":3433},[3412,21272,3451],{"class":3450},[3412,21274,3946],{"class":3433},[3412,21276,21277],{"class":3429},"bool",[3412,21279,3434],{"class":3433},[3412,21281,21282,21284,21287,21290,21292,21295],{"class":3414,"line":5573},[3412,21283,3955],{"class":3530},[3412,21285,21286],{"class":3924}," 200",[3412,21288,21289],{"class":3433}," \u003C= ",[3412,21291,3451],{"class":3425},[3412,21293,21294],{"class":3433},".status \u003C ",[3412,21296,21297],{"class":3924},"300\n",[3412,21299,21300],{"class":3414,"line":5579},[3412,21301,3554],{"emptyLinePlaceholder":3553},[3412,21303,21304,21306],{"class":3414,"line":5595},[3412,21305,6844],{"class":3443},[3412,21307,13430],{"class":3429},[3412,21309,21310,21312,21315,21317,21319,21321,21323],{"class":3414,"line":5609},[3412,21311,3440],{"class":3425},[3412,21313,21314],{"class":3443}," status_line",[3412,21316,3447],{"class":3433},[3412,21318,3451],{"class":3450},[3412,21320,3946],{"class":3433},[3412,21322,3880],{"class":3429},[3412,21324,3434],{"class":3433},[3412,21326,21327,21330,21332,21335,21337,21340,21343],{"class":3414,"line":7232},[3412,21328,21329],{"class":3433},"        phrase = ",[3412,21331,3451],{"class":3425},[3412,21333,21334],{"class":3433},".STATUS_PHRASES.get(",[3412,21336,3451],{"class":3425},[3412,21338,21339],{"class":3433},".status, ",[3412,21341,21342],{"class":3495},"\"Unknown\"",[3412,21344,4069],{"class":3433},[3412,21346,21347,21349,21351,21354,21356,21359,21361,21364,21367,21369],{"class":3414,"line":7261},[3412,21348,3955],{"class":3530},[3412,21350,3958],{"class":3425},[3412,21352,21353],{"class":3495},"\"HTTP\u002F1.1 ",[3412,21355,3964],{"class":3425},[3412,21357,21358],{"class":3433},".status",[3412,21360,3504],{"class":3425},[3412,21362,21363],{"class":3425}," {",[3412,21365,21366],{"class":3433},"phrase",[3412,21368,3504],{"class":3425},[3412,21370,8434],{"class":3495},[3412,21372,21373],{"class":3414,"line":7266},[3412,21374,3554],{"emptyLinePlaceholder":3553},[3412,21376,21377,21379,21381,21383,21385,21387,21389],{"class":3414,"line":7271},[3412,21378,3440],{"class":3425},[3412,21380,4082],{"class":3443},[3412,21382,3447],{"class":3433},[3412,21384,3451],{"class":3450},[3412,21386,3946],{"class":3433},[3412,21388,3880],{"class":3429},[3412,21390,3434],{"class":3433},[3412,21392,21393,21396,21399,21401,21403,21406,21408],{"class":3414,"line":7277},[3412,21394,21395],{"class":3433},"        icon = ",[3412,21397,21398],{"class":3495},"\"✅\"",[3412,21400,13606],{"class":3530},[3412,21402,10741],{"class":3425},[3412,21404,21405],{"class":3433},".ok ",[3412,21407,11044],{"class":3530},[3412,21409,21410],{"class":3495}," \"❌\"\n",[3412,21412,21413,21415,21417,21419,21421,21424,21426,21429,21431,21433,21435,21437,21439,21442,21445,21447,21449],{"class":3414,"line":7310},[3412,21414,3955],{"class":3530},[3412,21416,3958],{"class":3425},[3412,21418,3507],{"class":3495},[3412,21420,3499],{"class":3425},[3412,21422,21423],{"class":3433},"icon",[3412,21425,3504],{"class":3425},[3412,21427,21428],{"class":3495}," Response(",[3412,21430,3964],{"class":3425},[3412,21432,21358],{"class":3433},[3412,21434,3504],{"class":3425},[3412,21436,3877],{"class":3495},[3412,21438,3964],{"class":3425},[3412,21440,21441],{"class":3433},".body[:",[3412,21443,21444],{"class":3924},"40",[3412,21446,9311],{"class":3433},[3412,21448,4125],{"class":3425},[3412,21450,4137],{"class":3495},[3403,21452,21455],{"className":3405,"code":21453,"filename":21454,"language":3407,"meta":3408,"style":3408},"import time\nfrom collections import defaultdict\nfrom models import Request, Response\n\n\n# ── Базовий обробник ──────────────────────────────────────────────────────────\n\nclass BaseHandler:\n    \"\"\"\n    Базовий клас для всіх HTTP-обробників.\n\n    Публічний метод `handle()` — шаблонний: викликає `_handle()`,\n    який підкласи зобов'язані реалізувати.\n    \"\"\"\n\n    def __init__(self, **kwargs):\n        # Кооперативний super(): передаємо kwargs далі по MRO\n        super().__init__(**kwargs)\n\n    def handle(self, request: Request) -> Response:\n        \"\"\"Публічна точка входу. НЕ перевизначайте — перевизначайте `_handle()`.\"\"\"\n        return self._handle(request)\n\n    def _handle(self, request: Request) -> Response:\n        \"\"\"Шаблонний метод — конкретна бізнес-логіка обробника.\"\"\"\n        raise NotImplementedError(\n            f\"{type(self).__name__} має реалізувати _handle()\"\n        )\n\n\n# ── Mixins ────────────────────────────────────────────────────────────────────\n\nclass LoggingMixin:\n    \"\"\"\n    Mixin: логує кожен запит і відповідь.\n    Перехоплює handle() ПЕРЕД іншими Mixin'ами (стоїть ліворуч у MRO).\n    \"\"\"\n\n    def __init__(self, **kwargs):\n        super().__init__(**kwargs)\n\n    def _log(self, message: str, level: str = \"INFO\") -> None:\n        ts = time.strftime(\"%H:%M:%S\")\n        icon = {\"INFO\": \"📋\", \"WARN\": \"⚠️\", \"ERROR\": \"🔴\"}.get(level, \"•\")\n        print(f\"[{ts}] {icon} [{level}] [{type(self).__name__}] {message}\")\n\n    def handle(self, request: Request) -> Response:\n        self._log(f\"→ {request.method} {request.path}\")\n        response = super().handle(request)   # → наступний у MRO\n        icon = \"✅\" if response.ok else \"❌\"\n        self._log(\n            f\"← {icon} {response.status} | {request.method} {request.path}\",\n            level=\"INFO\" if response.ok else \"WARN\",\n        )\n        return response\n\n\nclass AuthMixin:\n    \"\"\"\n    Mixin: перевіряє Bearer-токен у заголовку Authorization.\n    Симулює JWT: токен = 'Bearer \u003Cusername>:\u003Crole>'.\n    \"\"\"\n\n    # Симульована база токенів {token: (username, role)}\n    _TOKEN_DB: dict[str, tuple[str, str]] = {\n        \"admin-token\":  (\"admin\",   \"admin\"),\n        \"user-token\":   (\"alice\",   \"user\"),\n        \"vip-token\":    (\"bob\",     \"vip\"),\n    }\n\n    def __init__(self, required_roles: list[str] | None = None, **kwargs):\n        super().__init__(**kwargs)\n        self._required_roles = required_roles or []\n\n    def handle(self, request: Request) -> Response:\n        auth_header = request.headers.get(\"Authorization\", \"\")\n\n        if not auth_header.startswith(\"Bearer \"):\n            return Response(\n                Response.UNAUTHORIZED,\n                body=\"401 Unauthorized: відсутній токен\",\n            )\n\n        token = auth_header[7:]\n        user_info = self._TOKEN_DB.get(token)\n\n        if user_info is None:\n            return Response(\n                Response.UNAUTHORIZED,\n                body=f\"401 Unauthorized: невалідний токен\",\n            )\n\n        username, role = user_info\n        request.user = username   # збагачуємо об'єкт запиту\n\n        if self._required_roles and role not in self._required_roles:\n            return Response(\n                Response.FORBIDDEN,\n                body=f\"403 Forbidden: потрібна роль {self._required_roles}, є {role!r}\",\n            )\n\n        return super().handle(request)   # → наступний у MRO\n\n\nclass RateLimitMixin:\n    \"\"\"\n    Mixin: обмежує кількість запитів на секунду (per-user або per-IP).\n    Внутрішній стан захищений через __request_log (Name Mangling).\n    \"\"\"\n\n    def __init__(self, max_rps: int = 10, **kwargs):\n        super().__init__(**kwargs)\n        self._max_rps = max_rps\n        # __request_log → _RateLimitMixin__request_log (захист від підкласів)\n        self.__request_log: dict[str, list[float]] = defaultdict(list)\n\n    def handle(self, request: Request) -> Response:\n        key = request.user or request.headers.get(\"X-Forwarded-For\", \"anonymous\")\n        now = time.time()\n\n        # Залишаємо лише запити останньої секунди\n        self.__request_log[key] = [\n            t for t in self.__request_log[key] if now - t \u003C 1.0\n        ]\n\n        if len(self.__request_log[key]) >= self._max_rps:\n            return Response(\n                Response.TOO_MANY,\n                body=f\"429 Too Many Requests: ліміт {self._max_rps} req\u002Fs\",\n                headers={\"Retry-After\": \"1\"},\n            )\n\n        self.__request_log[key].append(now)\n        return super().handle(request)   # → наступний у MRO\n\n\nclass CORSMixin:\n    \"\"\"\n    Mixin: додає CORS-заголовки до кожної відповіді.\n    \"\"\"\n\n    DEFAULT_ORIGINS = [\"*\"]\n\n    def __init__(self, allowed_origins: list[str] | None = None, **kwargs):\n        super().__init__(**kwargs)\n        self._allowed_origins = allowed_origins or self.DEFAULT_ORIGINS\n\n    def handle(self, request: Request) -> Response:\n        origin = request.headers.get(\"Origin\", \"*\")\n        response = super().handle(request)   # → наступний у MRO\n\n        # Додаємо CORS-заголовки до відповіді\n        allowed = \"*\" if \"*\" in self._allowed_origins else (\n            origin if origin in self._allowed_origins else self._allowed_origins[0]\n        )\n        response.headers.update({\n            \"Access-Control-Allow-Origin\":  allowed,\n            \"Access-Control-Allow-Methods\": \"GET, POST, PUT, DELETE\",\n            \"Access-Control-Allow-Headers\": \"Authorization, Content-Type\",\n        })\n        return response\n\n\n# ── Конкретні обробники маршрутів ─────────────────────────────────────────────\n\nclass PublicHandler(LoggingMixin, CORSMixin, BaseHandler):\n    \"\"\"\n    Публічний ендпоінт: лише логування + CORS.\n    MRO: PublicHandler → LoggingMixin → CORSMixin → BaseHandler → object\n    \"\"\"\n\n    def _handle(self, request: Request) -> Response:\n        return Response(Response.OK, body='{\"status\": \"public\", \"data\": \"open\"}')\n\n\nclass ProtectedHandler(LoggingMixin, AuthMixin, RateLimitMixin, CORSMixin, BaseHandler):\n    \"\"\"\n    Захищений ендпоінт: логування → автентифікація → rate limit → CORS → логіка.\n    MRO: ProtectedHandler → LoggingMixin → AuthMixin → RateLimitMixin → CORSMixin → BaseHandler\n    \"\"\"\n\n    def __init__(self, **kwargs):\n        super().__init__(\n            required_roles=[\"user\", \"vip\", \"admin\"],\n            max_rps=5,\n            allowed_origins=[\"https:\u002F\u002Fkostyl.dev\", \"http:\u002F\u002Flocalhost:3000\"],\n            **kwargs,\n        )\n\n    def _handle(self, request: Request) -> Response:\n        return Response(\n            Response.OK,\n            body=f'{{\"status\": \"ok\", \"user\": \"{request.user}\", \"path\": \"{request.path}\"}}',\n        )\n\n\nclass AdminHandler(LoggingMixin, AuthMixin, RateLimitMixin, CORSMixin, BaseHandler):\n    \"\"\"\n    Адмін-ендпоінт: тільки для role='admin'.\n    \"\"\"\n\n    def __init__(self, **kwargs):\n        super().__init__(\n            required_roles=[\"admin\"],\n            max_rps=100,\n            **kwargs,\n        )\n\n    def _handle(self, request: Request) -> Response:\n        return Response(\n            Response.OK,\n            body=f'{{\"admin\": true, \"user\": \"{request.user}\", \"action\": \"permitted\"}}',\n        )\n","handlers.py",[3390,21456,21457,21464,21476,21488,21492,21496,21501,21505,21514,21518,21523,21527,21532,21537,21541,21545,21561,21566,21576,21580,21599,21604,21613,21617,21634,21639,21647,21670,21674,21678,21682,21687,21691,21699,21703,21708,21713,21717,21721,21737,21747,21751,21788,21798,21837,21898,21902,21918,21948,21961,21976,21983,22021,22041,22045,22052,22056,22060,22069,22073,22078,22083,22087,22091,22096,22115,22133,22151,22170,22174,22178,22212,22222,22233,22237,22253,22267,22271,22285,22292,22297,22309,22313,22317,22328,22338,22342,22355,22361,22365,22378,22382,22386,22391,22399,22403,22428,22434,22439,22471,22475,22479,22490,22494,22498,22507,22511,22516,22521,22525,22529,22558,22568,22575,22580,22601,22605,22621,22641,22646,22650,22655,22662,22687,22692,22696,22714,22720,22725,22748,22767,22771,22775,22782,22792,22796,22800,22809,22813,22818,22822,22826,22836,22840,22873,22883,22897,22901,22917,22931,22941,22945,22950,22973,23000,23004,23009,23017,23029,23041,23046,23052,23056,23060,23065,23069,23091,23096,23102,23108,23113,23118,23135,23153,23158,23163,23193,23198,23204,23210,23215,23220,23237,23248,23269,23281,23299,23305,23310,23315,23332,23339,23345,23388,23393,23398,23403,23433,23438,23444,23449,23454,23471,23482,23493,23504,23509,23514,23519,23536,23543,23548,23579],{"__ignoreMap":3408},[3412,21458,21459,21461],{"class":3414,"line":3415},[3412,21460,4154],{"class":3530},[3412,21462,21463],{"class":3433}," time\n",[3412,21465,21466,21468,21471,21473],{"class":3414,"line":3422},[3412,21467,4148],{"class":3530},[3412,21469,21470],{"class":3433}," collections ",[3412,21472,4154],{"class":3530},[3412,21474,21475],{"class":3433}," defaultdict\n",[3412,21477,21478,21480,21483,21485],{"class":3414,"line":3437},[3412,21479,4148],{"class":3530},[3412,21481,21482],{"class":3433}," models ",[3412,21484,4154],{"class":3530},[3412,21486,21487],{"class":3433}," Request, Response\n",[3412,21489,21490],{"class":3414,"line":3468},[3412,21491,3554],{"emptyLinePlaceholder":3553},[3412,21493,21494],{"class":3414,"line":3516},[3412,21495,3554],{"emptyLinePlaceholder":3553},[3412,21497,21498],{"class":3414,"line":3550},[3412,21499,21500],{"class":3418},"# ── Базовий обробник ──────────────────────────────────────────────────────────\n",[3412,21502,21503],{"class":3414,"line":3557},[3412,21504,3554],{"emptyLinePlaceholder":3553},[3412,21506,21507,21509,21512],{"class":3414,"line":3567},[3412,21508,3426],{"class":3425},[3412,21510,21511],{"class":3429}," BaseHandler",[3412,21513,3434],{"class":3433},[3412,21515,21516],{"class":3414,"line":3588},[3412,21517,10012],{"class":3495},[3412,21519,21520],{"class":3414,"line":3628},[3412,21521,21522],{"class":3495},"    Базовий клас для всіх HTTP-обробників.\n",[3412,21524,21525],{"class":3414,"line":3655},[3412,21526,3554],{"emptyLinePlaceholder":3553},[3412,21528,21529],{"class":3414,"line":3660},[3412,21530,21531],{"class":3495},"    Публічний метод `handle()` — шаблонний: викликає `_handle()`,\n",[3412,21533,21534],{"class":3414,"line":3670},[3412,21535,21536],{"class":3495},"    який підкласи зобов'язані реалізувати.\n",[3412,21538,21539],{"class":3414,"line":3691},[3412,21540,10012],{"class":3495},[3412,21542,21543],{"class":3414,"line":3731},[3412,21544,3554],{"emptyLinePlaceholder":3553},[3412,21546,21547,21549,21551,21553,21555,21557,21559],{"class":3414,"line":4072},[3412,21548,3440],{"class":3425},[3412,21550,3865],{"class":3443},[3412,21552,3447],{"class":3433},[3412,21554,3451],{"class":3450},[3412,21556,7534],{"class":3433},[3412,21558,7537],{"class":3450},[3412,21560,3893],{"class":3433},[3412,21562,21563],{"class":3414,"line":4077},[3412,21564,21565],{"class":3418},"        # Кооперативний super(): передаємо kwargs далі по MRO\n",[3412,21567,21568,21570,21572,21574],{"class":3414,"line":4095},[3412,21569,4236],{"class":3429},[3412,21571,4239],{"class":3433},[3412,21573,4242],{"class":3443},[3412,21575,7749],{"class":3433},[3412,21577,21578],{"class":3414,"line":5014},[3412,21579,3554],{"emptyLinePlaceholder":3553},[3412,21581,21582,21584,21587,21589,21591,21593,21596],{"class":3414,"line":5020},[3412,21583,3440],{"class":3425},[3412,21585,21586],{"class":3443}," handle",[3412,21588,3447],{"class":3433},[3412,21590,3451],{"class":3450},[3412,21592,3454],{"class":3433},[3412,21594,21595],{"class":3450},"request",[3412,21597,21598],{"class":3433},": Request) -> Response:\n",[3412,21600,21601],{"class":3414,"line":5026},[3412,21602,21603],{"class":3495},"        \"\"\"Публічна точка входу. НЕ перевизначайте — перевизначайте `_handle()`.\"\"\"\n",[3412,21605,21606,21608,21610],{"class":3414,"line":5035},[3412,21607,3955],{"class":3530},[3412,21609,10741],{"class":3425},[3412,21611,21612],{"class":3433},"._handle(request)\n",[3412,21614,21615],{"class":3414,"line":5046},[3412,21616,3554],{"emptyLinePlaceholder":3553},[3412,21618,21619,21621,21624,21626,21628,21630,21632],{"class":3414,"line":5057},[3412,21620,3440],{"class":3425},[3412,21622,21623],{"class":3443}," _handle",[3412,21625,3447],{"class":3433},[3412,21627,3451],{"class":3450},[3412,21629,3454],{"class":3433},[3412,21631,21595],{"class":3450},[3412,21633,21598],{"class":3433},[3412,21635,21636],{"class":3414,"line":5345},[3412,21637,21638],{"class":3495},"        \"\"\"Шаблонний метод — конкретна бізнес-логіка обробника.\"\"\"\n",[3412,21640,21641,21643,21645],{"class":3414,"line":5364},[3412,21642,15914],{"class":3530},[3412,21644,15917],{"class":3429},[3412,21646,6908],{"class":3433},[3412,21648,21649,21651,21653,21655,21657,21659,21661,21663,21665,21667],{"class":3414,"line":5370},[3412,21650,16026],{"class":3425},[3412,21652,3507],{"class":3495},[3412,21654,3499],{"class":3425},[3412,21656,5110],{"class":3429},[3412,21658,3447],{"class":3433},[3412,21660,3451],{"class":3425},[3412,21662,6479],{"class":3433},[3412,21664,4113],{"class":3450},[3412,21666,3504],{"class":3425},[3412,21668,21669],{"class":3495}," має реалізувати _handle()\"\n",[3412,21671,21672],{"class":3414,"line":5376},[3412,21673,6941],{"class":3433},[3412,21675,21676],{"class":3414,"line":5382},[3412,21677,3554],{"emptyLinePlaceholder":3553},[3412,21679,21680],{"class":3414,"line":5388},[3412,21681,3554],{"emptyLinePlaceholder":3553},[3412,21683,21684],{"class":3414,"line":5393},[3412,21685,21686],{"class":3418},"# ── Mixins ────────────────────────────────────────────────────────────────────\n",[3412,21688,21689],{"class":3414,"line":5399},[3412,21690,3554],{"emptyLinePlaceholder":3553},[3412,21692,21693,21695,21697],{"class":3414,"line":5422},[3412,21694,3426],{"class":3425},[3412,21696,12943],{"class":3429},[3412,21698,3434],{"class":3433},[3412,21700,21701],{"class":3414,"line":5428},[3412,21702,10012],{"class":3495},[3412,21704,21705],{"class":3414,"line":5434},[3412,21706,21707],{"class":3495},"    Mixin: логує кожен запит і відповідь.\n",[3412,21709,21710],{"class":3414,"line":5439},[3412,21711,21712],{"class":3495},"    Перехоплює handle() ПЕРЕД іншими Mixin'ами (стоїть ліворуч у MRO).\n",[3412,21714,21715],{"class":3414,"line":5456},[3412,21716,10012],{"class":3495},[3412,21718,21719],{"class":3414,"line":5462},[3412,21720,3554],{"emptyLinePlaceholder":3553},[3412,21722,21723,21725,21727,21729,21731,21733,21735],{"class":3414,"line":5467},[3412,21724,3440],{"class":3425},[3412,21726,3865],{"class":3443},[3412,21728,3447],{"class":3433},[3412,21730,3451],{"class":3450},[3412,21732,7534],{"class":3433},[3412,21734,7537],{"class":3450},[3412,21736,3893],{"class":3433},[3412,21738,21739,21741,21743,21745],{"class":3414,"line":5473},[3412,21740,4236],{"class":3429},[3412,21742,4239],{"class":3433},[3412,21744,4242],{"class":3443},[3412,21746,7749],{"class":3433},[3412,21748,21749],{"class":3414,"line":5491},[3412,21750,3554],{"emptyLinePlaceholder":3553},[3412,21752,21753,21755,21758,21760,21762,21764,21766,21768,21770,21772,21774,21776,21778,21780,21782,21784,21786],{"class":3414,"line":5496},[3412,21754,3440],{"class":3425},[3412,21756,21757],{"class":3443}," _log",[3412,21759,3447],{"class":3433},[3412,21761,3451],{"class":3450},[3412,21763,3454],{"class":3433},[3412,21765,12982],{"class":3450},[3412,21767,3877],{"class":3433},[3412,21769,3880],{"class":3429},[3412,21771,3454],{"class":3433},[3412,21773,12991],{"class":3450},[3412,21775,3877],{"class":3433},[3412,21777,3880],{"class":3429},[3412,21779,3921],{"class":3433},[3412,21781,13000],{"class":3495},[3412,21783,3946],{"class":3433},[3412,21785,4011],{"class":3425},[3412,21787,3434],{"class":3433},[3412,21789,21790,21793,21796],{"class":3414,"line":5502},[3412,21791,21792],{"class":3433},"        ts = time.strftime(",[3412,21794,21795],{"class":3495},"\"%H:%M:%S\"",[3412,21797,4069],{"class":3433},[3412,21799,21800,21803,21805,21807,21810,21812,21815,21817,21820,21822,21824,21826,21829,21832,21835],{"class":3414,"line":5518},[3412,21801,21802],{"class":3433},"        icon = {",[3412,21804,13000],{"class":3495},[3412,21806,3877],{"class":3433},[3412,21808,21809],{"class":3495},"\"📋\"",[3412,21811,3454],{"class":3433},[3412,21813,21814],{"class":3495},"\"WARN\"",[3412,21816,3877],{"class":3433},[3412,21818,21819],{"class":3495},"\"⚠️\"",[3412,21821,3454],{"class":3433},[3412,21823,13165],{"class":3495},[3412,21825,3877],{"class":3433},[3412,21827,21828],{"class":3495},"\"🔴\"",[3412,21830,21831],{"class":3433},"}.get(level, ",[3412,21833,21834],{"class":3495},"\"•\"",[3412,21836,4069],{"class":3433},[3412,21838,21839,21841,21843,21845,21847,21849,21852,21854,21856,21858,21860,21862,21864,21866,21868,21870,21872,21874,21876,21878,21880,21882,21884,21886,21888,21890,21892,21894,21896],{"class":3414,"line":5533},[3412,21840,4039],{"class":3443},[3412,21842,3447],{"class":3433},[3412,21844,3492],{"class":3425},[3412,21846,13053],{"class":3495},[3412,21848,3499],{"class":3425},[3412,21850,21851],{"class":3433},"ts",[3412,21853,3504],{"class":3425},[3412,21855,13082],{"class":3495},[3412,21857,3499],{"class":3425},[3412,21859,21423],{"class":3433},[3412,21861,3504],{"class":3425},[3412,21863,16043],{"class":3495},[3412,21865,3499],{"class":3425},[3412,21867,12991],{"class":3433},[3412,21869,3504],{"class":3425},[3412,21871,13063],{"class":3495},[3412,21873,3499],{"class":3425},[3412,21875,5110],{"class":3429},[3412,21877,3447],{"class":3433},[3412,21879,3451],{"class":3425},[3412,21881,6479],{"class":3433},[3412,21883,4113],{"class":3450},[3412,21885,3504],{"class":3425},[3412,21887,13082],{"class":3495},[3412,21889,3499],{"class":3425},[3412,21891,12982],{"class":3433},[3412,21893,3504],{"class":3425},[3412,21895,3507],{"class":3495},[3412,21897,4069],{"class":3433},[3412,21899,21900],{"class":3414,"line":5548},[3412,21901,3554],{"emptyLinePlaceholder":3553},[3412,21903,21904,21906,21908,21910,21912,21914,21916],{"class":3414,"line":5568},[3412,21905,3440],{"class":3425},[3412,21907,21586],{"class":3443},[3412,21909,3447],{"class":3433},[3412,21911,3451],{"class":3450},[3412,21913,3454],{"class":3433},[3412,21915,21595],{"class":3450},[3412,21917,21598],{"class":3433},[3412,21919,21920,21922,21925,21927,21930,21932,21935,21937,21939,21942,21944,21946],{"class":3414,"line":5573},[3412,21921,3898],{"class":3425},[3412,21923,21924],{"class":3433},"._log(",[3412,21926,3492],{"class":3425},[3412,21928,21929],{"class":3495},"\"→ ",[3412,21931,3499],{"class":3425},[3412,21933,21934],{"class":3433},"request.method",[3412,21936,3504],{"class":3425},[3412,21938,21363],{"class":3425},[3412,21940,21941],{"class":3433},"request.path",[3412,21943,3504],{"class":3425},[3412,21945,3507],{"class":3495},[3412,21947,4069],{"class":3433},[3412,21949,21950,21953,21955,21958],{"class":3414,"line":5579},[3412,21951,21952],{"class":3433},"        response = ",[3412,21954,4285],{"class":3429},[3412,21956,21957],{"class":3433},"().handle(request)   ",[3412,21959,21960],{"class":3418},"# → наступний у MRO\n",[3412,21962,21963,21965,21967,21969,21972,21974],{"class":3414,"line":5595},[3412,21964,21395],{"class":3433},[3412,21966,21398],{"class":3495},[3412,21968,13606],{"class":3530},[3412,21970,21971],{"class":3433}," response.ok ",[3412,21973,11044],{"class":3530},[3412,21975,21410],{"class":3495},[3412,21977,21978,21980],{"class":3414,"line":5609},[3412,21979,3898],{"class":3425},[3412,21981,21982],{"class":3433},"._log(\n",[3412,21984,21985,21987,21990,21992,21994,21996,21998,22001,22003,22005,22007,22009,22011,22013,22015,22017,22019],{"class":3414,"line":7232},[3412,21986,16026],{"class":3425},[3412,21988,21989],{"class":3495},"\"← ",[3412,21991,3499],{"class":3425},[3412,21993,21423],{"class":3433},[3412,21995,3504],{"class":3425},[3412,21997,21363],{"class":3425},[3412,21999,22000],{"class":3433},"response.status",[3412,22002,3504],{"class":3425},[3412,22004,9654],{"class":3495},[3412,22006,3499],{"class":3425},[3412,22008,21934],{"class":3433},[3412,22010,3504],{"class":3425},[3412,22012,21363],{"class":3425},[3412,22014,21941],{"class":3433},[3412,22016,3504],{"class":3425},[3412,22018,3507],{"class":3495},[3412,22020,7936],{"class":3433},[3412,22022,22023,22026,22028,22030,22032,22034,22036,22039],{"class":3414,"line":7261},[3412,22024,22025],{"class":3450},"            level",[3412,22027,4691],{"class":3433},[3412,22029,13000],{"class":3495},[3412,22031,13606],{"class":3530},[3412,22033,21971],{"class":3433},[3412,22035,11044],{"class":3530},[3412,22037,22038],{"class":3495}," \"WARN\"",[3412,22040,7936],{"class":3433},[3412,22042,22043],{"class":3414,"line":7266},[3412,22044,6941],{"class":3433},[3412,22046,22047,22049],{"class":3414,"line":7271},[3412,22048,3955],{"class":3530},[3412,22050,22051],{"class":3433}," response\n",[3412,22053,22054],{"class":3414,"line":7277},[3412,22055,3554],{"emptyLinePlaceholder":3553},[3412,22057,22058],{"class":3414,"line":7310},[3412,22059,3554],{"emptyLinePlaceholder":3553},[3412,22061,22062,22064,22067],{"class":3414,"line":7315},[3412,22063,3426],{"class":3425},[3412,22065,22066],{"class":3429}," AuthMixin",[3412,22068,3434],{"class":3433},[3412,22070,22071],{"class":3414,"line":7321},[3412,22072,10012],{"class":3495},[3412,22074,22075],{"class":3414,"line":7346},[3412,22076,22077],{"class":3495},"    Mixin: перевіряє Bearer-токен у заголовку Authorization.\n",[3412,22079,22080],{"class":3414,"line":7361},[3412,22081,22082],{"class":3495},"    Симулює JWT: токен = 'Bearer \u003Cusername>:\u003Crole>'.\n",[3412,22084,22085],{"class":3414,"line":7366},[3412,22086,10012],{"class":3495},[3412,22088,22089],{"class":3414,"line":7372},[3412,22090,3554],{"emptyLinePlaceholder":3553},[3412,22092,22093],{"class":3414,"line":7378},[3412,22094,22095],{"class":3418},"    # Симульована база токенів {token: (username, role)}\n",[3412,22097,22098,22101,22103,22106,22108,22110,22112],{"class":3414,"line":7393},[3412,22099,22100],{"class":3433},"    _TOKEN_DB: dict[",[3412,22102,3880],{"class":3429},[3412,22104,22105],{"class":3433},", tuple[",[3412,22107,3880],{"class":3429},[3412,22109,3454],{"class":3433},[3412,22111,3880],{"class":3429},[3412,22113,22114],{"class":3433},"]] = {\n",[3412,22116,22117,22120,22123,22126,22129,22131],{"class":3414,"line":13280},[3412,22118,22119],{"class":3495},"        \"admin-token\"",[3412,22121,22122],{"class":3433},":  (",[3412,22124,22125],{"class":3495},"\"admin\"",[3412,22127,22128],{"class":3433},",   ",[3412,22130,22125],{"class":3495},[3412,22132,4671],{"class":3433},[3412,22134,22135,22138,22141,22144,22146,22149],{"class":3414,"line":13288},[3412,22136,22137],{"class":3495},"        \"user-token\"",[3412,22139,22140],{"class":3433},":   (",[3412,22142,22143],{"class":3495},"\"alice\"",[3412,22145,22128],{"class":3433},[3412,22147,22148],{"class":3495},"\"user\"",[3412,22150,4671],{"class":3433},[3412,22152,22153,22156,22159,22162,22165,22168],{"class":3414,"line":13294},[3412,22154,22155],{"class":3495},"        \"vip-token\"",[3412,22157,22158],{"class":3433},":    (",[3412,22160,22161],{"class":3495},"\"bob\"",[3412,22163,22164],{"class":3433},",     ",[3412,22166,22167],{"class":3495},"\"vip\"",[3412,22169,4671],{"class":3433},[3412,22171,22172],{"class":3414,"line":13302},[3412,22173,8659],{"class":3433},[3412,22175,22176],{"class":3414,"line":13307},[3412,22177,3554],{"emptyLinePlaceholder":3553},[3412,22179,22180,22182,22184,22186,22188,22190,22193,22195,22197,22200,22202,22204,22206,22208,22210],{"class":3414,"line":13325},[3412,22181,3440],{"class":3425},[3412,22183,3865],{"class":3443},[3412,22185,3447],{"class":3433},[3412,22187,3451],{"class":3450},[3412,22189,3454],{"class":3433},[3412,22191,22192],{"class":3450},"required_roles",[3412,22194,18702],{"class":3433},[3412,22196,3880],{"class":3429},[3412,22198,22199],{"class":3433},"] | ",[3412,22201,4011],{"class":3425},[3412,22203,3921],{"class":3433},[3412,22205,4011],{"class":3425},[3412,22207,7534],{"class":3433},[3412,22209,7537],{"class":3450},[3412,22211,3893],{"class":3433},[3412,22213,22214,22216,22218,22220],{"class":3414,"line":13331},[3412,22215,4236],{"class":3429},[3412,22217,4239],{"class":3433},[3412,22219,4242],{"class":3443},[3412,22221,7749],{"class":3433},[3412,22223,22224,22226,22229,22231],{"class":3414,"line":13342},[3412,22225,3898],{"class":3425},[3412,22227,22228],{"class":3433},"._required_roles = required_roles ",[3412,22230,14195],{"class":3425},[3412,22232,19118],{"class":3433},[3412,22234,22235],{"class":3414,"line":13350},[3412,22236,3554],{"emptyLinePlaceholder":3553},[3412,22238,22239,22241,22243,22245,22247,22249,22251],{"class":3414,"line":13378},[3412,22240,3440],{"class":3425},[3412,22242,21586],{"class":3443},[3412,22244,3447],{"class":3433},[3412,22246,3451],{"class":3450},[3412,22248,3454],{"class":3433},[3412,22250,21595],{"class":3450},[3412,22252,21598],{"class":3433},[3412,22254,22255,22258,22261,22263,22265],{"class":3414,"line":13383},[3412,22256,22257],{"class":3433},"        auth_header = request.headers.get(",[3412,22259,22260],{"class":3495},"\"Authorization\"",[3412,22262,3454],{"class":3433},[3412,22264,14221],{"class":3495},[3412,22266,4069],{"class":3433},[3412,22268,22269],{"class":3414,"line":13388},[3412,22270,3554],{"emptyLinePlaceholder":3553},[3412,22272,22273,22275,22277,22280,22283],{"class":3414,"line":13398},[3412,22274,9686],{"class":3530},[3412,22276,10123],{"class":3425},[3412,22278,22279],{"class":3433}," auth_header.startswith(",[3412,22281,22282],{"class":3495},"\"Bearer \"",[3412,22284,3893],{"class":3433},[3412,22286,22287,22289],{"class":3414,"line":13403},[3412,22288,9707],{"class":3530},[3412,22290,22291],{"class":3433}," Response(\n",[3412,22293,22294],{"class":3414,"line":13409},[3412,22295,22296],{"class":3433},"                Response.UNAUTHORIZED,\n",[3412,22298,22299,22302,22304,22307],{"class":3414,"line":13415},[3412,22300,22301],{"class":3450},"                body",[3412,22303,4691],{"class":3433},[3412,22305,22306],{"class":3495},"\"401 Unauthorized: відсутній токен\"",[3412,22308,7936],{"class":3433},[3412,22310,22311],{"class":3414,"line":13420},[3412,22312,14955],{"class":3433},[3412,22314,22315],{"class":3414,"line":13425},[3412,22316,3554],{"emptyLinePlaceholder":3553},[3412,22318,22319,22322,22325],{"class":3414,"line":13433},[3412,22320,22321],{"class":3433},"        token = auth_header[",[3412,22323,22324],{"class":3924},"7",[3412,22326,22327],{"class":3433},":]\n",[3412,22329,22330,22333,22335],{"class":3414,"line":13451},[3412,22331,22332],{"class":3433},"        user_info = ",[3412,22334,3451],{"class":3425},[3412,22336,22337],{"class":3433},"._TOKEN_DB.get(token)\n",[3412,22339,22340],{"class":3414,"line":13457},[3412,22341,3554],{"emptyLinePlaceholder":3553},[3412,22343,22344,22346,22349,22351,22353],{"class":3414,"line":13477},[3412,22345,9686],{"class":3530},[3412,22347,22348],{"class":3433}," user_info ",[3412,22350,10120],{"class":3425},[3412,22352,10126],{"class":3425},[3412,22354,3434],{"class":3433},[3412,22356,22357,22359],{"class":3414,"line":13492},[3412,22358,9707],{"class":3530},[3412,22360,22291],{"class":3433},[3412,22362,22363],{"class":3414,"line":13508},[3412,22364,22296],{"class":3433},[3412,22366,22367,22369,22371,22373,22376],{"class":3414,"line":19288},[3412,22368,22301],{"class":3450},[3412,22370,4691],{"class":3433},[3412,22372,3492],{"class":3425},[3412,22374,22375],{"class":3495},"\"401 Unauthorized: невалідний токен\"",[3412,22377,7936],{"class":3433},[3412,22379,22380],{"class":3414,"line":19316},[3412,22381,14955],{"class":3433},[3412,22383,22384],{"class":3414,"line":19330},[3412,22385,3554],{"emptyLinePlaceholder":3553},[3412,22387,22388],{"class":3414,"line":19335},[3412,22389,22390],{"class":3433},"        username, role = user_info\n",[3412,22392,22393,22396],{"class":3414,"line":19340},[3412,22394,22395],{"class":3433},"        request.user = username   ",[3412,22397,22398],{"class":3418},"# збагачуємо об'єкт запиту\n",[3412,22400,22401],{"class":3414,"line":19363},[3412,22402,3554],{"emptyLinePlaceholder":3553},[3412,22404,22405,22407,22409,22412,22415,22418,22421,22423,22425],{"class":3414,"line":19369},[3412,22406,9686],{"class":3530},[3412,22408,10741],{"class":3425},[3412,22410,22411],{"class":3433},"._required_roles ",[3412,22413,22414],{"class":3425},"and",[3412,22416,22417],{"class":3433}," role ",[3412,22419,22420],{"class":3425},"not",[3412,22422,5334],{"class":3425},[3412,22424,10741],{"class":3425},[3412,22426,22427],{"class":3433},"._required_roles:\n",[3412,22429,22430,22432],{"class":3414,"line":19384},[3412,22431,9707],{"class":3530},[3412,22433,22291],{"class":3433},[3412,22435,22436],{"class":3414,"line":19389},[3412,22437,22438],{"class":3433},"                Response.FORBIDDEN,\n",[3412,22440,22441,22443,22445,22447,22450,22452,22455,22457,22460,22462,22465,22467,22469],{"class":3414,"line":19414},[3412,22442,22301],{"class":3450},[3412,22444,4691],{"class":3433},[3412,22446,3492],{"class":3425},[3412,22448,22449],{"class":3495},"\"403 Forbidden: потрібна роль ",[3412,22451,3964],{"class":3425},[3412,22453,22454],{"class":3433},"._required_roles",[3412,22456,3504],{"class":3425},[3412,22458,22459],{"class":3495},", є ",[3412,22461,3499],{"class":3425},[3412,22463,22464],{"class":3433},"role",[3412,22466,4125],{"class":3425},[3412,22468,3507],{"class":3495},[3412,22470,7936],{"class":3433},[3412,22472,22473],{"class":3414,"line":19421},[3412,22474,14955],{"class":3433},[3412,22476,22477],{"class":3414,"line":19442},[3412,22478,3554],{"emptyLinePlaceholder":3553},[3412,22480,22481,22483,22486,22488],{"class":3414,"line":19447},[3412,22482,3955],{"class":3530},[3412,22484,22485],{"class":3429}," super",[3412,22487,21957],{"class":3433},[3412,22489,21960],{"class":3418},[3412,22491,22492],{"class":3414,"line":19472},[3412,22493,3554],{"emptyLinePlaceholder":3553},[3412,22495,22496],{"class":3414,"line":19479},[3412,22497,3554],{"emptyLinePlaceholder":3553},[3412,22499,22500,22502,22505],{"class":3414,"line":19510},[3412,22501,3426],{"class":3425},[3412,22503,22504],{"class":3429}," RateLimitMixin",[3412,22506,3434],{"class":3433},[3412,22508,22509],{"class":3414,"line":19515},[3412,22510,10012],{"class":3495},[3412,22512,22513],{"class":3414,"line":19520},[3412,22514,22515],{"class":3495},"    Mixin: обмежує кількість запитів на секунду (per-user або per-IP).\n",[3412,22517,22518],{"class":3414,"line":19543},[3412,22519,22520],{"class":3495},"    Внутрішній стан захищений через __request_log (Name Mangling).\n",[3412,22522,22523],{"class":3414,"line":19549},[3412,22524,10012],{"class":3495},[3412,22526,22527],{"class":3414,"line":19554},[3412,22528,3554],{"emptyLinePlaceholder":3553},[3412,22530,22531,22533,22535,22537,22539,22541,22544,22546,22548,22550,22552,22554,22556],{"class":3414,"line":19579},[3412,22532,3440],{"class":3425},[3412,22534,3865],{"class":3443},[3412,22536,3447],{"class":3433},[3412,22538,3451],{"class":3450},[3412,22540,3454],{"class":3433},[3412,22542,22543],{"class":3450},"max_rps",[3412,22545,3877],{"class":3433},[3412,22547,3890],{"class":3429},[3412,22549,3921],{"class":3433},[3412,22551,7711],{"class":3924},[3412,22553,7534],{"class":3433},[3412,22555,7537],{"class":3450},[3412,22557,3893],{"class":3433},[3412,22559,22560,22562,22564,22566],{"class":3414,"line":19585},[3412,22561,4236],{"class":3429},[3412,22563,4239],{"class":3433},[3412,22565,4242],{"class":3443},[3412,22567,7749],{"class":3433},[3412,22569,22570,22572],{"class":3414,"line":19594},[3412,22571,3898],{"class":3425},[3412,22573,22574],{"class":3433},"._max_rps = max_rps\n",[3412,22576,22577],{"class":3414,"line":19601},[3412,22578,22579],{"class":3418},"        # __request_log → _RateLimitMixin__request_log (захист від підкласів)\n",[3412,22581,22582,22584,22587,22589,22592,22594,22597,22599],{"class":3414,"line":19621},[3412,22583,3898],{"class":3425},[3412,22585,22586],{"class":3433},".__request_log: dict[",[3412,22588,3880],{"class":3429},[3412,22590,22591],{"class":3433},", list[",[3412,22593,3918],{"class":3429},[3412,22595,22596],{"class":3433},"]] = defaultdict(",[3412,22598,13214],{"class":3429},[3412,22600,4069],{"class":3433},[3412,22602,22603],{"class":3414,"line":19659},[3412,22604,3554],{"emptyLinePlaceholder":3553},[3412,22606,22607,22609,22611,22613,22615,22617,22619],{"class":3414,"line":19664},[3412,22608,3440],{"class":3425},[3412,22610,21586],{"class":3443},[3412,22612,3447],{"class":3433},[3412,22614,3451],{"class":3450},[3412,22616,3454],{"class":3433},[3412,22618,21595],{"class":3450},[3412,22620,21598],{"class":3433},[3412,22622,22623,22626,22628,22631,22634,22636,22639],{"class":3414,"line":19689},[3412,22624,22625],{"class":3433},"        key = request.user ",[3412,22627,14195],{"class":3425},[3412,22629,22630],{"class":3433}," request.headers.get(",[3412,22632,22633],{"class":3495},"\"X-Forwarded-For\"",[3412,22635,3454],{"class":3433},[3412,22637,22638],{"class":3495},"\"anonymous\"",[3412,22640,4069],{"class":3433},[3412,22642,22643],{"class":3414,"line":19698},[3412,22644,22645],{"class":3433},"        now = time.time()\n",[3412,22647,22648],{"class":3414,"line":19705},[3412,22649,3554],{"emptyLinePlaceholder":3553},[3412,22651,22652],{"class":3414,"line":19718},[3412,22653,22654],{"class":3418},"        # Залишаємо лише запити останньої секунди\n",[3412,22656,22657,22659],{"class":3414,"line":19731},[3412,22658,3898],{"class":3425},[3412,22660,22661],{"class":3433},".__request_log[key] = [\n",[3412,22663,22664,22667,22669,22672,22674,22676,22679,22681,22684],{"class":3414,"line":19742},[3412,22665,22666],{"class":3433},"            t ",[3412,22668,4738],{"class":3530},[3412,22670,22671],{"class":3433}," t ",[3412,22673,4744],{"class":3530},[3412,22675,10741],{"class":3425},[3412,22677,22678],{"class":3433},".__request_log[key] ",[3412,22680,11031],{"class":3530},[3412,22682,22683],{"class":3433}," now - t \u003C ",[3412,22685,22686],{"class":3924},"1.0\n",[3412,22688,22689],{"class":3414,"line":19781},[3412,22690,22691],{"class":3433},"        ]\n",[3412,22693,22694],{"class":3414,"line":19794},[3412,22695,3554],{"emptyLinePlaceholder":3553},[3412,22697,22698,22700,22702,22704,22706,22709,22711],{"class":3414,"line":19799},[3412,22699,9686],{"class":3530},[3412,22701,13881],{"class":3443},[3412,22703,3447],{"class":3433},[3412,22705,3451],{"class":3425},[3412,22707,22708],{"class":3433},".__request_log[key]) >= ",[3412,22710,3451],{"class":3425},[3412,22712,22713],{"class":3433},"._max_rps:\n",[3412,22715,22716,22718],{"class":3414,"line":19804},[3412,22717,9707],{"class":3530},[3412,22719,22291],{"class":3433},[3412,22721,22722],{"class":3414,"line":19810},[3412,22723,22724],{"class":3433},"                Response.TOO_MANY,\n",[3412,22726,22727,22729,22731,22733,22736,22738,22741,22743,22746],{"class":3414,"line":19815},[3412,22728,22301],{"class":3450},[3412,22730,4691],{"class":3433},[3412,22732,3492],{"class":3425},[3412,22734,22735],{"class":3495},"\"429 Too Many Requests: ліміт ",[3412,22737,3964],{"class":3425},[3412,22739,22740],{"class":3433},"._max_rps",[3412,22742,3504],{"class":3425},[3412,22744,22745],{"class":3495}," req\u002Fs\"",[3412,22747,7936],{"class":3433},[3412,22749,22750,22753,22756,22759,22761,22764],{"class":3414,"line":19830},[3412,22751,22752],{"class":3450},"                headers",[3412,22754,22755],{"class":3433},"={",[3412,22757,22758],{"class":3495},"\"Retry-After\"",[3412,22760,3877],{"class":3433},[3412,22762,22763],{"class":3495},"\"1\"",[3412,22765,22766],{"class":3433},"},\n",[3412,22768,22769],{"class":3414,"line":19857},[3412,22770,14955],{"class":3433},[3412,22772,22773],{"class":3414,"line":19882},[3412,22774,3554],{"emptyLinePlaceholder":3553},[3412,22776,22777,22779],{"class":3414,"line":19887},[3412,22778,3898],{"class":3425},[3412,22780,22781],{"class":3433},".__request_log[key].append(now)\n",[3412,22783,22784,22786,22788,22790],{"class":3414,"line":19893},[3412,22785,3955],{"class":3530},[3412,22787,22485],{"class":3429},[3412,22789,21957],{"class":3433},[3412,22791,21960],{"class":3418},[3412,22793,22794],{"class":3414,"line":19902},[3412,22795,3554],{"emptyLinePlaceholder":3553},[3412,22797,22798],{"class":3414,"line":19908},[3412,22799,3554],{"emptyLinePlaceholder":3553},[3412,22801,22802,22804,22807],{"class":3414,"line":19914},[3412,22803,3426],{"class":3425},[3412,22805,22806],{"class":3429}," CORSMixin",[3412,22808,3434],{"class":3433},[3412,22810,22811],{"class":3414,"line":19920},[3412,22812,10012],{"class":3495},[3412,22814,22815],{"class":3414,"line":19925},[3412,22816,22817],{"class":3495},"    Mixin: додає CORS-заголовки до кожної відповіді.\n",[3412,22819,22820],{"class":3414,"line":19941},[3412,22821,10012],{"class":3495},[3412,22823,22824],{"class":3414,"line":19951},[3412,22825,3554],{"emptyLinePlaceholder":3553},[3412,22827,22828,22831,22834],{"class":3414,"line":19957},[3412,22829,22830],{"class":3433},"    DEFAULT_ORIGINS = [",[3412,22832,22833],{"class":3495},"\"*\"",[3412,22835,4724],{"class":3433},[3412,22837,22838],{"class":3414,"line":19983},[3412,22839,3554],{"emptyLinePlaceholder":3553},[3412,22841,22842,22844,22846,22848,22850,22852,22855,22857,22859,22861,22863,22865,22867,22869,22871],{"class":3414,"line":19988},[3412,22843,3440],{"class":3425},[3412,22845,3865],{"class":3443},[3412,22847,3447],{"class":3433},[3412,22849,3451],{"class":3450},[3412,22851,3454],{"class":3433},[3412,22853,22854],{"class":3450},"allowed_origins",[3412,22856,18702],{"class":3433},[3412,22858,3880],{"class":3429},[3412,22860,22199],{"class":3433},[3412,22862,4011],{"class":3425},[3412,22864,3921],{"class":3433},[3412,22866,4011],{"class":3425},[3412,22868,7534],{"class":3433},[3412,22870,7537],{"class":3450},[3412,22872,3893],{"class":3433},[3412,22874,22875,22877,22879,22881],{"class":3414,"line":19998},[3412,22876,4236],{"class":3429},[3412,22878,4239],{"class":3433},[3412,22880,4242],{"class":3443},[3412,22882,7749],{"class":3433},[3412,22884,22885,22887,22890,22892,22894],{"class":3414,"line":20004},[3412,22886,3898],{"class":3425},[3412,22888,22889],{"class":3433},"._allowed_origins = allowed_origins ",[3412,22891,14195],{"class":3425},[3412,22893,10741],{"class":3425},[3412,22895,22896],{"class":3433},".DEFAULT_ORIGINS\n",[3412,22898,22899],{"class":3414,"line":20016},[3412,22900,3554],{"emptyLinePlaceholder":3553},[3412,22902,22903,22905,22907,22909,22911,22913,22915],{"class":3414,"line":20024},[3412,22904,3440],{"class":3425},[3412,22906,21586],{"class":3443},[3412,22908,3447],{"class":3433},[3412,22910,3451],{"class":3450},[3412,22912,3454],{"class":3433},[3412,22914,21595],{"class":3450},[3412,22916,21598],{"class":3433},[3412,22918,22919,22922,22925,22927,22929],{"class":3414,"line":20029},[3412,22920,22921],{"class":3433},"        origin = request.headers.get(",[3412,22923,22924],{"class":3495},"\"Origin\"",[3412,22926,3454],{"class":3433},[3412,22928,22833],{"class":3495},[3412,22930,4069],{"class":3433},[3412,22932,22933,22935,22937,22939],{"class":3414,"line":20035},[3412,22934,21952],{"class":3433},[3412,22936,4285],{"class":3429},[3412,22938,21957],{"class":3433},[3412,22940,21960],{"class":3418},[3412,22942,22943],{"class":3414,"line":20051},[3412,22944,3554],{"emptyLinePlaceholder":3553},[3412,22946,22947],{"class":3414,"line":20074},[3412,22948,22949],{"class":3418},"        # Додаємо CORS-заголовки до відповіді\n",[3412,22951,22952,22955,22957,22959,22962,22964,22966,22969,22971],{"class":3414,"line":20092},[3412,22953,22954],{"class":3433},"        allowed = ",[3412,22956,22833],{"class":3495},[3412,22958,13606],{"class":3530},[3412,22960,22961],{"class":3495}," \"*\"",[3412,22963,5334],{"class":3425},[3412,22965,10741],{"class":3425},[3412,22967,22968],{"class":3433},"._allowed_origins ",[3412,22970,11044],{"class":3530},[3412,22972,16021],{"class":3433},[3412,22974,22975,22978,22980,22983,22985,22987,22989,22991,22993,22996,22998],{"class":3414,"line":20097},[3412,22976,22977],{"class":3433},"            origin ",[3412,22979,11031],{"class":3530},[3412,22981,22982],{"class":3433}," origin ",[3412,22984,4744],{"class":3425},[3412,22986,10741],{"class":3425},[3412,22988,22968],{"class":3433},[3412,22990,11044],{"class":3530},[3412,22992,10741],{"class":3425},[3412,22994,22995],{"class":3433},"._allowed_origins[",[3412,22997,10779],{"class":3924},[3412,22999,4724],{"class":3433},[3412,23001,23002],{"class":3414,"line":20108},[3412,23003,6941],{"class":3433},[3412,23005,23006],{"class":3414,"line":20116},[3412,23007,23008],{"class":3433},"        response.headers.update({\n",[3412,23010,23011,23014],{"class":3414,"line":20121},[3412,23012,23013],{"class":3495},"            \"Access-Control-Allow-Origin\"",[3412,23015,23016],{"class":3433},":  allowed,\n",[3412,23018,23019,23022,23024,23027],{"class":3414,"line":20127},[3412,23020,23021],{"class":3495},"            \"Access-Control-Allow-Methods\"",[3412,23023,3877],{"class":3433},[3412,23025,23026],{"class":3495},"\"GET, POST, PUT, DELETE\"",[3412,23028,7936],{"class":3433},[3412,23030,23031,23034,23036,23039],{"class":3414,"line":20135},[3412,23032,23033],{"class":3495},"            \"Access-Control-Allow-Headers\"",[3412,23035,3877],{"class":3433},[3412,23037,23038],{"class":3495},"\"Authorization, Content-Type\"",[3412,23040,7936],{"class":3433},[3412,23042,23043],{"class":3414,"line":20146},[3412,23044,23045],{"class":3433},"        })\n",[3412,23047,23048,23050],{"class":3414,"line":20158},[3412,23049,3955],{"class":3530},[3412,23051,22051],{"class":3433},[3412,23053,23054],{"class":3414,"line":20184},[3412,23055,3554],{"emptyLinePlaceholder":3553},[3412,23057,23058],{"class":3414,"line":20189},[3412,23059,3554],{"emptyLinePlaceholder":3553},[3412,23061,23062],{"class":3414,"line":20194},[3412,23063,23064],{"class":3418},"# ── Конкретні обробники маршрутів ─────────────────────────────────────────────\n",[3412,23066,23067],{"class":3414,"line":20210},[3412,23068,3554],{"emptyLinePlaceholder":3553},[3412,23070,23072,23074,23077,23079,23081,23083,23085,23087,23089],{"class":3414,"line":23071},166,[3412,23073,3426],{"class":3425},[3412,23075,23076],{"class":3429}," PublicHandler",[3412,23078,3447],{"class":3433},[3412,23080,13565],{"class":3429},[3412,23082,3454],{"class":3433},[3412,23084,20427],{"class":3429},[3412,23086,3454],{"class":3433},[3412,23088,20478],{"class":3429},[3412,23090,3893],{"class":3433},[3412,23092,23094],{"class":3414,"line":23093},167,[3412,23095,10012],{"class":3495},[3412,23097,23099],{"class":3414,"line":23098},168,[3412,23100,23101],{"class":3495},"    Публічний ендпоінт: лише логування + CORS.\n",[3412,23103,23105],{"class":3414,"line":23104},169,[3412,23106,23107],{"class":3495},"    MRO: PublicHandler → LoggingMixin → CORSMixin → BaseHandler → object\n",[3412,23109,23111],{"class":3414,"line":23110},170,[3412,23112,10012],{"class":3495},[3412,23114,23116],{"class":3414,"line":23115},171,[3412,23117,3554],{"emptyLinePlaceholder":3553},[3412,23119,23121,23123,23125,23127,23129,23131,23133],{"class":3414,"line":23120},172,[3412,23122,3440],{"class":3425},[3412,23124,21623],{"class":3443},[3412,23126,3447],{"class":3433},[3412,23128,3451],{"class":3450},[3412,23130,3454],{"class":3433},[3412,23132,21595],{"class":3450},[3412,23134,21598],{"class":3433},[3412,23136,23138,23140,23143,23146,23148,23151],{"class":3414,"line":23137},173,[3412,23139,3955],{"class":3530},[3412,23141,23142],{"class":3433}," Response(Response.OK, ",[3412,23144,23145],{"class":3450},"body",[3412,23147,4691],{"class":3433},[3412,23149,23150],{"class":3495},"'{\"status\": \"public\", \"data\": \"open\"}'",[3412,23152,4069],{"class":3433},[3412,23154,23156],{"class":3414,"line":23155},174,[3412,23157,3554],{"emptyLinePlaceholder":3553},[3412,23159,23161],{"class":3414,"line":23160},175,[3412,23162,3554],{"emptyLinePlaceholder":3553},[3412,23164,23166,23168,23171,23173,23175,23177,23179,23181,23183,23185,23187,23189,23191],{"class":3414,"line":23165},176,[3412,23167,3426],{"class":3425},[3412,23169,23170],{"class":3429}," ProtectedHandler",[3412,23172,3447],{"class":3433},[3412,23174,13565],{"class":3429},[3412,23176,3454],{"class":3433},[3412,23178,20421],{"class":3429},[3412,23180,3454],{"class":3433},[3412,23182,20424],{"class":3429},[3412,23184,3454],{"class":3433},[3412,23186,20427],{"class":3429},[3412,23188,3454],{"class":3433},[3412,23190,20478],{"class":3429},[3412,23192,3893],{"class":3433},[3412,23194,23196],{"class":3414,"line":23195},177,[3412,23197,10012],{"class":3495},[3412,23199,23201],{"class":3414,"line":23200},178,[3412,23202,23203],{"class":3495},"    Захищений ендпоінт: логування → автентифікація → rate limit → CORS → логіка.\n",[3412,23205,23207],{"class":3414,"line":23206},179,[3412,23208,23209],{"class":3495},"    MRO: ProtectedHandler → LoggingMixin → AuthMixin → RateLimitMixin → CORSMixin → BaseHandler\n",[3412,23211,23213],{"class":3414,"line":23212},180,[3412,23214,10012],{"class":3495},[3412,23216,23218],{"class":3414,"line":23217},181,[3412,23219,3554],{"emptyLinePlaceholder":3553},[3412,23221,23223,23225,23227,23229,23231,23233,23235],{"class":3414,"line":23222},182,[3412,23224,3440],{"class":3425},[3412,23226,3865],{"class":3443},[3412,23228,3447],{"class":3433},[3412,23230,3451],{"class":3450},[3412,23232,7534],{"class":3433},[3412,23234,7537],{"class":3450},[3412,23236,3893],{"class":3433},[3412,23238,23240,23242,23244,23246],{"class":3414,"line":23239},183,[3412,23241,4236],{"class":3429},[3412,23243,4239],{"class":3433},[3412,23245,4242],{"class":3443},[3412,23247,6908],{"class":3433},[3412,23249,23251,23254,23257,23259,23261,23263,23265,23267],{"class":3414,"line":23250},184,[3412,23252,23253],{"class":3450},"            required_roles",[3412,23255,23256],{"class":3433},"=[",[3412,23258,22148],{"class":3495},[3412,23260,3454],{"class":3433},[3412,23262,22167],{"class":3495},[3412,23264,3454],{"class":3433},[3412,23266,22125],{"class":3495},[3412,23268,6922],{"class":3433},[3412,23270,23272,23275,23277,23279],{"class":3414,"line":23271},185,[3412,23273,23274],{"class":3450},"            max_rps",[3412,23276,4691],{"class":3433},[3412,23278,10815],{"class":3924},[3412,23280,7936],{"class":3433},[3412,23282,23284,23287,23289,23292,23294,23297],{"class":3414,"line":23283},186,[3412,23285,23286],{"class":3450},"            allowed_origins",[3412,23288,23256],{"class":3433},[3412,23290,23291],{"class":3495},"\"https:\u002F\u002Fkostyl.dev\"",[3412,23293,3454],{"class":3433},[3412,23295,23296],{"class":3495},"\"http:\u002F\u002Flocalhost:3000\"",[3412,23298,6922],{"class":3433},[3412,23300,23302],{"class":3414,"line":23301},187,[3412,23303,23304],{"class":3433},"            **kwargs,\n",[3412,23306,23308],{"class":3414,"line":23307},188,[3412,23309,6941],{"class":3433},[3412,23311,23313],{"class":3414,"line":23312},189,[3412,23314,3554],{"emptyLinePlaceholder":3553},[3412,23316,23318,23320,23322,23324,23326,23328,23330],{"class":3414,"line":23317},190,[3412,23319,3440],{"class":3425},[3412,23321,21623],{"class":3443},[3412,23323,3447],{"class":3433},[3412,23325,3451],{"class":3450},[3412,23327,3454],{"class":3433},[3412,23329,21595],{"class":3450},[3412,23331,21598],{"class":3433},[3412,23333,23335,23337],{"class":3414,"line":23334},191,[3412,23336,3955],{"class":3530},[3412,23338,22291],{"class":3433},[3412,23340,23342],{"class":3414,"line":23341},192,[3412,23343,23344],{"class":3433},"            Response.OK,\n",[3412,23346,23348,23351,23353,23355,23357,23360,23363,23365,23368,23370,23373,23375,23377,23379,23381,23384,23386],{"class":3414,"line":23347},193,[3412,23349,23350],{"class":3450},"            body",[3412,23352,4691],{"class":3433},[3412,23354,3492],{"class":3425},[3412,23356,19611],{"class":3495},[3412,23358,23359],{"class":5318},"{{",[3412,23361,23362],{"class":3495},"\"status\": \"ok\", \"user\": \"",[3412,23364,3499],{"class":3425},[3412,23366,23367],{"class":3433},"request.user",[3412,23369,3504],{"class":3425},[3412,23371,23372],{"class":3495},"\", \"path\": \"",[3412,23374,3499],{"class":3425},[3412,23376,21941],{"class":3433},[3412,23378,3504],{"class":3425},[3412,23380,3507],{"class":3495},[3412,23382,23383],{"class":5318},"}}",[3412,23385,19611],{"class":3495},[3412,23387,7936],{"class":3433},[3412,23389,23391],{"class":3414,"line":23390},194,[3412,23392,6941],{"class":3433},[3412,23394,23396],{"class":3414,"line":23395},195,[3412,23397,3554],{"emptyLinePlaceholder":3553},[3412,23399,23401],{"class":3414,"line":23400},196,[3412,23402,3554],{"emptyLinePlaceholder":3553},[3412,23404,23406,23408,23411,23413,23415,23417,23419,23421,23423,23425,23427,23429,23431],{"class":3414,"line":23405},197,[3412,23407,3426],{"class":3425},[3412,23409,23410],{"class":3429}," AdminHandler",[3412,23412,3447],{"class":3433},[3412,23414,13565],{"class":3429},[3412,23416,3454],{"class":3433},[3412,23418,20421],{"class":3429},[3412,23420,3454],{"class":3433},[3412,23422,20424],{"class":3429},[3412,23424,3454],{"class":3433},[3412,23426,20427],{"class":3429},[3412,23428,3454],{"class":3433},[3412,23430,20478],{"class":3429},[3412,23432,3893],{"class":3433},[3412,23434,23436],{"class":3414,"line":23435},198,[3412,23437,10012],{"class":3495},[3412,23439,23441],{"class":3414,"line":23440},199,[3412,23442,23443],{"class":3495},"    Адмін-ендпоінт: тільки для role='admin'.\n",[3412,23445,23447],{"class":3414,"line":23446},200,[3412,23448,10012],{"class":3495},[3412,23450,23452],{"class":3414,"line":23451},201,[3412,23453,3554],{"emptyLinePlaceholder":3553},[3412,23455,23457,23459,23461,23463,23465,23467,23469],{"class":3414,"line":23456},202,[3412,23458,3440],{"class":3425},[3412,23460,3865],{"class":3443},[3412,23462,3447],{"class":3433},[3412,23464,3451],{"class":3450},[3412,23466,7534],{"class":3433},[3412,23468,7537],{"class":3450},[3412,23470,3893],{"class":3433},[3412,23472,23474,23476,23478,23480],{"class":3414,"line":23473},203,[3412,23475,4236],{"class":3429},[3412,23477,4239],{"class":3433},[3412,23479,4242],{"class":3443},[3412,23481,6908],{"class":3433},[3412,23483,23485,23487,23489,23491],{"class":3414,"line":23484},204,[3412,23486,23253],{"class":3450},[3412,23488,23256],{"class":3433},[3412,23490,22125],{"class":3495},[3412,23492,6922],{"class":3433},[3412,23494,23496,23498,23500,23502],{"class":3414,"line":23495},205,[3412,23497,23274],{"class":3450},[3412,23499,4691],{"class":3433},[3412,23501,4576],{"class":3924},[3412,23503,7936],{"class":3433},[3412,23505,23507],{"class":3414,"line":23506},206,[3412,23508,23304],{"class":3433},[3412,23510,23512],{"class":3414,"line":23511},207,[3412,23513,6941],{"class":3433},[3412,23515,23517],{"class":3414,"line":23516},208,[3412,23518,3554],{"emptyLinePlaceholder":3553},[3412,23520,23522,23524,23526,23528,23530,23532,23534],{"class":3414,"line":23521},209,[3412,23523,3440],{"class":3425},[3412,23525,21623],{"class":3443},[3412,23527,3447],{"class":3433},[3412,23529,3451],{"class":3450},[3412,23531,3454],{"class":3433},[3412,23533,21595],{"class":3450},[3412,23535,21598],{"class":3433},[3412,23537,23539,23541],{"class":3414,"line":23538},210,[3412,23540,3955],{"class":3530},[3412,23542,22291],{"class":3433},[3412,23544,23546],{"class":3414,"line":23545},211,[3412,23547,23344],{"class":3433},[3412,23549,23551,23553,23555,23557,23559,23561,23564,23566,23568,23570,23573,23575,23577],{"class":3414,"line":23550},212,[3412,23552,23350],{"class":3450},[3412,23554,4691],{"class":3433},[3412,23556,3492],{"class":3425},[3412,23558,19611],{"class":3495},[3412,23560,23359],{"class":5318},[3412,23562,23563],{"class":3495},"\"admin\": true, \"user\": \"",[3412,23565,3499],{"class":3425},[3412,23567,23367],{"class":3433},[3412,23569,3504],{"class":3425},[3412,23571,23572],{"class":3495},"\", \"action\": \"permitted\"",[3412,23574,23383],{"class":5318},[3412,23576,19611],{"class":3495},[3412,23578,7936],{"class":3433},[3412,23580,23582],{"class":3414,"line":23581},213,[3412,23583,6941],{"class":3433},[3403,23585,23588],{"className":3405,"code":23586,"filename":23587,"language":3407,"meta":3408,"style":3408},"from models import Request, Response\nfrom handlers import BaseHandler\n\n\nclass Router:\n    \"\"\"\n    Маршрутизатор запитів.\n    Використовує __init_subclass__ для автоматичної реєстрації маршрутів.\n    \"\"\"\n\n    _routes: dict[str, type[BaseHandler]] = {}\n\n    def register(self, path: str, handler_cls: type[BaseHandler]) -> None:\n        \"\"\"Ручна реєстрація маршруту.\"\"\"\n        if not issubclass(handler_cls, BaseHandler):\n            raise TypeError(\n                f\"{handler_cls.__name__} має бути підкласом BaseHandler\"\n            )\n        self._routes[path] = handler_cls\n        print(f\"[Router] Зареєстровано: {path!r} → {handler_cls.__name__}\")\n\n    def dispatch(self, request: Request) -> Response:\n        \"\"\"Знаходить обробник і виконує запит.\"\"\"\n        handler_cls = self._routes.get(request.path)\n        if handler_cls is None:\n            return Response(\n                Response.NOT_FOUND,\n                body=f\"404 Not Found: {request.path!r}\",\n            )\n\n        # isinstance для перевірки типу — чи є це BaseHandler?\n        handler = handler_cls()\n        assert isinstance(handler, BaseHandler), \"Обробник має бути BaseHandler\"\n        return handler.handle(request)\n\n    def show_mro(self, handler_cls: type) -> None:\n        \"\"\"Виводить MRO класу — корисно для налагодження.\"\"\"\n        print(f\"\\nMRO {handler_cls.__name__}:\")\n        for i, cls in enumerate(handler_cls.__mro__):\n            prefix = \"  └─\" if i == len(handler_cls.__mro__) - 1 else \"  ├─\"\n            print(f\"{prefix} {cls.__name__}\")\n","router.py",[3390,23589,23590,23600,23612,23616,23620,23629,23633,23638,23643,23647,23651,23661,23665,23697,23702,23714,23722,23740,23744,23751,23782,23786,23803,23808,23818,23831,23837,23842,23863,23867,23871,23876,23881,23895,23902,23906,23931,23936,23964,23985,24014],{"__ignoreMap":3408},[3412,23591,23592,23594,23596,23598],{"class":3414,"line":3415},[3412,23593,4148],{"class":3530},[3412,23595,21482],{"class":3433},[3412,23597,4154],{"class":3530},[3412,23599,21487],{"class":3433},[3412,23601,23602,23604,23607,23609],{"class":3414,"line":3422},[3412,23603,4148],{"class":3530},[3412,23605,23606],{"class":3433}," handlers ",[3412,23608,4154],{"class":3530},[3412,23610,23611],{"class":3433}," BaseHandler\n",[3412,23613,23614],{"class":3414,"line":3437},[3412,23615,3554],{"emptyLinePlaceholder":3553},[3412,23617,23618],{"class":3414,"line":3468},[3412,23619,3554],{"emptyLinePlaceholder":3553},[3412,23621,23622,23624,23627],{"class":3414,"line":3516},[3412,23623,3426],{"class":3425},[3412,23625,23626],{"class":3429}," Router",[3412,23628,3434],{"class":3433},[3412,23630,23631],{"class":3414,"line":3550},[3412,23632,10012],{"class":3495},[3412,23634,23635],{"class":3414,"line":3557},[3412,23636,23637],{"class":3495},"    Маршрутизатор запитів.\n",[3412,23639,23640],{"class":3414,"line":3567},[3412,23641,23642],{"class":3495},"    Використовує __init_subclass__ для автоматичної реєстрації маршрутів.\n",[3412,23644,23645],{"class":3414,"line":3588},[3412,23646,10012],{"class":3495},[3412,23648,23649],{"class":3414,"line":3628},[3412,23650,3554],{"emptyLinePlaceholder":3553},[3412,23652,23653,23656,23658],{"class":3414,"line":3655},[3412,23654,23655],{"class":3433},"    _routes: dict[",[3412,23657,3880],{"class":3429},[3412,23659,23660],{"class":3433},", type[BaseHandler]] = {}\n",[3412,23662,23663],{"class":3414,"line":3660},[3412,23664,3554],{"emptyLinePlaceholder":3553},[3412,23666,23667,23669,23672,23674,23676,23678,23681,23683,23685,23687,23690,23693,23695],{"class":3414,"line":3670},[3412,23668,3440],{"class":3425},[3412,23670,23671],{"class":3443}," register",[3412,23673,3447],{"class":3433},[3412,23675,3451],{"class":3450},[3412,23677,3454],{"class":3433},[3412,23679,23680],{"class":3450},"path",[3412,23682,3877],{"class":3433},[3412,23684,3880],{"class":3429},[3412,23686,3454],{"class":3433},[3412,23688,23689],{"class":3450},"handler_cls",[3412,23691,23692],{"class":3433},": type[BaseHandler]) -> ",[3412,23694,4011],{"class":3425},[3412,23696,3434],{"class":3433},[3412,23698,23699],{"class":3414,"line":3691},[3412,23700,23701],{"class":3495},"        \"\"\"Ручна реєстрація маршруту.\"\"\"\n",[3412,23703,23704,23706,23708,23711],{"class":3414,"line":3731},[3412,23705,9686],{"class":3530},[3412,23707,10123],{"class":3425},[3412,23709,23710],{"class":3443}," issubclass",[3412,23712,23713],{"class":3433},"(handler_cls, BaseHandler):\n",[3412,23715,23716,23718,23720],{"class":3414,"line":4072},[3412,23717,10786],{"class":3530},[3412,23719,13482],{"class":3429},[3412,23721,6908],{"class":3433},[3412,23723,23724,23726,23728,23730,23733,23735,23737],{"class":3414,"line":4077},[3412,23725,14924],{"class":3425},[3412,23727,3507],{"class":3495},[3412,23729,3499],{"class":3425},[3412,23731,23732],{"class":3433},"handler_cls.",[3412,23734,4113],{"class":3450},[3412,23736,3504],{"class":3425},[3412,23738,23739],{"class":3495}," має бути підкласом BaseHandler\"\n",[3412,23741,23742],{"class":3414,"line":4095},[3412,23743,14955],{"class":3433},[3412,23745,23746,23748],{"class":3414,"line":5014},[3412,23747,3898],{"class":3425},[3412,23749,23750],{"class":3433},"._routes[path] = handler_cls\n",[3412,23752,23753,23755,23757,23759,23762,23764,23766,23768,23770,23772,23774,23776,23778,23780],{"class":3414,"line":5020},[3412,23754,4039],{"class":3443},[3412,23756,3447],{"class":3433},[3412,23758,3492],{"class":3425},[3412,23760,23761],{"class":3495},"\"[Router] Зареєстровано: ",[3412,23763,3499],{"class":3425},[3412,23765,23680],{"class":3433},[3412,23767,4125],{"class":3425},[3412,23769,9067],{"class":3495},[3412,23771,3499],{"class":3425},[3412,23773,23732],{"class":3433},[3412,23775,4113],{"class":3450},[3412,23777,3504],{"class":3425},[3412,23779,3507],{"class":3495},[3412,23781,4069],{"class":3433},[3412,23783,23784],{"class":3414,"line":5026},[3412,23785,3554],{"emptyLinePlaceholder":3553},[3412,23787,23788,23790,23793,23795,23797,23799,23801],{"class":3414,"line":5035},[3412,23789,3440],{"class":3425},[3412,23791,23792],{"class":3443}," dispatch",[3412,23794,3447],{"class":3433},[3412,23796,3451],{"class":3450},[3412,23798,3454],{"class":3433},[3412,23800,21595],{"class":3450},[3412,23802,21598],{"class":3433},[3412,23804,23805],{"class":3414,"line":5046},[3412,23806,23807],{"class":3495},"        \"\"\"Знаходить обробник і виконує запит.\"\"\"\n",[3412,23809,23810,23813,23815],{"class":3414,"line":5057},[3412,23811,23812],{"class":3433},"        handler_cls = ",[3412,23814,3451],{"class":3425},[3412,23816,23817],{"class":3433},"._routes.get(request.path)\n",[3412,23819,23820,23822,23825,23827,23829],{"class":3414,"line":5345},[3412,23821,9686],{"class":3530},[3412,23823,23824],{"class":3433}," handler_cls ",[3412,23826,10120],{"class":3425},[3412,23828,10126],{"class":3425},[3412,23830,3434],{"class":3433},[3412,23832,23833,23835],{"class":3414,"line":5364},[3412,23834,9707],{"class":3530},[3412,23836,22291],{"class":3433},[3412,23838,23839],{"class":3414,"line":5370},[3412,23840,23841],{"class":3433},"                Response.NOT_FOUND,\n",[3412,23843,23844,23846,23848,23850,23853,23855,23857,23859,23861],{"class":3414,"line":5376},[3412,23845,22301],{"class":3450},[3412,23847,4691],{"class":3433},[3412,23849,3492],{"class":3425},[3412,23851,23852],{"class":3495},"\"404 Not Found: ",[3412,23854,3499],{"class":3425},[3412,23856,21941],{"class":3433},[3412,23858,4125],{"class":3425},[3412,23860,3507],{"class":3495},[3412,23862,7936],{"class":3433},[3412,23864,23865],{"class":3414,"line":5382},[3412,23866,14955],{"class":3433},[3412,23868,23869],{"class":3414,"line":5388},[3412,23870,3554],{"emptyLinePlaceholder":3553},[3412,23872,23873],{"class":3414,"line":5393},[3412,23874,23875],{"class":3418},"        # isinstance для перевірки типу — чи є це BaseHandler?\n",[3412,23877,23878],{"class":3414,"line":5399},[3412,23879,23880],{"class":3433},"        handler = handler_cls()\n",[3412,23882,23883,23886,23889,23892],{"class":3414,"line":5422},[3412,23884,23885],{"class":3530},"        assert",[3412,23887,23888],{"class":3443}," isinstance",[3412,23890,23891],{"class":3433},"(handler, BaseHandler), ",[3412,23893,23894],{"class":3495},"\"Обробник має бути BaseHandler\"\n",[3412,23896,23897,23899],{"class":3414,"line":5428},[3412,23898,3955],{"class":3530},[3412,23900,23901],{"class":3433}," handler.handle(request)\n",[3412,23903,23904],{"class":3414,"line":5434},[3412,23905,3554],{"emptyLinePlaceholder":3553},[3412,23907,23908,23910,23913,23915,23917,23919,23921,23923,23925,23927,23929],{"class":3414,"line":5439},[3412,23909,3440],{"class":3425},[3412,23911,23912],{"class":3443}," show_mro",[3412,23914,3447],{"class":3433},[3412,23916,3451],{"class":3450},[3412,23918,3454],{"class":3433},[3412,23920,23689],{"class":3450},[3412,23922,3877],{"class":3433},[3412,23924,5110],{"class":3429},[3412,23926,3946],{"class":3433},[3412,23928,4011],{"class":3425},[3412,23930,3434],{"class":3433},[3412,23932,23933],{"class":3414,"line":5456},[3412,23934,23935],{"class":3495},"        \"\"\"Виводить MRO класу — корисно для налагодження.\"\"\"\n",[3412,23937,23938,23940,23942,23944,23946,23948,23951,23953,23955,23957,23959,23962],{"class":3414,"line":5462},[3412,23939,4039],{"class":3443},[3412,23941,3447],{"class":3433},[3412,23943,3492],{"class":3425},[3412,23945,3507],{"class":3495},[3412,23947,5319],{"class":5318},[3412,23949,23950],{"class":3495},"MRO ",[3412,23952,3499],{"class":3425},[3412,23954,23732],{"class":3433},[3412,23956,4113],{"class":3450},[3412,23958,3504],{"class":3425},[3412,23960,23961],{"class":3495},":\"",[3412,23963,4069],{"class":3433},[3412,23965,23966,23968,23971,23973,23975,23978,23981,23983],{"class":3414,"line":5467},[3412,23967,13258],{"class":3530},[3412,23969,23970],{"class":3433}," i, ",[3412,23972,6497],{"class":3425},[3412,23974,5334],{"class":3530},[3412,23976,23977],{"class":3443}," enumerate",[3412,23979,23980],{"class":3433},"(handler_cls.",[3412,23982,5340],{"class":3450},[3412,23984,3893],{"class":3433},[3412,23986,23987,23990,23993,23995,23998,24000,24002,24004,24007,24009,24011],{"class":3414,"line":5473},[3412,23988,23989],{"class":3433},"            prefix = ",[3412,23991,23992],{"class":3495},"\"  └─\"",[3412,23994,13606],{"class":3530},[3412,23996,23997],{"class":3433}," i == ",[3412,23999,19971],{"class":3443},[3412,24001,23980],{"class":3433},[3412,24003,5340],{"class":3450},[3412,24005,24006],{"class":3433},") - ",[3412,24008,7331],{"class":3924},[3412,24010,13616],{"class":3530},[3412,24012,24013],{"class":3495}," \"  ├─\"\n",[3412,24015,24016,24018,24020,24022,24024,24026,24029,24031,24034,24036,24038,24040,24042],{"class":3414,"line":5491},[3412,24017,18595],{"class":3443},[3412,24019,3447],{"class":3433},[3412,24021,3492],{"class":3425},[3412,24023,3507],{"class":3495},[3412,24025,3499],{"class":3425},[3412,24027,24028],{"class":3433},"prefix",[3412,24030,3504],{"class":3425},[3412,24032,24033],{"class":3425}," {cls",[3412,24035,3539],{"class":3433},[3412,24037,4113],{"class":3450},[3412,24039,3504],{"class":3425},[3412,24041,3507],{"class":3495},[3412,24043,4069],{"class":3433},[3403,24045,24047],{"className":3405,"code":24046,"filename":4612,"language":3407,"meta":3408,"style":3408},"from models import Request, Response\nfrom handlers import PublicHandler, ProtectedHandler, AdminHandler\nfrom router import Router\n\n# ── Налаштування роутера ──────────────────────────────────────────────────────\nrouter = Router()\nrouter.register(\"\u002Fapi\u002Fpublic\",    PublicHandler)\nrouter.register(\"\u002Fapi\u002Fdata\",      ProtectedHandler)\nrouter.register(\"\u002Fapi\u002Fadmin\",     AdminHandler)\n\n# ── MRO-аналіз ────────────────────────────────────────────────────────────────\nrouter.show_mro(ProtectedHandler)\n\nprint(\"\\n\" + \"=\" * 55)\n\n# ── Сценарій 1: Публічний запит ───────────────────────────────────────────────\nprint(\"\\n[1] Публічний ендпоінт:\")\nresp = router.dispatch(Request(\"GET\", \"\u002Fapi\u002Fpublic\"))\nprint(f\"    Відповідь: {resp}\")\n\n# ── Сценарій 2: Запит без токена ──────────────────────────────────────────────\nprint(\"\\n[2] Захищений ендпоінт без токена:\")\nresp = router.dispatch(Request(\"GET\", \"\u002Fapi\u002Fdata\"))\nprint(f\"    Відповідь: {resp}\")\n\n# ── Сценарій 3: Запит з валідним токеном ─────────────────────────────────────\nprint(\"\\n[3] Захищений ендпоінт з валідним токеном (user-token):\")\nresp = router.dispatch(Request(\n    \"GET\", \"\u002Fapi\u002Fdata\",\n    headers={\"Authorization\": \"Bearer user-token\", \"Origin\": \"https:\u002F\u002Fkostyl.dev\"},\n))\nprint(f\"    Відповідь: {resp}\")\nprint(f\"    CORS: {resp.headers.get('Access-Control-Allow-Origin')}\")\n\n# ── Сценарій 4: Недостатня роль ───────────────────────────────────────────────\nprint(\"\\n[4] Admin ендпоінт з роллю 'user':\")\nresp = router.dispatch(Request(\n    \"GET\", \"\u002Fapi\u002Fadmin\",\n    headers={\"Authorization\": \"Bearer user-token\"},\n))\nprint(f\"    Відповідь: {resp}\")\n\n# ── Сценарій 5: Адмін з правильною роллю ─────────────────────────────────────\nprint(\"\\n[5] Admin ендпоінт з роллю 'admin':\")\nresp = router.dispatch(Request(\n    \"POST\", \"\u002Fapi\u002Fadmin\",\n    headers={\"Authorization\": \"Bearer admin-token\"},\n    body='{\"action\": \"delete_user\", \"target\": \"spammer\"}',\n))\nprint(f\"    Відповідь: {resp}\")\n\n# ── Сценарій 6: Rate Limiting ─────────────────────────────────────────────────\nprint(\"\\n[6] Rate Limiting (6 запитів при ліміті 5 req\u002Fs):\")\nfor i in range(6):\n    r = router.dispatch(Request(\n        \"GET\", \"\u002Fapi\u002Fdata\",\n        headers={\"Authorization\": \"Bearer vip-token\"},\n    ))\n    status_icon = \"✅\" if r.ok else \"❌\"\n    print(f\"    Запит #{i+1}: {status_icon} {r.status}\")\n\n# ── Сценарій 7: Невідомий маршрут ────────────────────────────────────────────\nprint(\"\\n[7] Невідомий маршрут:\")\nresp = router.dispatch(Request(\"GET\", \"\u002Fapi\u002Fsecret\"))\nprint(f\"    Відповідь: {resp}\")\n\n# ── issubclass демонстрація ───────────────────────────────────────────────────\nprint(\"\\n[8] issubclass перевірки:\")\nfrom handlers import BaseHandler, LoggingMixin, AuthMixin\nfor cls in [PublicHandler, ProtectedHandler, AdminHandler]:\n    print(f\"    issubclass({cls.__name__}, BaseHandler):   \"\n          f\"{issubclass(cls, BaseHandler)}\")\n    print(f\"    issubclass({cls.__name__}, LoggingMixin):  \"\n          f\"{issubclass(cls, LoggingMixin)}\")\n    has_auth = issubclass(cls, AuthMixin)\n    print(f\"    issubclass({cls.__name__}, AuthMixin):     \"\n          f\"{has_auth} {'← захист!' if has_auth else ''}\")\n",[3390,24048,24049,24059,24070,24082,24086,24091,24096,24107,24117,24127,24131,24136,24141,24145,24170,24174,24179,24194,24209,24231,24235,24240,24255,24267,24287,24291,24296,24311,24316,24327,24351,24355,24375,24402,24406,24411,24426,24430,24440,24454,24458,24478,24482,24487,24502,24506,24517,24532,24544,24548,24568,24572,24577,24592,24610,24615,24626,24642,24647,24663,24703,24707,24712,24727,24740,24760,24764,24769,24784,24795,24806,24828,24852,24873,24896,24910,24931],{"__ignoreMap":3408},[3412,24050,24051,24053,24055,24057],{"class":3414,"line":3415},[3412,24052,4148],{"class":3530},[3412,24054,21482],{"class":3433},[3412,24056,4154],{"class":3530},[3412,24058,21487],{"class":3433},[3412,24060,24061,24063,24065,24067],{"class":3414,"line":3422},[3412,24062,4148],{"class":3530},[3412,24064,23606],{"class":3433},[3412,24066,4154],{"class":3530},[3412,24068,24069],{"class":3433}," PublicHandler, ProtectedHandler, AdminHandler\n",[3412,24071,24072,24074,24077,24079],{"class":3414,"line":3437},[3412,24073,4148],{"class":3530},[3412,24075,24076],{"class":3433}," router ",[3412,24078,4154],{"class":3530},[3412,24080,24081],{"class":3433}," Router\n",[3412,24083,24084],{"class":3414,"line":3468},[3412,24085,3554],{"emptyLinePlaceholder":3553},[3412,24087,24088],{"class":3414,"line":3516},[3412,24089,24090],{"class":3418},"# ── Налаштування роутера ──────────────────────────────────────────────────────\n",[3412,24092,24093],{"class":3414,"line":3550},[3412,24094,24095],{"class":3433},"router = Router()\n",[3412,24097,24098,24101,24104],{"class":3414,"line":3557},[3412,24099,24100],{"class":3433},"router.register(",[3412,24102,24103],{"class":3495},"\"\u002Fapi\u002Fpublic\"",[3412,24105,24106],{"class":3433},",    PublicHandler)\n",[3412,24108,24109,24111,24114],{"class":3414,"line":3567},[3412,24110,24100],{"class":3433},[3412,24112,24113],{"class":3495},"\"\u002Fapi\u002Fdata\"",[3412,24115,24116],{"class":3433},",      ProtectedHandler)\n",[3412,24118,24119,24121,24124],{"class":3414,"line":3588},[3412,24120,24100],{"class":3433},[3412,24122,24123],{"class":3495},"\"\u002Fapi\u002Fadmin\"",[3412,24125,24126],{"class":3433},",     AdminHandler)\n",[3412,24128,24129],{"class":3414,"line":3628},[3412,24130,3554],{"emptyLinePlaceholder":3553},[3412,24132,24133],{"class":3414,"line":3655},[3412,24134,24135],{"class":3418},"# ── MRO-аналіз ────────────────────────────────────────────────────────────────\n",[3412,24137,24138],{"class":3414,"line":3660},[3412,24139,24140],{"class":3433},"router.show_mro(ProtectedHandler)\n",[3412,24142,24143],{"class":3414,"line":3670},[3412,24144,3554],{"emptyLinePlaceholder":3553},[3412,24146,24147,24149,24151,24153,24155,24157,24160,24163,24165,24168],{"class":3414,"line":3691},[3412,24148,3487],{"class":3443},[3412,24150,3447],{"class":3433},[3412,24152,3507],{"class":3495},[3412,24154,5319],{"class":5318},[3412,24156,3507],{"class":3495},[3412,24158,24159],{"class":3433}," + ",[3412,24161,24162],{"class":3495},"\"=\"",[3412,24164,11612],{"class":3433},[3412,24166,24167],{"class":3924},"55",[3412,24169,4069],{"class":3433},[3412,24171,24172],{"class":3414,"line":3731},[3412,24173,3554],{"emptyLinePlaceholder":3553},[3412,24175,24176],{"class":3414,"line":4072},[3412,24177,24178],{"class":3418},"# ── Сценарій 1: Публічний запит ───────────────────────────────────────────────\n",[3412,24180,24181,24183,24185,24187,24189,24192],{"class":3414,"line":4077},[3412,24182,3487],{"class":3443},[3412,24184,3447],{"class":3433},[3412,24186,3507],{"class":3495},[3412,24188,5319],{"class":5318},[3412,24190,24191],{"class":3495},"[1] Публічний ендпоінт:\"",[3412,24193,4069],{"class":3433},[3412,24195,24196,24199,24202,24204,24206],{"class":3414,"line":4095},[3412,24197,24198],{"class":3433},"resp = router.dispatch(Request(",[3412,24200,24201],{"class":3495},"\"GET\"",[3412,24203,3454],{"class":3433},[3412,24205,24103],{"class":3495},[3412,24207,24208],{"class":3433},"))\n",[3412,24210,24211,24213,24215,24217,24220,24222,24225,24227,24229],{"class":3414,"line":5014},[3412,24212,3487],{"class":3443},[3412,24214,3447],{"class":3433},[3412,24216,3492],{"class":3425},[3412,24218,24219],{"class":3495},"\"    Відповідь: ",[3412,24221,3499],{"class":3425},[3412,24223,24224],{"class":3433},"resp",[3412,24226,3504],{"class":3425},[3412,24228,3507],{"class":3495},[3412,24230,4069],{"class":3433},[3412,24232,24233],{"class":3414,"line":5020},[3412,24234,3554],{"emptyLinePlaceholder":3553},[3412,24236,24237],{"class":3414,"line":5026},[3412,24238,24239],{"class":3418},"# ── Сценарій 2: Запит без токена ──────────────────────────────────────────────\n",[3412,24241,24242,24244,24246,24248,24250,24253],{"class":3414,"line":5035},[3412,24243,3487],{"class":3443},[3412,24245,3447],{"class":3433},[3412,24247,3507],{"class":3495},[3412,24249,5319],{"class":5318},[3412,24251,24252],{"class":3495},"[2] Захищений ендпоінт без токена:\"",[3412,24254,4069],{"class":3433},[3412,24256,24257,24259,24261,24263,24265],{"class":3414,"line":5046},[3412,24258,24198],{"class":3433},[3412,24260,24201],{"class":3495},[3412,24262,3454],{"class":3433},[3412,24264,24113],{"class":3495},[3412,24266,24208],{"class":3433},[3412,24268,24269,24271,24273,24275,24277,24279,24281,24283,24285],{"class":3414,"line":5057},[3412,24270,3487],{"class":3443},[3412,24272,3447],{"class":3433},[3412,24274,3492],{"class":3425},[3412,24276,24219],{"class":3495},[3412,24278,3499],{"class":3425},[3412,24280,24224],{"class":3433},[3412,24282,3504],{"class":3425},[3412,24284,3507],{"class":3495},[3412,24286,4069],{"class":3433},[3412,24288,24289],{"class":3414,"line":5345},[3412,24290,3554],{"emptyLinePlaceholder":3553},[3412,24292,24293],{"class":3414,"line":5364},[3412,24294,24295],{"class":3418},"# ── Сценарій 3: Запит з валідним токеном ─────────────────────────────────────\n",[3412,24297,24298,24300,24302,24304,24306,24309],{"class":3414,"line":5370},[3412,24299,3487],{"class":3443},[3412,24301,3447],{"class":3433},[3412,24303,3507],{"class":3495},[3412,24305,5319],{"class":5318},[3412,24307,24308],{"class":3495},"[3] Захищений ендпоінт з валідним токеном (user-token):\"",[3412,24310,4069],{"class":3433},[3412,24312,24313],{"class":3414,"line":5376},[3412,24314,24315],{"class":3433},"resp = router.dispatch(Request(\n",[3412,24317,24318,24321,24323,24325],{"class":3414,"line":5382},[3412,24319,24320],{"class":3495},"    \"GET\"",[3412,24322,3454],{"class":3433},[3412,24324,24113],{"class":3495},[3412,24326,7936],{"class":3433},[3412,24328,24329,24332,24334,24336,24338,24341,24343,24345,24347,24349],{"class":3414,"line":5388},[3412,24330,24331],{"class":3450},"    headers",[3412,24333,22755],{"class":3433},[3412,24335,22260],{"class":3495},[3412,24337,3877],{"class":3433},[3412,24339,24340],{"class":3495},"\"Bearer user-token\"",[3412,24342,3454],{"class":3433},[3412,24344,22924],{"class":3495},[3412,24346,3877],{"class":3433},[3412,24348,23291],{"class":3495},[3412,24350,22766],{"class":3433},[3412,24352,24353],{"class":3414,"line":5393},[3412,24354,24208],{"class":3433},[3412,24356,24357,24359,24361,24363,24365,24367,24369,24371,24373],{"class":3414,"line":5399},[3412,24358,3487],{"class":3443},[3412,24360,3447],{"class":3433},[3412,24362,3492],{"class":3425},[3412,24364,24219],{"class":3495},[3412,24366,3499],{"class":3425},[3412,24368,24224],{"class":3433},[3412,24370,3504],{"class":3425},[3412,24372,3507],{"class":3495},[3412,24374,4069],{"class":3433},[3412,24376,24377,24379,24381,24383,24386,24388,24391,24394,24396,24398,24400],{"class":3414,"line":5422},[3412,24378,3487],{"class":3443},[3412,24380,3447],{"class":3433},[3412,24382,3492],{"class":3425},[3412,24384,24385],{"class":3495},"\"    CORS: ",[3412,24387,3499],{"class":3425},[3412,24389,24390],{"class":3433},"resp.headers.get(",[3412,24392,24393],{"class":3495},"'Access-Control-Allow-Origin'",[3412,24395,6370],{"class":3433},[3412,24397,3504],{"class":3425},[3412,24399,3507],{"class":3495},[3412,24401,4069],{"class":3433},[3412,24403,24404],{"class":3414,"line":5428},[3412,24405,3554],{"emptyLinePlaceholder":3553},[3412,24407,24408],{"class":3414,"line":5434},[3412,24409,24410],{"class":3418},"# ── Сценарій 4: Недостатня роль ───────────────────────────────────────────────\n",[3412,24412,24413,24415,24417,24419,24421,24424],{"class":3414,"line":5439},[3412,24414,3487],{"class":3443},[3412,24416,3447],{"class":3433},[3412,24418,3507],{"class":3495},[3412,24420,5319],{"class":5318},[3412,24422,24423],{"class":3495},"[4] Admin ендпоінт з роллю 'user':\"",[3412,24425,4069],{"class":3433},[3412,24427,24428],{"class":3414,"line":5456},[3412,24429,24315],{"class":3433},[3412,24431,24432,24434,24436,24438],{"class":3414,"line":5462},[3412,24433,24320],{"class":3495},[3412,24435,3454],{"class":3433},[3412,24437,24123],{"class":3495},[3412,24439,7936],{"class":3433},[3412,24441,24442,24444,24446,24448,24450,24452],{"class":3414,"line":5467},[3412,24443,24331],{"class":3450},[3412,24445,22755],{"class":3433},[3412,24447,22260],{"class":3495},[3412,24449,3877],{"class":3433},[3412,24451,24340],{"class":3495},[3412,24453,22766],{"class":3433},[3412,24455,24456],{"class":3414,"line":5473},[3412,24457,24208],{"class":3433},[3412,24459,24460,24462,24464,24466,24468,24470,24472,24474,24476],{"class":3414,"line":5491},[3412,24461,3487],{"class":3443},[3412,24463,3447],{"class":3433},[3412,24465,3492],{"class":3425},[3412,24467,24219],{"class":3495},[3412,24469,3499],{"class":3425},[3412,24471,24224],{"class":3433},[3412,24473,3504],{"class":3425},[3412,24475,3507],{"class":3495},[3412,24477,4069],{"class":3433},[3412,24479,24480],{"class":3414,"line":5496},[3412,24481,3554],{"emptyLinePlaceholder":3553},[3412,24483,24484],{"class":3414,"line":5502},[3412,24485,24486],{"class":3418},"# ── Сценарій 5: Адмін з правильною роллю ─────────────────────────────────────\n",[3412,24488,24489,24491,24493,24495,24497,24500],{"class":3414,"line":5518},[3412,24490,3487],{"class":3443},[3412,24492,3447],{"class":3433},[3412,24494,3507],{"class":3495},[3412,24496,5319],{"class":5318},[3412,24498,24499],{"class":3495},"[5] Admin ендпоінт з роллю 'admin':\"",[3412,24501,4069],{"class":3433},[3412,24503,24504],{"class":3414,"line":5533},[3412,24505,24315],{"class":3433},[3412,24507,24508,24511,24513,24515],{"class":3414,"line":5548},[3412,24509,24510],{"class":3495},"    \"POST\"",[3412,24512,3454],{"class":3433},[3412,24514,24123],{"class":3495},[3412,24516,7936],{"class":3433},[3412,24518,24519,24521,24523,24525,24527,24530],{"class":3414,"line":5568},[3412,24520,24331],{"class":3450},[3412,24522,22755],{"class":3433},[3412,24524,22260],{"class":3495},[3412,24526,3877],{"class":3433},[3412,24528,24529],{"class":3495},"\"Bearer admin-token\"",[3412,24531,22766],{"class":3433},[3412,24533,24534,24537,24539,24542],{"class":3414,"line":5573},[3412,24535,24536],{"class":3450},"    body",[3412,24538,4691],{"class":3433},[3412,24540,24541],{"class":3495},"'{\"action\": \"delete_user\", \"target\": \"spammer\"}'",[3412,24543,7936],{"class":3433},[3412,24545,24546],{"class":3414,"line":5579},[3412,24547,24208],{"class":3433},[3412,24549,24550,24552,24554,24556,24558,24560,24562,24564,24566],{"class":3414,"line":5595},[3412,24551,3487],{"class":3443},[3412,24553,3447],{"class":3433},[3412,24555,3492],{"class":3425},[3412,24557,24219],{"class":3495},[3412,24559,3499],{"class":3425},[3412,24561,24224],{"class":3433},[3412,24563,3504],{"class":3425},[3412,24565,3507],{"class":3495},[3412,24567,4069],{"class":3433},[3412,24569,24570],{"class":3414,"line":5609},[3412,24571,3554],{"emptyLinePlaceholder":3553},[3412,24573,24574],{"class":3414,"line":7232},[3412,24575,24576],{"class":3418},"# ── Сценарій 6: Rate Limiting ─────────────────────────────────────────────────\n",[3412,24578,24579,24581,24583,24585,24587,24590],{"class":3414,"line":7261},[3412,24580,3487],{"class":3443},[3412,24582,3447],{"class":3433},[3412,24584,3507],{"class":3495},[3412,24586,5319],{"class":5318},[3412,24588,24589],{"class":3495},"[6] Rate Limiting (6 запитів при ліміті 5 req\u002Fs):\"",[3412,24591,4069],{"class":3433},[3412,24593,24594,24596,24599,24601,24604,24606,24608],{"class":3414,"line":7266},[3412,24595,4738],{"class":3530},[3412,24597,24598],{"class":3433}," i ",[3412,24600,4744],{"class":3530},[3412,24602,24603],{"class":3443}," range",[3412,24605,3447],{"class":3433},[3412,24607,12239],{"class":3924},[3412,24609,3893],{"class":3433},[3412,24611,24612],{"class":3414,"line":7271},[3412,24613,24614],{"class":3433},"    r = router.dispatch(Request(\n",[3412,24616,24617,24620,24622,24624],{"class":3414,"line":7277},[3412,24618,24619],{"class":3495},"        \"GET\"",[3412,24621,3454],{"class":3433},[3412,24623,24113],{"class":3495},[3412,24625,7936],{"class":3433},[3412,24627,24628,24631,24633,24635,24637,24640],{"class":3414,"line":7310},[3412,24629,24630],{"class":3450},"        headers",[3412,24632,22755],{"class":3433},[3412,24634,22260],{"class":3495},[3412,24636,3877],{"class":3433},[3412,24638,24639],{"class":3495},"\"Bearer vip-token\"",[3412,24641,22766],{"class":3433},[3412,24643,24644],{"class":3414,"line":7315},[3412,24645,24646],{"class":3433},"    ))\n",[3412,24648,24649,24652,24654,24656,24659,24661],{"class":3414,"line":7321},[3412,24650,24651],{"class":3433},"    status_icon = ",[3412,24653,21398],{"class":3495},[3412,24655,13606],{"class":3530},[3412,24657,24658],{"class":3433}," r.ok ",[3412,24660,11044],{"class":3530},[3412,24662,21410],{"class":3495},[3412,24664,24665,24667,24669,24671,24674,24676,24679,24681,24683,24685,24687,24690,24692,24694,24697,24699,24701],{"class":3414,"line":7346},[3412,24666,4752],{"class":3443},[3412,24668,3447],{"class":3433},[3412,24670,3492],{"class":3425},[3412,24672,24673],{"class":3495},"\"    Запит #",[3412,24675,3499],{"class":3425},[3412,24677,24678],{"class":3433},"i+",[3412,24680,7331],{"class":3924},[3412,24682,3504],{"class":3425},[3412,24684,3877],{"class":3495},[3412,24686,3499],{"class":3425},[3412,24688,24689],{"class":3433},"status_icon",[3412,24691,3504],{"class":3425},[3412,24693,21363],{"class":3425},[3412,24695,24696],{"class":3433},"r.status",[3412,24698,3504],{"class":3425},[3412,24700,3507],{"class":3495},[3412,24702,4069],{"class":3433},[3412,24704,24705],{"class":3414,"line":7361},[3412,24706,3554],{"emptyLinePlaceholder":3553},[3412,24708,24709],{"class":3414,"line":7366},[3412,24710,24711],{"class":3418},"# ── Сценарій 7: Невідомий маршрут ────────────────────────────────────────────\n",[3412,24713,24714,24716,24718,24720,24722,24725],{"class":3414,"line":7372},[3412,24715,3487],{"class":3443},[3412,24717,3447],{"class":3433},[3412,24719,3507],{"class":3495},[3412,24721,5319],{"class":5318},[3412,24723,24724],{"class":3495},"[7] Невідомий маршрут:\"",[3412,24726,4069],{"class":3433},[3412,24728,24729,24731,24733,24735,24738],{"class":3414,"line":7378},[3412,24730,24198],{"class":3433},[3412,24732,24201],{"class":3495},[3412,24734,3454],{"class":3433},[3412,24736,24737],{"class":3495},"\"\u002Fapi\u002Fsecret\"",[3412,24739,24208],{"class":3433},[3412,24741,24742,24744,24746,24748,24750,24752,24754,24756,24758],{"class":3414,"line":7393},[3412,24743,3487],{"class":3443},[3412,24745,3447],{"class":3433},[3412,24747,3492],{"class":3425},[3412,24749,24219],{"class":3495},[3412,24751,3499],{"class":3425},[3412,24753,24224],{"class":3433},[3412,24755,3504],{"class":3425},[3412,24757,3507],{"class":3495},[3412,24759,4069],{"class":3433},[3412,24761,24762],{"class":3414,"line":13280},[3412,24763,3554],{"emptyLinePlaceholder":3553},[3412,24765,24766],{"class":3414,"line":13288},[3412,24767,24768],{"class":3418},"# ── issubclass демонстрація ───────────────────────────────────────────────────\n",[3412,24770,24771,24773,24775,24777,24779,24782],{"class":3414,"line":13294},[3412,24772,3487],{"class":3443},[3412,24774,3447],{"class":3433},[3412,24776,3507],{"class":3495},[3412,24778,5319],{"class":5318},[3412,24780,24781],{"class":3495},"[8] issubclass перевірки:\"",[3412,24783,4069],{"class":3433},[3412,24785,24786,24788,24790,24792],{"class":3414,"line":13302},[3412,24787,4148],{"class":3530},[3412,24789,23606],{"class":3433},[3412,24791,4154],{"class":3530},[3412,24793,24794],{"class":3433}," BaseHandler, LoggingMixin, AuthMixin\n",[3412,24796,24797,24799,24801,24803],{"class":3414,"line":13307},[3412,24798,4738],{"class":3530},[3412,24800,5331],{"class":3425},[3412,24802,5334],{"class":3530},[3412,24804,24805],{"class":3433}," [PublicHandler, ProtectedHandler, AdminHandler]:\n",[3412,24807,24808,24810,24812,24814,24817,24819,24821,24823,24825],{"class":3414,"line":13325},[3412,24809,4752],{"class":3443},[3412,24811,3447],{"class":3433},[3412,24813,3492],{"class":3425},[3412,24815,24816],{"class":3495},"\"    issubclass(",[3412,24818,15071],{"class":3425},[3412,24820,3539],{"class":3433},[3412,24822,4113],{"class":3450},[3412,24824,3504],{"class":3425},[3412,24826,24827],{"class":3495},", BaseHandler):   \"\n",[3412,24829,24830,24833,24835,24837,24839,24841,24843,24846,24848,24850],{"class":3414,"line":13331},[3412,24831,24832],{"class":3425},"          f",[3412,24834,3507],{"class":3495},[3412,24836,3499],{"class":3425},[3412,24838,5586],{"class":3443},[3412,24840,3447],{"class":3433},[3412,24842,6497],{"class":3425},[3412,24844,24845],{"class":3433},", BaseHandler)",[3412,24847,3504],{"class":3425},[3412,24849,3507],{"class":3495},[3412,24851,4069],{"class":3433},[3412,24853,24854,24856,24858,24860,24862,24864,24866,24868,24870],{"class":3414,"line":13342},[3412,24855,4752],{"class":3443},[3412,24857,3447],{"class":3433},[3412,24859,3492],{"class":3425},[3412,24861,24816],{"class":3495},[3412,24863,15071],{"class":3425},[3412,24865,3539],{"class":3433},[3412,24867,4113],{"class":3450},[3412,24869,3504],{"class":3425},[3412,24871,24872],{"class":3495},", LoggingMixin):  \"\n",[3412,24874,24875,24877,24879,24881,24883,24885,24887,24890,24892,24894],{"class":3414,"line":13350},[3412,24876,24832],{"class":3425},[3412,24878,3507],{"class":3495},[3412,24880,3499],{"class":3425},[3412,24882,5586],{"class":3443},[3412,24884,3447],{"class":3433},[3412,24886,6497],{"class":3425},[3412,24888,24889],{"class":3433},", LoggingMixin)",[3412,24891,3504],{"class":3425},[3412,24893,3507],{"class":3495},[3412,24895,4069],{"class":3433},[3412,24897,24898,24901,24903,24905,24907],{"class":3414,"line":13378},[3412,24899,24900],{"class":3433},"    has_auth = ",[3412,24902,5586],{"class":3443},[3412,24904,3447],{"class":3433},[3412,24906,6497],{"class":3425},[3412,24908,24909],{"class":3433},", AuthMixin)\n",[3412,24911,24912,24914,24916,24918,24920,24922,24924,24926,24928],{"class":3414,"line":13383},[3412,24913,4752],{"class":3443},[3412,24915,3447],{"class":3433},[3412,24917,3492],{"class":3425},[3412,24919,24816],{"class":3495},[3412,24921,15071],{"class":3425},[3412,24923,3539],{"class":3433},[3412,24925,4113],{"class":3450},[3412,24927,3504],{"class":3425},[3412,24929,24930],{"class":3495},", AuthMixin):     \"\n",[3412,24932,24933,24935,24937,24939,24942,24944,24946,24949,24951,24954,24956,24959,24961,24963],{"class":3414,"line":13388},[3412,24934,24832],{"class":3425},[3412,24936,3507],{"class":3495},[3412,24938,3499],{"class":3425},[3412,24940,24941],{"class":3433},"has_auth",[3412,24943,3504],{"class":3425},[3412,24945,21363],{"class":3425},[3412,24947,24948],{"class":3495},"'← захист!'",[3412,24950,13606],{"class":3530},[3412,24952,24953],{"class":3433}," has_auth ",[3412,24955,11044],{"class":3530},[3412,24957,24958],{"class":3495}," ''",[3412,24960,3504],{"class":3425},[3412,24962,3507],{"class":3495},[3412,24964,4069],{"class":3433},[4757,24966,24967,24975,24984,24992,25000,25003,25007,25015,25021,25027,25033,25039,25045,25052,25055,25059,25063,25067,25075,25078,25082,25086,25090,25097,25100,25104,25107,25111,25118,25126,25129,25133,25137,25141,25148,25151,25155,25159,25163,25170,25173,25177,25185,25192,25199,25206,25213,25225,25228,25232,25239,25242,25246,25253,25260,25267,25275],{"title":4759},[4761,24968,24970,4769,24973],{"className":24969},[3414],[3412,24971,4768],{"className":24972},[4767],[3764,24974,4759],{},[4761,24976,24978,24979,24983],{"className":24977},[3414],"[Router] Зареєстровано: ",[3412,24980,24982],{"className":24981},[4792],"'\u002Fapi\u002Fpublic'"," → PublicHandler",[4761,24985,24978,24987,24991],{"className":24986},[3414],[3412,24988,24990],{"className":24989},[4792],"'\u002Fapi\u002Fdata'","   → ProtectedHandler",[4761,24993,24978,24995,24999],{"className":24994},[3414],[3412,24996,24998],{"className":24997},[4792],"'\u002Fapi\u002Fadmin'","  → AdminHandler",[4761,25001],{"className":25002},[3414],[4761,25004,25006],{"className":25005},[3414],"MRO ProtectedHandler:",[4761,25008,25010,25011],{"className":25009},[3414],"  ├─ ",[3412,25012,25014],{"className":25013},[4800],"ProtectedHandler",[4761,25016,25010,25018],{"className":25017},[3414],[3412,25019,13565],{"className":25020},[4779],[4761,25022,25010,25024],{"className":25023},[3414],[3412,25025,20421],{"className":25026},[4779],[4761,25028,25010,25030],{"className":25029},[3414],[3412,25031,20424],{"className":25032},[4779],[4761,25034,25010,25036],{"className":25035},[3414],[3412,25037,20427],{"className":25038},[4779],[4761,25040,25010,25042],{"className":25041},[3414],[3412,25043,20478],{"className":25044},[4792],[4761,25046,25048,25049],{"className":25047},[3414],"  └─ ",[3412,25050,3815],{"className":25051},[5673],[4761,25053],{"className":25054},[3414],[4761,25056,25058],{"className":25057},[3414],"[1] Публічний ендпоінт:",[4761,25060,25062],{"className":25061},[3414],"[14:55:01] 📋 [INFO] [PublicHandler] → GET \u002Fapi\u002Fpublic",[4761,25064,25066],{"className":25065},[3414],"[14:55:01] 📋 [INFO] [PublicHandler] ← ✅ 200 | GET \u002Fapi\u002Fpublic",[4761,25068,25070,25071],{"className":25069},[3414],"    Відповідь: ",[3412,25072,25074],{"className":25073},[4800],"✅ Response(200: '{\"status\": \"public\"...')",[4761,25076],{"className":25077},[3414],[4761,25079,25081],{"className":25080},[3414],"[2] Захищений ендпоінт без токена:",[4761,25083,25085],{"className":25084},[3414],"[14:55:01] 📋 [INFO] [ProtectedHandler] → GET \u002Fapi\u002Fdata",[4761,25087,25089],{"className":25088},[3414],"[14:55:01] ⚠️ [WARN] [ProtectedHandler] ← ❌ 401 | GET \u002Fapi\u002Fdata",[4761,25091,25070,25093],{"className":25092},[3414],[3412,25094,25096],{"className":25095},[5732],"❌ Response(401: '401 Unauthorized: відсутній токен')",[4761,25098],{"className":25099},[3414],[4761,25101,25103],{"className":25102},[3414],"[3] Захищений ендпоінт з валідним токеном:",[4761,25105,25085],{"className":25106},[3414],[4761,25108,25110],{"className":25109},[3414],"[14:55:01] 📋 [INFO] [ProtectedHandler] ← ✅ 200 | GET \u002Fapi\u002Fdata",[4761,25112,25070,25114],{"className":25113},[3414],[3412,25115,25117],{"className":25116},[4800],"✅ Response(200: '{\"status\": \"ok\", \"user\": \"alice\"...')",[4761,25119,25121,25122],{"className":25120},[3414],"    CORS: ",[3412,25123,25125],{"className":25124},[4792],"https:\u002F\u002Fkostyl.dev",[4761,25127],{"className":25128},[3414],[4761,25130,25132],{"className":25131},[3414],"[4] Admin ендпоінт з роллю 'user':",[4761,25134,25136],{"className":25135},[3414],"[14:55:01] 📋 [INFO] [AdminHandler] → GET \u002Fapi\u002Fadmin",[4761,25138,25140],{"className":25139},[3414],"[14:55:01] ⚠️ [WARN] [AdminHandler] ← ❌ 403 | GET \u002Fapi\u002Fadmin",[4761,25142,25070,25144],{"className":25143},[3414],[3412,25145,25147],{"className":25146},[5732],"❌ Response(403: \"403 Forbidden: потрібна роль ['admin']...\")",[4761,25149],{"className":25150},[3414],[4761,25152,25154],{"className":25153},[3414],"[5] Admin ендпоінт з роллю 'admin':",[4761,25156,25158],{"className":25157},[3414],"[14:55:01] 📋 [INFO] [AdminHandler] → POST \u002Fapi\u002Fadmin",[4761,25160,25162],{"className":25161},[3414],"[14:55:01] 📋 [INFO] [AdminHandler] ← ✅ 200 | POST \u002Fapi\u002Fadmin",[4761,25164,25070,25166],{"className":25165},[3414],[3412,25167,25169],{"className":25168},[4800],"✅ Response(200: '{\"admin\": true, \"user\": \"admin\"...')",[4761,25171],{"className":25172},[3414],[4761,25174,25176],{"className":25175},[3414],"[6] Rate Limiting (6 запитів при ліміті 5 req\u002Fs):",[4761,25178,25180,25181],{"className":25179},[3414],"    Запит #1: ",[3412,25182,25184],{"className":25183},[4800],"✅ 200",[4761,25186,25188,25189],{"className":25187},[3414],"    Запит #2: ",[3412,25190,25184],{"className":25191},[4800],[4761,25193,25195,25196],{"className":25194},[3414],"    Запит #3: ",[3412,25197,25184],{"className":25198},[4800],[4761,25200,25202,25203],{"className":25201},[3414],"    Запит #4: ",[3412,25204,25184],{"className":25205},[4800],[4761,25207,25209,25210],{"className":25208},[3414],"    Запит #5: ",[3412,25211,25184],{"className":25212},[4800],[4761,25214,25216,25217,5684,25221],{"className":25215},[3414],"    Запит #6: ",[3412,25218,25220],{"className":25219},[5732],"❌ 429",[3412,25222,25224],{"className":25223},[5673],"# Rate limit!",[4761,25226],{"className":25227},[3414],[4761,25229,25231],{"className":25230},[3414],"[7] Невідомий маршрут:",[4761,25233,25070,25235],{"className":25234},[3414],[3412,25236,25238],{"className":25237},[5732],"❌ Response(404: '404 Not Found: ...')",[4761,25240],{"className":25241},[3414],[4761,25243,25245],{"className":25244},[3414],"[8] issubclass перевірки:",[4761,25247,25249,25250],{"className":25248},[3414],"    issubclass(PublicHandler, BaseHandler):    ",[3412,25251,5719],{"className":25252},[4800],[4761,25254,25256,25257],{"className":25255},[3414],"    issubclass(PublicHandler, LoggingMixin):   ",[3412,25258,5719],{"className":25259},[4800],[4761,25261,25263,25264],{"className":25262},[3414],"    issubclass(PublicHandler, AuthMixin):      ",[3412,25265,5733],{"className":25266},[5732],[4761,25268,25270,25271,25274],{"className":25269},[3414],"    issubclass(ProtectedHandler, AuthMixin):   ",[3412,25272,5719],{"className":25273},[4800],"  ← захист!",[4761,25276,25278,25279,25274],{"className":25277},[3414],"    issubclass(AdminHandler, AuthMixin):       ",[3412,25280,5719],{"className":25281},[4800],[3796,25283],{},[3394,25285,25287],{"id":25286},"підсумки-та-найкращі-практики","Підсумки та найкращі практики",[3769,25289,25290,25298,25318,25333,25346,25361],{},[3772,25291,25294,25295,3539],{"icon":25292,"title":25293},"i-heroicons-squares-plus","IS-A vs HAS-A","Наслідуйте тільки при чіткому IS-A відношенні. Перевіряйте через LSP: чи можна замінити батька підкласом без зміни поведінки програми? Якщо ні — використовуйте ",[3764,25296,25297],{},"композицію",[3772,25299,25302,25303,25305,25306,25308,25309,25312,25313,25315,25316,3539],{"icon":25300,"title":25301},"i-heroicons-arrow-path-solid","Кооперативний super()","При множинному або кооперативному наслідуванні ",[3764,25304,8145],{}," передавайте ",[3390,25307,7484],{}," через ",[3390,25310,25311],{},"super().__init__(**kwargs)",". Якщо хоч один клас у ланцюжку пропустить це — ",[3390,25314,8137],{}," отримає невідомі аргументи і впаде з ",[3390,25317,8141],{},[3772,25319,25322,25323,25325,25326,25329,25330,3539],{"icon":25320,"title":25321},"i-heroicons-shield-check","Гігієна Mixins","Mixins — без стану, з суфіксом ",[3390,25324,12619],{},", ліворуч від основного класу. Одна домішка — одна відповідальність. Замість глибокої ієрархії ",[3390,25327,25328],{},"Base → Loggable → Serializable → Product"," — плоска ",[3390,25331,25332],{},"Product(LoggingMixin, JSONSerializableMixin)",[3772,25334,25337,25338,25341,25342,25345],{"icon":25335,"title":25336},"i-heroicons-magnifying-glass","Перевірка MRO","При будь-якій неочікуваній поведінці — негайно перевіряйте ",[3390,25339,25340],{},"ClassName.mro()",". Використовуйте ",[3390,25343,25344],{},"find_method_owner(cls, 'method_name')"," щоб точно знайти, де визначено конкретний метод.",[3772,25347,25350,25351,25353,25354,25357,25358,3539],{"icon":25348,"title":25349},"i-heroicons-scale","LSP як критерій","Перед кожним ",[3390,25352,12275],{}," запитуйте: чи виконує підклас ",[3764,25355,25356],{},"всі"," передумови та постумови батьківського класу? Класичний приклад порушення — ",[3390,25359,25360],{},"Square(Rectangle)",[3772,25362,25365,25366,25368],{"icon":25363,"title":25364},"i-heroicons-wrench-screwdriver","**init_subclass** замість метакласів","Для автоматичної реєстрації підкласів у 90% випадків достатньо ",[3390,25367,14977],{}," (PEP 487, Python 3.6+). Метакласи потрібні лише для найбільш складних сценаріїв модифікації самого процесу створення класу.",[25370,25371,25372],"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 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 .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 .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":3408,"searchDepth":3422,"depth":3422,"links":25374},[25375,25376,25383,25396,25400,25415,25423,25426,25433,25439,25443],{"id":3396,"depth":3422,"text":3397},{"id":3800,"depth":3422,"text":3801,"children":25377},[25378,25379,25381],{"id":3805,"depth":3437,"text":3806},{"id":4825,"depth":3437,"text":25380},"__dict__ екземпляра проти __dict__ класу",{"id":5090,"depth":3437,"text":25382},"Дослідження ієрархії: __base__, __bases__ та __subclasses__()",{"id":5760,"depth":3422,"text":25384,"children":25385},"Частина II: super() — проксі-об'єкт, а не батьківський клас",[25386,25388,25390,25392,25394],{"id":5767,"depth":3437,"text":25387},"Чому super() — це не просто «виклик батька»",{"id":6194,"depth":3437,"text":25389},"__class__ як cell variable: чому super() без аргументів не є магією",{"id":6454,"depth":3437,"text":25391},"Повна сигнатура super()",{"id":6718,"depth":3437,"text":25393},"super() у @classmethod: фабричний патерн",{"id":7468,"depth":3437,"text":25395},"Кооперативне наслідування та *args, **kwargs",{"id":8154,"depth":3422,"text":8155,"children":25397},[25398,25399],{"id":8158,"depth":3437,"text":8159},{"id":8579,"depth":3437,"text":8580},{"id":8776,"depth":3422,"text":8777,"children":25401},[25402,25403,25404,25406,25408,25410,25412,25413],{"id":8780,"depth":3437,"text":8781},{"id":8910,"depth":3437,"text":8911},{"id":9052,"depth":3437,"text":25405},"Крок 1: Беремо голову першого списку — B",{"id":9117,"depth":3437,"text":25407},"Крок 2: Беремо голову першого списку — A",{"id":9196,"depth":3437,"text":25409},"Крок 3: Беремо голову — A",{"id":9238,"depth":3437,"text":25411},"Крок 4: Залишається object",{"id":9347,"depth":3437,"text":9348},{"id":9525,"depth":3437,"text":25414},"Налагодження MRO: __mro__ vs mro()",{"id":9799,"depth":3422,"text":9800,"children":25416},[25417,25419,25421,25422],{"id":9803,"depth":3437,"text":25418},"PyTypeObject та tp_mro",{"id":9962,"depth":3437,"text":25420},"Як Python шукає метод: LOAD_ATTR та type_getattro",{"id":10319,"depth":3437,"text":10320},{"id":10886,"depth":3437,"text":10887},{"id":11225,"depth":3422,"text":11226,"children":25424},[25425],{"id":11229,"depth":3437,"text":11230},{"id":12288,"depth":3422,"text":12289,"children":25427},[25428,25429,25430,25431],{"id":12292,"depth":3437,"text":12293},{"id":12623,"depth":3437,"text":12624},{"id":14536,"depth":3437,"text":14537},{"id":14974,"depth":3437,"text":25432},"__init_subclass__: сучасна альтернатива метакласам",{"id":15790,"depth":3422,"text":15791,"children":25434},[25435,25436,25437],{"id":15794,"depth":3437,"text":15795},{"id":17049,"depth":3437,"text":17050},{"id":18405,"depth":3437,"text":25438},"Рівень 3 (Advanced): Міні-фреймворк реєстру плагінів через __init_subclass__",{"id":20348,"depth":3422,"text":20349,"children":25440},[25441,25442],{"id":20481,"depth":3437,"text":20482},{"id":20802,"depth":3437,"text":20803},{"id":25286,"depth":3422,"text":25287},"Глибоке дослідження механізмів наслідування в Python — від одиночного наслідування та перевизначення методів до cooperative multiple inheritance, внутрішнього влаштування super() як проксі-об'єкта, детального математичного аналізу C3-лінеаризації, CPython Internals MRO та патерну Mixins.","md",null,{},{"title":2565,"description":25444},"04TeRh50g49HoABBs3v6Tqad880b35Azo6sOLfd02As",[25451,25453],{"title":2561,"path":2562,"stem":2563,"description":25452,"children":-1},"Глибоке дослідження інкапсуляції в Python — від філософії «дорослих людей» та угод про іменування до механізму Name Mangling, обчислювальних властивостей з @property, кастомних дескрипторів та валідації даних через геттери й сеттери.",{"title":2569,"path":2570,"stem":2571,"description":25454,"children":-1},"Глибокий аналіз двох підходів до абстракції в Python — формальних Abstract Base Classes (ABC) та структурних Protocols (PEP 544). Номінативна та структурна типізація, mypy, runtime-перевірки та вибір правильного інструменту для production-коду.",1783248143919]