On Thu, 21 Oct 2004 12:26:24 -0500, Ian Bicking email@example.com wrote:
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.
It may be a matter of style; I also tend to shy away from subclassing, because often it leads to a 'parallel' class hierarchy to handle things that could be represented by different interfaces, adaptations or aspects of the original classes. In this case:
IntType -> optint IPAdddressType -> optipaddress CustomBooleanType -> optcustomboolean
Of course, over-generalization in this case can lead to overly complex classes that try to do too much stuff for every possible situation. But simple interfaces -- as the ones required for configuration support -- don't add that much complexity to the native object anyway. In fact, conversion to & from strings is a so useful extension that I tend to provide it for many of my classes, albeit not with a true standard interface.
(In the examples above, there is another caveat -- some types, as BooleanType, can't be subclassed, and this makes things more difficult to generalize under my proposal)
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()).
This is one of the situations where 'practicality beats purity', IMHO, because the standard Python prompt returns the repr() of the object. If all objects _always_ returned full reversible representations to repr() -- which should be possible, given enough care -- then the command prompt would be unusable for complex objects (just calling a method that return a complex object would cause a lot of stuff to be printed out). Perhaps the standard prompt could be changed to return str() instead of repr(), so repr could _really_ be used for the generic representation case... but it's probably too late to change it.