Suggestions: dict.flow_update and dict.__add__

SUMMARY Instead of using dict + dict, perhaps use dict.flow_update. Here, flow_update is just like update, except that it returns self. BACKGROUND There's a difference between a sorted copy of a list, and sorting the list in place. >>> items = [2, 0, 1, 9] >>> sorted(items), items ([0, 1, 2, 9], [2, 0, 1, 9]) >>> items.sort(), items (None, [0, 1, 2, 9]) In Python, mutating methods generally return None. Here, this prevents beginners thinking their code has produced a sorted copy of a list, when in fact it has done an in-place sort on the list. If they write >>> aaa = my_list.sort() they'll get a None error when they use aaa. The same goes for dict.update. This is a useful feature, particularly for beginners. It helps them think clearly, and express themselves clearly. THE PROBLEM This returning None can be a nuisance, sometimes. Suppose we have a dictionary of default values, and a dictionary of use supplied options. We wish to combine the two dictionaries, say into a new combined dictionary. One way to do this is: combined = defaults.copy() combined.update(options) But this is awkward when you're in the middle of calling a function: call_big_method( # lots of arguments, one to a line, with comments arg = combined, # Look up to see what combined is. # more arguments ) USING + There's a suggestion, that instead one extends Python so that this works: arg = defaults + options # What does '+' mean here? USING flow_update Here's another suggestion. Instead write: dict_arg = defaults.copy().flow_update(options) # Is this clearer? IMPLEMENTATION Here's an implementation, as a subclass of dict. class mydict(dict): def flow_update(self, *argv, **kwargs): self.update(*argv, **kwargs) return self def copy(self): return self.__class__(self) A DIRTY HACK Not tested, using an assignment expression. dict_arg = (tmp := defaults.copy(), tmp.update(options))[0] Not recommend. -- Jonathan

If you have to tell such a long and convoluted story to explain a name that you've picked out of the blue and that has no equivalent in other Python data types, it's probably a bad idea. If you're proposing that other mutating methods also gain a flow_XXX variant, please, no! That's like the theory of supersymmetry (SUSY) in particle physics, where ever known particle from the Standard Model would have to have a much heavier "superpartner" just to make some esoteric idea work. On Tue, Mar 5, 2019 at 12:54 AM Jonathan Fine <jfine2358@gmail.com> wrote:
-- --Guido van Rossum (python.org/~guido)

dicttoolz has functions for working with these objects; including dicttoolz.merge (which returns a reference to the merged dicts but does not mutate the arguments passed). https://toolz.readthedocs.io/en/latest/api.html#dicttoolz https://toolz.readthedocs.io/en/latest/api.html#toolz.dicttoolz.merge pyrsistent has a PRecord class with invariants and type checking that precedes dataclasses. pyrsistent also has 'freeze' and 'thaw' functions for immutability. PRecord extends PMap, which implements __add__ as self.update(arg) (which does not mutate self) https://github.com/tobgu/pyrsistent/blob/master/README.rst#precord https://github.com/tobgu/pyrsistent/blob/master/pyrsistent/_pmap.py On Tuesday, March 5, 2019, Guido van Rossum <guido@python.org> wrote:

On Tue, Mar 5, 2019 at 12:53 AM Jonathan Fine <jfine2358@gmail.com> wrote:
That violates an important convention in Python: mutating methods do not return self. We really want to preserve that convention. On the other hand, as seen in other recent threads, there is a desire for chaining operations of many sorts, so a .flow_update() that returned a new dict would provide that feature. Though I would only recommend that if it was decided that we wanted to generally support that approach for all mutable containers — which would mean adding quite a few methods. And we could then use the same naming convention for them all. I’m not sure I like “flow_” though, it’s not very commonly known jargon. -CHB -- Christopher Barker, PhD Python Language Consulting - Teaching - Scientific Software Development - Desktop GUI and Web Development - wxPython, numpy, scipy, Cython

I thank Guido and Christopher for their thoughtful comments. You certainly found some weak points. I chose the name 'flow' to match: https://en.wikipedia.org/wiki/Fluent_interface#Python Instead of my previous arg = defaults.copy().flow_update(options) one could instead from somewhere import flow, and then write arg = flow(defaults.copy()).update(options) This avoids a profusion of flow_ methods, and also the need to subclass dict. Once could of course use a different name. Perhaps 'follow' would be better. And it would work 'out the box' in other situations. Christopher might prefer the flow(obj).update approach, as it respects the convention "mutating methods do not return self." (Thank you for your clear statement, Christopher.) (Aside: For the non-experts, a word if I may about the implementation. The key point is that in Python the programmer 'owns the dot' and so the desired semantics can be implemented. We use a custom __getattribute__ .) Finally, please forgive my fictional use case. I think studying real-world use cases for dict + dict would be very helpful to the discussion. I don't recall seeing any, but I haven't looked hard. Instructive use cases should, of course, be placed in the PEP. -- Jonathan

