[Python-ideas] Re: Auto-assign attributes from __init__ arguments

2020-05-17 Thread Alex Hall
Steven D'Aprano 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.
> 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}
> [...]
> Another pattern would be to pass on your parameters to the superclasses:
> super().method(**parameters())

I took a stab at this. This should work regardless of where the function is
defined.

```
import inspect


def parameters(*, expand_kwargs=True):
frame = inspect.currentframe().f_back
code = frame.f_code
varnames = code.co_varnames
end = code.co_argcount + code.co_kwonlyargcount
result = {
name: frame.f_locals[name]
for name in varnames[:end]
}

# **kwargs
if code.co_flags & inspect.CO_VARKEYWORDS:
index = end
if code.co_flags & inspect.CO_VARARGS:
index += 1

name = varnames[index]
var = frame.f_locals[name]
if expand_kwargs:
result.update(var)
else:
result[var] = name

return result


def foo1(a, b=1, *args, c, **d):
x = 1
y = 2
print(parameters())
print(a, b, c, d)
foo2(**parameters())


def foo2(a, b=1, *args, c, **d):
print(a, b, c, d)


foo1(1, 2, 3, 4, c=5, e=6, f=7)
```

Output:

```
{'a': 1, 'b': 2, 'c': 5, 'e': 6, 'f': 7}
1 2 5 {'e': 6, 'f': 7}
1 2 5 {'e': 6, 'f': 7}
```

Note that `parameters()` intentionally doesn't contain `'args': (3, 4)`
because then `foo2(**parameters())` would see `args` as a keyword argument
within `d` instead of bound to the parameter `*args`. So naturally I tried
`foo2(*args, **parameters())` but that gave me `TypeError: foo2() got
multiple values for argument 'a'`. So if you want to pass all arguments
from one function to another, you need more than a dict if the inner
signature include `*args`, and obviously even more so if it includes
positional only parameters.
___
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/QQKU24OU67ZPPFSWJHN7OCJXR7V2Z62A/
Code of Conduct: http://python.org/psf/codeofconduct/


[Python-ideas] Re: Auto-assign attributes from __init__ arguments

2020-05-16 Thread Alex Hall
On Sat, May 16, 2020 at 10:07 PM Eric V. Smith  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.
___
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/CO4MMDU4BFTZ275VYQOV6SPNLMUUFV37/
Code of Conduct: http://python.org/psf/codeofconduct/


[Python-ideas] Re: Auto-assign attributes from __init__ arguments

2020-05-16 Thread Eric V. Smith

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-automatic-attribute-assignment-in-python-and-is-it-a 
.


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

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


[Python-ideas] Re: Auto-assign attributes from __init__ arguments

2020-05-16 Thread Lewis Ball
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
___
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/A7QFIGNCA35ZLUEEDYI4EGEQFM7HBZDJ/
Code of Conduct: http://python.org/psf/codeofconduct/


[Python-ideas] Re: Auto-assign attributes from __init__ arguments

2020-05-16 Thread Joao S. O. Bueno
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  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  wrote:
> >
> > On Mon, 4 May 2020 at 19:13, Lewis Ball  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,  
> > > 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  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, 
> > >>>  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  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, 

[Python-ideas] Re: Auto-assign attributes from __init__ arguments

2020-05-08 Thread Andrew Barnert via Python-ideas
On May 4, 2020, at 17:26, Steven D'Aprano  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.

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


[Python-ideas] Re: Auto-assign attributes from __init__ arguments

2020-05-08 Thread Joao S. O. Bueno
On Wed, 6 May 2020 at 15:08, Ricky Teachey  wrote:
>
> On Wed, May 6, 2020 at 1:44 PM Alex Hall  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 

[Python-ideas] Re: Auto-assign attributes from __init__ arguments

2020-05-07 Thread Lewis Ball
> 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.
___
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/3LEYVR6EL52EHUVIPULHBIKLKWBYDX3E/
Code of Conduct: http://python.org/psf/codeofconduct/


[Python-ideas] Re: Auto-assign attributes from __init__ arguments

2020-05-07 Thread Joao S. O. Bueno
On Wed, 6 May 2020 at 18:56, Lewis Ball  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-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/IMBONGILTSVKQ6JXYU3PEEN5XY4ICD5K/
> 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/4ADWBAY6AGIXG4K5XUUXDBMLZ7RE3PLL/
Code of Conduct: http://python.org/psf/codeofconduct/


[Python-ideas] Re: Auto-assign attributes from __init__ arguments

2020-05-07 Thread Pablo Alcain
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  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-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/IMBONGILTSVKQ6JXYU3PEEN5XY4ICD5K/
> 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/C54WVBCSL5ID4XD5MYGLOSK6V24EYHWN/
Code of Conduct: http://python.org/psf/codeofconduct/


[Python-ideas] Re: Auto-assign attributes from __init__ arguments

2020-05-06 Thread Lewis Ball
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-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/IMBONGILTSVKQ6JXYU3PEEN5XY4ICD5K/
Code of Conduct: http://python.org/psf/codeofconduct/


[Python-ideas] Re: Auto-assign attributes from __init__ arguments

2020-05-06 Thread Ricky Teachey
> 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.
___
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/WBOXPHFAG2HERLAL6RTAFKG7QHW24XYA/
Code of Conduct: http://python.org/psf/codeofconduct/


