I for one am quite happy to take past/future designs into account when we’re making choices here. Consistency is one of the main reasons we set up a CAPI working group, after all.
This works? Yeah, we should probably do it anyway.
Perhaps, but it’s also entirely unavoidable. You should not mutate an immutable set, therefore, you need to mutate the mutable set first and then copy its values in.
For cases where we have the same internal representation, the As*AndClear metaphor lets us transfer the internal state without an O(N) copy. If the frozen version has a different internal state from the mutable one, we must copy, because we must recalculate the new internal state. At that point, simple construction is by far the best metaphor.
If I had to generalise, then I’d say you’ll get better lookup behaviour when it’s calculated from all the values in one go than by adding them one at a time (think of choosing dict buckets, but when you know it’s immutable then you can do even more efficient bucketing based on the values, like a trie).
Plus when we use our own data structure for the temporary storage, everything stays safe enough during construction. Code can be reused (e.g. a helper function that uses PyObject_SetItem or even PyDict_SetItem), and our API stays smaller and more learnable. Constructing an immutable value in Python is a specialist activity - it’s never strictly necessary (except where the value is only immutable, such as strings and integers) - and so it doesn’t need ten of its own APIs when a single conversion from an equivalent value can exist and be efficient.
All of that to say, if we had a builder API here or for frozenset, it would have exactly the same shape, behaviour and characteristics as the existing API for dict and set, just with different names, and we’d be forcing users to learn and use a set of new APIs rather than a single conversion that they don’t really need.