2013-02-17

Resolving a TOOWTDI interface problem for attributes

TL;DR: choose one way to signify the lack of information in an API either as making the attribute optional or setting a default value (e.g. None), but not both.

When you read the docs for importlib.find_loader() you find that an exception is raised when __loader__ is set to None. But if you read the docs for importlib.abc.Loader.load_module() you will notice that __loader__ "should be set" (italics mine). So one part of the docs says having a value of None is fine while another says the attribute doesn't even have to exist. So the former is a LBYL version of the API while the latter is a EAFP version. While that's technically fine, I do like the concept of TOOWTDI in Python, so I would prefer choosing one of the approaches as the definitive way to signal that a module's loader is not known.

Does long-term (think in timescales of years) backwards-compatibility suggest a preference of one over the other? As it stands now, one must do:

if getattr(module, '__loader__', None) is not None:
  # Use loader 

That handles both the LBYL and EAFP approaches of either not setting the attribute or setting it to None. If this were to translate to LBYL it would become:

if module.__loader__ is not None:
  # Use loader

Not a huge difference, just easier to read. The EAFP approach would be:

try:
  loader = module.__loader__
except AttributeError:
  pass

else:
  # Use loader

Longer, but still totally readable and psychologically makes more sense since the attribute is set more often than not (importlib actually sets the attribute along with __package__ after importing if they are not already set).

But since most code that cares whether __loader__ is set already uses the getattr() approach, the None value approach is the least disruptive to changing to the eventual idiom.

But the thing that tipped the scales for me is I don't want the attribute to be optional but be required in the long run (think Python 4 long run; side-effect of how long Python versions last), so I plan to change the default attributes on the module type to always have __loader__ and __package__ and set them to None by default in Python 3.4. That means the optional approach won't mean anything going forward, so that makes the LBYL approach the one I plan to go with even if I personally prefer the EAFP approach for optional API attributes; I don't want this part of the API being viewed as optional by loader authors.

If you care about any of this specific API cleanup, you can follow issue #17115 as I clean up importlib's mixed approach to __loader__.