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

Copyright

This document has been placed in the public domain.

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