[Python-ideas] Method chaining notation

Ron Adam ron3200 at gmail.com
Tue Feb 25 09:53:49 CET 2014



On 02/24/2014 05:29 PM, Andrew Barnert wrote:
> On Feb 24, 2014, at 7:01, Ron
> Adam<ron3200 at gmail.com>  wrote:
>
>>> On 02/24/2014 01:34 AM, Andrew Barnert wrote:
>>>>>
>>>>> So, is your argument is "my code looks kind of like some
>>>>> horribly unreadable and unpythonic, but legal, code, and
>>>>> therefore it should also be legal despite being unreadable and
>>>>> unpythonic?"
>>>
>>> Wow, tough crowd here..:-)
>>>
>>> Both the separation of the '.', and the use of the already special
>>> __add__ isn't important as far as the actual suggestion is
>>> concerned.  Those are unrelated style issues.
>>>
>>> You would probably see it used more often like this...
>>>
>>> def names(defaults, pos_names, pos_args, kwds):
 >>>     return {}.=update(defaults) \
 >>>              .=update(zip(pos_names, pos_args) \
>>>              .=update(kwds)
>>>
>>> Normally .update returns None.  The reason for that is so that it's
>>> clear you are mutating an object instead of creating a new one.

> And so you can't chain it, so you can only mutate one thing in a
> statement. People keep assuming that's an accidental unwanted side
> effect of the rule, but Guido explicitly saying that he doesn't like
> method chaining, can't you imagine it's at least possible that this is
> intentional, not a bug?

Yes, I know it's an intentional design choice.

Did he ever say why he doesn't like chained methods?

I tried to look it up, and all I found was a lot of other people saying he 
doesn't.  But nothing that indicated what his reasoning for it was.


>>> By using .=, it can return self, but still maintain the clarity
>>> between mutation and non-mutation.
>>>
>>> This particular syntax is consistent with the use of OP+equal to
>>> mean mutate in place.  But you might prefer, "..", or something
>>> else.

> You're missing a key distinction here. OP+equal does not return self. In
> fact, it doesn't return_anything_, because it's not an expression at
> all, it's a statement.

I was referring to the dunder method that gets called as you noted below.


> It would be very easy to make augmented assignment an expression, and
> even easier to make it return self (after all, it's implemented by
> calling dunder methods that_do_  return self!). But it wasn't designed
> that way. Intentionally.

How else would it be designed?


>>> The other alternative is to use a function.  But it would be
>>> difficult to get the same behaviour along with the same efficiency.

> How do you think a regular function would have less efficiency than an
> operator? Operators work by doing a complex chain of lookups to find a
> function to call. Ordinary calls do a simpler lookup. They both call the
> function the same way once they find it.

Ok, you lost me with this..  what complex chain of lookups are you 
referring to?  (Besides the normal name and method resolution.)

Operators are one level above methods... And yes, so are direct method 
calls.  But the alternative I was talking about was to write a function to 
get the same behaviour with chained methods as I was describing, That adds 
another layer, so obviously it wouldn't be quite as efficient.  I wasn't 
saying functions aren't efficient.

> You're also missing the other alternative: write it Pythonically. For
> example:

Never said writing functions was bad.  I use functions all the time to make 
code nicer and cleaner.  So no, I'm not missing that.


> return merge_dicts( defaults, zip(pos_names, pos_args), kwds)
>
> It's shorter, it has less extraneous syntax, it doesn't need awkward
> backslash continuations, and it created or modifies one value in one
> place. It's easier to read, and easier to reason about. Why would you
> want to write it the other way?
>
> Of course that merge_dicts function isn't in the stdlib. Maybe it should
> be. But you can write it yourself trivially. For example:
>
> def merge_dicts(*args): return {k: v for arg in args for (k, v) in
>     dict(arg).items()}
>
> That nested comprehension might be a little too complicated; if you
> think so, you can split it into two expressions, or even write an
> explicit loop around dict.update. Whatever; this is something you write
> once and use every time you want to merge a bunch of dicts.

Much slower too.  Yes, the dict.update is nicer and quicker than the 
comprehension.

I'd probably do it this way...

      def merge_dicts(*args):
         D = {}
         for a in args:
             D.update(a)
         return D

Chaining methods isn't a do everything everywhere kind of thing.  There are 
times when it's handy and times when a function is better.

Cheers,
   Ron




More information about the Python-ideas mailing list