Use magic methods __setitem__() and __getitem__() in Python#

In Python, there are two magic methods that can be used to implement the behavior of the subscript operator []. These methods are object.__setitem__() and object.__getitem__(). The first one is used to assign a value to an item, the second one is used to retrieve an item.

The following example shows how to use these methods to implement a memory class as a list of bytes that was used in the 6502 emulator. The memory class has two methods get() and set() that are used to retrieve and assign a value to a memory address.

Using methods get() and set() in Python#
#!/usr/bin/env python3

class Memory:
    def __init__(self, size: int = 65536) -> None:
        self.size = size
        self.memory = [0] * self.size
        for i in range(self.size):
            self.memory[i] = 0x00

    def get(self, address: int) -> int:
        return self.memory[address]

    def set(self, address: int, value: int) -> int:
        self.memory[address] = value
        return self.memory[address]


def main():
    memory = Memory()
    memory.set(1, 2)
    print(memory.get(1))

if __name__ == "__main__":
    main()

The same functionality can be implemented using magic methods object.__setitem__() and object.__getitem__`(). The advantage of using magic methods is that the memory class can be used as a list of bytes. The following example shows how to use the memory class as a list of bytes. The memory class can be used as a list of bytes because it implements the magic methods object.__setitem__() and object.__getitem__().

Using magic methods object.__setitem__() and object.__getitem__() in Python#
#!/usr/bin/env python3

class Memory:
    def __init__(self, size: int = 65536) -> None:
        self.size = size
        self.memory = [0] * self.size
        for i in range(self.size):
            self.memory[i] = 0x00

    def __getitem__(self, address: int) -> int:
        return self.memory[address]

    def __setitem__(self, address: int, value: int) -> int:
        self.memory[address] = value
        return self.memory[address]


def main():
    memory = Memory()
    memory[1] = 2
    print(memory[1])


if __name__ == "__main__":
    main()

The changes in second example show that interacting with the class becomes more natural. The memory class can be used as a list of bytes because it implements the magic methods object.__setitem__() and object.__getitem__(). This way the memory class can be used as a list of bytes by hiding all the implementation details.