Welcome to the Treehouse Community

Want to collaborate on code errors? Have bugs you need feedback on? Looking for an extra set of eyes on your latest project? Get support with fellow developers, designers, and programmers of all backgrounds and skill levels here with the Treehouse Community! While you're at it, check out some resources Treehouse students have shared here.

Looking to learn something new?

Treehouse offers a seven day free trial for new students. Get access to thousands of hours of content and join thousands of Treehouse students and alumni in the community today.

Start your free trial

Python Object-Oriented Python Advanced Objects Frustration

Harris Handoko
Harris Handoko
3,932 Points

Help needed on frustration.py

Kenneth is a brilliant guy, but I really have been lost following his lectures on OOP. But I really want to get to the bottom of this: I know people have been posting the answer on the forums, which was really the only way I managed to pass this challenge task.

import random


class Liar(list):
    def __init__(self,*arg):
        self.arg = []
    def __len__(self):
        super().__len__()
        randomNum = random.randint(0,10)
        x = randomNum + len(self.arg)
        return x

A couple of them seems to have overriden init again in their Liar(list) class.

Why is it necessary to define init again? Or is it the conventional way? If I just removed the init and moved that self.arg = [] into the len method, it works just the same.

import random

class Liar(list):

    def __len__(self, *arg):
        self.arg = []
        super().__len__()
        randomNum = random.randint(0,10)
        x = randomNum + len(self.arg)
        return x

Why is it that if I don't use random library, the checker won't let me pass? I thought as long as it spits out anything other than the actual value, it should be fine. I tried removing those import random, randomNum = random.randint(0,10) lines and changed the randomNum to some value: x = 100 + len(self.arg)... and it didn't work.

2 Answers

Louise St. Germain
Louise St. Germain
19,424 Points

Hi Harris,

As you suspected, a lot of the code above is not necessary. I was able to pass the challenge with only 3 simple lines of code, as follows:

class Liar(list):
    def __len__(self):
        return super().__len__() + 2

Line by line, this is what it's doing:

  1. Create the Liar class as an extension of the list class.
  2. Define a method called _ _ len _ _ , which defines what it will do when someone calls the method on an instance of the Liar class. e.g., liar_list_length = len(my_liar_instance).
  3. When someone does call my Liar class len() method, tell Python to just use the regular len function that's already defined for lists (super() refers to the parent class, in this case, list; and _ _ len _ _() refers to the list class's pre-defined len() function). Then add 2 to whatever comes from that, to change it from the true length to a fake length.

A few quick comments:

  • You don't need a random number. You could use one, as in the example you posted, but it's an extra complexity that is not necessary here. Also, you wouldn't want 0 as an option (as above), since if you add 0 to the real length, you still have the real length, and not a fake length! My code just arbitrarily adds 2 to whatever the actual length is, and it was fine.
  • You also don't need a special _ _ init _ _ method. You would put code here if you wanted it to do something special that doesn't happen with a regular list, but since it's supposed to just create a list like usual (and this is a quick coding challenge!), I'm just letting it defer to the parent _ _ init _ _ method.
  • The *arg is not a necessary addition to the parameters of the _ _ len _ _ method. If you include it, it's because you want to collect any extra values that the user might pass when they call the len() method on a Liar object. For example, if they call len(my_liar_instance, 12, "apple", "photo_of_grandma.jpg") instead of just len(my_liar_instance). But since you have no use for any extra arguments here, you don't need to worry about any of that inside the method. The list that we want the length of is part of self, and is not within those extra args. I chose to leave *args out entirely since there's no good reason that anyone would call len() with extra arguments.
  • Also, in the original code, self.arg is redundant. It's initialized to an empty list, and it's never updated anywhere. So there's no point in having it there anyway, because its length is always 0. Python won't make any automatic connection between that and the *arg in the parameter list.
  • Meanwhile, in the original code, super()._ _ len _ _() is called, which is what you want (that's what gives you the length of your list). However, the result of it is not captured anywhere, so the result just disappears into a black hole.

I hope this helps a bit!

Harris Handoko
Harris Handoko
3,932 Points

Wow, A very well-explained answer! Thank you for taking the time! Just one more thing I am unclear about is: Is it then proper to assume that the class list in general, already has a _ len _ defined in it? Or is it only specific to this context?

Anthony Grodowski
Anthony Grodowski
4,902 Points

I know it works, but I don't get how its useful even tho it doesn't have self in return super().__len__(HERE) + 2. I've done like this: class Liar(list): def __len__(self): return list.__len__(self) + 2 and without self after return it doesn't work. Why in your solution it works without self and in mine it doesn't AND if I put self in your code it throws an error (TypeError: expected 0 arguments, got 1)

Also can someone tell me how to put my code-block in an answer into a black boc like Loiuse did? TIA

Louise St. Germain
Louise St. Germain
19,424 Points

Hi Harris,

Yes, the list in general already has a _ _ len _ _ defined - it's not specific to this context. Let's say you do this (non-OOP) type of thing:

my_list = [1, 2, 3]  # This creates a list.
length = len(my_list)  # This will return 3

When you created the list in the first line, in the background, Python called the list class's _ _ init _ _ method. This was "behind the scenes" as far as you're concerned as a user.

Similarly, when you called len(my_list), what Python really did was translate that into a call to the list class's _ _ len _ _ method that's built into that class, and the argument my_list that you passed to it is what turns into self when you're looking at it from inside the function. It's a reference/pointer to an instance of a list-class object so that the function knows which list you're talking about.

In general, those "dunder" functions represent some sort of pre-defined operation you can do on the built-in classes, and they are usually represented with simple operators on the user side. Python then translates these into the dunder methods behind the scenes. You only need to worry about them in OOP because then, you are going "behind the scenes" yourself to build your own classes.

More examples:

sum = 1 + 2  # calls the __add__ method for int class (normal addition)
counter += 1  # calls the __iadd__ method for int class (in-place addition)
"apple" == "orange"  # calls the __eq__ method for str class (checking for equality)
Harris Handoko
Harris Handoko
3,932 Points

Louise, thank you so much for the easy-to-follow explanations!

Jiawei Hu
Jiawei Hu
9,740 Points

Your explanations are awesome !!

Louise St. Germain
Louise St. Germain
19,424 Points

Thank you - I'm glad they were helpful! Happy programming! :-)