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/MRME6WF4TUNIERSG3DUCBNFGTXNFQFM5/ Code of Conduct: http://python.org/psf/codeofconduct/