Christopher Barker wrote:
That violates an important convention in Python: mutating methods do not return self. We really want to preserve that convention.
Smalltalk has an abbreviated way of writing a series of method calls to the same object: x doThis; doThatWith: y; doTheOther. is equivalent to x doThis. x doThatWith: y. x doTheOther. Something like this could no doubt be added to Python, but I'm not sure it would be worth the bother. Giving a short name to the recipient and then writing the calls out explicitly isn't much harder and is clearer to read, IMO. -- Greg

Do go read the recent thread about this - there is a lot there! Titled something like “fluent programming” Sorry — on a phone, kinda hard to check now. -CHB -- Christopher Barker, PhD Python Language Consulting - Teaching - Scientific Software Development - Desktop GUI and Web Development - wxPython, numpy, scipy, Cython

On Wed, Mar 6, 2019 at 9:12 AM Christopher Barker <pythonchb@gmail.com> wrote:
[...]
Sorry — on a phone, kinda hard to check now.
A point of order: if you're away from a real keyboard/screen, maybe it's better to wait. The conversation isn't real-time, and you don't win points by answering first. We could all be reminded of the goal for StackOverflow: the intent there is to create a useful artifact. While it's not quite the same for python-ideas, in the end we're trying to get to insights and (tentative) decisions, which should be long-lasting. Fewer posts may be better. NOTE: I'm not picking on you specifically Chris! I see this a lot, and I do it myself too (and regularly regret it). -- --Guido van Rossum (python.org/~guido)

Sorry about adding a few words here, i know you are all more 'advanced' programmers than me. I just wanted to ask the list to keep threads informative. Just today i decided to take the bulls by the horn and read the add dictionaries by using the + operator. Midway, i asked myself if i was getting value from reading each mail one by one. I don't think i can use the lists as a hands-on reference (was not meant to but could've been), it fits more as an nlp data sci project with data cleaning and all. I don't want an exact SO line though in the sense of absolutely no crap for beginners would be scared away. The talking of it's members sometimes do keep things lively. But for technical posts i think we can keep it to technical points and avoid the like of
if we do this some trajic things might happen
what do you mean by "trajic"?
and so on. Those create diversions in the road to understanding a topic. For discussions posts like CoC update or whatever things off code, i think there can be a little leniency. I have only neen using python since 3.4, i have a lot to catch up and learn from you all. yours, Abdur-Rahmaan Janhangeer http://www.pythonmembers.club | https://github.com/Abdur-rahmaanJ Mauritius

I've just learnt something new. Look at >>> from operator import iadd >>> lst = [1, 2, 3] >>> iadd(lst, 'hi') [1, 2, 3, 'h', 'i'] >>> lst [1, 2, 3, 'h', 'i'] This shows that the proposals dict.flow_update and dict.__iadd__ are basically the same. (I think this is quite important for understanding the attraction of fluent programming. We ALREADY like and use it, in the form of augmented assignment of mutables.) This also shows that combined = defaults.copy() combined.update(options) could, if the proposal is accepted, be written as defaults.copy().__iadd__(options) I got the idea from the withdrawn PEP (thank you, Nick Coghlan, for writing it): PEP 577 -- Augmented Assignment Expressions https://www.python.org/dev/peps/pep-0577/ -- Jonathan

