Mastering Object-Oriented Python - Second Edition

Book description

Gain comprehensive insights into programming practices, and code portability and reuse to build flexible and maintainable apps using object-oriented principles

Key Features

  • Extend core OOP techniques to increase integration of classes created with Python
  • Explore various Python libraries for handling persistence and object serialization
  • Learn alternative approaches for solving programming problems with different attributes to address your problem domain

Book Description

Object-oriented programming (OOP) is a relatively complex discipline to master, and it can be difficult to see how general principles apply to each language's unique features. With the help of the latest edition of Mastering Objected-Oriented Python, you'll be shown how to effectively implement OOP in Python, and even explore Python 3.x.

Complete with practical examples, the book guides you through the advanced concepts of OOP in Python, and demonstrates how you can apply them to solve complex problems in OOP. You will learn how to create high-quality Python programs by exploring design alternatives and determining which design offers the best performance. Next, you'll work through special methods for handling simple object conversions and also learn about hashing and comparison of objects. As you cover later chapters, you'll discover how essential it is to locate the best algorithms and optimal data structures for developing robust solutions to programming problems with minimal computer processing. Finally, the book will assist you in leveraging various Python features by implementing object-oriented designs in your programs.

By the end of this book, you will have learned a number of alternate approaches with different attributes to confidently solve programming problems in Python.

What you will learn

  • Explore a variety of different design patterns for the __init__() method
  • Learn to use Flask to build a RESTful web service
  • Discover SOLID design patterns and principles
  • Use the features of Python 3's abstract base
  • Create classes for your own applications
  • Design testable code using pytest and fixtures
  • Understand how to design context managers that leverage the 'with' statement
  • Create a new type of collection using standard library and design techniques
  • Develop new number types above and beyond the built-in classes of numbers

Who this book is for

This book is for developers who want to use Python to create efficient programs. A good understanding of Python programming is required to make the most out of this book. Knowledge of concepts related to object-oriented design patterns will also be useful.

Table of contents

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

Product information

  • Title: Mastering Object-Oriented Python - Second Edition
  • Author(s): Steven F. Lott
  • Release date: June 2019
  • Publisher(s): Packt Publishing
  • ISBN: 9781789531367