Guido van Rossum wrote:
...but -- to answer your question -- the point here isn't really the 'singleness' of the factory function, but the fact that it is type-independent, which (in principle) would allow it to be extended to handle arbitrary types by delegating some functionality to the types themselves.
This is all a nice generalization, but I don't see that much use for it. There's only a handful of types that are worth supporting here. So the cost of the generalization isn't worth the benefits.
I definitely disagree. A common case is a constrained type, where only a limited number of strings are allowed. Or an IP address, or domain name, or an internationalized boolean converter ("si" -> True), or a color specification, or a valid CSS class name, or... well, the list goes on forever.
The advantage of putting this in the parser is that you could have better error messages when the values were malformed. If the parser doesn't do the conversion, you are likely to have lost the location information by the time you try to do the conversion. Good error messages are one of the primary visible features for people who use the configuration files.
Sure, I agree with all of that. But my original (optint, optstr, optbool, optfloat) proposal can easily be extended the same way; in fact it is in some sense easier than an API that expects a type object. (Unless you have an adaptation framework in place; until we have a general one, inventing one just for this purpose definitely feels like overkill.
OK. I guess you could subclass opt* to get a new type; I wasn't thinking of that. I shy away from subclassing, but it might be appropriate here. It makes it easier to hang different parameters onto the type as well, like not_empty (for strings), max, min, etc. It would even be easier to hang serialization onto it.
I don't think adaptation fits well here, since adaptation seems to generally be context-insensitive, and this conversion process is done in the context of a specific type declaration.
An additional complication, though; if you plan to make the configuration file writable, these types shouldn't just support converting from a string to a Python type, but the other direction -- so that ambiguous Python types (like a boolean; easily confused as an integer) can be converted in specific ways to a configuration string. I don't think __repr__ or __str__ of the value to be converted are necessarily appropriate.
Actually, repr() or str() probably *is* the right answer for this, even if calling the constructor with a string argument isn't the answer for parsing and validation.
In my experience, this stops working as the types become more complex. For instance, consider a converter that takes a string that has comma-separated names and creates a list of strings; there is a specific way to convert back to that representation (','.join(value)), and both repr() and str() will be incorrect.
Potentially you could create a list subclass that has the right repr(), but that seems prone to error. repr() only gives an estimated Python representation of an object -- it is neither reliable (since obj == eval(repr(obj)) isn't true for a large number of objects), nor is it appropriate, since we're trying to generate configuration expressions that are tightly bound to a context, not Python expressions. In the case of generating a config file, if the conversion isn't reliable or is ambiguous it should be an error (which doesn't happen with repr() or str()).