unittest.TestCase, lambda and __getitem__

S

Steven Bethard

So, GvR said a few times that he would like to get rid of lambda in
Python 3000. Not to start up that war again, but I'm trying to
eliminate unnecessary lambdas from my code, and I ran into a case
using unittest.TestCase that I don't really know how to deal with.

Previously, I had written some code like:

self.assertRaises(ValueError, lambda: method(arg1, arg2))

This was a simple fix because assertRaises takes *args and **kwds, so
I fixed it to look like:

self.assertRaises(ValueError, method, arg1, arg2)

which is much cleaner anyway (and what I should have been doing from
the start). Where I get uneasy is when I run into code like:

self.assertRaises(ValueError, lambda: obj[index])

Presumably, I could write this as:

self.assertRaises(ValueError, obj.__getitem__, index)

I guess this makes me uneasy because I'm not entirely certain that
obj[item] is *always* translated to obj.__getitem__(index). Is it?
That is, is there any way that obj[item] would get translated into a
different method call?

Or is there a better/clearer way of handling this kind of test case?

Thanks,

Steve
 
A

Alex Martelli

Steven Bethard said:
Previously, I had written some code like:

self.assertRaises(ValueError, lambda: method(arg1, arg2))

This was a simple fix because assertRaises takes *args and **kwds, so
I fixed it to look like:

self.assertRaises(ValueError, method, arg1, arg2)

which is much cleaner anyway (and what I should have been doing from
Agreed.

the start). Where I get uneasy is when I run into code like:

self.assertRaises(ValueError, lambda: obj[index])

Presumably, I could write this as:

self.assertRaises(ValueError, obj.__getitem__, index)

I guess this makes me uneasy because I'm not entirely certain that
obj[item] is *always* translated to obj.__getitem__(index). Is it?
Yes.

That is, is there any way that obj[item] would get translated into a
different method call?
No.

Or is there a better/clearer way of handling this kind of test case?

Sure:

def wrong_indexing(): return obj[index]
self.assertRaises(ValueError, wrong_indexing)

Whatever you can do with self.assertRaises(X, lambda: YZT) you can
(essentially idenfically) do with the two statements:

def anyname(): return YZT
self.assertRaises(X, anyname)

If you have a series of slightly different lambdas on several
assertRaises calls, you can reuse the same name (if that makes sense),
since another def for the same name simply rebinds the name, just like
multiple successive assignments to the same name would. Often you may
respect "once, and only once" better by factoring several lambdas into a
single def with an argument or two, of course. But for a mechanical
transformation changing each lambda into one def works just fine, and I
think it's preferable in terms of reliability to delving into internals
to find out what special methods get called when.

Besides, the delving approach may be far from trivial if you ever try to
translate something like lambda:x+y since in THAT case you cannot
necessarily tell what method gets called on what object (__add__ on x,
or __radd__ on y?) -- here you might try operator.add, but what then
about lambda:x+y*z ...?


Alex
 
S

Steven Bethard

Alex Martelli said:
Steven Bethard said:
Or is there a better/clearer way of handling this kind of test case?

Sure:

def wrong_indexing(): return obj[index]
self.assertRaises(ValueError, wrong_indexing)

Yeah, I guess I was just begging someone to give me that response. ;)

First, I have to mention that I'd probably have to write your code as

def wrong_indexing():
return obj[index]
self.assertRaises(ValueError, wrong_indexing)

because GvR has also commented that he wishes he hadn't allowed the one-
line "if: statement" syntax, so by analogy, I assume he'd also rather no one-
line def statement. So I'm stuck replacing a single line with three... I was
hoping to avoid this... (Of course, if I have a bunch of obj[index] type
calls, I can average the additional line cost over all of these by making the
function suitably general.)

Regardless, your analogy between obj[index] and an arbitrary mathematical
expression was helpful. It clarifies that, what I really want is an anonymous
code block...

Hmm... Well, if this is really the only option, I'll probably leave these
lambdas until I absolutely have to remove them...

Steve
 
A

Alex Martelli

Steven Bethard said:
Alex Martelli said:
Steven Bethard said:
Or is there a better/clearer way of handling this kind of test case?

Sure:

def wrong_indexing(): return obj[index]
self.assertRaises(ValueError, wrong_indexing)

Yeah, I guess I was just begging someone to give me that response. ;)

First, I have to mention that I'd probably have to write your code as

def wrong_indexing():
return obj[index]
self.assertRaises(ValueError, wrong_indexing)

because GvR has also commented that he wishes he hadn't allowed the one-
line "if: statement" syntax, so by analogy, I assume he'd also rather no one-
line def statement. So I'm stuck replacing a single line with three... I was

I don't think the two issues are comparable. Consider the PEP:

http://www.python.org/peps/pep-3000.html

which, while of course still very tentative, DOES express Guido's
current plans for the future, non-backwards-compatible Python 3000.

"The lambda statement" is first on the list of things "to be removed".
(Of course, there IS no such thing -- it's not a statement -- but I hope
that's just a minor error in the PEP;-).

There is no indication on the PEP that Guido means to remove the ability
to use a single line such as def name(): return 23. The PEP does pick
up many specific changes coming from the 'Regrets' document,
http://www.python.org/doc/essays/ppt/regrets/PythonRegrets.pdf , and
specifically points to that document; but the PEP does not mention as
planned changes any of the lexical issues in the Regrets document (use
of \ for continuation, one-line if, use of tabs). Until further notice
I think this means these lexical aspects aren't going away.
hoping to avoid this... (Of course, if I have a bunch of obj[index] type
calls, I can average the additional line cost over all of these by making the
function suitably general.)

Regardless, your analogy between obj[index] and an arbitrary mathematical
expression was helpful. It clarifies that, what I really want is an anonymous
code block...

It generally is: lambda isn't a block and def isn't anonymous -- given
the tiny investment required to cook up a name for def, I think this
means def is much closer than lambda to what you want. When you change
lambdas to defs you often find that the modest effort of naming is
actually USEFUL, since it helps clarify your code, and sometimes you get
to generalize and better apply "once and only once".

All in all I think changing lambdas into defs is a worthy endeavour and
enhances your code. And if it should ever turn out in the future that
you do need a line break after the : in "def name(): ...", it's a
trivial and totally mechanical transformation with any editor (or an
auxiliary Python script), no effort at all. So I wouldn't let that
distant prospect worry me in the slightest.

Hmm... Well, if this is really the only option, I'll probably leave these
lambdas until I absolutely have to remove them...

I've never seen a syntax proposed for an "anonymous def" that Guido
would find in the least acceptable, if that's what you mean. I still
think that perfectly ordinary defs are fine, and better than lambdas,
but of course it's your code so it's your decision.


Alex
 
R

Roy Smith

Steven Bethard said:
self.assertRaises(ValueError, lambda: obj[index])

Presumably, I could write this as:

self.assertRaises(ValueError, obj.__getitem__, index)

I'm with you, I don't like the idea of calling __getitem__ directly,
because you're not testing what you want to test; you're testing
something one step removed. Yes, it's a small step, but the idea in
unit testing is to test as small a thing as possible.

I think you need to just create a little function and call it:

def tryIndex (object, index):
return obj [index]

self.assertRaises (ValueError, tryIndex, object, index)

Another possibility would be:

self.assertRaises (ValueError, eval ("object [index]"))

From a readability/understandability standpoint, I think I like the eval
version better.
 
S

Scott David Daniels

Roy said:
.... I think you need to just create a little function and call it:

def tryIndex (object, index):
return obj [index]

For python 2.4 and beyond, the function is called operator.getitem,
so I'd call it getitem.

-Scott David Daniels
(e-mail address removed)
 
S

Steven Bethard

Alex Martelli said:
There is no indication on the PEP that Guido means to remove the ability
to use a single line such as def name(): return 23.

Yeah, I noticed the if thing wasn't in the PEP, but it is in the Wiki at
http://www.python.org/moin/Python3_2e0. 'Course the Wiki is really just
things he's been quoted as saying -- presumably the PEP is thing that he's
been quoted as saying and intends to pursue...

Regardless, I personally can't bring myself to write one-line defs. ;)
All in all I think changing lambdas into defs is a worthy endeavour and
enhances your code.

Yeah, I actually did end up changing some of them. For example, one of my
testing methods now looks something like:

def getitem(obj, item):
return obj[item]

self.assertRaises(IndexError, getitem, myobj, -10)
self.assertRaises(IndexError, getitem, myobj, 9)
....
self.assertRaises(IndexError, getitem, myobj2, -5)
self.assertRaises(IndexError, getitem, myobj2, 4)
....

It does make sense in situations like this when you're repeating the same kind
of code a number of times, and because it's apparent from the code that
getitem does in fact execute obj[item], I feel more comfortable with this than
something like operator.getitem where I can't see what code is really executed.
And if it should ever turn out in the future that
you do need a line break after the : in "def name(): ...", it's a
trivial and totally mechanical transformation...

Good point.
I've never seen a syntax proposed for an "anonymous def" that Guido
would find in the least acceptable, if that's what you mean.

No, I wasn't actually expecting anonymous defs. They've come up so many times
without ever being accepted that I expect they won't ever make it into the
language. All-in-all, I don't really consider this a loss. Really,
assertRaises is the only time I've really wanted them, and only because
testing code often has lots of special cases that don't necessarily generalize
well.

STeve

Steve
 
A

Alex Martelli

Scott David Daniels said:
Roy said:
.... I think you need to just create a little function and call it:

def tryIndex (object, index):
return obj [index]

For python 2.4 and beyond, the function is called operator.getitem,
so I'd call it getitem.

It's named just the same way in 2.3:

kallisti:~ alex$ python2.3
Python 2.3 (#1, Sep 13 2003, 00:49:11)
[GCC 3.3 20030304 (Apple Computer, Inc. build 1495)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
What's new in 2.4 is operator.itemgetter, a subtle higher-order
function...


Alex
 

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

Staff online

Members online

Forum statistics

Threads
473,769
Messages
2,569,577
Members
45,052
Latest member
LucyCarper

Latest Threads

Top