Initialization with type validation

Runtime type validation is rarely a sensible requirement. In a way, this might be a failure to fully understand Python. Python's type system permits numerous extensions. Runtime type checking tends to defeat this. Using mypy provides extensive type checking without the runtime overheads.

The notional objective behind runtime checking is to validate that all of the arguments are of a proper type. The issue with trying to do this is that the definition of proper is often far too narrow to be truly useful.

Type checking is different from checking ranges and domains within a type. Numeric range checking, for example, may be essential to prevent infinite loops at runtime. 

What can create problems is trying to do something like the following in an __init__() method:

class ValidPlayer:

def __init__(self, table, bet_strategy, game_strategy):
assert isinstance(table, Table)
assert isinstance(bet_strategy, BettingStrategy)
assert isinstance(game_strategy, GameStrategy)

self.bet_strategy = bet_strategy
self.game_strategy = game_strategy
self.table = table

The isinstance() method checks circumvent Python's normal duck typing. We're unable to provide instances without strictly following the class hierarchy defined by isinstance() checks.

We write a casino game simulation in order to experiment with endless variations on GameStrategy. These class definitions are very simple: they have four method definitions. There's little real benefit to using inheritance from a GameStrategy superclass. We should be allowed to define classes independently, not by referencing an overall superclass.

The initialization error-checking shown in this example would force us to create subclasses merely to pass a runtime error check. No usable code is inherited from the abstract superclass.

One of the biggest duck typing issues surrounds numeric types. Different numeric types will work in different contexts. Attempts to validate the types of arguments may prevent a perfectly sensible numeric type from working properly. When attempting validation, we have the following two choices in Python:

  • We write validation so that a relatively narrow collection of types is permitted, and someday the code will break because a new type that would have worked sensibly is prohibited.
  • We eschew validation so that a broad collection of types is permitted, and someday the code will break because a type that does not work sensibly is used.

Note that both are essentially the same: the code could perhaps break someday. It will either break because a type is prevented from being used, even though it's sensible, or because a type that's not really sensible is used.

Just allow it
Generally, it's considered better Python style to simply permit any type of data to be used. We'll return to this in Chapter 5, The ABCs of Consistent Design.

The question is this: why add runtime type checking when it will restrict potential future use cases?

If there's no good reason to restrict potential future use cases, then runtime type checking should be avoided.

Rather than preventing a sensible, but possibly unforeseen, use case, we provide type hints and use mypy to evaluate the hints. Additionally, of course, unit testing, debug logging, and documentation can help us to understand any restrictions on the types that can be processed.

With a few exceptions, all of the examples in this book use type hints to show the types of values expected and produced. The mypy utility can be run to confirm that the definitions are used properly. While the standard library has extensive type hints, not all packages are fully covered by hints. In Chapter 12, Storing and Retrieving Objects via SQLite, we'll use the SQLAlchemy package, which doesn't provide complete type hints.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset