Returning different types based on input parameters

Steven D'Aprano steve at REMOVE-THIS-cybersource.com.au
Mon Apr 6 18:50:00 EDT 2009


On Mon, 06 Apr 2009 14:02:26 -0700, George Sakkis wrote:

> That's more of a general API design question but I'd like to get an idea
> if and how things are different in Python context. AFAIK it's generally
> considered bad form (or worse) for functions/methods to return values of
> different "type" depending on the number, type and/or values of the
> passed parameters. I'm using "type" loosely in a duck- typing sense, not
> necessarily as a concrete class and its descendants, although I'm not
> sure if even duck-typing is endorsed for return values (as opposed to
> input parameters).
> 
> For example, it is common for a function f(x) to expect x to be simply
> iterable, without caring of its exact type. Is it ok though for f to
> return a list for some types/values of x, a tuple for others and a
> generator for everything else (assuming it's documented), or it should
> always return the most general (iterator in this example) ?

Arguably, if the only promise you make is that f() returns an iterable, 
then you could return any of list, tuple etc and still meet that promise. 
I'd consider that acceptable but eccentric. However, I'd consider it bad 
form to *not* warn that the actual type returned is an implementation 
detail that may vary.

Alternatively, I'm very fond of what the built-in filter function does: 
it tries to match the return type to the input type, so that if you pass 
a string as input, it returns a string, and if you pass it a tuple, it 
returns a tuple.


> To take it further, what if f wants to return different types, differing
> even in a duck-type sense? That's easier to illustrate in a
> API-extension scenario. Say that there is an existing function `solve
> (x)` that returns `Result` instances.  Later someone wants to extend f
> by allowing an extra optional parameter `foo`, making the signature
> `solve(x, foo=None)`. As long as the return value remains backward
> compatible, everything's fine. However, what if in the extended case,
> solve() has to return some *additional* information apart from `Result`,
> say the confidence that the result is correct ? In short, the extended
> API would be:
> 
>     def solve(x, foo=None):
>         '''
>         @rtype: `Result` if foo is None; (`Result`, confidence)
> otherwise.
>         '''
> 
> Strictly speaking, the extension is backwards compatible; previous code
> that used `solve(x)` will still get back `Result`s. The problem is that
> in new code you can't tell what `solve(x,y)` returns unless you know
> something about `y`. My question is, is this totally unacceptable and
> should better be replaced by a new function `solve2 (x, foo=None)` that
> always returns (`Result`, confidence) tuples, or it might be a
> justifiable cost ? Any other API extension approaches that are
> applicable to such situations ?

I dislike that, although I've been tempted to write functions like that 
myself. Better, I think, to create a second function, xsolve() which 
takes a second argument, and refactor the common parts of solve/xsolve 
out into a third private function so you avoid code duplication.



-- 
Steven



More information about the Python-list mailing list