FAFO on GitHub

Let’s see about getting set creation sorted. We need to be better able to create sets with any possible base implementation. Deep confusion ensues.

Hello, friends!

At present, all XSets are created with their implementation as an instance of XFrozen. We need to be able to give them other subclasses of the XImplementation interface. At present, the XSet creation looks like this:

class XSet:

    @classmethod
    def classical_set(cls, a_list) -> Self:
        null = cls([])
        wrapped = [(item, null) for item in a_list]
        return cls(wrapped)

    @classmethod
    def tuple_set(cls, a_list) -> Self:
        wrapped = [(item, index+1) for index, item in enumerate(a_list)]
        return cls(wrapped)

    def __init__(self, a_list):
        def is_2_tuple(a):
            return isinstance(a, tuple) and len(a) == 2

        self.implementation = XFrozen(frozenset(a_list))
        if not all(is_2_tuple(a) for a in self.implementation):
            raise AttributeError

We’re going to break some tests here, unless we do something clever, because many of them just call XSet with a bunch of tuples. In fact, I think we even do that in the production code, since there’s kind of no other way.

I suppose we could make the __init__ check what is coming in as the first step. If it’s an XImplemetation subclass, just set it into the implementation, otherwise treat it as tuples. That’s not good but it might let us proceed more expeditiously.

Better idea might be to provide the new class method intended to take tuples, then refactor underneath after converting all the XSet users to use it.

Also I think that tuple_set should be renamed to n_tuple. We’ll start there. Done, green, commit: rename tuple_set class method to n_tuple

Now the “main” class method will be from_tuples until I get a better idea.

    @classmethod
    def from_tuples(cls, tuples):
        return cls(tuples)

Now we’ll just find all the references to XSet() and fix them up to call from_tuples. Luckily? there are only 65 references to deal with. Should be straightforward.

That just took a couple of minutes and I probably could have done better with some kind of global replace thingie, which doubtless PyCharm has. Commit: all XSet creation from tuples now probably uses from_tuples.

Now then. What do we want? Well, we want the __init__ method to receive just an instance of XImplementation. As an extra safety measure, let’s require a second parameter amounting to a password.

Shall we try to do this without ever breaking a test? Let’s do, just for fun.

We’ll check in the init to see if we have an implementation and if not assume it’s still tuples, the only other possibility. One thing I don’t know: does a subclass of XImplementation show up as an instance of XImplementation?.

Curiously, this test does not pass:

    def test_isinstance(self):
        set = XSet.from_tuples(())
        imp = set.implementation
        assert isinstance(imp, XFrozen)

It says:

>       assert isinstance(imp, XFrozen)
E       assert False
E        +  where False = isinstance(frozenset(), XFrozen)

Huh?

Note
Here begins an hour of absolute confusion, as many debugging checks and many prints and many assertions show me that the implementation is an XFrozen, while the test continues to assert that it is not. My reluctant conclusion was that somehow it was getting changed. That was not the case.

I leave the confusion here, because I write about what happens, not some idealized Coding Session of the Gods, and this is what happened. Skip or scan, it’s up to you.

Unless you just love to watch someone who is confused, click to skip.

    def __init__(self, a_list):
        def is_2_tuple(a):
            return isinstance(a, tuple) and len(a) == 2

        self.implementation = XFrozen(frozenset(a_list))
        if not all(is_2_tuple(a) for a in self.implementation):
            raise AttributeError

class XFrozen(XImplementation):
    def __init__(self, fs):
        self.data = fs

How is that not returning an instance of XFrozen?

    def test_isinstance(self):
        xf = XFrozen(frozenset())
        assert isinstance(xf, XFrozen)
        set = XSet.from_tuples(())
        imp = set.implementation
        assert isinstance(imp, XFrozen)

The first assert passes. The constructor does return the XFrozen. What is happening in the set?

class XSet
    def __init__(self, a_list):
        def is_2_tuple(a):
            return isinstance(a, tuple) and len(a) == 2

        self.implementation = XFrozen(frozenset(a_list))
        assert isinstance(self.implementation, XFrozen)
        if not all(is_2_tuple(a) for a in self.implementation):
            raise AttributeError

That assertion passes.

