[issue47030] singledispatch does not work with positional arguments with default values.
New submission from Randolf Scholz : from functools import singledispatch from typing import Optional @singledispatch def load(key: Optional[str] = None, /) -> None: raise NotImplementedError @load.register def _(key: None, /) -> None: print(f"loaded {key=}") @load.register def _(key: str, /) -> None: print(f"loaded {key=}") load() # TypeError: load requires at least 1 positional argument -- messages: 415274 nosy: randolf.scholz priority: normal severity: normal status: open title: singledispatch does not work with positional arguments with default values. type: enhancement versions: Python 3.10, Python 3.9 ___ Python tracker <https://bugs.python.org/issue47030> ___ ___ Python-bugs-list mailing list Unsubscribe: https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com
[issue45356] Calling `help` executes @classmethod @property decorated methods
Randolf Scholz added the comment: @Alex Regarding my proposal, the crucial part is the desiderata, not the hacked up implementation I presented. And I really believe that at least that part I got hopefully right. After all, what does `@classmethod` functionally do? It makes the first argument of the function receive the class type instead of the object instance and makes it possible to call it directly from the class without instantiating it. I would still expect `MyClass.__dict__["themethod"]` to behave, as an object, much the same, regardless if it was decorated with `@classmethod` or not. Regarding code breakage - yes that is a problem, but code is already broken and imo Python needs to make a big decision going forward: 1. Embrace decorator chaining and try hard to make it work universally (which afaik was never intended originally when decorators were first introduced). As a mathematician I would love this, also adding `@` as a general purpose function composition operator would add quite some useful functional programming aspects to python. 2. Revert changes from 3.9 and generally discourage decorator chaining. At this point however I want to emphasize that I am neither a CS major nor a python expert (I only started working with the language 3 years ago), so take everything I say as what it is - the opinion of some random stranger from the internet (: -- ___ Python tracker <https://bugs.python.org/issue45356> ___ ___ Python-bugs-list mailing list Unsubscribe: https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com
[issue45356] Calling `help` executes @classmethod @property decorated methods
Randolf Scholz added the comment: Dear Raymond, I think decorator chaining is a very useful concept. At the very least, if all decorators are functional (following the `functools.wraps` recipe), things work out great -- we even get associativity of function composition if things are done properly! The question is: can we get similar behaviour when allowing decoration with stateful objects, i.e. classes? This seems a lot more difficult. At the very least, in the case of `@classmethod` I think one can formulate a straightforward desiderata: ### Expectation - `classmethod(property)` should still be a `property`! - More generally: `classmethod(decorated)` should always be a subclass of `decorated`! Using your pure-python versions of property / classmethod from <https://docs.python.org/3.9/howto/descriptor.html>, I was able to write down a variant of `classmethod` that works mostly as expected in conjunction with `property`. The main idea is rewrite the `classmethod` to dynamically be a subclass of whatever it wrapped; roughly: ```python def ClassMethod(func): class Wrapped(type(func)): def __get__(self, obj, cls=None): if cls is None: cls = type(obj) if hasattr(type(self.__func__), '__get__'): return self.__func__.__get__(cls) return MethodType(self.__func__, cls) return Wrapped(func) ``` I attached a full MWE. Unfortunately, this doesn't fix the `help` bug, though it is kind of weird because the decorated class-property now correctly shows up under the "Readonly properties" section. Maybe `help` internally checks `isinstance(cls.func, property)` at some point instead of `isinstance(cls.__dict__["func"], property)`? ### Some further Proposals / Ideas 1. Decorators could always have an attribute that points to whatever object they wrapped. For the sake of argument, let's take `__func__`. ⟹ raise Error when typing `@decorator` if `not hasattr(decorated, "__func__")` ⟹ Regular functions/methods should probably by default have `__func__` as a pointer to themselves? ⟹ This could hae several subsidiary benefits, for example, currently, how would you implement a pair of decorators `@print_args_and_kwargs` and `@time_execution` such that both of them only apply to the base function, no matter the order in which they are decorating it? The proposed `__func__` convention would make this very easy, almost trivial. 2. `type.__setattr__` could support checking if `attr` already exists and has `__set__` implemented. ⟹ This would allow true class-properties with `getter`, `setter` and `deleter`. I provide a MWE here: <https://mail.google.com/mail/u/0/#label/Python+Ideas/FMfcgzGlkPRbJVRkHHtkRPhMCxNsFHpl> 3. I think an argument can be made that it would be really, really cool if `@` could become a general purpose function composition operator? ⟹ This is already kind of what it is doing with decorators ⟹ This is already exacltly what it is doing in numpy -- matrix multiplication \*is\* the composition of linear functions. ⟹ In fact this is a frequently requested feature on python-ideas! ⟹ But here is probably the wrong place to discuss this. -- Added file: https://bugs.python.org/file50344/ClassPropertyIdea.py ___ Python tracker <https://bugs.python.org/issue45356> ___ ___ Python-bugs-list mailing list Unsubscribe: https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com
[issue45356] Calling `help` executes @classmethod @property decorated methods
Randolf Scholz added the comment: If fact, in the current state it seem that it is impossible to implement real class-properties, for a simple reason: descriptor.__set__ is only called when setting the attribute of an instance, but not of a class!! ```python import math class TrigConst: const = math.pi def __get__(self, obj, objtype=None): print("__get__ called") return self.const def __set__(self, obj, value): print("__set__ called") self.const = value class Trig: const = TrigConst() # Descriptor instance ``` ```python Trig().const # calls TrigConst.__get__ Trig().const = math.tau # calls TrigConst.__set__ Trig.const # calls TrigConst.__get__ Trig.const = math.pi # overwrites TrigConst attribute with float. ``` -- ___ Python tracker <https://bugs.python.org/issue45356> ___ ___ Python-bugs-list mailing list Unsubscribe: https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com
[issue45356] Calling `help` executes @classmethod @property decorated methods
Randolf Scholz added the comment: @Terry I think the problem boils down to the fact that `@classmethod @property` decorated methods end up not being real properties. Calling `MyBaseClass.__dict__` reveals: ```python mappingproxy({'__module__': '__main__', 'expensive_class_property': , 'expensive_instance_property': , '__dict__': , '__weakref__': , '__doc__': None, '__abstractmethods__': frozenset(), '_abc_impl': <_abc._abc_data at 0x7f893fb98740>}) ``` Two main issues: 1. Any analytics or documentation tool that has special treatment for properties may not identify 'expensive_class_property' as a property if they simply check via `isinstance(func, property)`. Of course, this could be fixed by the tool-makers by doing a conditional check: ```python isinstance(func, property) or (`isinstance(func, classmethod)` and `isinstance(func.__func__, property)` ``` 2. `expensive_class_property` does not implement `getter`, `setter`, `deleter`. This seems to be the real dealbreaker, for example, if we do ```python MyBaseClass.expensive_class_property = 2 MyBaseClass().expensive_instance_property = 2 ``` Then the first line erroneously executes, such that MyBaseClass.__dict__['expensive_class_property'] is now `int` instead of `classmethod`, while the second line correctly raises `AttributeError: can't set attribute` since the setter method is not implemented. -- ___ Python tracker <https://bugs.python.org/issue45356> ___ ___ Python-bugs-list mailing list Unsubscribe: https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com
[issue45356] Calling `help` executes @classmethod @property decorated methods
Randolf Scholz added the comment: I updated the script with dome more info. The class-property gets actually executed 5 times when calling `help(MyClass)` ``` Computing class property of ...DONE! Computing class property of ...DONE! Computing class property of ...DONE! Computing class property of ...DONE! Computing class property of ...DONE! ``` -- Added file: https://bugs.python.org/file50326/classmethod_property.py ___ Python tracker <https://bugs.python.org/issue45356> ___ ___ Python-bugs-list mailing list Unsubscribe: https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com
[issue45356] Calling `help` executes @classmethod @property decorated methods
New submission from Randolf Scholz : I noticed some strange behaviour when calling `help` on a class inheriting from a class or having itself @classmethod @property decorated methods. ```python from time import sleep from abc import ABC, ABCMeta, abstractmethod class MyMetaClass(ABCMeta): @classmethod @property def expensive_metaclass_property(cls): """This may take a while to compute!""" print("computing metaclass property"); sleep(3) return "Phew, that was a lot of work!" class MyBaseClass(ABC, metaclass=MyMetaClass): @classmethod @property def expensive_class_property(cls): """This may take a while to compute!""" print("computing class property .."); sleep(3) return "Phew, that was a lot of work!" @property def expensive_instance_property(self): """This may take a while to compute!""" print("computing instance property ..."); sleep(3) return "Phew, that was a lot of work!" class MyClass(MyBaseClass): """Some subclass of MyBaseClass""" help(MyClass) ``` Calling `help(MyClass)` will cause `expensive_class_property` to be executed 4 times (!) The other two properties, `expensive_instance_property` and `expensive_metaclass_property` are not executed. Secondly, only `expensive_instance_property` is listed as a read-only property; `expensive_class_property` is listed as a classmethod and `expensive_metaclass_property` is unlisted. The problem is also present in '3.10.0rc2 (default, Sep 28 2021, 17:57:14) [GCC 10.2.1 20210110]' Stack Overflow thread: https://stackoverflow.com/questions/69426309 -- files: classmethod_property.py messages: 403109 nosy: randolf.scholz priority: normal severity: normal status: open title: Calling `help` executes @classmethod @property decorated methods type: behavior versions: Python 3.10, Python 3.9 Added file: https://bugs.python.org/file50325/classmethod_property.py ___ Python tracker <https://bugs.python.org/issue45356> ___ ___ Python-bugs-list mailing list Unsubscribe: https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com