[Python-ideas] Re: Auto-assign attributes from __init__ arguments

2020-05-06 Thread Alex Hall
On Wed, May 6, 2020 at 8:49 PM Ricky Teachey  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.
___
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/MBMETIFR6OARP36JL5OYRBSBYA5PCSTT/
Code of Conduct: http://python.org/psf/codeofconduct/


[Python-ideas] Re: Auto-assign attributes from __init__ arguments

2020-05-06 Thread Ricky Teachey
>
>
>> 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
___
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/HDYWMI2FWR4VHSSELMRG27GD4J5HWQPV/
Code of Conduct: http://python.org/psf/codeofconduct/


[Python-ideas] Re: Auto-assign attributes from __init__ arguments

2020-05-06 Thread Alex Hall
On Wed, May 6, 2020 at 8:08 PM Ricky Teachey  wrote:

> On Wed, May 6, 2020 at 1:44 PM Alex Hall  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.
___
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/FH5V2AZK6EJQSLGIFAHOOSTZWRUNA322/
Code of Conduct: http://python.org/psf/codeofconduct/


[Python-ideas] Re: Auto-assign attributes from __init__ arguments

2020-05-06 Thread Ricky Teachey
On Wed, May 6, 2020 at 1:44 PM Alex Hall  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
___
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/SUAVBPXQOEELRCYNLIKIEYRPGOFMWT4E/
Code of Conduct: http://python.org/psf/codeofconduct/


[Python-ideas] Re: Auto-assign attributes from __init__ arguments

2020-05-06 Thread Alex Hall
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 
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
> """
>
>
>
> ```
>
___
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/65RKVROC6NKPI3DH2V7Y23D4MYKVIHS7/
Code of Conduct: http://python.org/psf/codeofconduct/


[Python-ideas] Re: Auto-assign attributes from __init__ arguments

2020-05-06 Thread Joao S. O. Bueno
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  wrote:
>
> On Mon, 4 May 2020 at 19:13, Lewis Ball  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,  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  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, 
> >>>  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  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, 

[Python-ideas] Re: Auto-assign attributes from __init__ arguments

2020-05-06 Thread Joao S. O. Bueno
On Mon, 4 May 2020 at 19:13, Lewis Ball  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,  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  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,  
>>> 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  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+ 
> 

[Python-ideas] Re: Auto-assign attributes from __init__ arguments

2020-05-05 Thread Alex Hall
On Tue, May 5, 2020 at 3:14 PM Steven D'Aprano  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?
___
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/SXY6QC5TMZEJ77CR3APW5AYB2GR4LB34/
Code of Conduct: http://python.org/psf/codeofconduct/


[Python-ideas] Re: Auto-assign attributes from __init__ arguments

2020-05-05 Thread Steven D'Aprano
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
___
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/ECA4EHXIPF2SIY4V667BQ3VFMT4HUAEN/
Code of Conduct: http://python.org/psf/codeofconduct/


[Python-ideas] Re: Auto-assign attributes from __init__ arguments

2020-05-05 Thread Alex Hall
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  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, 
> 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
>>
>
___
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/5Y3QLLTVT6VXGGUPTLM3SH5PKL5R36AD/
Code of Conduct: http://python.org/psf/codeofconduct/


[Python-ideas] Re: Auto-assign attributes from __init__ arguments

2020-05-05 Thread Chris Angelico
On Tue, May 5, 2020 at 10:08 PM Steven D'Aprano  wrote:
>
> On Tue, May 05, 2020 at 04:05:20AM -, 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
___
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/ZROJCADGLY4HOARP4K7OH3JJEORWNZC4/
Code of Conduct: http://python.org/psf/codeofconduct/


[Python-ideas] Re: Auto-assign attributes from __init__ arguments

2020-05-05 Thread Steven D'Aprano
On Tue, May 05, 2020 at 04:05:20AM -, 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
___
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/LG72WHK3GAKM6HZ3LGSLFXAGZRPWMPTG/
Code of Conduct: http://python.org/psf/codeofconduct/


[Python-ideas] Re: Auto-assign attributes from __init__ arguments

2020-05-04 Thread Brandt Bucher
> 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
___
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/FUH5ZS5NPR6673PNRP5BBV2W2DT7727K/
Code of Conduct: http://python.org/psf/codeofconduct/


[Python-ideas] Re: Auto-assign attributes from __init__ arguments

2020-05-04 Thread Steven D'Aprano
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
___
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/KEE3TE5ZFIQT3TG3XMTIPPROR6IGFPDB/
Code of Conduct: http://python.org/psf/codeofconduct/


[Python-ideas] Re: Auto-assign attributes from __init__ arguments

2020-05-04 Thread Lewis Ball
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,  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  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, 
>> 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  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__. 

[Python-ideas] Re: Auto-assign attributes from __init__ arguments

2020-05-04 Thread Steele Farnsworth
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  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, 
> 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  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
>>> 

[Python-ideas] Re: Auto-assign attributes from __init__ arguments

2020-05-04 Thread Lewis Ball
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, 
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  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] Re: Auto-assign attributes from __init__ arguments

2020-05-04 Thread Henk-Jaap Wagenaar
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  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/6O7NA2QVZ7IHDS3PWAZZF56OVGFTRBRN/
Code of Conduct: http://python.org/psf/codeofconduct/