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 <jsbu...@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 <jsbu...@python.org.br> wrote: > > > > On Mon, 4 May 2020 at 19:13, Lewis Ball <lrjb...@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, <swfarnswo...@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 <lrjb...@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, > > >>> <wagenaarhenkj...@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 <lrjb...@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-le...@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/VLI3DOFA5VWMGJMJGRDC7JZTRKEPPZNU/ > > >>>>> Code of Conduct: http://python.org/psf/codeofconduct/ > > >>> > > >>> _______________________________________________ > > >>> Python-ideas mailing list -- python-ideas@python.org > > >>> To unsubscribe send an email to python-ideas-le...@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/SCTXSEKOWDRDGVXXOEB7JUC6WE7XKGMO/ > > >>> Code of Conduct: http://python.org/psf/codeofconduct/ > > >> > > >> _______________________________________________ > > >> Python-ideas mailing list -- python-ideas@python.org > > >> To unsubscribe send an email to python-ideas-le...@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/3QY6NIT7Y37PHKCYGJXJAONS35E3YZWH/ > > >> Code of Conduct: http://python.org/psf/codeofconduct/ > > > > > > _______________________________________________ > > > Python-ideas mailing list -- python-ideas@python.org > > > To unsubscribe send an email to python-ideas-le...@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/4SQF4E3XWQ4XKAS3DMTNYJYJBZEUQIKC/ > > > Code of Conduct: http://python.org/psf/codeofconduct/ _______________________________________________ Python-ideas mailing list -- python-ideas@python.org To unsubscribe send an email to python-ideas-le...@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/6CJNOJPLPLTBOSZ6QNK5APPINRZ4PORO/ Code of Conduct: http://python.org/psf/codeofconduct/