PEP 549: Instance Properties (aka: module properties)
I've written a PEP proposing a language change: https://www.python.org/dev/peps/pep-0549/ The TL;DR summary: add support for property objects to modules. I've already posted a prototype. How's that sound? //arry/
On Tue, Sep 5, 2017 at 3:03 PM, Larry Hastings <larry@hastings.org> wrote:
I've written a PEP proposing a language change:
https://www.python.org/dev/peps/pep-0549/
The TL;DR summary: add support for property objects to modules. I've already posted a prototype.
Interesting idea! It's definitely less arcane than the __class__ assignment support that was added in 3.5. I guess the question is whether to add another language feature here, or to provide better documentation/helpers for the existing feature. If anyone's curious what the __class__ trick looks like in practice, here's some simple deprecation machinery: https://github.com/njsmith/trio/blob/ee8d909e34a2b28d55b5c6137707e8861eee323... And here's what it looks like in use: https://github.com/njsmith/trio/blob/ee8d909e34a2b28d55b5c6137707e8861eee323... Advantages of PEP 549: - easier to explain and use Advantages of the __class__ trick: - faster (no need to add an extra step to the normal attribute lookup fast path); only those who need the feature pay for it - exposes the full power of Python's class model. Notice that the above code overrides __getattr__ but not __dir__, so the attributes are accessible via direct lookup but not listed in dir(mod). This is on purpose, for two reasons: (a) tab completion shouldn't be suggesting deprecated attributes, (b) when I did expose them in __dir__, I had trouble with test runners that iterated through dir(mod) looking for tests, and ended up spewing tons of spurious deprecation warnings. (This is especially bad when you have a policy of running your tests with DeprecationWarnings converted to errors.) I don't think there's any way to do this with PEP 549. - already supported in CPython 3.5+ and PyPy3, and with a bit of care can be faked on all older CPython releases (including, crucially, CPython 2). PEP 549 OTOH AFAICT will only be usable for packages that have 3.7 as their minimum supported version. I don't imagine that I would actually use PEP 549 any time in the foreseeable future, due to the inability to override __dir__ and the minimum version requirement. If you only need to support CPython 3.5+ and PyPy3 5.9+, then you can effectively get PEP 549's functionality at the cost of 3 lines of code and a block indent: import sys, types class _MyModuleType(types.ModuleType): @property def ... @property def ... sys.modules[__name__].__class__ = _MyModuleType It's definitely true though that they're not the most obvious lines of code :-) -n -- Nathaniel J. Smith -- https://vorpus.org
So we've seen a real use case for __class__ assignment: deprecating things on access. That use case could also be solved if modules natively supported defining __getattr__ (with the same "only used if attribute not found otherwise" semantics as it has on classes), but it couldn't be solved using @property (or at least it would be quite hacky). Is there a real use case for @property? Otherwise, if we're going to mess with module's getattro, it makes more sense to add __getattr__, which would have made Nathaniel's use case somewhat simpler. (Except for the __dir__ thing -- what else might we need?) On Tue, Sep 5, 2017 at 3:52 PM, Nathaniel Smith <njs@pobox.com> wrote:
On Tue, Sep 5, 2017 at 3:03 PM, Larry Hastings <larry@hastings.org> wrote:
I've written a PEP proposing a language change:
https://www.python.org/dev/peps/pep-0549/
The TL;DR summary: add support for property objects to modules. I've already posted a prototype.
Interesting idea! It's definitely less arcane than the __class__ assignment support that was added in 3.5. I guess the question is whether to add another language feature here, or to provide better documentation/helpers for the existing feature.
If anyone's curious what the __class__ trick looks like in practice, here's some simple deprecation machinery: https://github.com/njsmith/trio/blob/ee8d909e34a2b28d55b5c6137707e8 861eee3234/trio/_deprecate.py#L102-L138
And here's what it looks like in use: https://github.com/njsmith/trio/blob/ee8d909e34a2b28d55b5c6137707e8 861eee3234/trio/__init__.py#L91-L115
Advantages of PEP 549: - easier to explain and use
Advantages of the __class__ trick: - faster (no need to add an extra step to the normal attribute lookup fast path); only those who need the feature pay for it
- exposes the full power of Python's class model. Notice that the above code overrides __getattr__ but not __dir__, so the attributes are accessible via direct lookup but not listed in dir(mod). This is on purpose, for two reasons: (a) tab completion shouldn't be suggesting deprecated attributes, (b) when I did expose them in __dir__, I had trouble with test runners that iterated through dir(mod) looking for tests, and ended up spewing tons of spurious deprecation warnings. (This is especially bad when you have a policy of running your tests with DeprecationWarnings converted to errors.) I don't think there's any way to do this with PEP 549.
- already supported in CPython 3.5+ and PyPy3, and with a bit of care can be faked on all older CPython releases (including, crucially, CPython 2). PEP 549 OTOH AFAICT will only be usable for packages that have 3.7 as their minimum supported version.
I don't imagine that I would actually use PEP 549 any time in the foreseeable future, due to the inability to override __dir__ and the minimum version requirement. If you only need to support CPython 3.5+ and PyPy3 5.9+, then you can effectively get PEP 549's functionality at the cost of 3 lines of code and a block indent:
import sys, types class _MyModuleType(types.ModuleType): @property def ...
@property def ... sys.modules[__name__].__class__ = _MyModuleType
It's definitely true though that they're not the most obvious lines of code :-)
-n
-- Nathaniel J. Smith -- https://vorpus.org _______________________________________________ Python-Dev mailing list Python-Dev@python.org https://mail.python.org/mailman/listinfo/python-dev Unsubscribe: https://mail.python.org/mailman/options/python-dev/ guido%40python.org
-- --Guido van Rossum (python.org/~guido)
On 6 September 2017 at 17:26, Guido van Rossum <guido@python.org> wrote:
Is there a real use case for @property? Otherwise, if we're going to mess with module's getattro, it makes more sense to add __getattr__, which would have made Nathaniel's use case somewhat simpler. (Except for the __dir__ thing -- what else might we need?)
One additional (IMO quite strong) argument in favor of module level __getattr__ is that this is already used by PEP 484 for stub files and is supported by mypy, see https://github.com/python/mypy/pull/3647 -- Ivan
On Wed, Sep 6, 2017 at 9:15 AM, Ivan Levkivskyi <levkivskyi@gmail.com> wrote:
On 6 September 2017 at 17:26, Guido van Rossum <guido@python.org> wrote:
Is there a real use case for @property? Otherwise, if we're going to mess with module's getattro, it makes more sense to add __getattr__, which would have made Nathaniel's use case somewhat simpler. (Except for the __dir__ thing -- what else might we need?)
One additional (IMO quite strong) argument in favor of module level __getattr__ is that this is already used by PEP 484 for stub files and is supported by mypy, see https://github.com/python/mypy/pull/3647
So we're looking for a competing PEP here. Shouldn't be long, just summarize the discussion about use cases and generality here. -- --Guido van Rossum (python.org/~guido)
On 09/06/2017 09:45 AM, Guido van Rossum wrote:
So we're looking for a competing PEP here. Shouldn't be long, just summarize the discussion about use cases and generality here.
I don't think it's necessarily a /competing/ PEP; in my opinion, they serve slightly different use cases. After all, property (and __getattribute__) were added long after __getattr__; if __getattr__ was a reasonable solution for property's use cases, why did we bother adding property? One guiding principle I use when writing Python: if I need to provide an API, but there's conceptually only one of the thing, build it directly into a module as opposed to writing a class and making users use an instance. (For example: the random module, although these days it provides both.) So I see the situation as symmetric between modules and classes. What is the use case for property / __getattr__ / __getattribute__ on a module? The same as the use case for property / __getattr__ / __getattribute__ on a class. Excluding Lib/test, there are 375 uses of "@property" in the stdlib in trunk, 60 uses of __getattr__, and 34 of __getattribute__. Of course, property is used once per member, whereas each instance of __getattr__ and __getattribute__ could be used for arbitrarily-many members. On the other hand, it's also possible that some uses of __getattr__ are legacy uses, and if property had been available it would have used that instead. Anyway I assert that property is easily the most popular of these three techniques. TBH I forgot the specific use case that inspired this--it's on a project I haven't touched in a while, in favor of devoting time to the Gilectomy. But I can cite at least one place in the standard library that would have been better if it'd been implemented as a module property: os.stat_float_times(). //arry/
On 09/07/2017 03:49 PM, Larry Hastings wrote:
Excluding Lib/test, there are 375 uses of "@property" in the stdlib in trunk, 60 uses of __getattr__, and 34 of __getattribute__.
I spent a minute looking at the output and realized there were a bunch of hits inside pydoc_data/topics.py, aka documentation. And then I realized I was only interested in definitions, not docs or calls or other hackery. Removing pydoc_data/topics.py and changing the search terms results in: "@property" 375 hits "def __getattr__" 28 hits "def __getattribute__(" 2 hits Unless the average use of __getattr__ serve in excess of 13 members each, property is the most popular technique of the three. //arry/
On Thu, Sep 7, 2017 at 3:49 PM, Larry Hastings <larry@hastings.org> wrote:
But I can cite at least one place in the standard library that would have been better if it'd been implemented as a module property:
os.stat_float_times().
I wish there were a property feature available almost very time I encounter a "get*" method in the stdlib (or any where): There are a heck of a lot in the os module: In [4]: [s for s in dir(os) if s.startswith('get')] Out[4]: ['get_blocking', 'get_exec_path', 'get_inheritable', 'get_terminal_size', 'getcwd', 'getcwdb', 'getegid', 'getenv', 'getenvb', 'geteuid', 'getgid', 'getgrouplist', 'getgroups', 'getloadavg', 'getlogin', 'getpgid', 'getpgrp', 'getpid', 'getppid', 'getpriority', 'getsid', 'getuid'] Many of those may be good use-cases for getters, but still... And just yesterday I was getting annoyed by some in sysconfig: In [6]: [s for s in dir(sysconfig) if s.startswith('get')] Out[6]: ['get_config_h_filename', 'get_config_var', 'get_config_vars', 'get_makefile_filename', 'get_path', 'get_path_names', 'get_paths', 'get_platform', 'get_python_version', 'get_scheme_names'] modules serve the very useful function of a global singleton -- if we think properties are a good idea for classes, then they are a good idea for modules... -CHB -- Christopher Barker, Ph.D. Oceanographer Emergency Response Division NOAA/NOS/OR&R (206) 526-6959 voice 7600 Sand Point Way NE (206) 526-6329 fax Seattle, WA 98115 (206) 526-6317 main reception Chris.Barker@noaa.gov
2017-09-11 19:00 GMT+02:00 Chris Barker <chris.barker@noaa.gov>:
I wish there were a property feature available almost very time I encounter a "get*" method in the stdlib (or any where):
There are a heck of a lot in the os module:
In [4]: [s for s in dir(os) if s.startswith('get')] Out[4]:
['get_blocking',
This one is not a good example: it takes a parameter. You cannot convert it to a property.
'getcwd',
And just yesterday I was getting annoyed by some in sysconfig:
In [6]: [s for s in dir(sysconfig) if s.startswith('get')] Out[6]:
['get_config_h_filename', 'get_config_var', 'get_config_vars', 'get_makefile_filename', 'get_path', 'get_path_names', 'get_paths', 'get_platform', 'get_python_version', 'get_scheme_names']
When designing an API, when I have to choose between property and function/method, I prefer function/method over a property when the code is slow, especially at the first call. I prefer to "warn" users than a call (like the first one which fills a cache) can be slow. Well, it's not a strong rule, sometimes I use a property even if the first call has to fill a cache :-) Here the sysconfig has to build an internal cache at the first call ... if I recall correctly. Victor
On Mon, Sep 11, 2017 at 10:20 AM, Victor Stinner <victor.stinner@gmail.com> wrote:
2017-09-11 19:00 GMT+02:00 Chris Barker <chris.barker@noaa.gov>:
There are a heck of a lot in the os module: ['get_blocking',
This one is not a good example: it takes a parameter. You cannot convert it to a property.
I'm sure there are many that really do have a good reason for a getter function, but not all, by any means. When designing an API, when I have to choose between property and
function/method, I prefer function/method over a property when the code is slow, especially at the first call.
I prefer to "warn" users than a call (like the first one which fills a cache) can be slow.
Well, it's not a strong rule, sometimes I use a property even if the first call has to fill a cache :-)
Here the sysconfig has to build an internal cache at the first call ... if I recall correctly.
If we do get properties on modules, then there is plenty of room to discuss best API for a given functionality. And chances are, we won't gratuitously re-factor the standard library. But your point is well taken, and makes my point in a way -- if we had properties, then there would be a getter function only if there was a good reason for it. As it stands, I have no idea if calling, for instance, sysconfig.get_config_vars is an expensive operation. -CHB -- Christopher Barker, Ph.D. Oceanographer Emergency Response Division NOAA/NOS/OR&R (206) 526-6959 voice 7600 Sand Point Way NE (206) 526-6329 fax Seattle, WA 98115 (206) 526-6317 main reception Chris.Barker@noaa.gov
On Wed, Sep 6, 2017 at 10:26 AM, Guido van Rossum <guido@python.org> wrote:
So we've seen a real use case for __class__ assignment: deprecating things on access. That use case could also be solved if modules natively supported defining __getattr__ (with the same "only used if attribute not found otherwise" semantics as it has on classes), but it couldn't be solved using @property (or at least it would be quite hacky).
Is there a real use case for @property? Otherwise, if we're going to mess with module's getattro, it makes more sense to add __getattr__, which would have made Nathaniel's use case somewhat simpler. (Except for the __dir__ thing -- what else might we need?) -- --Guido van Rossum (python.org/~guido)
I think a more natural way for the __dir__ problem would be to update module_dir() in moduleobject.c to check if __all__ is defined and then just return that list if it is defined. I think that would be a friendlier default for __dir__ anyway. Cody
On 09/06/2017 08:26 AM, Guido van Rossum wrote:
So we've seen a real use case for __class__ assignment: deprecating things on access. That use case could also be solved if modules natively supported defining __getattr__ (with the same "only used if attribute not found otherwise" semantics as it has on classes), but it couldn't be solved using @property (or at least it would be quite hacky).
I guess it's a matter of perspective. I solved this problem using @property, and I don't think it's particularly hacky. (See my implementation at the bottom of this email). The worst thing it does is look up the current module via sys.modules[__name__]... which Nathaniel's code also must do. My example is a lot simpler than Nathaniel's code but it's just a proof-of-concept. Nathaniel's code does much more. In particular, I didn't override __dir__ to hide the deprecated attributes; doing that would mean assigning to __class__ just as Nathaniel does. (If you're actually curious to see it I could add that to the proof-of-concept.) IMO my PEP strikes the right balance. @property is far more popular than __getattr__ or __getattribute__; 19 times out of 20, people use @property. Meanwhile, people for whom @property is insufficient (like Nathaniel) can already assign to module.__class__ and override __getattr__, __getattribute__, __del__, or any other existing magic method. In other words: the commonplace is easy, and the unusual is possible. Perfect! //arry ----- /test_deprecated.py: import depmod print(depmod.depr1) # throws an exception depmod.py: # module with deprecated properties import sys _deprecated_properies = ( ("depr1", 33), ("depr2", 44), ) __module__ = sys.modules[__name__] def set_deprecated_property(name, value): @property def prop(self): raise RuntimeError(f"property '{name}' is deprecated") return value setattr(__module__, name, prop) for name, value in _deprecated_properies: set_deprecated_property(name, value)
On 09/06/2017 08:26 AM, Guido van Rossum wrote:
So we've seen a real use case for __class__ assignment: deprecating things on access. That use case could also be solved if modules natively supported defining __getattr__ (with the same "only used if attribute not found otherwise" semantics as it has on classes), but it couldn't be solved using @property (or at least it would be quite hacky).
Is there a real use case for @property? Otherwise, if we're going to mess with module's getattro, it makes more sense to add __getattr__, which would have made Nathaniel's use case somewhat simpler. (Except for the __dir__ thing -- what else might we need?)
Doesn't assigning a module's __class__ make it so we can already write properties, descriptors, __getattr__, etc. ? Are these other things so common that we need /more/ syntax for them? -- ~Ethan~
On Wed, Sep 6, 2017 at 1:52 AM, Nathaniel Smith <njs@pobox.com> wrote: [...]
import sys, types class _MyModuleType(types.ModuleType): @property def ...
@property def ... sys.modules[__name__].__class__ = _MyModuleType
It's definitely true though that they're not the most obvious lines of code :-)
It would kind of be in line with the present behavior if you could simply write something like this in the module: class __class__(types.ModuleType): @property def hello(self): return "hello" def __dir__(self): return ["hello"] assuming it would be equivalent to setting __class__ afterwards. --Koos -- + Koos Zevenhoven + http://twitter.com/k7hoven +
On 6 Sep 2017, at 00:03, Larry Hastings <larry@hastings.org> wrote:
I've written a PEP proposing a language change: https://www.python.org/dev/peps/pep-0549/ <https://www.python.org/dev/peps/pep-0549/> The TL;DR summary: add support for property objects to modules. I've already posted a prototype.
How's that sound?
To be honest this sounds like a fairly crude hack. Updating the __class__ of a module object feels dirty, but at least you get normal behavior w.r.t. properties. Why is there no mechanism to add new descriptors that can work in this context? BTW. The interaction with import is interesting… Module properties only work as naive users expect when accessing them as attributes of the module object, in particular importing the name using “from module import prop” would only call the property getter once and that may not be the intended behavior. Ronald
On 09/06/2017 02:13 PM, Ronald Oussoren wrote:
To be honest this sounds like a fairly crude hack. Updating the __class__ of a module object feels dirty, but at least you get normal behavior w.r.t. properties.
Okay. Obviously I disagree, I think it's reasonable. But I'll assume you're -1.
Why is there no mechanism to add new descriptors that can work in this context?
I've updated the prototype to add one. I added it as "collections.abc.InstanceDescriptor"; that's a base class you can inherit from, and then your descriptor will work in a module. Bikeshedding the name is fine.
BTW. The interaction with import is interesting… Module properties only work as naive users expect when accessing them as attributes of the module object, in particular importing the name using “from module import prop” would only call the property getter once and that may not be the intended behavior.
I spent a fair amount of time thinking about this. The short answer is: we /could/ fix it. We /could/ make it so that "from x import y", when x.y is an instance descriptor, ensures that y honors the descriptor protocol when referenced. We'd have to do it for three contexts: * global scope (aka module scope) * class scope * function scope The first two are pretty similar; create a proxy object that retains the module instance so it remains "bound" to that. The third one is trickier; it'd mean a new bytecode (LOAD_FAST_DESCRIPTOR), but it wouldn't slow down people who didn't use it. Anyway, long story short, I think this would be worse than simply having "from x import y" only calling the getter once. As the Zen says: special cases aren't special enough to break the rules. //arry/
On Sun, Sep 10, 2017 at 8:52 PM, Larry Hastings <larry@hastings.org> wrote:
On 09/06/2017 02:13 PM, Ronald Oussoren wrote:
To be honest this sounds like a fairly crude hack. Updating the __class__ of a module object feels dirty, but at least you get normal behavior w.r.t. properties.
Okay. Obviously I disagree, I think it's reasonable. But I'll assume you're -1.
I'm still poindering this. I am far from decided about whether it's better to just add @property support, or whether we should add __getattr__ instead (it can do everything @property can do, plus other things, but the @property support would be a little clumsier), or everything that a class can do with attributes (e.g. __getattribute__, __dir__, __setattr__), or leave things alone (__class__ assignment can solve everything). I worry that in the end @property isn't general enough and the major use cases end up still having to use __class__ assignment, and then we'd have a fairly useless feature that we cant withdraw, ever.
Why is there no mechanism to add new descriptors that can work in this context?
I've updated the prototype to add one. I added it as "collections.abc.InstanceDescriptor"; that's a base class you can inherit from, and then your descriptor will work in a module. Bikeshedding the name is fine.
I don't understand the question, or the answer. (And finding the prototype is taking longer than writing this email.)
BTW. The interaction with import is interesting… Module properties only work as naive users expect when accessing them as attributes of the module object, in particular importing the name using “from module import prop” would only call the property getter once and that may not be the intended behavior.
I spent a fair amount of time thinking about this. The short answer is: we *could* fix it. We *could* make it so that "from x import y", when x.y is an instance descriptor, ensures that y honors the descriptor protocol when referenced. We'd have to do it for three contexts:
- global scope (aka module scope) - class scope - function scope
The first two are pretty similar; create a proxy object that retains the module instance so it remains "bound" to that. The third one is trickier; it'd mean a new bytecode (LOAD_FAST_DESCRIPTOR), but it wouldn't slow down people who didn't use it.
Anyway, long story short, I think this would be worse than simply having "from x import y" only calling the getter once. As the Zen says: special cases aren't special enough to break the rules.
I agree -- let's please not fix this, it's way too complicated for something that started out as a nice localized tweak. When we write "from x import y" we already voluntarily give up seeing any later assignments to x.y. If this prevents a certain use case for lazy import, then so be it -- I don't think lazy import can ever be made so transparent that everything will work lazily without understanding the difference between lazy and eager import. (People already have to understand many subtleties of import, e.g. the difference between import and loading, and how circular imports work.) -- --Guido van Rossum (python.org/~guido)
On 09/11/2017 08:44 AM, Guido van Rossum wrote:
I worry that in the end @property isn't general enough and the major use cases end up still having to use __class__ assignment, and then we'd have a fairly useless feature that we cant withdraw, ever.
What can I say--I don't have that worry ;-) As previously mentioned in this thread, I counted up uses of property, __getattr__, and __getattribute__ in 3.7/Lib. I grepped for the following strings, ignored pydoc_data/topics.py, and got these totals: "@property" 375 hits "def __getattr__" 28 hits "def __getattribute__(" 2 hits @property seems pretty popular.
Why is there no mechanism to add new descriptors that can work in this context?
I've updated the prototype to add one. I added it as "collections.abc.InstanceDescriptor"; that's a base class you can inherit from, and then your descriptor will work in a module. Bikeshedding the name is fine.
I don't understand the question, or the answer. (And finding the prototype is taking longer than writing this email.)
Ronald was basically asking: what about user classes? The first revision of the prototype didn't provide a way to write your own instance descriptors. The only thing that could be a instance descriptor was property. So, I updated the prototype and added collections.abc.InstanceDescriptor, a base class user classes can inherit from that lets them be instance descriptors. The prototype is linked to from the PEP; for your convenience here's a link: https://github.com/larryhastings/cpython/tree/module-properties //arry/
On Mon, Sep 11, 2017 at 6:04 PM, Larry Hastings <larry@hastings.org> wrote:
On 09/11/2017 08:44 AM, Guido van Rossum wrote:
I worry that in the end @property isn't general enough and the major use cases end up still having to use __class__ assignment, and then we'd have a fairly useless feature that we cant withdraw, ever.
What can I say--I don't have that worry ;-)
As previously mentioned in this thread, I counted up uses of property, __getattr__, and __getattribute__ in 3.7/Lib. I grepped for the following strings, ignored pydoc_data/topics.py, and got these totals:
"@property" 375 hits "def __getattr__" 28 hits "def __getattribute__(" 2 hits
@property seems pretty popular.
I saw that the first time but it doesn't address my worry. (I don't feel like explaining the worry again though, I feel I've done all I can.)
Why is there no mechanism to add new descriptors that can work in this
context?
I've updated the prototype to add one. I added it as "collections.abc.InstanceDescriptor"; that's a base class you can inherit from, and then your descriptor will work in a module. Bikeshedding the name is fine.
I don't understand the question, or the answer. (And finding the prototype is taking longer than writing this email.)
Ronald was basically asking: what about user classes? The first revision of the prototype didn't provide a way to write your own instance descriptors. The only thing that could be a instance descriptor was property. So, I updated the prototype and added collections.abc.InstanceDescriptor, a base class user classes can inherit from that lets them be instance descriptors.
I still don't follow. How does one use InstanceDescriptor? Is this still about modules, or is it a generalization of properties for non-module instances? (If it is specific to modules, why doesn't it have "module" in its name? Or if it's not, why is it in this discussion?)
The prototype is linked to from the PEP; for your convenience here's a link:
https://github.com/larryhastings/cpython/tree/module-properties
I found that link in the PEP, but it's just a branch of a fork of cpython. It would be easier to review the prototype as a PR to upstream cpython.
-- --Guido van Rossum (python.org/~guido)
On 09/11/2017 07:22 PM, Guido van Rossum wrote:
I still don't follow. How does one use InstanceDescriptor?
If you write a class that inherits from InstanceDescriptor and supports the descriptor protocol, module objects will call the descriptor protocol functions when that object is accessed inside a module instance. Example: if this is "module.py": import collections.abc class CustomInstanceProperty(collections.abc.InstanceDescriptor): def __get__(self, instance, owner): print("CustomInstanceProperty.__get__ called!") return 44 cip = CustomInstanceProperty() If you then "import module" and reference "module.cip", it'll print "CustomInstanceProperty.__get__ called!" and you'll get the value 44. All three magic methods for descriptors work fine. (BTW, I didn't know the best way to make this work. I wound up implementing InstanceDescriptor by copying-and-pasting PyBaseObject_Type, setting the special tp_flag, and ensuring the special flag gets propagated to subclasses in inherit_special(). If there's a better way to do it I'd happily improve the implementation.)
Is this still about modules, or is it a generalization of properties for non-module instances? (If it is specific to modules, why doesn't it have "module" in its name? Or if it's not, why is it in this discussion?)
It's a generalization of supporting descriptors for instances, but currently it's only supported by modules. It's opt-in and modules are the only class doing so.
The prototype is linked to from the PEP; for your convenience here's a link:
https://github.com/larryhastings/cpython/tree/module-properties <https://github.com/larryhastings/cpython/tree/module-properties>
I found that link in the PEP, but it's just a branch of a fork of cpython. It would be easier to review the prototype as a PR to upstream cpython.
Okay, I'll make a PR tomorrow and post a reply here (and update the PEP). //arry/
On 09/12/2017 12:38 AM, Larry Hastings wrote:
On 09/11/2017 07:22 PM, Guido van Rossum wrote:
The prototype is linked to from the PEP; for your convenience here's a link:
https://github.com/larryhastings/cpython/tree/module-properties <https://github.com/larryhastings/cpython/tree/module-properties>
I found that link in the PEP, but it's just a branch of a fork of cpython. It would be easier to review the prototype as a PR to upstream cpython.
Okay, I'll make a PR tomorrow and post a reply here (and update the PEP).
PR is here: https://github.com/python/cpython/pull/3534 Cheers, //arry/
Why not adding both? Properties do have their uses as does __getattr__. Cheers, Sven On 13.09.2017 11:43, Larry Hastings wrote:
On 09/12/2017 12:38 AM, Larry Hastings wrote:
On 09/11/2017 07:22 PM, Guido van Rossum wrote:
The prototype is linked to from the PEP; for your convenience here's a link:
https://github.com/larryhastings/cpython/tree/module-properties <https://github.com/larryhastings/cpython/tree/module-properties>
I found that link in the PEP, but it's just a branch of a fork of cpython. It would be easier to review the prototype as a PR to upstream cpython.
Okay, I'll make a PR tomorrow and post a reply here (and update the PEP).
PR is here:
https://github.com/python/cpython/pull/3534
Cheers,
//arry/
_______________________________________________ Python-Dev mailing list Python-Dev@python.org https://mail.python.org/mailman/listinfo/python-dev Unsubscribe: https://mail.python.org/mailman/options/python-dev/srkunze%40mail.de
Why not adding both? Properties do have their uses as does __getattr__.
In that case I would just add __getattr__ to module.c, and add a recipe or perhaps a utility module that implements a __getattr__ you can put into your module if you want @property support. That way you can have both but you only need a little bit of code in module.c to check for __getattr__ and call it when you'd otherwise raise AttributeError. -- --Guido van Rossum (python.org/~guido <http://python.org/%7Eguido>)
On Wed, Sep 13, 2017 at 11:49 AM, Guido van Rossum <guido@python.org> wrote:
Why not adding both? Properties do have their uses as does __getattr__.
In that case I would just add __getattr__ to module.c, and add a recipe or perhaps a utility module that implements a __getattr__ you can put into your module if you want @property support. That way you can have both but you only need a little bit of code in module.c to check for __getattr__ and call it when you'd otherwise raise AttributeError.
Unfortunately I don't think this works. If there's a @property object present in the module's instance dict, then __getattribute__ will return it directly instead of calling __getattr__. (I guess for full property emulation you'd also need to override __setattr__ and __dir__, but I don't know how important that is.) We could consider letting modules overload __getattribute__ instead of __getattr__, but I don't think this is viable either -- a key feature of __getattr__ is that it doesn't add overhead to normal lookups. If you implement deprecation warnings by overloading __getattribute__, then it makes all your users slower, even the ones who never touch the deprecated attributes. __getattr__ is much better than __getattribute__ for this purpose. Alternatively we can have a recipe that implements @property support using __class__ assignment and overriding __getattribute__/__setattr__/__dir__, so instead of 'from module_helper.property_emulation import __getattr__' it'd be 'from module_helper import enable_property_emulation; enable_property_emulation(__name__)'. Still has the slowdown problem but it would work. -n -- Nathaniel J. Smith -- https://vorpus.org
On Wed, Sep 13, 2017 at 2:00 PM, Nathaniel Smith <njs@pobox.com> wrote:
On Wed, Sep 13, 2017 at 11:49 AM, Guido van Rossum <guido@python.org> wrote:
Why not adding both? Properties do have their uses as does __getattr__.
In that case I would just add __getattr__ to module.c, and add a recipe or perhaps a utility module that implements a __getattr__ you can put into your module if you want @property support. That way you can have both but you only need a little bit of code in module.c to check for __getattr__ and call it when you'd otherwise raise AttributeError.
Unfortunately I don't think this works. If there's a @property object present in the module's instance dict, then __getattribute__ will return it directly instead of calling __getattr__.
Hm, that's a good point. One would have to introduce some kind of convention where you can write properties with a leading _: @property def _foo(): return 42 and then a utility __getattr__ like this: def __getattr__(name): g = globals() name = '_' + name if name in g: return g[name]() raise AttributeError(...)
(I guess for full property emulation you'd also need to override __setattr__ and __dir__, but I don't know how important that is.)
At that point maybe __class__ assignment is better.
We could consider letting modules overload __getattribute__ instead of __getattr__, but I don't think this is viable either -- a key feature of __getattr__ is that it doesn't add overhead to normal lookups. If you implement deprecation warnings by overloading __getattribute__, then it makes all your users slower, even the ones who never touch the deprecated attributes. __getattr__ is much better than __getattribute__ for this purpose.
Agreed. Alternatively we can have a recipe that implements @property support
using __class__ assignment and overriding __getattribute__/__setattr__/__dir__, so instead of 'from module_helper.property_emulation import __getattr__' it'd be 'from module_helper import enable_property_emulation; enable_property_emulation(__name__)'. Still has the slowdown problem but it would work.
The emulation could do something less drastic than __class__ assignment -- it could look for globals that are properties, move them into some other dict (e.g. __properties__), and install a __getattr__ that looks things up in that dict and calls them. def __getattr__(name): if name in __properties__: return __properties__[name]() raise AttributeError(...) Still, proposals for sys.py notwithstanding, I'm worried that all of this is a solution looking for a problem. Yes, it's a cute hack. But is it art? -- --Guido van Rossum (python.org/~guido)
@Guido
One would have to introduce some kind of convention where you can write properties with a leading _
One doesn't even need the @property decorator in this case. For example: def __getattr__(name): g = globals() name = '_' + name if name in g: return g[name]() raise AttributeError(...) def _attr(): "do something" return actual_object One can even write a decorator that would change the name automatically (but this will require a call at the end of module to unbind original names). The space for play is infinite. The question is what exactly is needed. I would say that already basic customisation (like __getattr__) will be enough. IIUC, this will be actually a bit faster than setting __class__ since only "customized" attributes will be affected. -- Ivan
On Wed, Sep 13, 2017 at 3:01 PM, Ivan Levkivskyi <levkivskyi@gmail.com> wrote:
@Guido
One would have to introduce some kind of convention where you can write properties with a leading _
One doesn't even need the @property decorator in this case. For example:
def __getattr__(name): g = globals() name = '_' + name if name in g: return g[name]() raise AttributeError(...)
def _attr(): "do something" return actual_object
One can even write a decorator that would change the name automatically (but this will require a call at the end of module to unbind original names). The space for play is infinite. The question is what exactly is needed. I would say that already basic customisation (like __getattr__) will be enough. IIUC, this will be actually a bit faster than setting __class__ since only "customized" attributes will be affected.
That last sentence is a key observation. Do we even know whether there are (non-toy) things that you can do *in principle* with __class__ assignment but which are too slow *in practice* to bother? And if yes, is __getattr__ fast enough? @property? IMO we're still looking for applications. -- --Guido van Rossum (python.org/~guido)
On 14 September 2017 at 01:13, Guido van Rossum <guido@python.org> wrote:
That last sentence is a key observation. Do we even know whether there are (non-toy) things that you can do *in principle* with __class__ assignment but which are too slow *in practice* to bother? And if yes, is __getattr__ fast enough? @property?
I myself have never implemented deprecation warnings management nor lazy loading, so it is hard to say if __class__ assignment is fast enough. For me it is more combination of three factors: * modest performance improvement * some people might find __getattr__ clearer than __class__ assignment * this would be consistent with how stubs work
IMO we're still looking for applications.
How about this def allow_forward_references(*allowed): caller_globals = sys._getframe().__globals__ def typing_getattr(name): if name in allowed: return name raise AttributeError(...) caller_globals.__getattr__ = typing_getattr from typing_extensions import allow_forward_references allow_forward_references('Vertex', 'Edge') T = TypeVar('T', bound=Edge) class Vertex(List[Edge]): def copy(self: T) -> T: ... class Edge: ends: Tuple[Vertex, Vertex] ... Look mum, no quotes! :-) -- Ivan
(sorry for obvious mistakes in the example in previous e-mail) On 14 September 2017 at 21:08, Ivan Levkivskyi <levkivskyi@gmail.com> wrote:
On 14 September 2017 at 01:13, Guido van Rossum <guido@python.org> wrote:
That last sentence is a key observation. Do we even know whether there are (non-toy) things that you can do *in principle* with __class__ assignment but which are too slow *in practice* to bother? And if yes, is __getattr__ fast enough? @property?
I myself have never implemented deprecation warnings management nor lazy loading, so it is hard to say if __class__ assignment is fast enough. For me it is more combination of three factors:
* modest performance improvement * some people might find __getattr__ clearer than __class__ assignment * this would be consistent with how stubs work
IMO we're still looking for applications.
How about this
def allow_forward_references(*allowed): caller_globals = sys._getframe().__globals__ def typing_getattr(name): if name in allowed: return name raise AttributeError(...) caller_globals.__getattr__ = typing_getattr
from typing_extensions import allow_forward_references allow_forward_references('Vertex', 'Edge')
T = TypeVar('T', bound=Edge)
class Vertex(List[Edge]): def copy(self: T) -> T: ...
class Edge: ends: Tuple[Vertex, Vertex] ...
Look mum, no quotes! :-)
-- Ivan
On 09/14/2017 12:08 PM, Ivan Levkivskyi wrote:
On 14 September 2017 at 01:13, Guido van Rossum wrote:
That last sentence is a key observation. Do we even know whether there are (non-toy) things that you can do *in principle* with __class__ assignment but which are too slow *in practice* to bother? And if yes, is __getattr__ fast enough? @property?
I myself have never implemented deprecation warnings management nor lazy loading, so it is hard to say if __class__ assignment is fast enough. For me it is more combination of three factors:
* modest performance improvement * some people might find __getattr__ clearer than __class__ assignment * this would be consistent with how stubs work
IMO we're still looking for applications.
How about this
def allow_forward_references(*allowed): caller_globals = sys._getframe().__globals__ def typing_getattr(name): if name in allowed: return name raise AttributeError(...) caller_globals.__getattr__ = typing_getattr
from typing_extensions import allow_forward_references allow_forward_references('Vertex', 'Edge')
T = TypeVar('T', bound=Edge)
class Vertex(List[Edge]): def copy(self: T) -> T: ...
class Edge: ends: Tuple[Vertex, Vertex] ...
Look mum, no quotes! :-)
For comparison's sake, what would the above look like using __class__ assignment? And what is the performance difference? -- ~Ethan~
On 14 September 2017 at 22:07, Ethan Furman <ethan@stoneleaf.us> wrote:
For comparison's sake, what would the above look like using __class__ assignment? And what is the performance difference?
Actually I tried but I can't implement this without module __getattr__ so that one can just write: from typing_extensions import allow_forward_references allow_forward_references('Vertex', 'Edge') Maybe I am missing something, but either it is impossible in principle or highly non-trivial. -- Ivan
On 14 September 2017 at 22:21, Ivan Levkivskyi <levkivskyi@gmail.com> wrote:
On 14 September 2017 at 22:07, Ethan Furman <ethan@stoneleaf.us> wrote:
For comparison's sake, what would the above look like using __class__ assignment? And what is the performance difference?
Actually I tried but I can't implement this without module __getattr__ so that one can just write:
from typing_extensions import allow_forward_references allow_forward_references('Vertex', 'Edge')
Maybe I am missing something, but either it is impossible in principle or highly non-trivial.
Actually my version does not work either on my branch :-( But maybe this is a problem with my implementation. -- Ivan
On 14 September 2017 at 22:07, Ethan Furman <ethan@stoneleaf.us> wrote:
For comparison's sake, what would the above look like using __class__ assignment? And what is the performance difference?
FWIW I found a different solution: # file mod.py from typing_extensions import allow_forward_references allow_forward_references() from mod import Vertex, Edge # the import is from this same module. It works both with __class__ assignment and with __getattr__ -- Ivan
On 14 September 2017 at 23:02, Ivan Levkivskyi <levkivskyi@gmail.com> wrote:
On 14 September 2017 at 22:07, Ethan Furman <ethan@stoneleaf.us> wrote:
For comparison's sake, what would the above look like using __class__ assignment? And what is the performance difference?
FWIW I found a different solution:
# file mod.py
from typing_extensions import allow_forward_references allow_forward_references() from mod import Vertex, Edge # the import is from this same module.
It works both with __class__ assignment and with __getattr__
-- Ivan
Anyway, I don't think we should take this seriously, the way forward is PEP 563, we should have clear separation between runtime context and type context. In the latter forward references are OK, but in the former, they are quite weird. -- Ivan
On Thu, 14 Sep 2017 at 12:09 Ivan Levkivskyi <levkivskyi@gmail.com> wrote:
On 14 September 2017 at 01:13, Guido van Rossum <guido@python.org> wrote:
That last sentence is a key observation. Do we even know whether there are (non-toy) things that you can do *in principle* with __class__ assignment but which are too slow *in practice* to bother? And if yes, is __getattr__ fast enough? @property?
I myself have never implemented deprecation warnings management nor lazy loading, so it is hard to say if __class__ assignment is fast enough.
For lazy loading it is, but that's because after the lazy load, module.__class__ is set back to its original value, so any performance penalty is paid only once upon triggering __getattribute__ on the lazy module. IOW no one has complained and I know plenty of places using the lazy loading mechanism in importlib at scale. :) -Brett
For me it is more combination of three factors:
* modest performance improvement * some people might find __getattr__ clearer than __class__ assignment * this would be consistent with how stubs work
IMO we're still looking for applications.
How about this
def allow_forward_references(*allowed): caller_globals = sys._getframe().__globals__ def typing_getattr(name): if name in allowed: return name raise AttributeError(...) caller_globals.__getattr__ = typing_getattr
from typing_extensions import allow_forward_references allow_forward_references('Vertex', 'Edge')
T = TypeVar('T', bound=Edge)
class Vertex(List[Edge]): def copy(self: T) -> T: ...
class Edge: ends: Tuple[Vertex, Vertex] ...
Look mum, no quotes! :-)
-- Ivan
_______________________________________________ Python-Dev mailing list Python-Dev@python.org https://mail.python.org/mailman/listinfo/python-dev Unsubscribe: https://mail.python.org/mailman/options/python-dev/brett%40python.org
@Larry
"@property" 375 hits "def __getattr__" 28 hits
I don't think it is fair to compare occurrences of __getattr__ vs occurrences of @property, since in the first case one would use a single __getattr__ per class, while in the second case @property is required for every attribute. @Guido
I'm still poindering this. I am far from decided about whether it's better to ... The decision is up to you, I just wanted to bring an idea that is both simpler and more performance efficient.
-- Ivan
Larry Hastings <larry@hastings.org> wrote:
The TL;DR summary: add support for property objects to modules. I've already posted a prototype.
I posted an idea to python-ideas about lazy module loading. If the lazy loading idea works, having properties would allow modules to continue to be "lazy safe" but to easily do init logic when needed, e.g. getting of the property. There should be a very clean way to do that, IMHO. Using __class__ is not clean and it would be unfortunate to have the __class__ song-and-dance in a bunch of modules. Using property() seems more Pythonic.
participants (13)
-
Brett Cannon
-
Chris Barker
-
Cody Piersall
-
Ethan Furman
-
Guido van Rossum
-
Ivan Levkivskyi
-
Koos Zevenhoven
-
Larry Hastings
-
Nathaniel Smith
-
Neil Schemenauer
-
Ronald Oussoren
-
Sven R. Kunze
-
Victor Stinner