0

I just converted on of my classes to a "self registering" class. It was my first foray into python metaclasses. Oddly, many tests that had been passing started failing in odd ways. Many of those failing tests would pass when run by themselves and not as a part of the suite. This led me to believe that there was some sort of "data bleed" between tests. So I created some tests to find out if that is true, and I was right.

Now the question is, how do I resolve the issue. tearDown() methods with del() don't work. Adding a desctructor (del(self)) doesn't work. How do I unregister items from the _registry so that they don't cross-contaminate other tests?

Entertainingly, the first test below passed until I added the second.

class IterateRegister(type):
    def __iter__(cls):
        return iter(cls._registry)

class TEST_IterateRegister_instantiation(unittest.TestCase):
    class foo(metaclass=IterateRegister):
        _registry = []

        def __init__(self, name):
            self._registry.append(self)
            self.name = name

    def test__iterate_register_metaclass(self):
        names = ['Shaggy', 'Scooby', 'Snack']

        x1 = TEST_IterateRegister_instantiation.foo(names[0])
        x2 = TEST_IterateRegister_instantiation.foo(names[1])
        x3 = TEST_IterateRegister_instantiation.foo(names[2])

        self.assertEqual(names, [ f.name for f in TEST_IterateRegister_instantiation.foo ])

    def test__destruction_deletes_class_from_registry(self):
        names = ['Shaggy', 'Scooby', 'Snack']

        x1 = TEST_IterateRegister_instantiation.foo(names[0])
        x2 = TEST_IterateRegister_instantiation.foo(names[1])
        x3 = TEST_IterateRegister_instantiation.foo(names[2])

        del(x2)
        names.remove('Scooby')

        self.assertEqual(names, [ f.name for f in TEST_IterateRegister_instantiation.foo ])
Timothy Grant
  • 141
  • 3
  • 10

2 Answers2

1

I don't think you need a metaclass in this situation.

class foo(object):
    _registry = []

    def __init__(self, name):
        self._registry.append(self)
        self.name = name

Why not iterate on the _registry directly ? As for removing x2, foo._registry is no less a list than names is; so use the .remove method.

def test__destruction_deletes_class_from_registry(self):
    names = ['Shaggy', 'Scooby', 'Snack']

    x1 = self.foo(names[0])
    x2 = self.foo(names[1])
    x3 = self.foo(names[2])

    foo._registry.remove(x2)
    names.remove('Scooby')

    self.assertEqual(names, [ f.name for f in self.foo._registry ])
dilbert
  • 3,008
  • 1
  • 25
  • 34
  • Honestly, I used the metaclass because it was the first solution I came upon when looking for a self-registering class. I just tried this implementation and I get "TypeError: 'type' object is not iterable" – Timothy Grant Apr 30 '14 at 00:57
1

Why are you explicitly using the class name rather than self? In, for example:

x1 = TEST_IterateRegister_instantiation.foo(names[0])
x2 = TEST_IterateRegister_instantiation.foo(names[1])
x3 = TEST_IterateRegister_instantiation.foo(names[2])

Rather than:

x1 = self.foo(names[0])
x2 = self.foo(names[1])
x3 = self.foo(names[2])

Furthermore, I don't think that del is doing what you think it does. All del does is remove a bound identifier from the namespace, so:

del(x2)

is equivalent to writing x2 = None, which is not what you want to do. Python does not have destructors. The string is immutable and making changes to it will not change names.

Addressing your question, it sounds like your issue is that _registry is mutable, and so changes to cls._registry are visible to all the subclasses. The easiest way to fix this would be with a teardown method that sets IterateRegister._registry = []. However, I think that you should rethink this solution: if it's already causing you problems in your testing that IterateRegister is holding state, isn't it going to be that much harder to keep track of in production code?

Community
  • 1
  • 1
Patrick Collins
  • 10,306
  • 5
  • 30
  • 69
  • Thanks for the tips! your point about self should have been self-evident but for some reason when I looked at the class being defined within the second class I didn't go that direction. – Timothy Grant Apr 30 '14 at 01:10