148 lines
5.0 KiB
ReStructuredText
148 lines
5.0 KiB
ReStructuredText
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:
|