Bonus Challenge Discussion

One rabbit hole I’ve been exploring of late, and one knows this has been discussed of long last, is a class without an __init__() method.

>>> class SortedList(list):
	def append(self, value):
		super().append(value)
		self.sort()

		
>>> a = SortedList([4,3,6,54,7,2,8,1,9,7,8,5,6,3,4,5,6,1,2])
>>> a.append(19)
>>> a
[1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 6, 7, 7, 8, 8, 9, 19, 54]
>>> isinstance(a, SortedList)
True
>>> isinstance(a, list)
True
>>> 

The only thing missing is a sorted list upon instantiation. Once we append a dummy (or real) value the list is sorted. We can remove the dummy value if needs be.

>>> b = SortedList([14,23,36,54,47,52,68,71,89,97,18,25,36,43,54,65,76,81,92])
>>> b.append(19)
>>> b
[14, 18, 19, 23, 25, 36, 36, 43, 47, 52, 54, 54, 65, 68, 71, 76, 81, 89, 92, 97]
>>> a
[1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 6, 7, 7, 8, 8, 9, 19, 54]
>>> 

We can still have multiple instances of a class without initializing each instance, it would appear. This is worth exploring as it looks to be a vector by which we can deliver to our objects behavior that is not found in the Standard Library.

If you are talking about this bit of code that @itroyjan1 posted:

class SortedList(list):
  def __init__(self):
    self.sort()
  
  def append(self, value):
    super().append(value)
    self.sort()
    
print(SortedList([4, 1, 5]))

There are two issues with it.
First, the error that is thrown is because the __init__(self) should should take in a value for the new list to have, so it should be __init__(self, lst). The new code would look like this:

class SortedList(list):
  def __init__(self, lst):
    self.sort()
  
  def append(self, value):
    super().append(value)
    self.sort()
    
print(SortedList([4, 1, 5]))

Now for the second issue. Is you run the above code you’ll notice an empty list is printed, this is because we are not creating a new list with the value that was passed in. To do this, we need to call the list class’s __init__ method to initialise the internal state of the object with the passed in list argument. This is done with the super method.

So the final code would look like @suraj95a’s in the second post.

Is it possible to create that SortedList in one step as we would for a normal list, without defining our own class or instance variable?

I’m not 100% sure what you mean by this, I’m guessing you mean like SortedList([1,2,3])? If yes then the above solution will achieve that if not then you’d have to reword it for me.

1 Like

To say this is a class without an __init__ is slightly misleading. This is a class that does not declare an __init__, it does actually have one. In this case, it is list.__init__() that is being called to initialise the SortedList.

This is easily proven by the following:

print(SortedList.__init__ is list.__init__)

This is worth exploring as it looks to be a vector by which we can deliver to our objects behavior that is not found in the Standard Library.

I don’t understand this statement, how would not defining an initialiser allow you to add behaviour to an object?

If for whatever reason you really wanted to have the initial value list that is passed in be sorted without defining an init method then use a class decorator. Like so:

def sort_argument(func):
    def wrapper(lst):
        lst.sort()
        return func(lst)
    return wrapper
    
@sort_argument
class SortedList(list):
	def append(self, value):
		super().append(value)
		self.sort()
		
print(SortedList([15, 3, 2, 6, 9]))
1 Like

Good points, all around. Thanks for that. As I said, this is a bit of a rabbit hole for me, and many others.

I found it enough to override repr(). I also found issues trying to modify init and not sure what would be the best practice.
I leave my code below:

class SortedList(list):

def append(self, value):
super().append(value)
return self.sort()

def repr(self):
return “{}”.format(sorted(self))

lista = SortedList([5, 2, 10])
lista.append(8)
print(lista)

Is this what they meant in the exercise? I used the missing function to address the missing key part of the exercise.

class Superdict(dict):
  fallback_value = "Item not found!"
  def __init__(self, dic):
    super().__init__(dic)
    self.dic = dic
  def __missing__(self,key):
    return self.fallback_value
  

Hey @suraj95a,
I would suggest some improvement to your code:
In your method def append you can leave out the self.sort() because every newly created Object SortedList has it in the __init__ method and gets sorted automatically when the list is created.
Hence, also the append method itself is already in the Parent class of SortedList, so by calling the __init__method of the Parent class (Parent Class is list) with the code line: super().__init__(lst) you already have the append method in there. So you don’t have to define the append method again in your child class.

You can reduce your code to the following and it will give you the same result:

class SortedList(list):
  
  def __init__(self, lst):
    super().__init__(lst)
    self.sort()


new_list = SortedList([4, 1, 5])
print(new_list)
new_list.append(0)
print(new_list)

Without overriding the Parent Class list .append method with self.sort(), appending values to a SortedList object result in a normal append operation - where values are added to the end of a list without keeping values sorted. See below:

class SortedList(list):
  
  def __init__(self, lst):
    super().__init__(lst)
    self.sort()

new_list = SortedList([4, 1, 5])
print(new_list)
[1, 4, 5]
for x in range(3):
  new_list.append(x)
print(new_list)
[1, 4, 5, 0, 1, 2]

Inheriting and overriding the Parent Class list .__init__ with self.sort() doesn’t affect the behavior of the Parent Class’ .append method.

Keeping the overridden .append intact with self.sort() keeps the list constantly sorted. In other words, overriding the Parent Class .append method is still necessary here.

class SortedList(list):
  
  def __init__(self, value):
    super().__init__(value)
    self.sort()
  
  def append(self, value):
    super().append(value)
    self.sort()
  

new_list = SortedList([4, 1, 5])
print(new_list)
[1, 4, 5]
for x in range(3):
  new_list.append(x)
print(new_list)
[0, 1, 1, 2, 4, 5]

My solution:

# print(dir(list))
class SortedList(list):
  def append(self, value):
    super().append(value)
    self.sort()
    
  def __init__(self, values):
    super().__init__(values)
    self.sort()
  
# sl = SortedList([4, 1, 5])
# print(sl)
# sl.append(2)
# print(sl)

# print(dir(dict))
class NewDict(dict):
  fallback = 'No such a key found!'
  def __getitem__(self, key):
    try:
      return super().__getitem__(key)
    except KeyError:
      return self.fallback
      
  def __init__(self, values):
    super().__init__(values)

# nd = NewDict({"a": 1, "b": 2, "c": 3})
# print(nd["d"])