assertions to validate function parameters

Steven D'Aprano steve at
Sat Jan 27 01:45:03 CET 2007

On Fri, 26 Jan 2007 18:28:32 +0000, Matthew Woodcraft wrote:

> I have a question for you. Consider this function:
> def f(n):
>     """Return the largest natural power of 2 which does not exceed n."""
>     if n < 1:
>         raise ValueError
>     i = 1
>     while i <= n:
>         j = i
>         i *= 2
>     return j
> If I pass it an instance of MyNumericClass, it will return an int or a
> long, not an instance of MyNumericClass.
> In your view, is this a weakness of the implementation? Should the
> author of the function make an effort to have it return a value of the
> same type that it was passed?

Only if it makes sense in the context of the function. I'd say it
depends on the principle of "least surprise": if the caller would expect
that passing in a MyNumericClass or a float or a Rational should return
the same type, then Yes, otherwise its optional.

Numeric functions are probably the least convincing example of this,
because in general people expect numeric functions to coerce arguments in
not-always-intuitive ways, especially when they pass multiple arguments
of mixed types. What should g(MyNumericClass, int, float, Rational)
return? And it often doesn't matter, not if you're just doing arithmetic,
because (in principle) any numeric type is compatible with any other
numeric type.

The principle of least surprise is sometimes hard to follow because it
means putting yourself in the shoes of random callers. Who knows what they
expect? One rule of thumb I use is to consider the function I'm writing,
and its relationship to the argument. Would I consider that the result is
somehow _made_from_ the argument? If so, then I should return the same
type (unless there is a compelling reason not to).

I'm NOT talking about implementation here, I'm thinking abstract
functions. Whether your implementation actually transforms the initial
argument, or creates a new piece of data from scratch, is irrelevant.

In your above example, the result isn't "made from" the argument (although
some implementations, using log, might do so). In abstract, the result is
an integer that is chosen by comparison to the argument, not by
construction from the argument. So it is unimportant for it to be the same
type, and in fact the caller might expect that the result is an int no
matter what argument he passes.


More information about the Python-list mailing list