[Python-Dev] Replacement for print in Python 3.0 (original) (raw)
Guido van Rossum guido at python.org
Tue Sep 6 02:52:01 CEST 2005
- Previous message: [Python-Dev] Replacement for print in Python 3.0
- Next message: [Python-Dev] Replacement for print in Python 3.0
- Messages sorted by: [ date ] [ thread ] [ subject ] [ author ]
On 9/5/05, Kay Schluehr <kay.schluehr at gmx.net> wrote:
[...] is the most heavyweight solution, but can encapsulate options and is reusable:
>>> Writer(sep="//").print("some","text") some//text or writer = Writer(sep="//", file=sys.stderr) writer.print("some","error-text") writer.print("another","error text")
I am disappointed to see several proposals plunge into this type of generality (no matter how cool it is in its application of OO design patterns) without asking whether there is a need. Look at the example -- it is completely useless. I only made it up so that I could present the simpler version; I didn't have a use case myself for arbitrary delimiters.
My hypothesis is that there are actually only two use cases that matter enough to be supported directly:
(a) quickly print a bunch of items with spaces in between them and a trailing newline
(b) print one or more items with precise control over each character
If there are other use cases they can obviously be coded by using (b) and a modest amount of custom code. (I know there's the use case of printing sequences; I'll try to give an analysis of this use case in another post if one of its proponents doesn't do so soon.)
An additional use case that I am willing to entertain because there is a lot of prior art (like Python's logging package, Bill Janssen's note(), and of course many other languages) is format-directed printing. This can of course be reduced to use case (b) using the str.% operator, but it is common enough to at least consider providing a direct solution which avoids the pitfalls of the % operator. Call this use case (c).
Interesting, use case (b) can also easily be reduced to use case (c)!
In a different thread I mentioned a design principle for which I have no catchy name, but which has often helped me design better APIs. One way to state it is to say that instead of a single "swiss-army-knife" function with various options that choose different behavior variants, it's better to have different dedicated functions for each of the major functionality types. So let's call it the "Swiss Army Knife (...Not)" API design pattern.
There are a number of reasons why this API design is often better. These aren't quite the same reasons why a real life Swiss Army knife is often inferior to individual tools, if you have them available, so the analogy isn't perfect. (So sue me. :-)
It reduces the number of parameters, which reduces the cognitive overhead for the human reader. (It also reduces function call overhead some; but that's not the main reason.)
It puts the hint about the specific variant functionality at the front rather than at the end, so it is less likely overlooked.
If one variant is much more common than others, it is easier to learn just that behavior.
In the (common) case where the options are Booleans, it's often confusing whether True or False switches a particular behavior on or off (especially if they are allowed to be specified as positional parameters).
A good test to discover that you should have used this pattern is when you find that the argument specifying a particular option is a constant at every call site (perhaps excluding API wrappers). This is a hint that the different variants of the functionality are catering to different use cases; often you'll find that substituting a different variant behavior just wouldn't work because the use that is made of the returned value expects a specific variant.
Some examples of the design pattern in action are str.strip(), str.lstrip() and str.rstrip(), or str.find() and str.rfind().
A much stronger subcase of this pattern (with fewer exceptions) is that the return type of a function shouldn't depend on the value of an argument. I have a feeling that if we were to extend the notion of type to include invariants, you'd find that the basic pattern is actually the same -- often the variant behaviors change the key invariant relationships between input and output.
OK, still with me? This, together with the observation that the only use cases for the delimiter are space and no space, suggests that we should have separate printing APIs for each of the use cases (a), (b) and (c) above, rather than trying to fold (b) into (a) using a way to parameterize the separator (and the trailing newline, to which the same argument applies). For example:
(a) print(...) (b) printraw(...) or printbare(...) (c) printf(fmt, ...)
Each can take a keyword parameter to specify a different stream than sys.stdout; but no other options are needed. The names for (a) and (c) are pretty much fixed by convention (and by the clamoring when I proposed write() :-). I'm not so sure about the best name for (b), but I think picking the right name is important.
We could decide not to provide (b) directly, since it is easily reduced to (c) using an appropriate format string ("%s" times the number of arguments). But I expect that use case (b) is pretty important, and not everyone likes having to use format strings. This could be reduced to a special case of the Swiss Army Knife (...Not) rule.
BTW we could use "from future import printing" to disable the recognition of 'print' as a keyword in a particular module -- this would provide adequate future-proofing.
-- --Guido van Rossum (home page: http://www.python.org/~guido/)
- Previous message: [Python-Dev] Replacement for print in Python 3.0
- Next message: [Python-Dev] Replacement for print in Python 3.0
- Messages sorted by: [ date ] [ thread ] [ subject ] [ author ]