0

I'm creating class which behaves like list(), but you can add strings to it and these strings will be splitted by spaces on words and these words will be added to the class instance. Same with substraction: substraction removes words from class instance (if any).

I faced with strange behavior in nose tests: behavior changes when I'm simply changing order of assertions.

Here is test:

from nose.tools import *

def test_isub_str(self):
    u = params()

    u += "1 2 3 4"
    u -= "2 3"

    ok_("1" in u, u)
    ok_("2" not in u, u)
    ok_("3" not in u, u)
    ok_("4" in u, u)

This test fails with following output:

Traceback (most recent call last):
  File "tests.py", line 71, in test_isub_str
    ok_("4" in u, u)
    assert expr, msg
AssertionError: 1 4

Strange, the "u" is dumped as "1 4", but "4" wasn't found in "u" during assertion testing. And the most interesting happens when I change the order of assertions to following:

ok_("1" in u, u)
ok_("4" in u, u)
ok_("2" not in u, u)
ok_("3" not in u, u)

Then tests are passing OK and this is correct, expected behavior. It looks like "not in" statement changes behavior of following "in" statements. I'm surprized.

Finally, here is my class:

class params():
    def __init__(self):
        self.index = 0 # iterator index
        self.values = [] # list of stored values

    def __iadd__(self, addvalues):
        if isinstance(addvalues, str):
            addvalues = addvalues.split(" ")
        # add all values to this instance if they are list() or params()
        # and not exist in current instance
        if isinstance(addvalues, params) or isinstance(addvalues, list):
            for value in addvalues:
                if isinstance(value, str) and value not in self.values:
                    self.values.append(value)
                else:
                    raise TypeError
            return self
        else:
            raise TypeError

    def __isub__(self, subvalues):
        if isinstance(subvalues, str):
            subvalues = subvalues.split(" ")
        # substract all values from this instance if they are list() or params()
        # and existing in current instance
        if isinstance(subvalues, params) or isinstance(subvalues, list):
            for value in subvalues:
                if isinstance(value, str) and value in self.values:
                    self.values.remove(value)
                else:
                    raise TypeError
            return self
        else:
            raise TypeError

    def __iter__(self):
        return self

    def next(self):
        if self.index >= len(self.values):
            raise StopIteration
        else:
            self.index += 1
            return self.values[self.index - 1]

    def __str__(self):
        return " ".join(self.values)

So question is: why such strange behavior happens? I missed something in iterator functionality? Or this nosetests bug?

Oleksandr
  • 419
  • 3
  • 14

1 Answers1

0

No, it's not a nosetests bug, i think that when you do

ok_("1" in u, u)

u.index is 1, when you do:

ok_("2" not in u, u)
ok_("3" not in u, u)

u.index is 2. And when you do

ok_("4" in u, u)

u.index is still 2, and your "next" method raises StopIteration because 2 is >= len(self.values).

In your second case u.index is still 1 when you do:

ok_("4" in u, u)
Fedor Gogolev
  • 10,391
  • 4
  • 30
  • 36
  • Thanks! This is a reason! I wrote iterator method according to several different examples like [this one](http://stackoverflow.com/questions/19151/build-a-basic-python-iterator) and all of them simply returned `self`. Is this good practice to reset index/iterator on each __iter__() call? – Oleksandr Aug 05 '12 at 17:29
  • I think in this case it's better to remove method `next`(you actually haven't any state in you iterator) and change `__iter__` for something like this: `iter(self.values)`. – Fedor Gogolev Aug 05 '12 at 17:32
  • Thanks again. I did as you proposed and `__iter__()` now is really simple. – Oleksandr Aug 06 '12 at 08:30