One question many implementations.
# free bugs included
class MList(list):
def __init__(self, iterable, *, multi=False):
self.multi = multi
super().__init__(iterable)
def __mul__(self, other):
cls = self.__class__
return cls([[x*y for y in self] for x in other], multi=True)
def __add__(self, other):
cls = self.__class__
if isinstance(other, cls):
return cls([[x + y for y in self] for x in other])
elif self.multi:
return cls([[y + other for y in x] for x in self])
else:
return cls([x + other for x in self])
a = MList([1,2,3,4])
b = MList([0,1,2])
print(a * b + 1)
Output:
[[1, 1, 1, 1], [2, 3, 4, 5], [3, 5, 7, 9]]
No Error checking, no support for more dimensions...
If you do something unexpected, it blows up.
If you implement this all, the code grows fast.
It's good to know nested list comprehensions, as a takeaway.
The rest is a bit playing with magic methods of classes.
So you can change the behavior. Putting the complex stuff into
a class and reuse logically the operators (*,/,+,-,@,%,**,...).
The class MList inherits from class list (which is a built-in type).
All methods, which are not implemented in class MList, are
looked up in the list type. The list implements __len__ (on c level I think).
So,
len(a)
still works.
Also visible methods like sort, extend, append etc.
are still usable on the MList object. If you extend a
class/type with you own code, you may get some unexpected
behavior.
Doing this to get deeper into Python is not an easy task.
Often functions are simpler, shorter and easier.
Classes are often used in cases, where nobody needs them.
And there are libraries, which have solved most of our problems.
Everything I have shown, is interesting for small lists, toy applications.
If it's getting huge (many dimensions), the approach with lists will be very slow.
Numpy solves this problem for us.
PS: The example changed the API of a list. The + operator was before for list concatenation.