PEP: 549 Title: Instance Properties Version: $Revision$ Last-Modified: $Date$ Author: larry@hastings.org (Larry Hastings) Discussions-To: Python-Dev Status: Draft Type: Standards Track Content-Type: text/x-rst Created: 4-Sep-2017 Python-Version: 3.7 Post-History: 4-Sep-2017 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. Unfortunately this doesn't work with 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 is possible only by using hacks like: * peeking in ``sys.getframe()``, * changing the type of an object after it's created, or * replacing the object stored in ``sys.modules``. These techniques can be made to work, but they're fragile. 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 forsees 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 desciptor, and it should be honored directly when this object is looked up as an attribute of an instance". So far the only placed where this flag is set is on ``PyProperty_Type``. 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. References ========== .. [github] https://github.com/larryhastings/cpython/tree/module-properties 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: