unittest.TestCase, lambda and __getitem__

Discussion in 'Python' started by Steven Bethard, Sep 13, 2004.

  1. 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
    --
    You can wordify anything if you just verb it.
    - Bucky Katt, Get Fuzzy
    Steven Bethard, Sep 13, 2004
    #1
    1. Advertising

  2. Steven Bethard <> wrote:
    ...
    > 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
    Alex Martelli, Sep 13, 2004
    #2
    1. Advertising

  3. Alex Martelli <aleaxit <at> yahoo.com> writes:
    > Steven Bethard <steven.bethard <at> gmail.com> wrote:
    > > 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
    Steven Bethard, Sep 13, 2004
    #3
  4. Steven Bethard <> wrote:

    > Alex Martelli <aleaxit <at> yahoo.com> writes:
    > > Steven Bethard <steven.bethard <at> gmail.com> wrote:
    > > > 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
    Alex Martelli, Sep 13, 2004
    #4
  5. Steven Bethard

    Roy Smith Guest

    Steven Bethard <> wrote:
    > 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.
    Roy Smith, Sep 13, 2004
    #5
  6. Roy Smith wrote:
    >.... 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
    Scott David Daniels, Sep 13, 2004
    #6
  7. Alex Martelli <aleaxit <at> yahoo.com> writes:
    >
    > 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.

    > > 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.


    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
    Steven Bethard, Sep 13, 2004
    #7
  8. Scott David Daniels <> wrote:

    > Roy Smith wrote:
    > >.... 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.
    >>> import operator
    >>> operator.getitem

    <built-in function getitem>
    >>>


    What's new in 2.4 is operator.itemgetter, a subtle higher-order
    function...


    Alex
    Alex Martelli, Sep 14, 2004
    #8
    1. Advertising

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

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Jan Decaluwe
    Replies:
    2
    Views:
    303
    Peter Otten
    Mar 2, 2004
  2. Tom Harris
    Replies:
    1
    Views:
    291
    Diez B. Roggisch
    Nov 27, 2007
  3. Oltmans
    Replies:
    1
    Views:
    800
    Francesco Bochicchio
    Sep 28, 2009
  4. Joel Smith
    Replies:
    1
    Views:
    482
    Peter Otten
    Oct 6, 2009
  5. Scott
    Replies:
    1
    Views:
    107
    Timothy Hunter
    Aug 20, 2005
Loading...

Share This Page