Python 3 Deep Dive Part 4 Oop High Quality

are shared among all instances of a class ( ClassName.x ).

class ShoppingCart: def __init__(self): self._items = [] def __len__(self): return len(self._items) def __getitem__(self, index): return self._items[index] def __setitem__(self, index, value): self._items[index] = value

: Understanding the difference between functions and bound methods, as well as instance, class, and static methods. Properties

The course is structured to provide both theoretical lectures and practical coding sessions:

class PluginMeta(type): plugins = [] def __new__(cls, name, bases, dct): new_class = super().__new__(cls, name, bases, dct) if name != "Plugin": # Don't register base class cls.plugins.append(new_class) return new_class python 3 deep dive part 4 oop high quality

: Since classes are instances of type , you can create a custom metaclass by inheriting from type . This allows you to automatically modify classes at creation time—for example, to enforce that all methods have docstrings or to register classes in a central registry.

: Introduced in Python 3.6, this allows descriptors to know the name of the attribute they are assigned to, eliminating the need for hardcoded strings or complex metaclasses to track attribute names. 4. Advanced Inheritance and the MRO

class Rectangle(Shape): def __init__(self, width, height): self.width = width self.height = height

def deposit(self, amount): self.__balance += amount are shared among all instances of a class ( ClassName

: Use __slots__ to restrict attribute creation, which significantly reduces memory footprint for classes with thousands of instances. 3. Advanced Inheritance and Composition

class NonNegativeInteger: def __set_name__(self, owner, name): # Python 3.6+ automatically passes the variable name here self.protected_name = f"_name" def __get__(self, instance, owner): if instance is None: return self return getattr(instance, self.protected_name, 0) def __set__(self, instance, value): if not isinstance(value, int) or value < 0: raise ValueError(f"Value must be a non-negative integer. Got value") setattr(instance, self.protected_name, value) class InventoryItem: # Descriptors must be defined at the class level price = NonNegativeInteger() quantity = NonNegativeInteger() def __init__(self, name, price, quantity): self.name = name self.price = price # Triggers __set__ self.quantity = quantity # Triggers __set__ Use code with caution. 2. Metaprogramming and Metaclasses

class Person: age = IntegerValue()

| Anti‑pattern | Why it’s bad | Fix | |------------------------------|------------------------------------------|--------------------------------------| | | Single class does everything | Split into smaller, focused classes | | Getter/setter for every attr | Un‑Pythonic, verbose | Use @property only when needed | | Mutable defaults in __init__ | Shared across instances | Use None + create new inside | | Using type() to check class | Breaks polymorphism (subclasses) | Use isinstance() | | Inheriting from builtins incorrectly | Can break if not careful | Prefer composition or subclass collections.abc | This allows you to automatically modify classes at

Metaclasses let you intercept class creation. They are the “why” behind @dataclass , Enum , and ORMs.

allow custom objects to integrate seamlessly with Python syntax.

3. Abstract Base Classes vs. Protocols (Structural Subtyping)

: Instead of manual getters and setters (common in Java), Pythonic code uses properties to define read-only, computed, or validated attributes . This allows you to change internal implementation without breaking the public API.