Search code examples
pythonclassiteratorstatic-methodsmagic-methods

Iterate over a type without instantiating it


Problem

I want to be able to iterate over a type without instantiating it, similar to an enum.

class Foo:
    """Class I want to iterate over without instantiating."""
    ALLOWED_VALUES = (1, 2, 3)

# I want iterating over `Foo` to be equivalent to iterating over `Foo.ALLOWED_VALUES`
for val_from_tuple, val_from_foo in zip(Foo.ALLOWED_VALUES, Foo):
    assert val_from_tuple == val_from_foo

This behaviour is possible with an enum, but only if ALLOWED_VALUES are valid python names. I want to have the same iteration behaviour without this restriction.

What I tried

I tried implementing __iter__() as a staticmethod on Foo so that an instance of Foo wouldn't be needed to get an Iterator for it. This allows me to iterate over Foo.__iter__(), but iter(Foo) raises an error. This seems to be because iter(Foo) looks for an __iter__ method on type, not on Foo (since Foo is a type object).

class Foo:
    """Class I want to iterate over without instantiating."""
    ALLOWED_VALUES = (1, 2, 3)

    @staticmethod
    def __iter__():
        return Foo.ALLOWED_VALUES

# This works, but isn't what I want because it involves calling `__iter__()` explicitly.
for val in Foo.__iter__():
    print(val)

# This raises an error:
# `TypeError: 'type' object is not iterable`
for val in Foo:
    print(val)

Solution

  • Enum is iterable because it uses a different metaclass (EnumMeta rather than type) to create it. You can define your own metaclass to provide a definition of __iter__ which type itself lacks.

    class IterableClass(type):
        def __iter__(self):
            yield from self.ALLOWED_VALUES
    
    class Foo(metaclass=IterableClass):
        ALLOWED_VALUES = (1,2,3)
    
    for x, y in zip(Foo.ALLOWED_VALUES, Foo):
        assert x == y