function taking scalar or list argument

B

beliavsky

I can define a function that transforms either a scalar or each element in
a list as follows:

def twice(x):
try:
return map(twice,x)
except:
return 2*x

print twice(3) # 6
print twice([1,4,9]) # [2,8,18]

Is this good style? I intend to define many functions like this and want
to use the right method. Thanks.
 
P

Peter Otten

I can define a function that transforms either a scalar or each element in
a list as follows:

def twice(x):
try:
return map(twice,x)
except:

A bare except without the type of exception is always bad.
return 2*x

print twice(3) # 6
print twice([1,4,9]) # [2,8,18]

Is this good style? I intend to define many functions like this and want
to use the right method. Thanks.

I'd say you've just discovered a use case for a decorator:

def scalarOrVector(f):
def g(arg):
try:
return map(f, arg) # or map(g, arg) if you want to allow
# nested sequences
except TypeError:
return f(arg)
return g

#@scalarOrVector
def twice(a):
return 2*a
twice = scalarOrVector(twice)

print twice(3) # 6
print twice([1,4,9]) # [2,8,18]

Of course you will have to ensure that map(f, arg) _does_ throw an
exception.

Peter
 
S

Steven Bethard

beliavsky said:
def twice(x):
try:
return map(twice,x)
except:
return 2*x
[snip]
Is this good style?

First a general comment about catching exceptions -- you should probably write
your try-except block as:

try:
...
except TypeError:
...

Better to only catch the exceptions that your call to map will throw.


But to really answer your question, I would probably avoid this style. It
means for every number you double (whether an element of a list, or an element
alone), you'll throw and catch an exception. This needlessly complicates the
simple case. Exception handling is intended to deal with "exceptional" cases,
cases which should be relatively infrequent. And while Python's exception
handling is pretty efficient, it's still not particularly cheap to catch
exceptions. You would probably be better with:

def twice(x):
if isinstance(x, list):
return map(twice, x)
else:
return 2*x

In this case, instead of incurring the cost of a thrown and caught exception
for each number, you incur only the cost of a isinstance test. You also avoid
treating the common case as the exceptional one.

Steve

P.S. In reality, I probably wouldn't write a method like this, instead
relying on the caller to call map for themselves if necessary. The caller is
much more likely to know the type of the object they want to double anyway, so
they may not even need to use the isinstance test.
 
L

Larry Bates

You may want to take a look at isinstance() function.

def twice(x):
if isinstance(x, list): return map(twice, x)
if isinstance(x, (int, float, str)): return 2*x
return None

print twice(3) # 6
print twice([1,4,9]) # [2,8,18]
print twice('*')

HTH,
Larry Bates
Syscon, Inc.

I can define a function that transforms either a scalar or each element in
a list as follows:

def twice(x):
try:
return map(twice,x)
except:
return 2*x

print twice(3) # 6
print twice([1,4,9]) # [2,8,18]

Is this good style? I intend to define many functions like this and want
to use the right method. Thanks.



----== Posted via Newsfeed.Com - Unlimited-Uncensored-Secure Usenet News==----
http://www.newsfeed.com The #1 Newsgroup Service in the World! >100,000 Newsgroups
---= 19 East/West-Coast Specialized Servers - Total Privacy via Encryption
=---
 
J

Jeff Shannon

Larry said:
You may want to take a look at isinstance() function.

def twice(x):
if isinstance(x, list): return map(twice, x)
if isinstance(x, (int, float, str)): return 2*x
return None

Actually, I'd favor the try/except approach. It's entirely reasonable
to think that, if this function might be passed either scalars or lists,
it may also be passed a non-list-based sequence object. If you simply
try using map(), then it'll work correctly for any object that follows
the expected protocol, regardless of whether that object is (a subclass
of) list. By explicitly demanding a list, you're needlessly limiting
the usefulness of the function.

Jeff Shannon
Technician/Programmer
Credit International
 
S

Steven Bethard

Jeff Shannon said:
Actually, I'd favor the try/except approach. It's entirely reasonable
to think that, if this function might be passed either scalars or lists,
it may also be passed a non-list-based sequence object. If you simply
try using map(), then it'll work correctly for any object that follows
the expected protocol, regardless of whether that object is (a subclass
of) list. By explicitly demanding a list, you're needlessly limiting
the usefulness of the function.

Well, you can always test for the protocol, e.g.:

def twice(x):
if hasattr(x, "__iter__"):
return map(twice, x)
else:
return 2*x

It just seems like a misuse of the construct to me to treat the most frequent
case as an exceptional one. Note that the following solution (that doesn't
recurse) wouldn't be unreasonable if you expected to pass mostly sequences to
the function:

def twice(x):
try:
return map(lambda x: 2*x, x)
except TypeError:
return 2*x

The difference here is that we are now treating the sequence case as the
simple case -- if we're dealing with a sequence, no exception handling is
required. So if the sequence case is substantially more frequent, this would
be a perfectly reasonable solution. (Note that the difference between this
and the original solution is that this version does not recurse, so, unlike
the original solution, this one will not throw an exception for every element
in the sequence.)

Steve
 
S

Steven Bethard

Terry Reedy said:
In 2.2.1:
hasattr([1,2,3], '__iter__')
0
so this does not seem to work as you seem to expect. hasattr(iter(x),
'__iter__') will.

Yeah, I thought there was probably some special case I was missing there. The
problem with hasattr(iter(x), '__iter__') is that iter(x) can throw an
exception if the object is not iterable:
Traceback (most recent call last):
File "<interactive input>", line 1, in ?
TypeError: iteration over non-sequence

So if you call iter(x) in the test, and x is a number, you end up having to
try and catch the exception just like in the original solution.

Do you know at what version hasattr(x, '__iter__') started working? In 2.4a2:
True

Steve
 
P

Peter Otten

Terry said:
Steven Bethard said:
Well, you can always test for the protocol, e.g.:

def twice(x):
if hasattr(x, "__iter__"):
return map(twice, x)

In 2.2.1:
hasattr([1,2,3], '__iter__')
0
so this does not seem to work as you seem to expect. hasattr(iter(x),
'__iter__') will.

No it won't:

[Python 2.2]Traceback (most recent call last):
File "<stdin>", line 1, in ?
TypeError: iteration over non-sequence


Here the only reliable test for iterability is to just try it.
Unfortunately, while in most cases you want to treat them as scalars,
strings are iterables, too.

In 2.3 Steven's proposal works:
hasattr(1, "__iter__") False
hasattr([], "__iter__")
True

Peter
 
R

Rocco Moretti

I can define a function that transforms either a scalar or each element in
a list as follows:

def twice(x):
try:
return map(twice,x)
except:
return 2*x

print twice(3) # 6
print twice([1,4,9]) # [2,8,18]

Is this good style? I intend to define many functions like this and want
to use the right method. Thanks.

In addition to those options already mentioned, here's another alternative.

def twice(*args):
retval = [2*x for x in args]
if len(retval) == 1:
return retval[0]
else:
return retval

print twice() # []
print twice(3) # 6
print twice([1,4,9]) # [1, 4, 9, 1, 4, 9]

#BUT ...
print twice(*[1,4,9]) # [2,8,18]
# Note '*' -^
print twice(1,4,9) # [2,8,18]
# No '[]' --^----^

You can even omit the if ... else, if you don't mind getting a singleton
list for the one-parameter case. (i.e. twice(3) == [6])
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,755
Messages
2,569,536
Members
45,007
Latest member
obedient dusk

Latest Threads

Top