def trace(msg, end=''):
    print(f'{msg} ', end=end)                 # Wyświetla bez nowej linii

class Iters:
    def __init__(self, value):
        self.data = value

    def __getitem__(self, i):                 # Metoda zastępcza do użycia przez iterację
        trace(f'@get[{i}]')                   # oraz do indeksowania i wycinania
        return self.data[i]

    def __iter__(self):                       # Metoda preferowana w iteracji
        trace('@iter')                        #Pozwala na użycie tylko jednego iteratora
        self.ix = 0
        return self

    def __next__(self):
        trace('@next')
        if self.ix == len(self.data): raise StopIteration
        item = self.data[self.ix]
        self.ix += 1
        return item

    def __contains__(self, x):                # Metoda preferowana w operacji 'in'
        trace('@contains')
        return x in self.data

def self_test(Iters):
    X = Iters([1, 2, 3, 4])                            # Utworzenie instancji
    tests = 'In', 'For', 'Comp', 'Map', 'Manual'
    for test in tests:
        trace(test.ljust(max(map(len, tests)) + 1))
        match test:
            case 'In':
                trace(3 in X)                          # Przynależność
            case 'For':
                for i in X:                            # Pętle for
                    trace(i, end='| ')
            case 'Comp':
                trace([i ** 2 for i in X])             # Inne konteksty iteracyjne
            case 'Map':
                trace(list(map(bin, X)))
            case 'Manual':
                I = iter(X)                            # Ręczna iteracja
                while True:
                    try:
                        trace(next(I), end='| ')
                    except StopIteration:
                        break
        print()

if __name__ == '__main__': self_test(Iters)            # Testowanie klasy Iters

