Auto-assign attributes from __init__ arguments
Hi All, First of all, if this is something which has been discussed in the past the please point me in the right direction. *Problem:* When creating classes in Python, I find myself writing the __init__ method in a very similar way a lot of the time, that is: ``` def __init__(self, argument_1, argument_2, argument_3=None): self.argument_1 = argument_1 self.argument_2 = argument_2 self.argument_3 = argument_3 # then maybe some other attribute setting and logic follows ``` Every argument of __init__ gets a corresponding attribute with the same name. This means that each `argument_i` has been typed 3 times, which seems overly-verbose as well as being easy to mistype. This pattern is easy to find in various popular python libraries, and in some it is actually enforced. For example, I do quite a bit of work with classifiers using the sklearn estimator API, and for various reasons sklearn enforce this pattern for an __init__ (see here <https://scikit-learn.org/stable/developers/develop.html#instantiation> if interested). Here is an example of this pattern from the standard library (from textwrap.TextWrapper): ``` def __init__(self, width=70, initial_indent="", subsequent_indent="", expand_tabs=True, replace_whitespace=True, fix_sentence_endings=False, break_long_words=True, drop_whitespace=True, break_on_hyphens=True, tabsize=8, *, max_lines=None, placeholder=' [...]'): self.width = width self.initial_indent = initial_indent self.subsequent_indent = subsequent_indent self.expand_tabs = expand_tabs self.replace_whitespace = replace_whitespace self.fix_sentence_endings = fix_sentence_endings self.break_long_words = break_long_words self.drop_whitespace = drop_whitespace self.break_on_hyphens = break_on_hyphens self.tabsize = tabsize self.max_lines = max_lines self.placeholder = placeholder ``` With a quick scan of the top 50 or so most used python packages, *1 in 4* __init__ methods that takes arguments has the line `self.argument_i = argument_i` for every single argument, with several of them having 10+ arguments. *Suggestion:* A new built-in called something like `assign()` which would assign every single __init__ arg to a corresponding attribute. e.g. the snippet from above could be rewritten to: ``` def __init__(self, argument_1, argument_2, argument_3=None): assign() # other init logic goes here ``` This could alternatively be implemented as a decorator, like so ``` @assign def __init__(self, argument_1, argument_2, argument_3=None): # other init logic goes here ``` but then this requires a `pass` if no other logic is needed inside the __init__. There may also be some other syntax for this which would be even easier to use. Is this something that others would find useful? Thanks, Lewis
You are not the first to have this idea. Unless I am mistaken you might find what you are looking for in dataclasses which were added in Python 3.7: https://docs.python.org/3/library/dataclasses.html On Mon, 4 May 2020 at 19:06, Lewis Ball <lrjball@gmail.com> wrote:
Hi All,
First of all, if this is something which has been discussed in the past the please point me in the right direction.
*Problem:*
When creating classes in Python, I find myself writing the __init__ method in a very similar way a lot of the time, that is: ``` def __init__(self, argument_1, argument_2, argument_3=None): self.argument_1 = argument_1 self.argument_2 = argument_2 self.argument_3 = argument_3 # then maybe some other attribute setting and logic follows ```
Every argument of __init__ gets a corresponding attribute with the same name. This means that each `argument_i` has been typed 3 times, which seems overly-verbose as well as being easy to mistype. This pattern is easy to find in various popular python libraries, and in some it is actually enforced. For example, I do quite a bit of work with classifiers using the sklearn estimator API, and for various reasons sklearn enforce this pattern for an __init__ (see here <https://scikit-learn.org/stable/developers/develop.html#instantiation> if interested).
Here is an example of this pattern from the standard library (from textwrap.TextWrapper): ``` def __init__(self, width=70, initial_indent="", subsequent_indent="", expand_tabs=True, replace_whitespace=True, fix_sentence_endings=False, break_long_words=True, drop_whitespace=True, break_on_hyphens=True, tabsize=8, *, max_lines=None, placeholder=' [...]'): self.width = width self.initial_indent = initial_indent self.subsequent_indent = subsequent_indent self.expand_tabs = expand_tabs self.replace_whitespace = replace_whitespace self.fix_sentence_endings = fix_sentence_endings self.break_long_words = break_long_words self.drop_whitespace = drop_whitespace self.break_on_hyphens = break_on_hyphens self.tabsize = tabsize self.max_lines = max_lines self.placeholder = placeholder ```
With a quick scan of the top 50 or so most used python packages, *1 in 4* __init__ methods that takes arguments has the line `self.argument_i = argument_i` for every single argument, with several of them having 10+ arguments.
*Suggestion:*
A new built-in called something like `assign()` which would assign every single __init__ arg to a corresponding attribute. e.g. the snippet from above could be rewritten to: ``` def __init__(self, argument_1, argument_2, argument_3=None): assign() # other init logic goes here ```
This could alternatively be implemented as a decorator, like so ``` @assign def __init__(self, argument_1, argument_2, argument_3=None): # other init logic goes here ``` but then this requires a `pass` if no other logic is needed inside the __init__. There may also be some other syntax for this which would be even easier to use.
Is this something that others would find useful?
Thanks,
Lewis _______________________________________________ Python-ideas mailing list -- python-ideas@python.org To unsubscribe send an email to python-ideas-leave@python.org https://mail.python.org/mailman3/lists/python-ideas.python.org/ Message archived at https://mail.python.org/archives/list/python-ideas@python.org/message/VLI3DO... Code of Conduct: http://python.org/psf/codeofconduct/
I did think about data classes and although I haven't really used them much they do seem to be for a different use case, for example they don't support keyword-only args or positional-only args. I'm not sure if there are any other differences. Maybe a data class which supported kW-only args and pos-only args would suit my use case. On Mon, 4 May 2020, 21:19 Henk-Jaap Wagenaar, <wagenaarhenkjaap@gmail.com> wrote:
You are not the first to have this idea. Unless I am mistaken you might find what you are looking for in dataclasses which were added in Python 3.7:
https://docs.python.org/3/library/dataclasses.html
On Mon, 4 May 2020 at 19:06, Lewis Ball <lrjball@gmail.com> wrote:
Hi All,
First of all, if this is something which has been discussed in the past the please point me in the right direction.
*Problem:*
When creating classes in Python, I find myself writing the __init__ method in a very similar way a lot of the time, that is: ``` def __init__(self, argument_1, argument_2, argument_3=None): self.argument_1 = argument_1 self.argument_2 = argument_2 self.argument_3 = argument_3 # then maybe some other attribute setting and logic follows ```
Every argument of __init__ gets a corresponding attribute with the same name. This means that each `argument_i` has been typed 3 times, which seems overly-verbose as well as being easy to mistype. This pattern is easy to find in various popular python libraries, and in some it is actually enforced. For example, I do quite a bit of work with classifiers using the sklearn estimator API, and for various reasons sklearn enforce this pattern for an __init__ (see here <https://scikit-learn.org/stable/developers/develop.html#instantiation> if interested).
Here is an example of this pattern from the standard library (from textwrap.TextWrapper): ``` def __init__(self, width=70, initial_indent="", subsequent_indent="", expand_tabs=True, replace_whitespace=True, fix_sentence_endings=False, break_long_words=True, drop_whitespace=True, break_on_hyphens=True, tabsize=8, *, max_lines=None, placeholder=' [...]'): self.width = width self.initial_indent = initial_indent self.subsequent_indent = subsequent_indent self.expand_tabs = expand_tabs self.replace_whitespace = replace_whitespace self.fix_sentence_endings = fix_sentence_endings self.break_long_words = break_long_words self.drop_whitespace = drop_whitespace self.break_on_hyphens = break_on_hyphens self.tabsize = tabsize self.max_lines = max_lines self.placeholder = placeholder ```
With a quick scan of the top 50 or so most used python packages, *1 in 4* __init__ methods that takes arguments has the line `self.argument_i = argument_i` for every single argument, with several of them having 10+ arguments.
*Suggestion:*
A new built-in called something like `assign()` which would assign every single __init__ arg to a corresponding attribute. e.g. the snippet from above could be rewritten to: ``` def __init__(self, argument_1, argument_2, argument_3=None): assign() # other init logic goes here ```
This could alternatively be implemented as a decorator, like so ``` @assign def __init__(self, argument_1, argument_2, argument_3=None): # other init logic goes here ``` but then this requires a `pass` if no other logic is needed inside the __init__. There may also be some other syntax for this which would be even easier to use.
Is this something that others would find useful?
Thanks,
Lewis _______________________________________________ Python-ideas mailing list -- python-ideas@python.org To unsubscribe send an email to python-ideas-leave@python.org https://mail.python.org/mailman3/lists/python-ideas.python.org/ Message archived at https://mail.python.org/archives/list/python-ideas@python.org/message/VLI3DO... Code of Conduct: http://python.org/psf/codeofconduct/
I agree that dataclasses are for a slightly different use case. It looks like this could be implemented as a decorator using the functionality afforded by `inspect.signature`, though what I've come up with so far is a bit clunky because you have to account for parameters that could be positional or keyword and assigning default values for missing arguments. If this were added, I assume that static analysis tools would need to be updated to account for the assumption that each instance has attributes with the same names that appear in the `__init__` signature, and I have no idea what that would entail. It would probably pose a similar issue for automated refactoring. On Mon, May 4, 2020 at 4:48 PM Lewis Ball <lrjball@gmail.com> wrote:
I did think about data classes and although I haven't really used them much they do seem to be for a different use case, for example they don't support keyword-only args or positional-only args. I'm not sure if there are any other differences. Maybe a data class which supported kW-only args and pos-only args would suit my use case.
On Mon, 4 May 2020, 21:19 Henk-Jaap Wagenaar, <wagenaarhenkjaap@gmail.com> wrote:
You are not the first to have this idea. Unless I am mistaken you might find what you are looking for in dataclasses which were added in Python 3.7:
https://docs.python.org/3/library/dataclasses.html
On Mon, 4 May 2020 at 19:06, Lewis Ball <lrjball@gmail.com> wrote:
Hi All,
First of all, if this is something which has been discussed in the past the please point me in the right direction.
*Problem:*
When creating classes in Python, I find myself writing the __init__ method in a very similar way a lot of the time, that is: ``` def __init__(self, argument_1, argument_2, argument_3=None): self.argument_1 = argument_1 self.argument_2 = argument_2 self.argument_3 = argument_3 # then maybe some other attribute setting and logic follows ```
Every argument of __init__ gets a corresponding attribute with the same name. This means that each `argument_i` has been typed 3 times, which seems overly-verbose as well as being easy to mistype. This pattern is easy to find in various popular python libraries, and in some it is actually enforced. For example, I do quite a bit of work with classifiers using the sklearn estimator API, and for various reasons sklearn enforce this pattern for an __init__ (see here <https://scikit-learn.org/stable/developers/develop.html#instantiation> if interested).
Here is an example of this pattern from the standard library (from textwrap.TextWrapper): ``` def __init__(self, width=70, initial_indent="", subsequent_indent="", expand_tabs=True, replace_whitespace=True, fix_sentence_endings=False, break_long_words=True, drop_whitespace=True, break_on_hyphens=True, tabsize=8, *, max_lines=None, placeholder=' [...]'): self.width = width self.initial_indent = initial_indent self.subsequent_indent = subsequent_indent self.expand_tabs = expand_tabs self.replace_whitespace = replace_whitespace self.fix_sentence_endings = fix_sentence_endings self.break_long_words = break_long_words self.drop_whitespace = drop_whitespace self.break_on_hyphens = break_on_hyphens self.tabsize = tabsize self.max_lines = max_lines self.placeholder = placeholder ```
With a quick scan of the top 50 or so most used python packages, *1 in 4* __init__ methods that takes arguments has the line `self.argument_i = argument_i` for every single argument, with several of them having 10+ arguments.
*Suggestion:*
A new built-in called something like `assign()` which would assign every single __init__ arg to a corresponding attribute. e.g. the snippet from above could be rewritten to: ``` def __init__(self, argument_1, argument_2, argument_3=None): assign() # other init logic goes here ```
This could alternatively be implemented as a decorator, like so ``` @assign def __init__(self, argument_1, argument_2, argument_3=None): # other init logic goes here ``` but then this requires a `pass` if no other logic is needed inside the __init__. There may also be some other syntax for this which would be even easier to use.
Is this something that others would find useful?
Thanks,
Lewis _______________________________________________ Python-ideas mailing list -- python-ideas@python.org To unsubscribe send an email to python-ideas-leave@python.org https://mail.python.org/mailman3/lists/python-ideas.python.org/ Message archived at https://mail.python.org/archives/list/python-ideas@python.org/message/VLI3DO... Code of Conduct: http://python.org/psf/codeofconduct/
_______________________________________________ Python-ideas mailing list -- python-ideas@python.org To unsubscribe send an email to python-ideas-leave@python.org https://mail.python.org/mailman3/lists/python-ideas.python.org/ Message archived at https://mail.python.org/archives/list/python-ideas@python.org/message/SCTXSE... Code of Conduct: http://python.org/psf/codeofconduct/
I had a similar solution with a decorator using inspect.getfullargspec but it is pretty fiddly and it is pretty easy to miss some of the edge cases. Having a standard implementation would definitely take care of this. And I imagine static analysis tools would be able to cope with it, they do a good job with all of the current language features! On Mon, 4 May 2020, 22:11 Steele Farnsworth, <swfarnsworth@gmail.com> wrote:
I agree that dataclasses are for a slightly different use case.
It looks like this could be implemented as a decorator using the functionality afforded by `inspect.signature`, though what I've come up with so far is a bit clunky because you have to account for parameters that could be positional or keyword and assigning default values for missing arguments.
If this were added, I assume that static analysis tools would need to be updated to account for the assumption that each instance has attributes with the same names that appear in the `__init__` signature, and I have no idea what that would entail. It would probably pose a similar issue for automated refactoring.
On Mon, May 4, 2020 at 4:48 PM Lewis Ball <lrjball@gmail.com> wrote:
I did think about data classes and although I haven't really used them much they do seem to be for a different use case, for example they don't support keyword-only args or positional-only args. I'm not sure if there are any other differences. Maybe a data class which supported kW-only args and pos-only args would suit my use case.
On Mon, 4 May 2020, 21:19 Henk-Jaap Wagenaar, <wagenaarhenkjaap@gmail.com> wrote:
You are not the first to have this idea. Unless I am mistaken you might find what you are looking for in dataclasses which were added in Python 3.7:
https://docs.python.org/3/library/dataclasses.html
On Mon, 4 May 2020 at 19:06, Lewis Ball <lrjball@gmail.com> wrote:
Hi All,
First of all, if this is something which has been discussed in the past the please point me in the right direction.
*Problem:*
When creating classes in Python, I find myself writing the __init__ method in a very similar way a lot of the time, that is: ``` def __init__(self, argument_1, argument_2, argument_3=None): self.argument_1 = argument_1 self.argument_2 = argument_2 self.argument_3 = argument_3 # then maybe some other attribute setting and logic follows ```
Every argument of __init__ gets a corresponding attribute with the same name. This means that each `argument_i` has been typed 3 times, which seems overly-verbose as well as being easy to mistype. This pattern is easy to find in various popular python libraries, and in some it is actually enforced. For example, I do quite a bit of work with classifiers using the sklearn estimator API, and for various reasons sklearn enforce this pattern for an __init__ (see here <https://scikit-learn.org/stable/developers/develop.html#instantiation> if interested).
Here is an example of this pattern from the standard library (from textwrap.TextWrapper): ``` def __init__(self, width=70, initial_indent="", subsequent_indent="", expand_tabs=True, replace_whitespace=True, fix_sentence_endings=False, break_long_words=True, drop_whitespace=True, break_on_hyphens=True, tabsize=8, *, max_lines=None, placeholder=' [...]'): self.width = width self.initial_indent = initial_indent self.subsequent_indent = subsequent_indent self.expand_tabs = expand_tabs self.replace_whitespace = replace_whitespace self.fix_sentence_endings = fix_sentence_endings self.break_long_words = break_long_words self.drop_whitespace = drop_whitespace self.break_on_hyphens = break_on_hyphens self.tabsize = tabsize self.max_lines = max_lines self.placeholder = placeholder ```
With a quick scan of the top 50 or so most used python packages, *1 in 4* __init__ methods that takes arguments has the line `self.argument_i = argument_i` for every single argument, with several of them having 10+ arguments.
*Suggestion:*
A new built-in called something like `assign()` which would assign every single __init__ arg to a corresponding attribute. e.g. the snippet from above could be rewritten to: ``` def __init__(self, argument_1, argument_2, argument_3=None): assign() # other init logic goes here ```
This could alternatively be implemented as a decorator, like so ``` @assign def __init__(self, argument_1, argument_2, argument_3=None): # other init logic goes here ``` but then this requires a `pass` if no other logic is needed inside the __init__. There may also be some other syntax for this which would be even easier to use.
Is this something that others would find useful?
Thanks,
Lewis _______________________________________________ Python-ideas mailing list -- python-ideas@python.org To unsubscribe send an email to python-ideas-leave@python.org https://mail.python.org/mailman3/lists/python-ideas.python.org/ Message archived at https://mail.python.org/archives/list/python-ideas@python.org/message/VLI3DO... Code of Conduct: http://python.org/psf/codeofconduct/
_______________________________________________ Python-ideas mailing list -- python-ideas@python.org To unsubscribe send an email to python-ideas-leave@python.org https://mail.python.org/mailman3/lists/python-ideas.python.org/ Message archived at https://mail.python.org/archives/list/python-ideas@python.org/message/SCTXSE... Code of Conduct: http://python.org/psf/codeofconduct/
_______________________________________________ Python-ideas mailing list -- python-ideas@python.org To unsubscribe send an email to python-ideas-leave@python.org https://mail.python.org/mailman3/lists/python-ideas.python.org/ Message archived at https://mail.python.org/archives/list/python-ideas@python.org/message/3QY6NI... Code of Conduct: http://python.org/psf/codeofconduct/
On Mon, 4 May 2020 at 19:13, Lewis Ball <lrjball@gmail.com> wrote:
I had a similar solution with a decorator using inspect.getfullargspec but it is pretty fiddly and it is pretty easy to miss some of the edge cases. Having a standard implementation would definitely take care of this. And I imagine static analysis tools would be able to cope with it, they do a good job with all of the current language features!
I like this idea of having a decorator for that. Since the decorator can apply the arguments to the instance without actually fiddling into `locals` or on `__init__` code. It could also be used _with_ dataclasses, suplying the assignment boiler plate for when one wants to have an explicit __init__ method. (yes, there is post_init, but as seem recently in a thread here, it is tough to get it working cooperatively) But I suppose @autoassign def __init__(self, a, b, flag1, flag2, etcetera): ... could live in "functools" without causing great harm.
On Mon, 4 May 2020, 22:11 Steele Farnsworth, <swfarnsworth@gmail.com> wrote:
I agree that dataclasses are for a slightly different use case.
It looks like this could be implemented as a decorator using the functionality afforded by `inspect.signature`, though what I've come up with so far is a bit clunky because you have to account for parameters that could be positional or keyword and assigning default values for missing arguments.
If this were added, I assume that static analysis tools would need to be updated to account for the assumption that each instance has attributes with the same names that appear in the `__init__` signature, and I have no idea what that would entail. It would probably pose a similar issue for automated refactoring.
On Mon, May 4, 2020 at 4:48 PM Lewis Ball <lrjball@gmail.com> wrote:
I did think about data classes and although I haven't really used them much they do seem to be for a different use case, for example they don't support keyword-only args or positional-only args. I'm not sure if there are any other differences. Maybe a data class which supported kW-only args and pos-only args would suit my use case.
On Mon, 4 May 2020, 21:19 Henk-Jaap Wagenaar, <wagenaarhenkjaap@gmail.com> wrote:
You are not the first to have this idea. Unless I am mistaken you might find what you are looking for in dataclasses which were added in Python 3.7:
https://docs.python.org/3/library/dataclasses.html
On Mon, 4 May 2020 at 19:06, Lewis Ball <lrjball@gmail.com> wrote:
Hi All,
First of all, if this is something which has been discussed in the past the please point me in the right direction.
Problem:
When creating classes in Python, I find myself writing the __init__ method in a very similar way a lot of the time, that is: ``` def __init__(self, argument_1, argument_2, argument_3=None): self.argument_1 = argument_1 self.argument_2 = argument_2 self.argument_3 = argument_3 # then maybe some other attribute setting and logic follows ```
Every argument of __init__ gets a corresponding attribute with the same name. This means that each `argument_i` has been typed 3 times, which seems overly-verbose as well as being easy to mistype. This pattern is easy to find in various popular python libraries, and in some it is actually enforced. For example, I do quite a bit of work with classifiers using the sklearn estimator API, and for various reasons sklearn enforce this pattern for an __init__ (see here if interested).
Here is an example of this pattern from the standard library (from textwrap.TextWrapper): ``` def __init__(self, width=70, initial_indent="", subsequent_indent="", expand_tabs=True, replace_whitespace=True, fix_sentence_endings=False, break_long_words=True, drop_whitespace=True, break_on_hyphens=True, tabsize=8, *, max_lines=None, placeholder=' [...]'): self.width = width self.initial_indent = initial_indent self.subsequent_indent = subsequent_indent self.expand_tabs = expand_tabs self.replace_whitespace = replace_whitespace self.fix_sentence_endings = fix_sentence_endings self.break_long_words = break_long_words self.drop_whitespace = drop_whitespace self.break_on_hyphens = break_on_hyphens self.tabsize = tabsize self.max_lines = max_lines self.placeholder = placeholder ```
With a quick scan of the top 50 or so most used python packages, 1 in 4 __init__ methods that takes arguments has the line `self.argument_i = argument_i` for every single argument, with several of them having 10+ arguments.
Suggestion:
A new built-in called something like `assign()` which would assign every single __init__ arg to a corresponding attribute. e.g. the snippet from above could be rewritten to: ``` def __init__(self, argument_1, argument_2, argument_3=None): assign() # other init logic goes here ```
This could alternatively be implemented as a decorator, like so ``` @assign def __init__(self, argument_1, argument_2, argument_3=None): # other init logic goes here ``` but then this requires a `pass` if no other logic is needed inside the __init__. There may also be some other syntax for this which would be even easier to use.
Is this something that others would find useful?
Thanks,
Lewis _______________________________________________ Python-ideas mailing list -- python-ideas@python.org To unsubscribe send an email to python-ideas-leave@python.org https://mail.python.org/mailman3/lists/python-ideas.python.org/ Message archived at https://mail.python.org/archives/list/python-ideas@python.org/message/VLI3DO... Code of Conduct: http://python.org/psf/codeofconduct/
_______________________________________________ Python-ideas mailing list -- python-ideas@python.org To unsubscribe send an email to python-ideas-leave@python.org https://mail.python.org/mailman3/lists/python-ideas.python.org/ Message archived at https://mail.python.org/archives/list/python-ideas@python.org/message/SCTXSE... Code of Conduct: http://python.org/psf/codeofconduct/
_______________________________________________ Python-ideas mailing list -- python-ideas@python.org To unsubscribe send an email to python-ideas-leave@python.org https://mail.python.org/mailman3/lists/python-ideas.python.org/ Message archived at https://mail.python.org/archives/list/python-ideas@python.org/message/3QY6NI... Code of Conduct: http://python.org/psf/codeofconduct/
_______________________________________________ Python-ideas mailing list -- python-ideas@python.org To unsubscribe send an email to python-ideas-leave@python.org https://mail.python.org/mailman3/lists/python-ideas.python.org/ Message archived at https://mail.python.org/archives/list/python-ideas@python.org/message/4SQF4E... Code of Conduct: http://python.org/psf/codeofconduct/
Here - with the current inspect.Signature, it is straightforward to get a decorator that can do that covering most, if not all, corner cases, and even adding some extra functionality: https://gist.github.com/jsbueno/f689a181d50384f627b43b9b2aabe4f2 ``` from inspect import signature, Parameter from functools import wraps, partial def autoassign(func=None, *, expand_kwargs=False): if not func: return partial(autoassign, expand_kwargs=expand_kwargs) sig = signature(func) @wraps(func) def wrapper(*args, **kwargs): instance = args[0] bound_args = sig.bind(*args, **kwargs) bound_args.apply_defaults() for i, (key, value) in enumerate(bound_args.arguments.items()): if i == 0 or sig.parameters[key].kind == Parameter.POSITIONAL_ONLY: continue if expand_kwargs and sig.parameters[key].kind == Parameter.VAR_KEYWORD: for kwkey, kwvalue in value.items(): setattr(instance, kwkey, kwvalue) continue setattr(instance, key, value) return func(*args, **kwargs) return wrapper """ class A: @autoassign def __init__(self, a, b, c=3): pass a = A(1, 2) assert a.a == 1 and a.b == 2 and a.c == 3 """ ``` On Wed, 6 May 2020 at 11:11, Joao S. O. Bueno <jsbueno@python.org.br> wrote:
On Mon, 4 May 2020 at 19:13, Lewis Ball <lrjball@gmail.com> wrote:
I had a similar solution with a decorator using inspect.getfullargspec but it is pretty fiddly and it is pretty easy to miss some of the edge cases. Having a standard implementation would definitely take care of this. And I imagine static analysis tools would be able to cope with it, they do a good job with all of the current language features!
I like this idea of having a decorator for that. Since the decorator can apply the arguments to the instance without actually fiddling into `locals` or on `__init__` code.
It could also be used _with_ dataclasses, suplying the assignment boiler plate for when one wants to have an explicit __init__ method. (yes, there is post_init, but as seem recently in a thread here, it is tough to get it working cooperatively)
But I suppose
@autoassign def __init__(self, a, b, flag1, flag2, etcetera): ...
could live in "functools" without causing great harm.
On Mon, 4 May 2020, 22:11 Steele Farnsworth, <swfarnsworth@gmail.com> wrote:
I agree that dataclasses are for a slightly different use case.
It looks like this could be implemented as a decorator using the functionality afforded by `inspect.signature`, though what I've come up with so far is a bit clunky because you have to account for parameters that could be positional or keyword and assigning default values for missing arguments.
If this were added, I assume that static analysis tools would need to be updated to account for the assumption that each instance has attributes with the same names that appear in the `__init__` signature, and I have no idea what that would entail. It would probably pose a similar issue for automated refactoring.
On Mon, May 4, 2020 at 4:48 PM Lewis Ball <lrjball@gmail.com> wrote:
I did think about data classes and although I haven't really used them much they do seem to be for a different use case, for example they don't support keyword-only args or positional-only args. I'm not sure if there are any other differences. Maybe a data class which supported kW-only args and pos-only args would suit my use case.
On Mon, 4 May 2020, 21:19 Henk-Jaap Wagenaar, <wagenaarhenkjaap@gmail.com> wrote:
You are not the first to have this idea. Unless I am mistaken you might find what you are looking for in dataclasses which were added in Python 3.7:
https://docs.python.org/3/library/dataclasses.html
On Mon, 4 May 2020 at 19:06, Lewis Ball <lrjball@gmail.com> wrote:
Hi All,
First of all, if this is something which has been discussed in the past the please point me in the right direction.
Problem:
When creating classes in Python, I find myself writing the __init__ method in a very similar way a lot of the time, that is: ``` def __init__(self, argument_1, argument_2, argument_3=None): self.argument_1 = argument_1 self.argument_2 = argument_2 self.argument_3 = argument_3 # then maybe some other attribute setting and logic follows ```
Every argument of __init__ gets a corresponding attribute with the same name. This means that each `argument_i` has been typed 3 times, which seems overly-verbose as well as being easy to mistype. This pattern is easy to find in various popular python libraries, and in some it is actually enforced. For example, I do quite a bit of work with classifiers using the sklearn estimator API, and for various reasons sklearn enforce this pattern for an __init__ (see here if interested).
Here is an example of this pattern from the standard library (from textwrap.TextWrapper): ``` def __init__(self, width=70, initial_indent="", subsequent_indent="", expand_tabs=True, replace_whitespace=True, fix_sentence_endings=False, break_long_words=True, drop_whitespace=True, break_on_hyphens=True, tabsize=8, *, max_lines=None, placeholder=' [...]'): self.width = width self.initial_indent = initial_indent self.subsequent_indent = subsequent_indent self.expand_tabs = expand_tabs self.replace_whitespace = replace_whitespace self.fix_sentence_endings = fix_sentence_endings self.break_long_words = break_long_words self.drop_whitespace = drop_whitespace self.break_on_hyphens = break_on_hyphens self.tabsize = tabsize self.max_lines = max_lines self.placeholder = placeholder ```
With a quick scan of the top 50 or so most used python packages, 1 in 4 __init__ methods that takes arguments has the line `self.argument_i = argument_i` for every single argument, with several of them having 10+ arguments.
Suggestion:
A new built-in called something like `assign()` which would assign every single __init__ arg to a corresponding attribute. e.g. the snippet from above could be rewritten to: ``` def __init__(self, argument_1, argument_2, argument_3=None): assign() # other init logic goes here ```
This could alternatively be implemented as a decorator, like so ``` @assign def __init__(self, argument_1, argument_2, argument_3=None): # other init logic goes here ``` but then this requires a `pass` if no other logic is needed inside the __init__. There may also be some other syntax for this which would be even easier to use.
Is this something that others would find useful?
Thanks,
Lewis _______________________________________________ Python-ideas mailing list -- python-ideas@python.org To unsubscribe send an email to python-ideas-leave@python.org https://mail.python.org/mailman3/lists/python-ideas.python.org/ Message archived at https://mail.python.org/archives/list/python-ideas@python.org/message/VLI3DO... Code of Conduct: http://python.org/psf/codeofconduct/
_______________________________________________ Python-ideas mailing list -- python-ideas@python.org To unsubscribe send an email to python-ideas-leave@python.org https://mail.python.org/mailman3/lists/python-ideas.python.org/ Message archived at https://mail.python.org/archives/list/python-ideas@python.org/message/SCTXSE... Code of Conduct: http://python.org/psf/codeofconduct/
_______________________________________________ Python-ideas mailing list -- python-ideas@python.org To unsubscribe send an email to python-ideas-leave@python.org https://mail.python.org/mailman3/lists/python-ideas.python.org/ Message archived at https://mail.python.org/archives/list/python-ideas@python.org/message/3QY6NI... Code of Conduct: http://python.org/psf/codeofconduct/
_______________________________________________ Python-ideas mailing list -- python-ideas@python.org To unsubscribe send an email to python-ideas-leave@python.org https://mail.python.org/mailman3/lists/python-ideas.python.org/ Message archived at https://mail.python.org/archives/list/python-ideas@python.org/message/4SQF4E... Code of Conduct: http://python.org/psf/codeofconduct/
I think this looks great, I can't think of anything wrong with it. Could we put this into the standard library, so that IDEs and linters are programmed to recognise it? On Wed, May 6, 2020 at 4:55 PM Joao S. O. Bueno <jsbueno@python.org.br> wrote:
Here - with the current inspect.Signature, it is straightforward to get a decorator that can do that covering most, if not all, corner cases, and even adding some extra functionality:
https://gist.github.com/jsbueno/f689a181d50384f627b43b9b2aabe4f2
```
from inspect import signature, Parameter from functools import wraps, partial
def autoassign(func=None, *, expand_kwargs=False):
if not func: return partial(autoassign, expand_kwargs=expand_kwargs) sig = signature(func) @wraps(func) def wrapper(*args, **kwargs): instance = args[0] bound_args = sig.bind(*args, **kwargs) bound_args.apply_defaults() for i, (key, value) in enumerate(bound_args.arguments.items()): if i == 0 or sig.parameters[key].kind == Parameter.POSITIONAL_ONLY: continue if expand_kwargs and sig.parameters[key].kind == Parameter.VAR_KEYWORD: for kwkey, kwvalue in value.items(): setattr(instance, kwkey, kwvalue) continue setattr(instance, key, value) return func(*args, **kwargs) return wrapper
""" class A: @autoassign def __init__(self, a, b, c=3): pass
a = A(1, 2) assert a.a == 1 and a.b == 2 and a.c == 3 """
```
On Wed, May 6, 2020 at 1:44 PM Alex Hall <alex.mojaki@gmail.com> wrote:
I think this looks great, I can't think of anything wrong with it.
Could we put this into the standard library, so that IDEs and linters are programmed to recognise it?
If it does cover the majority of corner cases, I think this is a great thing to consider. However on the other hand, wouldn't there be an advantage for the user to be able to make adjustments to the arguments before passing them along, and to be able to control WHEN the autoassign action occurs? Isn't this a very common type thing to do? Using the same example: class A: @autoassign def __init__(self, a, b, c=3): b = MyEnum(b) In the example above, self.b is assigned the value of b, not Enum(b). And even if you called-- or gave the option to call-- func(*args, **kwargs) first, autoassign still wouldn't know that you want to modify the supplied parameter value. It seems to me like it would be more useful to be able to have access to some sort of partial namespace object, containing the objects that were passed to the function, that could then be passed along.... something like this: class A: def __init__(self, a, b, c=3): b = MyEnum(b) autoassign(A.get_partial_namespace()) The get_partial_namespace() method would basically be the same as locals(), except: 1. the name bound to the object that called the function is excluded (self) and 2. any other names that were not part of the call of the function are excluded. --- Ricky. "I've never met a Kentucky man who wasn't either thinking about going home or actually going home." - Happy Chandler
On Wed, May 6, 2020 at 8:08 PM Ricky Teachey <ricky@teachey.org> wrote:
On Wed, May 6, 2020 at 1:44 PM Alex Hall <alex.mojaki@gmail.com> wrote:
I think this looks great, I can't think of anything wrong with it.
Could we put this into the standard library, so that IDEs and linters are programmed to recognise it?
If it does cover the majority of corner cases, I think this is a great thing to consider.
However on the other hand, wouldn't there be an advantage for the user to be able to make adjustments to the arguments before passing them along, and to be able to control WHEN the autoassign action occurs? Isn't this a very common type thing to do?
Using the same example:
class A: @autoassign def __init__(self, a, b, c=3): b = MyEnum(b)
In the example above, self.b is assigned the value of b, not Enum(b). And even if you called-- or gave the option to call-- func(*args, **kwargs) first, autoassign still wouldn't know that you want to modify the supplied parameter value.
But you could just write `self.b = MyEnum(b)`, and it would overwrite the auto-assigned `self.b = b`.
It seems to me like it would be more useful to be able to have access to some sort of partial namespace object, containing the objects that were passed to the function, that could then be passed along.... something like this:
class A: def __init__(self, a, b, c=3): b = MyEnum(b) autoassign(A.get_partial_namespace())
The get_partial_namespace() method would basically be the same as locals(), except: 1. the name bound to the object that called the function is excluded (self) and 2. any other names that were not part of the call of the function are excluded.
This sounds very similar to Steven's parameters() proposal.
In the example above, self.b is assigned the value of b, not Enum(b). And even if you called-- or gave the option to call-- func(*args, **kwargs) first, autoassign still wouldn't know that you want to modify the supplied parameter value.
But you could just write `self.b = MyEnum(b)`, and it would overwrite the auto-assigned `self.b = b`.
I.... didn't think of that. In that case, though, you're assigning self.b twice. If it is a descriptor, that might not be desirable and result in all kinds of side effects.
The get_partial_namespace() method would basically be the same as locals(), except: 1. the name bound to the object that called the function is excluded (self) and 2. any other names that were not part of the call of the function are excluded.
This sounds very similar to Steven's parameters() proposal. It might be; I'm being a bad interlocutor and jumping in without having read the entire discussion as thoroughly as I should. Apologies. --- Ricky. "I've never met a Kentucky man who wasn't either thinking about going home or actually going home." - Happy Chandler
On Wed, May 6, 2020 at 8:49 PM Ricky Teachey <ricky@teachey.org> wrote:
In the example above, self.b is assigned the value of b, not Enum(b). And even if you called-- or gave the option to call-- func(*args, **kwargs) first, autoassign still wouldn't know that you want to modify the supplied parameter value.
But you could just write `self.b = MyEnum(b)`, and it would overwrite the auto-assigned `self.b = b`.
I.... didn't think of that. In that case, though, you're assigning self.b twice. If it is a descriptor, that might not be desirable and result in all kinds of side effects.
1. The combination of a non-idempotent descriptor and wanting to customise the input in `__init__` seems very rare. Since this is a convenience, I think it's fine to not support every use case. 2. The problem can be solved by putting the customisation in the descriptor, which is probably a good idea anyway. 3. A user would have to be particularly careless to do this by mistake and cause actual damage. It should be obvious from both a brief description of the decorator and some basic experimentation that it assigns to descriptors. A simple test of the user's actual code would also likely reveal this.
In the example above, self.b is assigned the value of b, not Enum(b). And
even if you called-- or gave the option to call-- func(*args, **kwargs) first, autoassign still wouldn't know that you want to modify the supplied parameter value.
But you could just write `self.b = MyEnum(b)`, and it would overwrite the auto-assigned `self.b = b`.
I.... didn't think of that. In that case, though, you're assigning self.b twice. If it is a descriptor, that might not be desirable and result in all kinds of side effects.
1. The combination of a non-idempotent descriptor and wanting to customise the input in `__init__` seems very rare. Since this is a convenience, I think it's fine to not support every use case. 2. The problem can be solved by putting the customisation in the descriptor, which is probably a good idea anyway. 3. A user would have to be particularly careless to do this by mistake and cause actual damage. It should be obvious from both a brief description of the decorator and some basic experimentation that it assigns to descriptors. A simple test of the user's actual code would also likely reveal this.
Good arguments. I'm not-not convinced.
Joao S. O. Bueno wrote:
Here - with the current inspect.Signature, it is straightforward to get a decorator that can do that covering most, if not all, corner cases, and even adding some extra functionality: https://gist.github.com/jsbueno/f689a181d50384f627b43b9b2aabe4f2
from inspect import signature, Parameter from functools import wraps, partial
def autoassign(func=None, *, expand_kwargs=False):
if not func: return partial(autoassign, expand_kwargs=expand_kwargs) sig = signature(func) @wraps(func) def wrapper(*args, **kwargs): instance = args[0] bound_args = sig.bind(*args, **kwargs) bound_args.apply_defaults() for i, (key, value) in enumerate(bound_args.arguments.items()): if i == 0 or sig.parameters[key].kind == Parameter.POSITIONAL_ONLY: continue if expand_kwargs and sig.parameters[key].kind == Parameter.VAR_KEYWORD: for kwkey, kwvalue in value.items(): setattr(instance, kwkey, kwvalue) continue setattr(instance, key, value) return func(*args, **kwargs) return wrapper
""" class A: @autoassign def __init__(self, a, b, c=3): pass
a = A(1, 2) assert a.a == 1 and a.b == 2 and a.c == 3 """
Is there a good reason to exclude positional only args from this? I imagine if you are passing them to init you still want them to be treated internally in the same way as the other args.
Could we put this into the standard library, so that IDEs and linters are programmed to recognise it?
I agree, without this being recognised by linters/IDEs any attrs will show with ugly warnings which would stop anyone from using it.
Hello everyone, I am Pablo from Argentina! This is my first email here, so just let me know if I am missing anything that is of use in this list, such as presenting myself. As for this topic in particular: Although I might use it a lot (more than I would like to admit), I don't like this feature. First of all, I think it favors not-very-pretty code, effectively reducing the cost you pay for writing too-many-argument functions. It'd also obfuscate the code, and here I would like to quote the Zen of Python "explicit is better than implicit". It looks kind of similar (I know it has nothing to do in practice, but I think in mindset) to creating a dictionary of parameters that you pass to the function in order to avoid writing multiple args. If you are thinking of doing that, maybe the problem is that you are using a wrong design, and a programming language should "punish" it somehow. On Wed, May 6, 2020 at 6:55 PM Lewis Ball <lrjball@gmail.com> wrote:
Joao S. O. Bueno wrote:
Here - with the current inspect.Signature, it is straightforward to get a decorator that can do that covering most, if not all, corner cases, and even adding some extra functionality: https://gist.github.com/jsbueno/f689a181d50384f627b43b9b2aabe4f2
from inspect import signature, Parameter from functools import wraps, partial
def autoassign(func=None, *, expand_kwargs=False):
if not func: return partial(autoassign, expand_kwargs=expand_kwargs) sig = signature(func) @wraps(func) def wrapper(*args, **kwargs): instance = args[0] bound_args = sig.bind(*args, **kwargs) bound_args.apply_defaults() for i, (key, value) in enumerate(bound_args.arguments.items()): if i == 0 or sig.parameters[key].kind == Parameter.POSITIONAL_ONLY: continue if expand_kwargs and sig.parameters[key].kind == Parameter.VAR_KEYWORD: for kwkey, kwvalue in value.items(): setattr(instance, kwkey, kwvalue) continue setattr(instance, key, value) return func(*args, **kwargs) return wrapper
""" class A: @autoassign def __init__(self, a, b, c=3): pass
a = A(1, 2) assert a.a == 1 and a.b == 2 and a.c == 3 """
Is there a good reason to exclude positional only args from this? I imagine if you are passing them to init you still want them to be treated internally in the same way as the other args.
Could we put this into the standard library, so that IDEs and linters are programmed to recognise it?
I agree, without this being recognised by linters/IDEs any attrs will show with ugly warnings which would stop anyone from using it. _______________________________________________ Python-ideas mailing list -- python-ideas@python.org To unsubscribe send an email to python-ideas-leave@python.org https://mail.python.org/mailman3/lists/python-ideas.python.org/ Message archived at https://mail.python.org/archives/list/python-ideas@python.org/message/IMBONG... Code of Conduct: http://python.org/psf/codeofconduct/
On Wed, 6 May 2020 at 18:56, Lewis Ball <lrjball@gmail.com> wrote:
Joao S. O. Bueno wrote:
Here - with the current inspect.Signature, it is straightforward to get a decorator that can do that covering most, if not all, corner cases, and even adding some extra functionality: https://gist.github.com/jsbueno/f689a181d50384f627b43b9b2aabe4f2
from inspect import signature, Parameter from functools import wraps, partial
def autoassign(func=None, *, expand_kwargs=False):
if not func: return partial(autoassign, expand_kwargs=expand_kwargs) sig = signature(func) @wraps(func) def wrapper(*args, **kwargs): instance = args[0] bound_args = sig.bind(*args, **kwargs) bound_args.apply_defaults() for i, (key, value) in enumerate(bound_args.arguments.items()): if i == 0 or sig.parameters[key].kind == Parameter.POSITIONAL_ONLY: continue if expand_kwargs and sig.parameters[key].kind == Parameter.VAR_KEYWORD: for kwkey, kwvalue in value.items(): setattr(instance, kwkey, kwvalue) continue setattr(instance, key, value) return func(*args, **kwargs) return wrapper
""" class A: @autoassign def __init__(self, a, b, c=3): pass
a = A(1, 2) assert a.a == 1 and a.b == 2 and a.c == 3 """
Is there a good reason to exclude positional only args from this? I imagine if you are passing them to init you still want them to be treated internally in the same way as the other args.
I think he idea of positional only args is that you don't want whoever is calling you to care about the variable names they will have inside your code whatsoever. If you don't care about the name - (ideally, introspection tools should even hide this name, if they have a suitable notation) - you can't care about the attribute that is created. Even if they are to be stored as passed in attributes, the semantics for them would be of "private" attributes - for which one can do the assignment manually.
Could we put this into the standard library, so that IDEs and linters are programmed to recognise it?
Some people on this thread seem to have liked this approach - of course I'd like and help getting this into the stdlib if it has support. And yes, you come to an interesting point there - while this could easily rest in pypi - linters and ide's might complain about "attributes not being assigned in __init__" Otherwise, I can get a Pypi package with this, and a few other bells and whistles along.
I agree, without this being recognised by linters/IDEs any attrs will show with ugly warnings which would stop anyone from using it. _______________________________________________ Python-ideas mailing list -- python-ideas@python.org To unsubscribe send an email to python-ideas-leave@python.org https://mail.python.org/mailman3/lists/python-ideas.python.org/ Message archived at https://mail.python.org/archives/list/python-ideas@python.org/message/IMBONG... Code of Conduct: http://python.org/psf/codeofconduct/
Although I might use it a lot (more than I would like to admit), I don't like this feature. First of all, I think it favors not-very-pretty code, effectively reducing the cost you pay for writing too-many-argument functions. It'd also obfuscate the code, and here I would like to quote the Zen of Python "explicit is better than implicit". Yeah I get what you are saying, although in my opinion having to write each argument name 3 times in the __init__ makes code harder to read and obfuscated the code more than this would. Also, there are times when too-many-argument functions are necessary, and several examples of these can be found in the stdlib.
I think he idea of positional only args is that you don't want whoever is calling you to care about the variable names they will have inside your code whatsoever.
I was thinking that excluding them might be more confusing though, and even not explicitly named when passed, they are probably set to an arribute with the same name as the arg like most other args. Either way, these minor details can be worried about if this becomes popular. I can't see this being used if it is implemented outside of stdlib, because the warning from linter would drive people mad. I suppose it needs to either be added to stdlib or forgotten about.
On Wed, 6 May 2020 at 15:08, Ricky Teachey <ricky@teachey.org> wrote:
On Wed, May 6, 2020 at 1:44 PM Alex Hall <alex.mojaki@gmail.com> wrote:
I think this looks great, I can't think of anything wrong with it.
Could we put this into the standard library, so that IDEs and linters are programmed to recognise it?
If it does cover the majority of corner cases, I think this is a great thing to consider.
However on the other hand, wouldn't there be an advantage for the user to be able to make adjustments to the arguments before passing them along, and to be able to control WHEN the autoassign action occurs? Isn't this a very common type thing to do?
It cn be done, but we would be stepping away from "KISS" - and maybe, if such control is needed ina certain class, auto-assigning won't be a thing for it. I guess the default being that arguments are already assigned to the instance when __init__ starts would be the most common use case. And if adjusts and transforms are needed, they can be done inside __init__ as usual, the original assignment will just be overwritten. In the case the class features attributes that trigger side-effects on assignment, then, unless you want them as passed, maybe auto-assigment should not be used.
Using the same example:
class A: @autoassign def __init__(self, a, b, c=3): b = MyEnum(b)
In the example above, self.b is assigned the value of b, not Enum(b).
Yes - that is a pattern I need a lot. Due to the considerations above, I think the better approach would be to extend the autoassign to pick this ("cast") transform from parameter annotations or from optional parameters to "@autoassign"
And even if you called-- or gave the option to call-- func(*args, **kwargs) first, autoassign still wouldn't know that you want to modify the supplied parameter value. It seems to me like it would be more useful to be able to have access to some sort of partial namespace object, containing the objects that were passed to the function, that could then be passed along.... something like this:
class A: def __init__(self, a, b, c=3): b = MyEnum(b) autoassign(A.get_partial_namespace())
The get_partial_namespace() method would basically be the same as locals(), except: 1. the name bound to the object that called the function is excluded (self) and 2. any other names that were not part of the call of the function are excluded. Yes, a mechanism to allow the actall assignment to the instancs to be made in the middle of "__init__" is feasible, and even something to make cause the assignements to take place on the return of "__init__" - but them, as I noted above, we are complicating things -
if we accept that most such transforms of parameters are a "cast" like thing - (and as noted, I need this pattern a lot) - maybe use annotations for that, or, to avoid super-charge annotations with even more semantics, just allow these 'transforms' to be indicated as parameters to auto-assign. Ah - please tell me if something like this makes sense: ``` import typing as T dev convert_to_enum(val: T.Union[MyEnum, int])->MyEnum: return MyEnum(val) class A: @autoassign(transforms={"b": convert_to_enum}) def __init__(self, a: T.any, b: MyEnum, c: int=3): #self.b is already assigned as a guaranteed 'MyEnum' ``` In that way, one could eventually come up with a static linter/whatever that could understand that. (And that would be a whole new level of complexity - the linter would have to do a lot of twists to account for that). Typing annotations apart, would the "transforms" parameter suffice for what you are asking for? It would work for the cases I need it (in my largest personal project, I want to auto-promote some arguments to "Point" and others to to "Color" objects, and allow those to be called by passing 2-tuples and 3-tuples respectively) As for the typing, on a second tough, in the case above, declaring the instance attributes with annotations normally would work - all the linters (including mypy) would need to do would be to "trust" @autoassign (it is that, or follow the 'transforms' argument) ``` class A: a: T.any b: MyEnum c: int @autoassign(cast_arguments=True) def __init__(self, a: T.any, b: T.Union[int, MyEnum], c: int): # everything already converted ``` This would require some extra code in autoassign, so that it would have to "know" how to cast arguments into the annotated attribute types. This would be nice, but I am afraid it would be "too much" for a simple stdlib inclusion. (One thing is if the annotation is a callable type that could do the conversion by itself, another is if the annotation is something like `b : T.List[T.Union[int, float]]` The "transforms" parameter OTOH seems to be feasible.
--- Ricky.
"I've never met a Kentucky man who wasn't either thinking about going home or actually going home." - Happy Chandler
So, this thread has stalled, but I saw no contrary opinions to a decorator like this. I think the plain version (not treating positional only arguments differently, no partially-initialized namespace, no annotations based auto-argument cast) could make it into a bpo - what do you say? On Wed, 6 May 2020 at 11:52, Joao S. O. Bueno <jsbueno@python.org.br> wrote:
Here - with the current inspect.Signature, it is straightforward to get a decorator that can do that covering most, if not all, corner cases, and even adding some extra functionality:
https://gist.github.com/jsbueno/f689a181d50384f627b43b9b2aabe4f2
```
from inspect import signature, Parameter from functools import wraps, partial
def autoassign(func=None, *, expand_kwargs=False):
if not func: return partial(autoassign, expand_kwargs=expand_kwargs) sig = signature(func) @wraps(func) def wrapper(*args, **kwargs): instance = args[0] bound_args = sig.bind(*args, **kwargs) bound_args.apply_defaults() for i, (key, value) in enumerate(bound_args.arguments.items()): if i == 0 or sig.parameters[key].kind == Parameter.POSITIONAL_ONLY: continue if expand_kwargs and sig.parameters[key].kind == Parameter.VAR_KEYWORD: for kwkey, kwvalue in value.items(): setattr(instance, kwkey, kwvalue) continue setattr(instance, key, value) return func(*args, **kwargs) return wrapper
""" class A: @autoassign def __init__(self, a, b, c=3): pass
a = A(1, 2) assert a.a == 1 and a.b == 2 and a.c == 3 """
```
On Wed, 6 May 2020 at 11:11, Joao S. O. Bueno <jsbueno@python.org.br> wrote:
On Mon, 4 May 2020 at 19:13, Lewis Ball <lrjball@gmail.com> wrote:
I had a similar solution with a decorator using inspect.getfullargspec but it is pretty fiddly and it is pretty easy to miss some of the edge cases. Having a standard implementation would definitely take care of this. And I imagine static analysis tools would be able to cope with it, they do a good job with all of the current language features!
I like this idea of having a decorator for that. Since the decorator can apply the arguments to the instance without actually fiddling into `locals` or on `__init__` code.
It could also be used _with_ dataclasses, suplying the assignment boiler plate for when one wants to have an explicit __init__ method. (yes, there is post_init, but as seem recently in a thread here, it is tough to get it working cooperatively)
But I suppose
@autoassign def __init__(self, a, b, flag1, flag2, etcetera): ...
could live in "functools" without causing great harm.
On Mon, 4 May 2020, 22:11 Steele Farnsworth, <swfarnsworth@gmail.com> wrote:
I agree that dataclasses are for a slightly different use case.
It looks like this could be implemented as a decorator using the functionality afforded by `inspect.signature`, though what I've come up with so far is a bit clunky because you have to account for parameters that could be positional or keyword and assigning default values for missing arguments.
If this were added, I assume that static analysis tools would need to be updated to account for the assumption that each instance has attributes with the same names that appear in the `__init__` signature, and I have no idea what that would entail. It would probably pose a similar issue for automated refactoring.
On Mon, May 4, 2020 at 4:48 PM Lewis Ball <lrjball@gmail.com> wrote:
I did think about data classes and although I haven't really used them much they do seem to be for a different use case, for example they don't support keyword-only args or positional-only args. I'm not sure if there are any other differences. Maybe a data class which supported kW-only args and pos-only args would suit my use case.
On Mon, 4 May 2020, 21:19 Henk-Jaap Wagenaar, <wagenaarhenkjaap@gmail.com> wrote:
You are not the first to have this idea. Unless I am mistaken you might find what you are looking for in dataclasses which were added in Python 3.7:
https://docs.python.org/3/library/dataclasses.html
On Mon, 4 May 2020 at 19:06, Lewis Ball <lrjball@gmail.com> wrote: > > Hi All, > > First of all, if this is something which has been discussed in the past the please point me in the right direction. > > Problem: > > When creating classes in Python, I find myself writing the __init__ method in a very similar way a lot of the time, that is: > ``` > def __init__(self, argument_1, argument_2, argument_3=None): > self.argument_1 = argument_1 > self.argument_2 = argument_2 > self.argument_3 = argument_3 > # then maybe some other attribute setting and logic follows > ``` > > Every argument of __init__ gets a corresponding attribute with the same name. This means that each `argument_i` has been typed 3 times, which seems overly-verbose as well as being easy to mistype. This pattern is easy to find in various popular python libraries, and in some it is actually enforced. For example, I do quite a bit of work with classifiers using the sklearn estimator API, and for various reasons sklearn enforce this pattern for an __init__ (see here if interested). > > Here is an example of this pattern from the standard library (from textwrap.TextWrapper): > ``` > def __init__(self, > width=70, > initial_indent="", > subsequent_indent="", > expand_tabs=True, > replace_whitespace=True, > fix_sentence_endings=False, > break_long_words=True, > drop_whitespace=True, > break_on_hyphens=True, > tabsize=8, > *, > max_lines=None, > placeholder=' [...]'): > self.width = width > self.initial_indent = initial_indent > self.subsequent_indent = subsequent_indent > self.expand_tabs = expand_tabs > self.replace_whitespace = replace_whitespace > self.fix_sentence_endings = fix_sentence_endings > self.break_long_words = break_long_words > self.drop_whitespace = drop_whitespace > self.break_on_hyphens = break_on_hyphens > self.tabsize = tabsize > self.max_lines = max_lines > self.placeholder = placeholder > ``` > > With a quick scan of the top 50 or so most used python packages, 1 in 4 __init__ methods that takes arguments has the line `self.argument_i = argument_i` for every single argument, with several of them having 10+ arguments. > > Suggestion: > > A new built-in called something like `assign()` which would assign every single __init__ arg to a corresponding attribute. e.g. the snippet from above could be rewritten to: > ``` > def __init__(self, argument_1, argument_2, argument_3=None): > assign() > # other init logic goes here > ``` > > This could alternatively be implemented as a decorator, like so > ``` > @assign > def __init__(self, argument_1, argument_2, argument_3=None): > # other init logic goes here > ``` > but then this requires a `pass` if no other logic is needed inside the __init__. There may also be some other syntax for this which would be even easier to use. > > Is this something that others would find useful? > > Thanks, > > Lewis > _______________________________________________ > Python-ideas mailing list -- python-ideas@python.org > To unsubscribe send an email to python-ideas-leave@python.org > https://mail.python.org/mailman3/lists/python-ideas.python.org/ > Message archived at https://mail.python.org/archives/list/python-ideas@python.org/message/VLI3DO... > Code of Conduct: http://python.org/psf/codeofconduct/
_______________________________________________ Python-ideas mailing list -- python-ideas@python.org To unsubscribe send an email to python-ideas-leave@python.org https://mail.python.org/mailman3/lists/python-ideas.python.org/ Message archived at https://mail.python.org/archives/list/python-ideas@python.org/message/SCTXSE... Code of Conduct: http://python.org/psf/codeofconduct/
_______________________________________________ Python-ideas mailing list -- python-ideas@python.org To unsubscribe send an email to python-ideas-leave@python.org https://mail.python.org/mailman3/lists/python-ideas.python.org/ Message archived at https://mail.python.org/archives/list/python-ideas@python.org/message/3QY6NI... Code of Conduct: http://python.org/psf/codeofconduct/
_______________________________________________ Python-ideas mailing list -- python-ideas@python.org To unsubscribe send an email to python-ideas-leave@python.org https://mail.python.org/mailman3/lists/python-ideas.python.org/ Message archived at https://mail.python.org/archives/list/python-ideas@python.org/message/4SQF4E... Code of Conduct: http://python.org/psf/codeofconduct/
Thanks for the support Joao, obviously I would be keen to have it in. Happy to help out with the PRs and reviews etc. if it does go ahead
On 5/16/2020 2:35 PM, Joao S. O. Bueno wrote:
So, this thread has stalled, but I saw no contrary opinions to a decorator like this.
I think the plain version (not treating positional only arguments differently, no partially-initialized namespace, no annotations based auto-argument cast) could make it into a bpo -
what do you say?
Has anyone looked at prior art? I didn't see much on PyPI, but there's this from 12 years ago: http://code.activestate.com/recipes/551763/ . It also has pointers to other versions. It was linked to from https://stackoverflow.com/questions/3652851/what-is-the-best-way-to-do-autom... . I'm sure the techniques available have improved since then. But my question is: If this technique has been available for 12 years, why is it not more popular? Eric
On Sat, May 16, 2020 at 10:07 PM Eric V. Smith <eric@trueblade.com> wrote:
But my question is: If this technique has been available for 12 years, why is it not more popular?
Because linters will not recognise a third party solution. Personally I've written code like: ``` def __init__(self, **kwargs): self.__dict__.update(kwargs) ``` and regretted it later when my IDE didn't know about any of the attributes. This particular proposal is not necessarily the best solution to this problem. Steven's parameters() function or a shortcut for keyword arguments with the same name would also help. But it needs to be something standard within Python. This solution doesn't require any new language features, which might mean it has a better chance than other proposals.
Perhaps we should take the energy that is going into this thread and direct it towards supporting keyword-only arguments in dataclasses, a discussion which is apparently struggling: https://github.com/python/cpython/pull/6238#issuecomment-584579729 On Mon, May 4, 2020 at 10:49 PM Lewis Ball <lrjball@gmail.com> wrote:
I did think about data classes and although I haven't really used them much they do seem to be for a different use case, for example they don't support keyword-only args or positional-only args. I'm not sure if there are any other differences. Maybe a data class which supported kW-only args and pos-only args would suit my use case.
On Mon, 4 May 2020, 21:19 Henk-Jaap Wagenaar, <wagenaarhenkjaap@gmail.com> wrote:
You are not the first to have this idea. Unless I am mistaken you might find what you are looking for in dataclasses which were added in Python 3.7:
On Tue, May 05, 2020 at 02:55:24PM +0200, Alex Hall wrote:
Perhaps we should take the energy that is going into this thread and direct it towards supporting keyword-only arguments in dataclasses, a discussion which is apparently struggling:
Why? How are dataclasses relevant (don't assume it is as obvious to everyone as it may be to you)? I'm especially interested in how dataclasses may help me solve the DRY problem for module-level functions: def function(spam, eggs, cheese, aardvark): do stuff call _private_function(spam, eggs, cheese, aardvark) since this bites me about twice as often as the `self.spam = spam` issue. (That's not me being snarky by the way, it's a genuine question: dataclasses are a mystery to me, so I don't know what they can and can't do.) -- Steven
On Tue, May 5, 2020 at 3:14 PM Steven D'Aprano <steve@pearwood.info> wrote:
On Tue, May 05, 2020 at 02:55:24PM +0200, Alex Hall wrote:
Perhaps we should take the energy that is going into this thread and direct it towards supporting keyword-only arguments in dataclasses, a discussion which is apparently struggling:
Why? How are dataclasses relevant (don't assume it is as obvious to everyone as it may be to you)? I'm especially interested in how dataclasses may help me solve the DRY problem for module-level functions:
def function(spam, eggs, cheese, aardvark): do stuff call _private_function(spam, eggs, cheese, aardvark)
since this bites me about twice as often as the `self.spam = spam` issue.
Well this thread is meant to be about assigning attributes in `__init__`, which dataclasses do quite well, and OP specifically said (which I quoted) "Maybe a data class which supported kW-only args and pos-only args would suit my use case." Solving that problem seems quite attainable. Your idea for parameters() seems pretty good, but it's an ambitious proposal which sounds like it belongs in a new thread. That's just my opinion though, I'm pretty new here and don't know how this stuff works. I think attempting to solve the larger problem of repeating names (both in your example and the self.spam=spam problem) is best done with a shortcut for same-named parameters as we've been discussing recently, as it's more general and flexible. dataclasses are a mystery to me, so I don't know what they can and can't
do.)
This is interesting to me. When I first came across attrs I thought it was awesome, but I've never used it. When I heard about dataclasses I was excited and read all about them, but didn't use them for a long time. I've still barely used them. I know others have similar experiences. I wonder why they look so good on paper but just aren't used that much?
On Tue, 5 May 2020 23:06:39 +1000 Steven D'Aprano <steve@pearwood.info> wrote:
... help me solve the DRY problem for module-level functions:
def function(spam, eggs, cheese, aardvark): do stuff call _private_function(spam, eggs, cheese, aardvark)
since this bites me about twice as often as the `self.spam = spam` issue.
(That's not me being snarky by the way, it's a genuine question: dataclasses are a mystery to me, so I don't know what they can and can't do.)
Lisp macros have a "&whole" feature that captures the entire collection of arguments to the macro: http://clhs.lisp.se/Body/03_dd.htm Perhaps Python could adopt something similar? Unlike *args and **kwargs, &whole captures all of the parameters, not just the non-positional, non-named ones. The idea would be something like this: def function(spam, eggs, cheese, aardvark, &whole): do_stuff _private_function(&whole) which would call _private_function as function was called. No, Lisp macros are not Python function calls. No, I'm not proposing that exact syntax. No, I haven't thought it through. Yes, it borders on magic, and may impinge on implicit vs. explicit. But evidently, Steven and I share this particular pattern, although I never considered it enough of a problem to "solve." Dan -- “Atoms are not things.” – Werner Heisenberg Dan Sommers, http://www.tombstonezero.net/dan
Here is a quick and dirty proof of concept: from inspect import stack, Signature def parameters(): caller = stack()[2][0].f_globals[stack()[1][3]] sig = Signature.from_callable(caller) vars = stack()[1][0].f_locals return sig.bind(**vars).arguments def func(spam, eggs): params = parameters() for name, value in params.items(): print(name, '=', value) Calling `func(2, 3)` prints: spam = 2 eggs = 3 -- Steven
On 5 May 2020, at 15:38, Steven D'Aprano <steve@pearwood.info> wrote:
Here is a quick and dirty proof of concept:
from inspect import stack, Signature
def parameters(): caller = stack()[2][0].f_globals[stack()[1][3]] sig = Signature.from_callable(caller) vars = stack()[1][0].f_locals return sig.bind(**vars).arguments
def func(spam, eggs): params = parameters() for name, value in params.items(): print(name, '=', value)
Calling `func(2, 3)` prints:
spam = 2 eggs = 3
Is this to avoid locals()? Do you have a version that works inside a func of a class? I tried the obvious and it TB'ed: from inspect import stack, Signature def parameters(): caller = stack()[2][0].f_globals[stack()[1][3]] sig = Signature.from_callable(caller) vars = stack()[1][0].f_locals return sig.bind(**vars).arguments class X: def func(self, spam, eggs): params = parameters() for name, value in params.items(): print(name, '=', value) x = X() x.func( 1, 2 ) Traceback (most recent call last): File "args.py", line 25, in <module> x.func( 1, 2 ) File "args.py", line 20, in func params = parameters() File "args.py", line 11, in parameters caller = stack()[2][0].f_globals[stack()[1][3]] KeyError: 'func' Barry
-- Steven _______________________________________________ Python-ideas mailing list -- python-ideas@python.org To unsubscribe send an email to python-ideas-leave@python.org https://mail.python.org/mailman3/lists/python-ideas.python.org/ Message archived at https://mail.python.org/archives/list/python-ideas@python.org/message/62ZDUV... Code of Conduct: http://python.org/psf/codeofconduct/
from inspect import stack, Signature
def parameters(): caller = stack()[2][0].f_globals[stack()[1][3]] sig = Signature.from_callable(caller) vars = stack()[1][0].f_locals return sig.bind(**vars).arguments
This also doesn't work for me if a variable is assigned inside the function before parameters() is called. Although I know you weren't suggesting this was a final working version, it does at least illustrate that this isn't a trivial problem to solve. Whilst this is slightly different from my idea of having a function to set the class attributes based on the parameters to init, having something like parameters() would reduce this to a one-liner, and I'd definitely find it useful for passing to super, which has been mentioned already. Lewis On Tue, 5 May 2020, 16:18 Barry Scott, <barry@barrys-emacs.org> wrote:
On 5 May 2020, at 15:38, Steven D'Aprano <steve@pearwood.info> wrote:
Here is a quick and dirty proof of concept:
from inspect import stack, Signature
def parameters(): caller = stack()[2][0].f_globals[stack()[1][3]] sig = Signature.from_callable(caller) vars = stack()[1][0].f_locals return sig.bind(**vars).arguments
def func(spam, eggs): params = parameters() for name, value in params.items(): print(name, '=', value)
Calling `func(2, 3)` prints:
spam = 2 eggs = 3
Is this to avoid locals()?
Do you have a version that works inside a func of a class?
I tried the obvious and it TB'ed:
from inspect import stack, Signature
def parameters(): caller = stack()[2][0].f_globals[stack()[1][3]] sig = Signature.from_callable(caller) vars = stack()[1][0].f_locals return sig.bind(**vars).arguments
class X:
def func(self, spam, eggs): params = parameters() for name, value in params.items(): print(name, '=', value)
x = X() x.func( 1, 2 )
Traceback (most recent call last): File "args.py", line 25, in <module> x.func( 1, 2 ) File "args.py", line 20, in func params = parameters() File "args.py", line 11, in parameters caller = stack()[2][0].f_globals[stack()[1][3]] KeyError: 'func'
Barry
-- Steven _______________________________________________ Python-ideas mailing list -- python-ideas@python.org To unsubscribe send an email to python-ideas-leave@python.org https://mail.python.org/mailman3/lists/python-ideas.python.org/ Message archived at
https://mail.python.org/archives/list/python-ideas@python.org/message/62ZDUV...
Code of Conduct: http://python.org/psf/codeofconduct/
_______________________________________________ Python-ideas mailing list -- python-ideas@python.org To unsubscribe send an email to python-ideas-leave@python.org https://mail.python.org/mailman3/lists/python-ideas.python.org/ Message archived at https://mail.python.org/archives/list/python-ideas@python.org/message/7BVSYM... Code of Conduct: http://python.org/psf/codeofconduct/
On 05.05.20 15:35, Dan Sommers wrote:
On Tue, 5 May 2020 23:06:39 +1000 Steven D'Aprano <steve@pearwood.info> wrote:
... help me solve the DRY problem for module-level functions:
def function(spam, eggs, cheese, aardvark): do stuff call _private_function(spam, eggs, cheese, aardvark)
since this bites me about twice as often as the `self.spam = spam` issue.
(That's not me being snarky by the way, it's a genuine question: dataclasses are a mystery to me, so I don't know what they can and can't do.) Lisp macros have a "&whole" feature that captures the entire collection of arguments to the macro:
http://clhs.lisp.se/Body/03_dd.htm
Perhaps Python could adopt something similar? Unlike *args and **kwargs, &whole captures all of the parameters, not just the non-positional, non-named ones. The idea would be something like this:
def function(spam, eggs, cheese, aardvark, &whole): do_stuff _private_function(&whole)
which would call _private_function as function was called.
What about a way for overloading function signatures? The arguments are then bound to both signatures and the function has access to all the parameters. For example: def function(spam, eggs, cheese, aardvark) with (*args): ... # do stuff _private_function(*args) Calling `function(1, 2, 3, 4)` results in `spam, eggs, cheese, aardvark = 1, 2, 3, 4` and `args = (1, 2, 3, 4)`.
On Mon, May 04, 2020 at 07:01:03PM +0100, Lewis Ball wrote:
Hi All,
First of all, if this is something which has been discussed in the past the please point me in the right direction.
It certainly has been, but with no conclusion one way or another. I think people agree that it is a pain point, but there are no good ideas for what to do about it. You can probably find examples of past discussion in this mailing list's archives, and on Python-List mailing list, or comp.lang.python if you prefer Usenet. (Sorry, I don't have time at the moment to trawl the archives.)
*Problem:*
When creating classes in Python, I find myself writing the __init__ method in a very similar way a lot of the time, that is: ``` def __init__(self, argument_1, argument_2, argument_3=None): self.argument_1 = argument_1 self.argument_2 = argument_2 self.argument_3 = argument_3 # then maybe some other attribute setting and logic follows ```
Every argument of __init__ gets a corresponding attribute with the same name. This means that each `argument_i` has been typed 3 times, which seems overly-verbose as well as being easy to mistype.
Yes, and a similar anti-pattern also occurs when you have a method that calls super, or some other method, with a series of `parameter=parameter` calls. See the recent threads * Keyword arguments self-assignment * Keyword Unpacking Shortcut last month. [...]
*Suggestion:*
A new built-in called something like `assign()` which would assign every single __init__ arg to a corresponding attribute. e.g. the snippet from above could be rewritten to: ``` def __init__(self, argument_1, argument_2, argument_3=None): assign() # other init logic goes here ```
One moderately common work around for this is to use **kwargs like so: vars(self).update(**kwargs) but that doesn't work when you have named parameters. (Or if you use `__slots__`.) But we can make it work. Proposal: We should have a mechanism that collects the current function or method's parameters into a dict, similar to the way locals() returns all local variables. This mechanism could be a new function,or it could even be a magic local variable inside each function, similar to what is done to make super() work. But for the sake of this discussion, I'll assume it is a function, `parameters()`, without worrying about whether it is a built-in or imported from the `inspect` module. So given a function signature: def func(spam, eggs, cheese=None): and a call to it `func(1, 2)`, then inside the body of the function, calling `parameters()` will return the dict: {'spam': 1, 'eggs': 2, 'cheese': None} It's just a dict, its not magic in any way at all, and the caller can then process it however they like: params = parameters() del params['spam'] params['eggs'] + 1 vars(self).update(params) but I expect that (with two exceptions) most of the use-cases will involve no post-processing. The two common exceptions may be: - The first parameter to methods, usually (but not always) spelled "self" in instance methods, or "cls" in class methods. - Catch-all `*args` and `**kwargs`. If dicts supported the difference operator, that would be easy to deal with: vars(self).update( parameters() - {'self', 'args', 'kw'} ) but they don't, so I don't know how best to handle this situation. But however we deal with this, having the function simply return the parameter list and their current values (at the moment `parameters()` is called) gives the caller maximum flexibility. for name, value in parameters().items(): setattr(self, name, value) will work if you have `__slots__`, and it should be pretty obvious how to skip unwanted parameters: for name, value in parameters().items(): if name not in ('self', 'args', 'kwargs', 'spam'): setattr(self, name, value) Another pattern would be to pass on your parameters to the superclasses: super().method(**parameters()) which would reduce the need for special keyword handling. [...]
Is this something that others would find useful?
Absolutely! -- Steven
We should have a mechanism that collects the current function or method's parameters into a dict, similar to the way locals() returns all local variables.
Maybe I'm missing something here, but how about... `locals`? It works exactly as you hope: ``` def __init__(self, argument_1, argument_2, argument_3=None): for name, value in locals().items(): if name != "self": setattr(self, name, value) ``` It's a fairly common idiom to just collect `locals()` on the first line of a function or method with lots of arguments, if they're just going to be passed along or processed directly. That way, you get the flexibility of `**kwargs`, but without losing tab-completion, annotations, helpful `help`, and other introspection. ```
def foo(bar, baz=7, *spam, eggs, cheese=7, **other): ... kwargs = locals() ... # Do some stuff... ... return kwargs # Just to see what's in here! ... foo(5, eggs=6, blah='blah') {'bar': 5, 'baz': 7, 'eggs': 6, 'cheese': 7, 'spam': (), 'other': {'blah': 'blah'}}
Brandt
On Tue, May 05, 2020 at 04:05:20AM -0000, Brandt Bucher wrote:
We should have a mechanism that collects the current function or method's parameters into a dict, similar to the way locals() returns all local variables.
Maybe I'm missing something here, but how about... `locals`? It works exactly as you hope:
Using locals() is fragile. Consider: # Works fine. def method(self, args, spam, eggs, cheese): var(self).update(locals()) for x in args: print(x) # Suprise! This is broken! def method(self, args, spam, eggs, cheese): for x in args: print(x) var(self).update(locals()) (For brevity, I have ignored the "filter out self" issue.) One might not even notice that you have exposed the local x. Most people write unit tests to check for the presence of expected attributes; I don't know anyone who writes unit tests for the *absense* of unexpected attributes. The problem with locals() is that as soon as you move the call to locals out of the very first executable statement in the method, you risk contaminating it with unwanted local variables that aren't parameters. The most insidious problem will be cases that *nearly always* work: if very_rare_condition: x = something ... vars(self).update(**locals())
It's a fairly common idiom to just collect `locals()` on the first line of a function or method with lots of arguments
Indeed, but it's that requirement that it must be precisely on the first executable statement of the function that makes it fragile. -- Steven
On Tue, May 5, 2020 at 10:08 PM Steven D'Aprano <steve@pearwood.info> wrote:
On Tue, May 05, 2020 at 04:05:20AM -0000, Brandt Bucher wrote:
It's a fairly common idiom to just collect `locals()` on the first line of a function or method with lots of arguments
Indeed, but it's that requirement that it must be precisely on the first executable statement of the function that makes it fragile.
Particularly sneaky is this problem: def __init__(self, x, y, z, a, b, c): stuff = locals() spam = "ham" # what's in stuff now? Does stuff["stuff"] have a value? What about stuff["spam"] ? You can't be sure. ChrisA
On May 4, 2020, at 17:26, Steven D'Aprano <steve@pearwood.info> wrote:
Proposal:
We should have a mechanism that collects the current function or method's parameters into a dict, similar to the way locals() returns all local variables.
This mechanism could be a new function,or it could even be a magic local variable inside each function, similar to what is done to make super() work. But for the sake of this discussion, I'll assume it is a function, `parameters()`, without worrying about whether it is a built-in or imported from the `inspect` module.
Some other popular languages have something pretty similar. (And they’re not all as horrible as perl $*.) For example, in JavaScript, there’s a magic local variable named arguments whose value is (a thing that duck-types as) a list of the arguments passed to the current function’s parameters. (Not a dict, but that’s just because JS doesn’t have keyword arguments.) > function spam(x, y) { console.log(arguments) } > spam(23, 42) [23, 42] Whether it’s called arguments or parameters, and whether it’s a magic variable or a magic function, are minor bikeshedding issues (which you already raised), not serious objections to considering them parallel. And I think all of the other differences are either irrelevant, or obviously compelled by differences between the languages (e.g., Python doesn’t need a rule for how it’s different between the two different kinds of functions, because lambda doesn’t produce a different kind of function). So, I think this counts as a prior-art/cross-language argument for your proposal.
participants (15)
-
Alex Hall
-
Andrew Barnert
-
Barry Scott
-
Brandt Bucher
-
Chris Angelico
-
Dan Sommers
-
Dominik Vilsmeier
-
Eric V. Smith
-
Henk-Jaap Wagenaar
-
Joao S. O. Bueno
-
Lewis Ball
-
Pablo Alcain
-
Ricky Teachey
-
Steele Farnsworth
-
Steven D'Aprano