On Fri, Mar 8, 2019 at 1:52 AM Jonathan Fine <jfine2358@gmail.com> wrote:
well, no -- the fact that __iadd__ acts like it does is essentially an accident of implementation, and calling __iadd__ directly is frowned upon (and I'm not totally sure if it is guaranteed to keep working that way by the language spec). And, in fact, it DOESN'T act like flow_merge method -- as it both mutates the original object, and returns itself -- which I think is a no-no in fluent programming, yes? (certainly in functional programming) In [10]: list1 = [1,2,3] In [11]: list2 = [4,5,6] In [12]: list3 = list1.__iadd__(list2) In [13]: list3 Out[13]: [1, 2, 3, 4, 5, 6] In [14]: list1 Out[14]: [1, 2, 3, 4, 5, 6] In [15]: list1 is list3 Out[15]: True This also shows that
did you mean: combined = defaults.copy().__iadd__(options) because the way you wrote it, you are making a copy, mutating it, and then throwing it away... in which case, yes it could, but it would not be recommended, and I can't see the advantage of it over: combined = defaults + options or even, if you REALLY want to use __ methods: combined = defaults.__add__(options) In [17]: list3 = list1.__add__(list2) In [18]: list1 Out[18]: [1, 2, 3] In [19]: list3 Out[19]: [1, 2, 3, 4, 5, 6]
Interestingly (to me) it was withdrawn for different reasons than what I would think -- mutating and assigning at once is dangerous. -CHB -- Christopher Barker, PhD Python Language Consulting - Teaching - Scientific Software Development - Desktop GUI and Web Development - wxPython, numpy, scipy, Cython

If you have to tell such a long and convoluted story to explain a name that you've picked out of the blue and that has no equivalent in other Python data types, it's probably a bad idea. If you're proposing that other mutating methods also gain a flow_XXX variant, please, no! That's like the theory of supersymmetry (SUSY) in particle physics, where ever known particle from the Standard Model would have to have a much heavier "superpartner" just to make some esoteric idea work. On Tue, Mar 5, 2019 at 12:54 AM Jonathan Fine <jfine2358@gmail.com> wrote:
-- --Guido van Rossum (python.org/~guido)

dicttoolz has functions for working with these objects; including dicttoolz.merge (which returns a reference to the merged dicts but does not mutate the arguments passed). https://toolz.readthedocs.io/en/latest/api.html#dicttoolz https://toolz.readthedocs.io/en/latest/api.html#toolz.dicttoolz.merge pyrsistent has a PRecord class with invariants and type checking that precedes dataclasses. pyrsistent also has 'freeze' and 'thaw' functions for immutability. PRecord extends PMap, which implements __add__ as self.update(arg) (which does not mutate self) https://github.com/tobgu/pyrsistent/blob/master/README.rst#precord https://github.com/tobgu/pyrsistent/blob/master/pyrsistent/_pmap.py On Tuesday, March 5, 2019, Guido van Rossum <guido@python.org> wrote:

On Tue, Mar 5, 2019 at 12:53 AM Jonathan Fine <jfine2358@gmail.com> wrote:
That violates an important convention in Python: mutating methods do not return self. We really want to preserve that convention. On the other hand, as seen in other recent threads, there is a desire for chaining operations of many sorts, so a .flow_update() that returned a new dict would provide that feature. Though I would only recommend that if it was decided that we wanted to generally support that approach for all mutable containers — which would mean adding quite a few methods. And we could then use the same naming convention for them all. I’m not sure I like “flow_” though, it’s not very commonly known jargon. -CHB -- Christopher Barker, PhD Python Language Consulting - Teaching - Scientific Software Development - Desktop GUI and Web Development - wxPython, numpy, scipy, Cython

I thank Guido and Christopher for their thoughtful comments. You certainly found some weak points. I chose the name 'flow' to match: https://en.wikipedia.org/wiki/Fluent_interface#Python Instead of my previous arg = defaults.copy().flow_update(options) one could instead from somewhere import flow, and then write arg = flow(defaults.copy()).update(options) This avoids a profusion of flow_ methods, and also the need to subclass dict. Once could of course use a different name. Perhaps 'follow' would be better. And it would work 'out the box' in other situations. Christopher might prefer the flow(obj).update approach, as it respects the convention "mutating methods do not return self." (Thank you for your clear statement, Christopher.) (Aside: For the non-experts, a word if I may about the implementation. The key point is that in Python the programmer 'owns the dot' and so the desired semantics can be implemented. We use a custom __getattribute__ .) Finally, please forgive my fictional use case. I think studying real-world use cases for dict + dict would be very helpful to the discussion. I don't recall seeing any, but I haven't looked hard. Instructive use cases should, of course, be placed in the PEP. -- Jonathan

Christopher Barker wrote:
That violates an important convention in Python: mutating methods do not return self. We really want to preserve that convention.
Smalltalk has an abbreviated way of writing a series of method calls to the same object: x doThis; doThatWith: y; doTheOther. is equivalent to x doThis. x doThatWith: y. x doTheOther. Something like this could no doubt be added to Python, but I'm not sure it would be worth the bother. Giving a short name to the recipient and then writing the calls out explicitly isn't much harder and is clearer to read, IMO. -- Greg

Do go read the recent thread about this - there is a lot there! Titled something like “fluent programming” Sorry — on a phone, kinda hard to check now. -CHB -- Christopher Barker, PhD Python Language Consulting - Teaching - Scientific Software Development - Desktop GUI and Web Development - wxPython, numpy, scipy, Cython

On Wed, Mar 6, 2019 at 9:12 AM Christopher Barker <pythonchb@gmail.com> wrote:
[...]
Sorry — on a phone, kinda hard to check now.
A point of order: if you're away from a real keyboard/screen, maybe it's better to wait. The conversation isn't real-time, and you don't win points by answering first. We could all be reminded of the goal for StackOverflow: the intent there is to create a useful artifact. While it's not quite the same for python-ideas, in the end we're trying to get to insights and (tentative) decisions, which should be long-lasting. Fewer posts may be better. NOTE: I'm not picking on you specifically Chris! I see this a lot, and I do it myself too (and regularly regret it). -- --Guido van Rossum (python.org/~guido)

Sorry about adding a few words here, i know you are all more 'advanced' programmers than me. I just wanted to ask the list to keep threads informative. Just today i decided to take the bulls by the horn and read the add dictionaries by using the + operator. Midway, i asked myself if i was getting value from reading each mail one by one. I don't think i can use the lists as a hands-on reference (was not meant to but could've been), it fits more as an nlp data sci project with data cleaning and all. I don't want an exact SO line though in the sense of absolutely no crap for beginners would be scared away. The talking of it's members sometimes do keep things lively. But for technical posts i think we can keep it to technical points and avoid the like of
if we do this some trajic things might happen
what do you mean by "trajic"?
and so on. Those create diversions in the road to understanding a topic. For discussions posts like CoC update or whatever things off code, i think there can be a little leniency. I have only neen using python since 3.4, i have a lot to catch up and learn from you all. yours, Abdur-Rahmaan Janhangeer http://www.pythonmembers.club | https://github.com/Abdur-rahmaanJ Mauritius

