Python Literacy Continue 1

This blog post summary covers several key object-oriented programming (OOP) and design principles, with a focus on Python implementation and idiom.


Class Construction and Separation of Concerns

  • Internal vs. External Data: When constructing a class, data passed as arguments from the user (like initial values) are external. Internal operational data, such as a running count or a list of entries, should be initialized automatically within the class (__init__) and not passed as arguments.
  • Single Responsibility Principle (SRP): This is demonstrated by separating the core logic of a class (e.g., a Journal managing entries) from the concern of persistence (saving/loading data). A separate PersistenceManager class (or static method like PM.save_to_file) handles file operations, keeping the journal class focused.

SOLID Principles

Open-Closed Principle (OCP)

  • Problem: Modifying a filtering class (ProductFilter) every time a new specification (like a new color or size) is needed violates the OCP, which states that software entities should be open for extension, but closed for modification.
  • Solution: Specification Pattern: This pattern uses inheritance and composition to allow for extension without modification.
    • A generic Specification class defines an is_satisfied(self, item) method.
    • Specific rules (e.g., ColorSpecification) inherit from Specification and implement the check.
    • A BetterFilter class takes a product list and a Specification object, applying the filter generically.
    • Combinators (like AndSpecification and the __and__ operator overload) allow complex rules to be built from simple ones.

Liskov Substitution Principle (LSP)

  • Principle: Subtypes must be substitutable for their base types without altering the correctness of the program.
  • Violation Example: If a Square class is derived from a Rectangle class, setting the width of the Square might also change its height (to maintain the square property). This breaks any function expecting a Rectangle where changing the width is not expected to change the height, thus violating the LSP. The interface of the base class (Rectangle) must be honored by the derived class (Square).

Design Patterns

Builder Pattern (Fluent Builder)

  • Purpose: To simplify the creation of complex objects with many optional parameters, avoiding “telescoping constructors” (long parameter lists).
  • Fluent Builder: Uses method chaining to construct an object step-by-step.
    • It uses nested specialized builder classes (PersonJobBuilder, PersonAddressBuilder) that inherit from a base PersonBuilder.
    • The specialized builders return self or a new builder to allow chaining, often exposed as @property methods (e.g., .works, .lives) on the base builder for a clean, fluent syntax.

Factory Pattern (and Python Idioms)

  • Goal: To control and encapsulate the logic for object creation. A factory defines how to build the product, not what the product is (the class is the blueprint).
  • Python Perspective: Python’s features like duck typing, first-class functions (lambdas), and closures often allow for a simpler, less verbose approach to configuration and object creation compared to the complex class hierarchies of the Abstract Factory pattern in other languages. However, the abstract factory can still be necessary for complex, type-heavy systems (like some OOP-heavy plugin architectures).

Prototype Pattern

  • Goal: Create new objects by cloning (deep copying) an existing “prototype” object instead of creating them from scratch.
  • Implementation: A copy.deepcopy() of the prototype is made, and then only the unique parts (e.g., a new employee’s name and office suite) are modified.

Singleton Pattern

  • Goal: Ensure that a class has only one instance and provides a global point of access to it.
  • Python Implementation: Achieved by overriding the __new__ method and using a class-level variable (cls._instance) to store the single instance. Decorators or metaclasses can also be used.

State and Typing

State vs. Statelessness

  • Stateless: An object or function that does not store any information that persists between calls. Its output depends only on its input (e.g., a simple add(a, b) function).
  • Stateful: An object that maintains internal information (state) that is modified by and affects subsequent calls (e.g., an Adder class with a running self.total).

Duck Typing and Protocols

  • Duck Typing: In Python, an object’s type is less important than what methods and attributes it has (“If it walks like a duck and quacks like a duck…”).
  • Protocols (via typing.Protocol): Used to address potential runtime bugs from duck typing. A Protocol defines an expected interface (e.g., Quackable). This allows static type checkers (like Mypy) to verify at compile-time that an object passed to a function has the necessary methods, even without explicit inheritance.

Class vs. Instance and Metaclasses

self vs. cls

  • self: Refers to the instance (object) of the class. Used in instance methods to access or modify instance data.
  • cls: Refers to the class itself. Used in class methods (@classmethod) to access or modify class-level data or to construct new instances of the class (like in a factory method: return cls(4)).
    • Calling a class method on an existing instance (p.from_fullname(...)) does not modify that instance; it constructs and returns a new instance.

Metaclass

  • A metaclass is the “class of a class.” While a normal class defines how its instances behave, a metaclass defines how classes themselves behave (e.g., controlling class creation, adding methods to all instances automatically).

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.