On 9/5/05, Kay Schluehr <[EMAIL PROTECTED]> 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)!

<intermezzo>

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.

</intermezzo>

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/)
_______________________________________________
Python-Dev mailing list
Python-Dev@python.org
http://mail.python.org/mailman/listinfo/python-dev
Unsubscribe: 
http://mail.python.org/mailman/options/python-dev/archive%40mail-archive.com

Reply via email to