PEP: 549
Title: Instance Descriptors
Version: $Revision$
Last-Modified: $Date$
Author: Larry Hastings <larry@hastings.org>
Discussions-To: python-dev@python.org
Status: Rejected
Type: Standards Track
Content-Type: text/x-rst
Created: 04-Sep-2017
Python-Version: 3.7
Post-History: 04-Sep-2017


Rejection Notice
================

https://mail.python.org/pipermail/python-dev/2017-November/150528.html

Abstract
========

Python's descriptor protocol requires that descriptors
be members of the *type* of an object.  This PEP proposes
an extension to the descriptor protocol allowing use of
the descriptor protocol for members of *instances.*  This
would permit using properties in modules.

Rationale
=========

Python's descriptor protocol guides programmers towards
elegant API design.  If your class supports a data-like
member, and you *might* someday need to run code when
changing the member's value, you're encouraged to
simply declare it as a simple data member of the class
for now.  If in the future you do need to run code, you
can change it to a "property", and happily the API doesn't
change.

But consider this second bit of best-practice Python API design:
if you're writing a singleton, don't write a class, just build
your code directly into a module.  Don't make your users
instantiate a singleton class, don't make your users have to
dereference through a singleton object stored in a module,
just have module-level functions and module-level data.

Unfortunately these two best practices are in opposition.
The problem is that properties aren't supported on modules.
Modules are instances of a single generic ``module`` type,
and it's not feasible to modify or subclass this type to add
a property to one's module.  This means that programmers
facing this API design decision, where the data-like member
is a singleton stored in a module, must preemptively add
ugly "getters" and "setters" for the data.

Adding support for module properties in pure Python has recently
become *possible;*
as of Python 3.5, Python permits assigning to the ``__class__``
attribute of module objects, specifically for this purpose.  Here's
an example of using this functionality to add a property to a module::

    import sys, types
    class _MyModuleType(types.ModuleType):
        @property
        def prop(self, instance, owner):
            ...

    sys.modules[__name__].__class__ = _MyModuleType

This works, and is supported behavior, but it's clumsy and obscure.

This PEP proposes a per-type opt-in extension to the descriptor
protocol specifically designed to enable properties in modules.
The mechanism is a way to honor the descriptor protocol for
members of *instances* of a class without the member being declared
as a class variable.

Although this is being proposed as a general mechanism, the author
currently only foresees this as being useful for module objects.

Implementation
==============

The basic idea is simple: modify the ``tp_descr_get`` and ``tp_descr_set``
functions exposed by ``PyModule_Type`` to inspect the attribute interacted
with, and if it supports the descriptor protocol, call the relevant
exposed function.

Our implementation faces two challenges:

1. Since this code will be run every time an attribute is looked up on a
   method, it needs to add very little overhead in the general case,
   where the object stored in the attribute is not a descriptor.

2. Since functions are descriptors, we must take care to *not* honor
   the descriptor protocol for all objects.  Otherwise, all module-level
   functions will suddenly become bound to the module instance as if
   they were method calls on the module object.  The module handle would
   be passed in as a "self" argument to all module-level functions.

Both challenges can be solved with the same approach: we define a new
"fast subclass" flag that means "This object is a descriptor, and it
should be honored directly when this object is looked up as an
attribute of an instance".  So far this flag is only set on two
types: ``property`` and ``collections.abc.InstanceDescriptor``.
The latter is an abstract base class, whose only purpose is
to allow user classes to inherit this "fast subclass" flag.

Prototype
=========

A prototype of this functionality is under development
at GitHub [github]_.

Acknowledgements
================

Armin Rigo essentially proposed this mechanism when presented
with the idea of "module properties", and educated the author
both on the complexities of the problem and the proper solution.
Nathaniel J. Smith pointed out the 3.5 extension about assigning
to ``__class__`` on module objects, and provided the example.

References
==========

.. [github]
   The branch is here:
       https://github.com/larryhastings/cpython/tree/module-properties
   A pull request against the main CPython repo is here:
       https://github.com/python/cpython/pull/3534

Copyright
---------

This document has been placed in the public domain.


..
   Local Variables:
   mode: indented-text
   indent-tabs-mode: nil
   sentence-end-double-space: t
   fill-column: 70
   coding: utf-8
   End:
