I like method chaining (not the same as builder patterns) for code that needs to run chained operations on objects. It is not exacrly the same because each chained operation usually does more than just setting a variable.
E.g.
signal = Sine(freq=440.0, amp=1.0)
.rectify()
.gain(2.0)
.center()
.clip(0.5)
Each of the methods may return a Signal object that can get processed by the next function, allowing you to chain them together to get complex results quickly. The ergonomics on this are stellar and because these methods can be implemented as generators, each step can yield values lazily instead of building full intermediate arrays.That way, you get the clean chaining style and efficient, streaming computation.
This also greatly increases API discoverability, because I can just type "." and see the IDE pop up me all the options right there, no need for docs.
Just keep in mind that idiomatic Python expects https://en.wikipedia.org/wiki/Command%E2%80%93query_separati... if you return instances to support chaining, please return new instances rather than modifying the original in-place. Take the example set by the builtins; `list.append` etc. return `None` for a reason. cf. https://stackoverflow.com/questions/11205254.
Good point.
Since the idea behind method chaining is a bit like UNIX pipes anyways, it would be silly (and unexpected) if commands later in the chain affected parts that came before, so you basically output a new instance each time. In my example above the signal wouldn't be a sine wave anymore after the first step, so that would make even less sense.
Editing existing instances is something I'd do for performance reasons only with no return value (lets exclude errors).