I am completely bamboozled. I’ve stepped into the thing and I’m still not seeing what I’m missing.

    def test_isinstance(self):
        xf = XFrozen(frozenset())
        assert isinstance(xf, XFrozen)
        xset = XSet.from_tuples([('joe', 'first'), ('smith', 'last')])
        assert isinstance(xset.implementation, XFrozen)

class XSet:
    def __init__(self, a_list):
        def is_2_tuple(a):
            return isinstance(a, tuple) and len(a) == 2

        self.implementation = XFrozen(frozenset(a_list))
        if not all(is_2_tuple(a) for a in self.implementation):
            raise AttributeError
        assert isinstance(self.implementation, XFrozen)

The assertion in the test fails. The assertion in the init does not. I’ve put similar assertions elsewhere. They all fail. The XFrozen just isn’t going in there … or is it some other anomaly.

I have embedded assertions and prints all over:

class XFrozen:
    def __iter__(self):
        print("xfrozen iterating")
        return iter(self.data)

class TestXST:        
    def test_isinstance(self):
        xf = XFrozen(frozenset())
        assert isinstance(xf, XFrozen)
        xset = XSet.from_tuples([('joe', 'first'), ('smith', 'last')])
        print("in test")
        for e,s in xset:
            print(e, s)
        print("in test done")
        impl = xset.implementation
        print("after assignment", impl)
        klass = impl.__class__
        print(klass)
        assert isinstance(impl, XFrozen)

The output looks like this:

checking
xfrozen iterating
done checking
in test
xfrozen iterating
smith last
joe first
in test done
after assignment frozenset({('smith', 'last'), ('joe', 'first')})
<class 'src.x_frozen.XFrozen'>

And then the error. So the class is XFrozen. If I change the last line to this:

        assert isinstance(xset.implementation, XFrozen)

The output changes to this:

xfrozen iterating
done checking
in test
xfrozen iterating
joe first
smith last
in test done
after assignment frozenset({('joe', 'first'), ('smith', 'last')})
<class 'src.x_frozen.XFrozen'>
xfrozen iterating

Note that referencing self.implementation in isinstance triggered another ‘xfrozen iterating’, a loop over the set. What in the everlasting #$%@! is going on??

Skip Point

I’ve got to take a break. This is too weird.

After not taking a break as I absolutely know that I should, I’m going to go ahead and implement the ability to set implementation to other classes, without using isinstance. This is probably foolish.

OK. I actually have it all working. One issue that I encountered was that I was passing generators in to the from_tuples method and the iteration checking was consuming the generator’s contents and the resulting set was null.

I think we’ll need to be very careful to materialize any generators, and I don’t mind saying that I am not entirely clear on when I’ve left a generator in a set and shouldn’t have.

I’m going to stop writing now, and take this up tomorrow.

Whew. Was an import problem. Bizarre. I am whipped. See you tomorrow.

Friday AM

As reported above, one issue was that a generator was being passed in and iterated before being saved as part of an implementation. Since the generator had been consumed, the set acted like it was empty. The reason that one assert failed turned out to be that I had an import that referred to XFrozen incorrectly, left over from a previous PyCharm setting. The reference worked, but gave the class a different mangled name, so it appeared different to the assert. Bizarre.

Here, for the record, are the current class methods of XSet. Note that the __init__ now expects an implementation. As things stand, it is not checked. We’ll pick up in the next article.

See you then!


class XSet:

    @classmethod
    def classical_set(cls, a_list) -> Self:
        null = cls.null
        wrapped = [(item, null) for item in a_list]
        return cls.from_tuples(wrapped)

    @classmethod
    def n_tuple(cls, a_list) -> Self:
        wrapped = [(item, index+1) for index, item in enumerate(a_list)]
        return cls.from_tuples(wrapped)

    @classmethod
    def from_tuples(cls, tuples):
        def is_2_tuple(a):
            return isinstance(a, tuple) and len(a) == 2

        concrete = list(tuples)  # ensure we didn't get passed a generator
        if not all(is_2_tuple(a) for a in concrete):
            raise AttributeError
        return cls(XFrozen(frozenset(concrete)))

    def __init__(self, an_implementation):
        self.implementation = an_implementation