In Python, you do not need to inherit from abc.Sequence to be a sequence. You only need to implement:
__len____getitem__
If you implement these two methods, Python automatically grants you:
- Iteration (
for x in obj) - Reversal (
reversed(obj)) - The
inoperator (x in obj)
When a user does obj[1:4], Python passes a slice object to __getitem__.
A robust sequence must handle this explicitly. Instead of returning a native list or array, a slice operation should ideally return a new instance of the same class.
def __getitem__(self, index):
if isinstance(index, slice):
return type(self)(self._components[index]) # Return a new Vector__getattr__is called as a fallback ONLY when an attribute lookup fails in the instance__dict__and the class tree.- If you implement
__getattr__to allow dynamic access (e.g.,v.xrouting tov[0]), you must also implement__setattr__. - Why? Because if a user does
v.x = 10, it bypasses__getattr__and creates a realxin the__dict__. From then on,v.xwill return 10 instead of dynamically fetchingv[0]. This is called shadowing.
To hash a tuple, Python hashes every element and combines them using a bitwise XOR. We can replicate this efficiently for N-dimensional objects using functools.reduce and operator.xor.
def __hash__(self):
# Hash each component, then XOR them all together
hashes = map(hash, self._components)
return functools.reduce(operator.xor, hashes, 0)When comparing two large sequences, creating a massive tuple in memory tuple(self) == tuple(other) is wasteful.
Instead, use zip() combined with a generator expression and all(). Because all() short-circuits, it will stop evaluating the moment it finds a mismatch.
def __eq__(self, other):
if len(self) != len(other):
return False
return all(a == b for a, b in zip(self, other))