I was writing a python thing where the class was going to have like at least 20 paramaters to configure it. Builder pattern was kind of feeling like a good idea to keep it cleaner for the user. But it is surprising to see in the python world. It felt like a mess of default values though for the user to handle.

Good example for Python not needing this pattern sometimes is Pulumi. Check out the differences between example code in Java and Python.

https://www.pulumi.com/docs/iac/get-started/kubernetes/revie...

In Java (or even Go) this pattern is required to enforce type safety. In Python it seems that they ignore the typing part and just pass a bunch of dicts. Looks much cleaner, even if not entirely typesafe.

Rarely have I seen a class that truly needs 20 parameters, that's most often a design flaw. There might be cases where this isn't true, but those are edge cases, so it's probably also fine to apply a special patterns, such as the builder pattern.

> Rarely have I seen a class that truly needs 20 parameters

If a class seems like it needs that many parameters, it is very common that one or both of these is true:

1. It is doing too much, or

2. There are things-that-should-be-their-own-classes hiding in groups of the parameters.

#2 is kind of a subset of #1, but the "doing too much" tends to be concentrated in validating relations between parameters rather than what happens after the object is constructed.

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).