How to set the "base class list" in derived class __init__ in Python?
I have a class that extends list; simplifying as much as possible, it looks like:
class grid(list):
def __init__(self, data):
"If data is an int, create empty grid of size data; if data is a list, initialize grid with that data."
if type(data) == grid: # make a new copy of the old grid instance 'data'
self += data
self.__dict__ |= data.__dict__.copy()
elif type(data) == list:
self += data
self.score = sum(len(row) for row in data)
elif type(data) == int:
self += [[] for _ in range(data)] # NOTE: [[]]*data creates multiple references to the same empty row!
self.score = 0
else: raise Exception("data must be integer or list!")
It works but I find it awkward that I must (?) use +=
to "append" the content I want to create to the initially empty "base list":
If use, e.g., self = data
, then self
becomes a basic list and setting attributes like "self.score = ..." will result in an error.
I think and/or know that I could also use super().__init__(data)
to copy the given initial data into the underlying list. But it looks like an overkill, unnecessarily "heavy". (Isn't my way of doing it (much) more efficient? After all, the empty list is already there, wouldn't super().__init__
create an entire new one?) For the data=int case, should I then say super().__init__([...])
creating two copies of the new "empty" list: the one given as argument to super.init and the copy created by that function!?
Also, is my code for creating a copy of an existing instance of grid
, in particular, self__dict__ |= data.__dict__.copy()
, the way to go? In reality, there are many more attributes in addition to .score
, some of them being lists. I know that I must (?) use individual copy()
s for each of them to effectively create new copies of these and not just references to the attributes of the old instance. Should I use some deepcopy
module to do this in a "cleaner"(?) way?
EDIT/UPDATE: To clarify, my question was whether there is a way to assign a value to the base list (in the present case, or base-whatever in the general case), as we assign values to the other components (i.e., attributes) of the object (for example, self.__inner_list = list(data)
.). The type of the "rows" (elements of the list) is irrelevant, I used [] in the empty grid for simplicity but actually the rows are sets in my real code, but I also allow lists and dicts - in particular because I must/want/do output empty rows using the usual mathematical notation for empty sets, "{}" (which however represents an empty dict
in Python), and for consistency also want to allow this as input. From the comments it appears that there is no such way to "simply" assign a value to the base object (which is logical, after all).
Solution 1:
Ignoring some other issues, I would write the class like this:
class grid(list):
def __init__(self, lists: list[list]):
self.extend([x] for x in lists)
@classmethod
def from_grid(cls, g: grid):
# A grid is already a list of lists
return cls(g)
@classmethod
def from_int(cls, n):
return cls([[] for _ in range(n)])
A call like list([1,2,3])
first uses list.__new__
to create an empty list (ignoring the argument, though possibly using it to preallocate enough for the new list to hold the three values), then passes that new list and the given iterable to list.__init__
to copy the values from the iterable into the new list. You can see this by overriding __init__
to examine the value of self
before and after calling super().__init__
:
class A(list):
def __init__(self, x):
print(self)
super().__init__(x)
print(self)
Then
>>> a = A([1,2,3])
[]
[1, 2, 3]