len() should always return something
Steven D'Aprano
steve at REMOVE-THIS-cybersource.com.au
Sat Jul 25 09:03:02 CEST 2009
On Fri, 24 Jul 2009 19:50:54 -0700, Dr. Phillip M. Feldman wrote:
> Here's a simple-minded example:
>
> def dumbfunc(xs):
> for x in xs:
> print x
>
> This function works fine if xs is a list of floats, but not if it is
> single float. It can be made to work as follows:
>
> def dumbfunc(xs):
> if isinstance(xs,(int,float,complex)):
> xs= [xs]
> for x in xs:
> print x
>
> Having to put such extra logic into practically every function is one of
> the annoying things about Python.
But it's not "practically every function". It's hardly any function at
all -- in my code, I don't think I've ever wanted this behavior. I would
consider it an error for function(42) and function([42]) to behave the
same way. One is a scalar, and the other is a vector -- they're different
things, it's poor programming practice to treat them identically.
(If Matlab does this, so much the worse for Matlab, in my opinion.)
However, if you want this behaviour, it's easy enough to get it with a
decorator:
from functools import wraps
def matlab(func):
"""Decorate func so that it behaves like Matlab, that is,
it considers a single scalar argument to be the same as a
vector of length one."""
@wraps(func)
def inner(arg, **kwargs):
# If arg is already a vector, don't touch it.
try:
# If this succeeds, it's a vector of some type.
iter(arg)
except TypeError:
# It's a scalar.
arg = [arg]
# Now call the decorated function (the original).
return func(arg, **kwargs)
return inner
With this decorator in hand, you can now easily get the behaviour you
want with a single extra line per function:
>>> @matlab
... def mean(numbers):
... return sum(numbers)/len(numbers)
...
>>> mean([4.5])
4.5
>>> mean(4.5)
4.5
>>> mean([4.5, 3.6])
4.0499999999999998
Decorators are extremely powerful constructs, and well worth learning. As
an example, here's a similar decorator that will accept multiple
arguments, like the built-in functions min() and max():
def one_or_many(func):
"""Decorate func so that it behaves like the built-ins
min() and max()."""
@wraps(func)
def inner(*args, **kwargs):
# If we're given a single argument, and it's a vector,
# use it as args.
if len(args) == 1:
try:
iter(args[0])
except TypeError:
pass
else:
# No exception was raised, so it's a vector.
args = args[0]
# Now call the decorated function (the original).
return func(args, **kwargs)
return inner
And then use it:
>>> @one_or_many
... def minmax(numbers):
... """Return the minimum and maximum element of numbers,
... making a single pass."""
... # the following will fail if given no arguments
... smallest = biggest = numbers[0]
... for x in numbers[1:]:
... if x < smallest:
... smallest = x
... elif x > biggest:
... biggest = x
... return (smallest, biggest)
...
>>>
>>> minmax([2, 4, 6, 8, 1, 3, 5, 7])
(1, 8)
>>> minmax(2, 4, 6, 8, 1, 3, 5, 7)
(1, 8)
--
Steven
More information about the Python-list
mailing list