
Christopher Barker writes:
But though Python blurs the lines between various callables and functions, there is a general "sense" of how they are used. And for the most part, zip is not used like a class. WE "use" classes, usually, by creating instances, and then calling various methods on them, etc.
I think this is a distinction without a difference at the margin. In the end, all callables return objects, aka instances. The class *statement* is special, of course (although to me it's remarkable how little is special about it). But then consider factory functions. From the outside, what's special about them is that conventionally they always return objects from a specific class, and that's why they get a special name. These distinctions are very useful to humans thinking about a program (well, they are to me!), but they're fuzzy.
So of course zip can be said to be used like a class. Its instances are constructed by calling it, the most common (only in practice?) method call is .__iter__, it's invariably called implicitly in a for statement, and usually the instance is ephemeral (ie, discarded when the for statement is exited). But it needn't be. Like any iterator, if it's not exhausted in one iteration context, you can continue it later. Or explicitly call next on it.
a zip instance on the other hand, returns an iterable, that does not provide any other methods or uses.
A zip instance *is* an iterator (though that's not in its name), and it has quite a few methods ;-) as you can check with dir(). It's just that the three most interesting ones (__init__, __iter__, and __next__) are invariably invoked implicitly.
So while yes, alternate constructors are a common pattern, I don't think they are a common pattern for classes like zip.
That's a matter of programming style, I think. There's no real difference between
zip(a, b, length='checksame')
and
zip.checksame(a, b)
They just initialize an internal attribute differently, which takes one of a very few values.
I think (after the initial shock ;-) I like the latter *better*, because the semantics of checksame and longest are significantly (to me, anyway) different. checksame is a constraint on correct behavior, and explicitly elicits an Exception on invalid input. longest is a variant specification of behavior on a certain class of valid input. I'm happier with those being different *functions* rather than values of an argument. YMMV, of course.
There's another possibility, as well:
zip(a,b).set_check_same()
but this is bad style IMO (it would have to return self or you couldn't "for pair in zip(a,b).set_check_same()", and I don't like methods that both mutate an object and return self, though some folks do).
Steve