Wouldn't a better approach be a way to customize the type of the module? That would allow people to define behavior for almost anything (__call__, __getattr__, __setattr__, __dir__, various operators etc). This question shouldn't exist "why can't I customize behavior X in a module when I can do it for a class". Why go half-way.


Thanks,
-- Ionel
Cristian Mărieș, http://blog.ionelmc.ro

On Sun, Sep 10, 2017 at 9:48 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/