I've just learnt something new. Look at >>> from operator import iadd >>> lst = [1, 2, 3] >>> iadd(lst, 'hi') [1, 2, 3, 'h', 'i'] >>> lst [1, 2, 3, 'h', 'i'] This shows that the proposals dict.flow_update and dict.__iadd__ are basically the same. (I think this is quite important for understanding the attraction of fluent programming. We ALREADY like and use it, in the form of augmented assignment of mutables.) This also shows that combined = defaults.copy() combined.update(options) could, if the proposal is accepted, be written as defaults.copy().__iadd__(options) I got the idea from the withdrawn PEP (thank you, Nick Coghlan, for writing it): PEP 577 -- Augmented Assignment Expressions https://www.python.org/dev/peps/pep-0577/ -- Jonathan

On Fri, Mar 8, 2019 at 1:52 AM Jonathan Fine <jfine2358@gmail.com> wrote:
well, no -- the fact that __iadd__ acts like it does is essentially an accident of implementation, and calling __iadd__ directly is frowned upon (and I'm not totally sure if it is guaranteed to keep working that way by the language spec). And, in fact, it DOESN'T act like flow_merge method -- as it both mutates the original object, and returns itself -- which I think is a no-no in fluent programming, yes? (certainly in functional programming) In [10]: list1 = [1,2,3] In [11]: list2 = [4,5,6] In [12]: list3 = list1.__iadd__(list2) In [13]: list3 Out[13]: [1, 2, 3, 4, 5, 6] In [14]: list1 Out[14]: [1, 2, 3, 4, 5, 6] In [15]: list1 is list3 Out[15]: True This also shows that
did you mean: combined = defaults.copy().__iadd__(options) because the way you wrote it, you are making a copy, mutating it, and then throwing it away... in which case, yes it could, but it would not be recommended, and I can't see the advantage of it over: combined = defaults + options or even, if you REALLY want to use __ methods: combined = defaults.__add__(options) In [17]: list3 = list1.__add__(list2) In [18]: list1 Out[18]: [1, 2, 3] In [19]: list3 Out[19]: [1, 2, 3, 4, 5, 6]
Interestingly (to me) it was withdrawn for different reasons than what I would think -- mutating and assigning at once is dangerous. -CHB -- Christopher Barker, PhD Python Language Consulting - Teaching - Scientific Software Development - Desktop GUI and Web Development - wxPython, numpy, scipy, Cython
participants (7)
-
Abdur-Rahmaan Janhangeer
-
Brice Parent
-
Christopher Barker
-
Greg Ewing
-
Guido van Rossum
-
Jonathan Fine
-
Wes Turner