Bonus Challenge Discussion

Only to see if it can be done.

Unlike in some languages, __init__ is not a constructor, as such since the object already exists, AND has a value, just not named.

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

		
>>> x = SortedList()
>>> x.extend([7, 4, 6, 1, -7])
>>> x
[-7, 1, 4, 6, 7]
>>> 

Wrap it in a function???

def SortedList(value):
    
    class SortedList(list):          
      def append(self, value):
        super().append(value)
        super().sort()
      def extend(self, value):
        super().extend(value)
        self.sort()
        
    value.sort()
    new_lst = SortedList(value)
    return new_lst

my_list = [4,1,5]

lst = SortedList(my_list)
print(lst)

lst.append(-25)
print(lst)

lst.append(3)
print(lst)

lst.extend([8,4,2,-1])
print(lst)

# Output:
[1, 4, 5]
[-25, 1, 4, 5]
[-25, 1, 3, 4, 5]
[-25, -1, 1, 2, 3, 4, 4, 5, 8]

Not pretty, but …

1 Like

A rather novel approach. I fear we may both be caught out for our naivete.

We may need to rustle up a curriculum advisor to lift the fog. @alyssavigil can you please scare one up to weigh in on this topic?

Thanks. Perhaps best to use a conventional initializer?

Sorry, I don’t have an answer as to what is best. There is a lesson here that we do not want to miss out on.

I don’t think ‘SuperDict’ needs a constructor. The structure of ‘SuperDict’ is essentially the same as the Python canonical class ‘dict’. I think you just need to override the Python get() method.

This code seems to work:

image

1 Like

Hello, I was reading Python doc and found something. You can use missing method in your customized dictionary and return whatever value you want in that method. NOTE that built-in dict class doesn’t have this missing method; so, don’t use super here Python will give you error.

class MyDict(dict):
  def __missing__(self, key):
    return 'Key not present'
7 Likes

I’m trying to add sort functionality to the init method for SortedList, but I’m having trouble figuring out how to store the list itself. I tried a few versions of the answer @itroyjan1 posted, without any success.

# instantiating a normal builtin list:
foo = [x, y, z]

# for a SortedList as defined in the lesson:
new_lst = SortedList()
new_lst.append(5)
new_lst.append(1)
new_lst.append(6)
print(new_lst)

# Output: [1, 5, 6]

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 think this could be a solution

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

list = SortedList([9, 2, 7])
print(list)
# Prints [2, 7, 9]

list.append(1)
print(list)
# Prints [1, 2, 7, 9]
1 Like

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.

2 Likes

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]))
2 Likes

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
  
1 Like

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]
4 Likes

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"])

How do we get repr to represent the answer correctly? My code is:

class SortedList(list):
  def __init__(self, lst):
    super().__init__(lst)
    self.lst = lst
    self.lst.sort()
  
  def __repr__(self):
    return str(self.lst)

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


print(SortedList([99, 6, 7]))

Also, why do we need to use super().__init__(lst) in our initialization? When I tried removing that line I got an error message.

I fixed the above code so it works correctly now, and I’ve realized that super can take another clarifying argument (see When do I need to use super()?). I’ve also tried the dictionary example, and I found another bit of weird behavior.

class BetterDict(dict):
  def __init__(self, dictionary):
    self.dictionary = dictionary
    super().__init__(dictionary)

  def get(self, key):
    if key in self.keys():
      return self.dictionary[key]
    else:
      return "No key found!"

my_dict = BetterDict({5 : "five", 3 : "three"})

print(my_dict.get(6))
# returns "No key found!"
print(my_dict.get(5))
# returns 5

When I remove the second line in init, my second print test returns the error message. Why is this? What does super().__init__(dictionary) do, exactly?

Try adding another variable and calling back to the parent class

class SortedList(list):
  def __init__(self,lst):
    super().__init__(lst)
    self.sort()
  def append(self, value):
    super().append(value)
    self.sort()
    
lst_to_sort = SortedList([10,6,5,13])
print(lst_to_sort)
1 Like

@itroyjan1, yup that works! Thanks!!