On Wed, 6 May 2020 at 15:08, 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?
It cn be done, but we would be stepping away from "KISS" - and maybe, if such control is needed ina certain class, auto-assigning won't be a thing for it. I guess the default being that arguments are already assigned to the instance when __init__ starts would be the most common use case. And if adjusts and transforms are needed, they can be done inside __init__ as usual, the original assignment will just be overwritten. In the case the class features attributes that trigger side-effects on assignment, then, unless you want them as passed, maybe auto-assigment should not be used.
Using the same example:
class A: @autoassign def __init__(self, a, b, c=3): b = MyEnum(b)
In the example above, self.b is assigned the value of b, not Enum(b).
Yes - that is a pattern I need a lot. Due to the considerations above, I think the better approach would be to extend the autoassign to pick this ("cast") transform from parameter annotations or from optional parameters to "@autoassign"
And even if you called-- or gave the option to call-- func(*args, **kwargs) first, autoassign still wouldn't know that you want to modify the supplied parameter value. It seems to me like it would be more useful to be able to have access to some sort of partial namespace object, containing the objects that were passed to the function, that could then be passed along.... something like this:
class A: def __init__(self, a, b, c=3): b = MyEnum(b) autoassign(A.get_partial_namespace())
The get_partial_namespace() method would basically be the same as locals(), except: 1. the name bound to the object that called the function is excluded (self) and 2. any other names that were not part of the call of the function are excluded. Yes, a mechanism to allow the actall assignment to the instancs to be made in the middle of "__init__" is feasible, and even something to make cause the assignements to take place on the return of "__init__" - but them, as I noted above, we are complicating things -
if we accept that most such transforms of parameters are a "cast" like thing - (and as noted, I need this pattern a lot) - maybe use annotations for that, or, to avoid super-charge annotations with even more semantics, just allow these 'transforms' to be indicated as parameters to auto-assign. Ah - please tell me if something like this makes sense: ``` import typing as T dev convert_to_enum(val: T.Union[MyEnum, int])->MyEnum: return MyEnum(val) class A: @autoassign(transforms={"b": convert_to_enum}) def __init__(self, a: T.any, b: MyEnum, c: int=3): #self.b is already assigned as a guaranteed 'MyEnum' ``` In that way, one could eventually come up with a static linter/whatever that could understand that. (And that would be a whole new level of complexity - the linter would have to do a lot of twists to account for that). Typing annotations apart, would the "transforms" parameter suffice for what you are asking for? It would work for the cases I need it (in my largest personal project, I want to auto-promote some arguments to "Point" and others to to "Color" objects, and allow those to be called by passing 2-tuples and 3-tuples respectively) As for the typing, on a second tough, in the case above, declaring the instance attributes with annotations normally would work - all the linters (including mypy) would need to do would be to "trust" @autoassign (it is that, or follow the 'transforms' argument) ``` class A: a: T.any b: MyEnum c: int @autoassign(cast_arguments=True) def __init__(self, a: T.any, b: T.Union[int, MyEnum], c: int): # everything already converted ``` This would require some extra code in autoassign, so that it would have to "know" how to cast arguments into the annotated attribute types. This would be nice, but I am afraid it would be "too much" for a simple stdlib inclusion. (One thing is if the annotation is a callable type that could do the conversion by itself, another is if the annotation is something like `b : T.List[T.Union[int, float]]` The "transforms" parameter OTOH seems to be feasible.
--- Ricky.
"I've never met a Kentucky man who wasn't either thinking about going home or actually going home." - Happy Chandler