Title Page Copyright and Credits Mastering Object-Oriented Python Second Edition About Packt Why subscribe? Contributors About the author About the reviewers Packt is searching for authors like you Preface Who this book is for What this book covers To get the most out of this book Download the example code files Code in Action Conventions used Get in touch Reviews Section 1: Tighter Integration Via Special Methods Preliminaries, Tools, and Techniques Technical requirements About the Blackjack game Playing the game Blackjack player strategies Object design for simulating Blackjack The Python runtime and special methods Interaction, scripting, and tools Selecting an IDE Consistency and style Type hints and the mypy program Performance – the timeit module Testing – unittest and doctest Documentation – sphinx and RST markup Installing components Summary The __init__() Method Technical requirements The implicit superclass – object The base class object __init__() method Implementing __init__() in a superclass Creating enumerated constants Leveraging __init__() via a factory function Faulty factory design and the vague else clause Simplicity and consistency using elif sequences Simplicity using mapping and class objects Two parallel mappings Mapping to a tuple of values The partial function solution Fluent APIs for factories Implementing __init__() in each subclass Composite objects Wrapping a collection class Extending a collection class More requirements and another design Complex composite objects Complete composite object initialization Stateless objects without __init__() Some additional class definitions Multi-strategy __init__() More complex initialization alternatives Initializing with static or class-level methods Yet more __init__() techniques Initialization with type validation Initialization, encapsulation, and privacy Summary Integrating Seamlessly - Basic Special Methods Technical requirements The __repr__() and __str__() methods Simple __str__() and __repr__() Collection __str__() and __repr__() The __format__() method Nested formatting specifications Collections and delegating format specifications The __hash__() method Deciding what to hash Inheriting definitions for immutable objects Overriding definitions for immutable objects Overriding definitions for mutable objects Making a frozen hand from a mutable hand The __bool__() method The __bytes__() method The comparison operator methods Designing comparisons Implementation of a comparison of objects of the same class Implementation of a comparison of the objects of mixed classes Hard totals, soft totals, and polymorphism A mixed class comparison example The __del__() method The reference count and destruction Circular references and garbage collection Circular references and the weakref module The __del__() and close() methods The __new__() method and immutable objects The __new__() method and metaclasses Metaclass example – class-level logger Summary Attribute Access, Properties, and Descriptors Technical requirements Basic attribute processing Attributes and the __init__() method Creating properties Eagerly computed properties The setter and deleter properties Using special methods for attribute access Limiting attribute names with __slots__ Dynamic attributes with __getattr__() Creating immutable objects as a NamedTuple subclass Eagerly computed attributes, dataclasses, and __post_init__() Incremental computation with __setattr__() The __getattribute__() method Creating descriptors Using a non-data descriptor Using a data descriptor Using type hints for attributes and properties Using the dataclasses module Attribute Design Patterns Properties versus attributes Designing with descriptors Summary The ABCs of Consistent Design Technical requirements Abstract base classes Base classes and polymorphism Callable Containers and collections Numbers Some additional abstractions The iterator abstraction Contexts and context managers The abc and typing modules Using the __subclasshook__() method Abstract classes using type hints Summary, design considerations, and trade-offs Looking forward Using Callables and Contexts Technical requirements Designing callables Improving performance Using memoization or caching Using functools for memoization Aiming for simplicity using a callable interface Complexities and the callable interface Managing contexts and the with statement Using the decimal context Other contexts Defining the __enter__() and __exit__() methods Handling exceptions Context manager as a factory Cleaning up in a context manager Summary Callable design considerations and trade-offs Context manager design considerations and trade-offs Looking forward Creating Containers and Collections Technical requirements ABCs of collections Examples of special methods Using the standard library extensions The typing.NamedTuple class The deque class The ChainMap use case The OrderedDict collection The defaultdict subclass The counter collection Creating new kinds of collections Narrowing a collection's type Defining a new kind of sequence A statistical list Choosing eager versus lazy calculation Working with __getitem__(), __setitem__(), __delitem__(), and slices Implementing __getitem__(), __setitem__(), and __delitem__() Wrapping a list and delegating Creating iterators with __iter__() Creating a new kind of mapping Creating a new kind of set Some design rationale Defining the Tree class Defining the TreeNode class Demonstrating the binary tree bag Design considerations and tradeoffs Summary Creating Numbers Technical requirements ABCs of numbers Deciding which types to use Method resolution and the reflected operator concept The arithmetic operator's special methods Creating a numeric class Defining FixedPoint initialization Defining FixedPoint binary arithmetic operators Defining FixedPoint unary arithmetic operators Implementing FixedPoint reflected operators Implementing FixedPoint comparison operators Computing a numeric hash Designing more useful rounding Implementing other special methods Optimization with the in-place operators Summary Decorators and Mixins - Cross-Cutting Aspects Technical requirements Class and meaning Type hints and attributes for decorators Attributes of a function Constructing a decorated class Some class design principles Aspect-oriented programming Using built-in decorators Using standard library decorators Using standard library mixin classes Using the enum with mixin classes Writing a simple function decorator Creating separate loggers Parameterizing a decorator Creating a method function decorator Creating a class decorator Adding methods to a class Using decorators for security Summary Section 2: Object Serialization and Persistence Serializing and Saving - JSON, YAML, Pickle, CSV, and XML Technical requirements Understanding persistence, class, state, and representation Common Python terminology Filesystem and network considerations Defining classes to support persistence Rendering blogs and posts Dumping and loading with JSON JSON type hints Supporting JSON in our classes Customizing JSON encoding Customizing JSON decoding Security and the eval() issue Refactoring the encode function Standardizing the date string Writing JSON to a file Dumping and loading with YAML Formatting YAML data on a file Extending the YAML representation Security and safe loading Dumping and loading with pickle Designing a class for reliable pickle processing Security and the global issue Dumping and loading with CSV Dumping simple sequences into CSV Loading simple sequences from CSV Handling containers and complex classes Dumping and loading multiple row types into a CSV file Filtering CSV rows with an iterator Dumping and loading joined rows into a CSV file Dumping and loading with XML Dumping objects using string templates Dumping objects with xml.etree.ElementTree Loading XML documents Summary Design considerations and tradeoffs Schema evolution Looking forward Storing and Retrieving Objects via Shelve Technical requirements Analyzing persistent object use cases The ACID properties Creating a shelf Designing shelvable objects Designing objects with type hints Designing keys for our objects Generating surrogate keys for objects Designing a class with a simple key Designing classes for containers or collections Referring to objects via foreign keys Designing CRUD operations for complex objects Searching, scanning, and querying Designing an access layer for shelve Writing a demonstration script Creating indexes to improve efficiency Creating a cache Adding yet more index maintenance The writeback alternative to index updates Schema evolution Summary Design considerations and tradeoffs Application software layers Looking forward Storing and Retrieving Objects via SQLite Technical requirements SQL databases, persistence, and objects The SQL data model – rows and tables CRUD processing via SQL DML statements Querying rows with the SQL SELECT statement SQL transactions and the ACID properties Designing primary and foreign database keys Processing application data with SQL Implementing class-like processing in pure SQL Mapping Python objects to SQLite BLOB columns Mapping Python objects to database rows manually Designing an access layer for SQLite Implementing container relationships Improving performance with indices Adding an ORM layer Designing ORM-friendly classes Building the schema with the ORM layer Manipulating objects with the ORM layer Querying posts that are given a tag Defining indices in the ORM layer Schema evolution Summary Design considerations and tradeoffs Mapping alternatives Key and key design Application software layers Looking forward Transmitting and Sharing Objects Technical requirements Class, state, and representation Using HTTP and REST to transmit objects Implementing CRUD operations via REST Implementing non-CRUD operations The REST protocol and ACID Choosing a representation – JSON, XML, or YAML Using Flask to build a RESTful web service Problem-domain objects to transfer Creating a simple application and server More sophisticated routing and responses Implementing a REST client Demonstrating and unit testing the RESTful services Handling stateful REST services Designing RESTful object identifiers Multiple layers of REST services Using a Flask blueprint Registering a blueprint  Creating a secure REST service Hashing user passwords Implementing REST with a web application framework Using a message queue to transmit objects Defining processes Building queues and supplying data Summary Design considerations and tradeoffs Schema evolution Application software layers Looking forward Configuration Files and Persistence Technical requirements Configuration file use cases Representation, persistence, state, and usability Application configuration design patterns Configuring via object construction Implementing a configuration hierarchy Storing the configuration in INI files Handling more literals via the eval() variants Storing the configuration in PY files Configuration via class definitions Configuration via SimpleNamespace Using Python with exec() for the configuration Why exec() is a non-problem Using ChainMap for defaults and overrides Storing the configuration in JSON or YAML files Using flattened JSON configurations Loading a YAML configuration Storing the configuration in properties files Parsing a properties file Using a properties file Using XML files – PLIST and others Customized XML configuration files Summary Design considerations and trade-offs Creating a shared configuration Schema evolution Looking forward Section 3: Object-Oriented Testing and Debugging Design Principles and Patterns Technical requirements The SOLID design principles The Interface Segregation Principle The Liskov Substitution Principle The Open/Closed Principle The Dependency Inversion Principle The Single Responsibility Principle A SOLID principle design test Building features through inheritance and composition Advanced composition patterns Parallels between Python and libstdc++ Summary The Logging and Warning Modules Technical requirements Creating a basic log Creating a class-level logger Configuring loggers Starting up and shutting down the logging system Naming loggers Extending logger levels Defining handlers for multiple destinations Managing propagation rules Configuration Gotcha Specialized logging for control, debugging, audit, and security Creating a debugging log Creating audit and security logs Using the warnings module Showing API changes with a warning Showing configuration problems with a warning Showing possible software problems with a warning Advanced logging – the last few messages and network destinations Building an automatic tail buffer Sending logging messages to a remote process Preventing queue overrun Summary Design considerations and trade-offs Looking ahead Designing for Testability Technical requirements Defining and isolating units for testing Minimizing dependencies Creating simple unit tests Creating a test suite Including edge and corner cases Using mock objects to eliminate dependencies Using mocks to observe behaviors Using doctest to define test cases Combining doctest and unittest Creating a more complete test package Using setup and teardown Using setup and teardown with OS resources Using setup and teardown with databases The TestCase class hierarchy Using externally defined expected results Using pytest and fixtures Assertion checking Using fixtures for test setup Using fixtures for setup and teardown Building parameterized fixtures Automated integration or performance testing Summary Design considerations and trade-offs Looking forward Coping with the Command Line Technical requirements The OS interface and the command line Arguments and options Using the pathlib module Parsing the command line with argparse A simple on–off option An option with an argument Positional arguments All other arguments --version display and exit --help display and exit Integrating command-line options and environment variables Providing more configurable defaults Overriding configuration file settings with environment variables Making the configuration aware of the None values Customizing the help output Creating a top-level main() function Ensuring DRY for the configuration Managing nested configuration contexts Programming in the large Designing command classes Adding the analysis command subclass Adding and packaging more features into an application Designing a higher-level, composite command Additional composite Command design patterns Integrating with other applications Summary Design considerations and trade-offs Looking forward Module and Package Design Technical requirements Designing a module Some module design patterns Modules compared with classes The expected content of a module Whole modules versus module items Designing a package Designing a module-package hybrid Designing a package with alternate implementations Using the ImportError exception Designing a main script and the __main__ module Creating an executable script file Creating a __main__ module Programming in the large Designing long-running applications Organizing code into src, scripts, tests, and docs Installing Python modules Summary Design considerations and tradeoffs Looking forward Quality and Documentation Technical requirements Writing docstrings for the help() function Using pydoc for documentation Better output via RST markup Blocks of text The RST inline markup RST directives Learning RST Writing effective docstrings Writing file-level docstrings, including modules and packages Writing API details in RST markup Writing class and method function docstrings Writing function docstrings More sophisticated markup techniques Using Sphinx to produce the documentation Using Sphinx quickstart Writing Sphinx documentation Filling in the 4+1 views for documentation Writing the implementation document Creating Sphinx cross-references Refactoring Sphinx files into directories Handling legacy documents Writing the documentation Literate programming Use cases for literate programming Working with a literate programming tool Summary Design considerations and tradeoffs Other Books You May Enjoy Leave a review - let other readers know what you think