{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "34ee3706",
   "metadata": {},
   "source": [
    "# Part 4: OOP in Python - Quick Review for Experienced Developers\n",
    "\n",
    "This document provides a concise overview of Object-Oriented Programming (OOP) syntax and concepts in Python for developers familiar with OOP principles.\n",
    "\n",
    "---\n",
    "\n",
    "### 1. Classes and Objects\n",
    "\n",
    "- **Basic Definition**: Use the `class` keyword. The first argument to instance methods is conventionally `self`.\n",
    "- **`__init__`**: The constructor method.\n",
    "- **`@dataclass`**: A decorator that auto-generates boilerplate code like `__init__()`, `__repr__()`, `__eq__()`, etc. Highly recommended for data-centric classes.\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "3792c65b",
   "metadata": {},
   "outputs": [],
   "source": [
    "from dataclasses import dataclass\n",
    "\n",
    "@dataclass\n",
    "class Point:\n",
    "    x: float\n",
    "    y: float\n",
    "\n",
    "    def distance_from_origin(self) -> float:\n",
    "        return (self.x**2 + self.y**2) ** 0.5\n",
    "\n",
    "p = Point(3, 4)\n",
    "print(p.distance_from_origin()) # Output: 5.0"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e162a17b",
   "metadata": {},
   "source": [
    "\n",
    "\n",
    "---\n",
    "\n",
    "### 2. Inheritance\n",
    "\n",
    "- **Syntax**: `class ChildClass(ParentClass):`\n",
    "- **`super()`**: Used to call methods from the parent class.\n",
    "- **Method Overriding**: Simply define a method with the same name in the child class.\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8c3281cc",
   "metadata": {},
   "outputs": [],
   "source": [
    "@dataclass\n",
    "class Point3D(Point): # Inherits from Point\n",
    "    z: float\n",
    "\n",
    "    # Overriding the method\n",
    "    def distance_from_origin(self) -> float:\n",
    "        # Calling parent's method using super()\n",
    "        d_2d = super().distance_from_origin()\n",
    "        return (d_2d**2 + self.z**2) ** 0.5\n",
    "\n",
    "p3d = Point3D(3, 4, 12)\n",
    "print(p3d.distance_from_origin()) # Output: 13.0"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1483ffdc",
   "metadata": {},
   "source": [
    "\n",
    "\n",
    "---\n",
    "\n",
    "### 3. Encapsulation\n",
    "\n",
    "- **Convention, not Enforcement**: Python doesn't have `private` or `protected` keywords like Java/C++.\n",
    "- **`_` (Single Underscore) Prefix**: A convention indicating an attribute is for internal use (protected).\n",
    "- **`__` (Double Underscore) Prefix**: \"Name mangling\". The interpreter changes the name to `_ClassName__attribute`, making it harder to access from outside but not truly private.\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0af16936",
   "metadata": {},
   "outputs": [],
   "source": [
    "class MyClass:\n",
    "    def __init__(self):\n",
    "        self._internal_var = 1 # Convention for internal use\n",
    "        self.__mangled_var = 2 # Name is mangled\n",
    "\n",
    "    def get_mangled(self):\n",
    "        return self.__mangled_var\n",
    "\n",
    "obj = MyClass()\n",
    "print(obj._internal_var) # 1 (Accessible)\n",
    "# print(obj.__mangled_var) # AttributeError\n",
    "print(obj._MyClass__mangled_var) # 2 (Still accessible if you know the mangled name)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f4add5b3",
   "metadata": {},
   "source": [
    "\n",
    "\n",
    "---\n",
    "\n",
    "### 4. Abstraction\n",
    "\n",
    "- **`abc` module**: Use the `ABC` (Abstract Base Class) and `@abstractmethod` decorator to define abstract classes and methods.\n",
    "- Subclasses must implement all abstract methods to be instantiated.\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "142ee467",
   "metadata": {},
   "outputs": [],
   "source": [
    "from abc import ABC, abstractmethod\n",
    "\n",
    "class Shape(ABC):\n",
    "    @abstractmethod\n",
    "    def area(self) -> float:\n",
    "        pass\n",
    "\n",
    "class Square(Shape):\n",
    "    def __init__(self, side):\n",
    "        self.side = side\n",
    "\n",
    "    def area(self) -> float: # Must implement this method\n",
    "        return self.side ** 2\n",
    "\n",
    "# s = Shape() # TypeError: Can't instantiate abstract class\n",
    "sq = Square(5) # OK"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8536f570",
   "metadata": {},
   "source": [
    "\n",
    "\n",
    "---\n",
    "\n",
    "### 5. Magic Methods (Dunder Methods)\n",
    "\n",
    "Methods with double underscores (`__method__`) that allow you to emulate the behavior of built-in types.\n",
    "\n",
    "| Method | Description | Called by |\n",
    "| :--- | :--- | :--- |\n",
    "| `__init__(self, ...)` | Object initializer | `MyClass(...)` |\n",
    "| `__str__(self)` | String representation for end-users | `str(obj)`, `print(obj)` |\n",
    "| `__repr__(self)` | Unambiguous representation for developers | `repr(obj)` |\n",
    "| `__len__(self)` | Length of the object | `len(obj)` |\n",
    "| `__eq__(self, other)`| Equality comparison | `obj1 == obj2` |\n",
    "| `__add__(self, other)`| Addition operator | `obj1 + obj2` |\n",
    "\n",
    "---\n",
    "For more detailed explanations, refer to the main [part_4_object_oriented_programming.md](./part_4_object_oriented_programming.md) document. "
   ]
  }
 ],
 "metadata": {},
 "nbformat": 4,
 "nbformat_minor": 5
}
