# 215. Immutable Class Design

#### **🔹 1. Basic Immutable Class Using `property`**

```python
class ImmutablePoint:
    def __init__(self, x, y):
        self._x = x  # Private variable
        self._y = y

    @property
    def x(self):
        return self._x

    @property
    def y(self):
        return self._y

p = ImmutablePoint(3, 4)
print(p.x)  # ✅ 3
p.x = 10  # ❌ AttributeError: can't set attribute
```

🔍 **Key Takeaway:**

* Using `@property`, we prevent modifications after object creation.

***

#### **🔹 2. Immutable Class with `__slots__`**

```python
class ImmutablePerson:
    __slots__ = ("_name", "_age")  # Restricts instance attributes

    def __init__(self, name, age):
        self._name = name
        self._age = age

    @property
    def name(self):
        return self._name

    @property
    def age(self):
        return self._age

p = ImmutablePerson("Alice", 30)
print(p.name)  # ✅ Alice
p.age = 35  # ❌ AttributeError: can't set attribute
```

🔍 **Key Takeaway:**

* `__slots__` prevents dynamic attribute creation, making the class **memory-efficient and immutable**.

***

#### **🔹 3. Using `namedtuple` for Immutable Objects**

```python
from collections import namedtuple

ImmutableCar = namedtuple("ImmutableCar", ["brand", "model", "year"])

car = ImmutableCar("Tesla", "Model S", 2023)
print(car.brand)  # ✅ Tesla
car.brand = "Ford"  # ❌ AttributeError: can't set attribute
```

🔍 **Key Takeaway:**

* `namedtuple` is a **built-in immutable class** with fixed fields.

***

#### **🔹 4. Using `dataclasses` with `frozen=True`**

```python
from dataclasses import dataclass

@dataclass(frozen=True)
class ImmutableUser:
    username: str
    email: str

user = ImmutableUser("john_doe", "john@example.com")
print(user.username)  # ✅ john_doe
user.username = "new_user"  # ❌ FrozenInstanceError: cannot assign to field
```

🔍 **Key Takeaway:**

* Setting `frozen=True` in `@dataclass` makes the class immutable.

***

#### **🔹 5. Enforcing Immutability with `__setattr__`**

```python
class ImmutableConfig:
    def __init__(self, setting):
        super().__setattr__("_setting", setting)  # Bypass __setattr__

    @property
    def setting(self):
        return self._setting

    def __setattr__(self, key, value):
        raise AttributeError("Cannot modify an immutable object")

config = ImmutableConfig("Dark Mode")
print(config.setting)  # ✅ Dark Mode
config.setting = "Light Mode"  # ❌ AttributeError: Cannot modify an immutable object
```

🔍 **Key Takeaway:**

* **Overrides `__setattr__` to block modifications**.

***

#### **🔹 6. Preventing Deletion with `__delattr__`**

```python
class ImmutableRecord:
    def __init__(self, value):
        self._value = value

    @property
    def value(self):
        return self._value

    def __delattr__(self, item):
        raise AttributeError("Cannot delete attributes")

record = ImmutableRecord(100)
del record.value  # ❌ AttributeError: Cannot delete attributes
```

🔍 **Key Takeaway:**

* `__delattr__` prevents accidental deletions.

***

#### **🔹 7. Immutable Singleton Class**

```python
class ImmutableSingleton:
    _instance = None

    def __new__(cls, value):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
            cls._instance._value = value
        return cls._instance

    @property
    def value(self):
        return self._value

singleton1 = ImmutableSingleton(42)
singleton2 = ImmutableSingleton(99)

print(singleton1.value)  # ✅ 42
print(singleton2.value)  # ✅ 42 (singleton1 and singleton2 are same)
```

🔍 **Key Takeaway:**

* Ensures **only one immutable instance** exists.

***

#### **🔹 8. Immutable Dictionary (`MappingProxyType`)**

```python
from types import MappingProxyType

config_data = {"api_key": "12345", "mode": "production"}
immutable_config = MappingProxyType(config_data)

print(immutable_config["api_key"])  # ✅ 12345
immutable_config["api_key"] = "67890"  # ❌ TypeError: 'mappingproxy' object does not support item assignment
```

🔍 **Key Takeaway:**

* `MappingProxyType` creates a **read-only dictionary**.

***

#### **🔹 9. Immutable Object with Custom Hashing**

```python
class ImmutableHashable:
    __slots__ = ("_data",)

    def __init__(self, data):
        self._data = data

    @property
    def data(self):
        return self._data

    def __hash__(self):
        return hash(self._data)

obj1 = ImmutableHashable(100)
obj2 = ImmutableHashable(100)

print(hash(obj1) == hash(obj2))  # ✅ True (Immutable objects can be hashed)
```

🔍 **Key Takeaway:**

* **Immutable objects are hashable**, allowing use in sets and dictionaries.

***

#### **🔹 10. Enforcing Attribute Restrictions with `__slots__` and `property`**

```python
class ImmutableEmployee:
    __slots__ = ("_name", "_salary")

    def __init__(self, name, salary):
        self._name = name
        self._salary = salary

    @property
    def name(self):
        return self._name

    @property
    def salary(self):
        return self._salary

emp = ImmutableEmployee("Alice", 50000)
print(emp.name)  # ✅ Alice
emp.salary = 60000  # ❌ AttributeError: can't set attribute
```

🔍 **Key Takeaway:**

* `__slots__` and `@property` **enforce immutability efficiently**.

***

#### **🚀 Summary: Immutable Class Design Techniques**

| **Method**                           | **Key Benefit**                                    |
| ------------------------------------ | -------------------------------------------------- |
| **`@property` Decorators**           | Prevent direct modification                        |
| **`__slots__`**                      | Prevents dynamic attribute creation & saves memory |
| **`namedtuple`**                     | Built-in immutable tuple-like object               |
| **`@dataclass(frozen=True)`**        | Simplifies immutable class creation                |
| **Overriding `__setattr__`**         | Blocks modification of attributes                  |
| **Overriding `__delattr__`**         | Prevents deletion of attributes                    |
| **Singleton Pattern**                | Ensures only one immutable instance                |
| **MappingProxyType**                 | Creates a read-only dictionary                     |
| **Custom `__hash__` Implementation** | Allows immutable objects in sets/dicts             |
