I'd really love to find a way to enable lazy loading by default, maybe with a way to opt-out old/problem/legacy modules instead of opt-in via __future__ or anything else. IME easily 95%+ of modules in the wild, today, will not even notice (I wrote an application bundler in past that enabled it globally by default without much fuss for several years). The only small annoyance is when it does cause problems the error can jump around, depending on how the lazy import was triggered.

module.__getattr__ works pretty well for normal access, after being imported by another module, but it doesn't properly trigger loading by functions defined in the module's own namespace. These functions are bound to module.__dict__ as their __globals__ so lazy loading of this variety is really dependant on a custom module.__dict__ that implements __getitem__ or __missing__.

I think this approach is the ideal path over existing PEPs. I've done it in the past and it worked very well. The impl looked something like this:

* Import statements and __import__ generate lazy-load marker objects instead of performing imports.
* Marker objects record the desired import and what identifiers were supposed to be added to namespace.
* module.__dict__.__setitem__ recognizes markers and records their identifiers as lazily imported somewhere, but **does not add them to namespace**.
* module.___getattribute__ will request the lazy attribute via module.__dict__ like regular objects and functions will request via their bound __globals__.
* Both will trigger module.__dict.__missing__, which looks to see if the requested identifier was previously marked as a lazy import, and if so, performs the import, saves to namespace properly, and returns the real import.

-- 


C Anthony


On Sep 10, 2017 1:49 PM, "Ivan Levkivskyi" <levkivskyi@gmail.com> wrote:
I have written a short PEP as a complement/alternative to PEP 549.
I will be grateful for comments and suggestions. The PEP should
appear online soon.

--
Ivan

***********************************************************

PEP: 562
Title: Module __getattr__
Author: Ivan Levkivskyi <levkivskyi@gmail.com>
Status: Draft
Type: Standards Track
Content-Type: text/x-rst
Created: 09-Sep-2017
Python-Version: 3.7
Post-History: 09-Sep-2017


Abstract
========

It is proposed to support ``__getattr__`` function defined on modules to
provide basic customization of module attribute access.


Rationale
=========

It is sometimes convenient to customize or otherwise have control over
access to module attributes. A typical example is managing deprecation
warnings. Typical workarounds are assigning ``__class__`` of a module object
to a custom subclass of ``types.ModuleType`` or substituting ``sys.modules``
item with a custom wrapper instance. It would be convenient to simplify this
procedure by recognizing ``__getattr__`` defined directly in a module that
would act like a normal ``__getattr__`` method, except that it will be defined
on module *instances*. For example::

  # lib.py

  from warnings import warn

  deprecated_names = ["old_function", ...]

  def _deprecated_old_function(arg, other):
      ...

  def __getattr__(name):
      if name in deprecated_names:
          warn(f"{name} is deprecated", DeprecationWarning)
          return globals()[f"_deprecated_{name}"]
      raise AttributeError(f"module {__name__} has no attribute {name}")

  # main.py

  from lib import old_function  # Works, but emits the warning

There is a related proposal PEP 549 that proposes to support instance
properties for a similar functionality. The difference is this PEP proposes
a faster and simpler mechanism, but provides more basic customization.
An additional motivation for this proposal is that PEP 484 already defines
the use of module ``__getattr__`` for this purpose in Python stub files,
see [1]_.


Specification
=============

The ``__getattr__`` function at the module level should accept one argument
which is a name of an attribute and return the computed value or raise
an ``AttributeError``::

  def __getattr__(name: str) -> Any: ...

This function will be called only if ``name`` is not found in the module
through the normal attribute lookup.

The reference implementation for this PEP can be found in [2]_.


Backwards compatibility and impact on performance
=================================================

This PEP may break code that uses module level (global) name ``__getattr__``.
The performance implications of this PEP are minimal, since ``__getattr__``
is called only for missing attributes.


References
==========

.. [1] PEP 484 section about ``__getattr__`` in stub files
   (https://www.python.org/dev/peps/pep-0484/#stub-files)

.. [2] The reference implementation
   (https://github.com/ilevkivskyi/cpython/pull/3/files)


Copyright
=========

This document has been placed in the public domain.

_______________________________________________
Python-ideas mailing list
Python-ideas@python.org
https://mail.python.org/mailman/listinfo/python-ideas
Code of Conduct: http://python.org/psf/codeofconduct/