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 Subclassing Built-ins

Why do I have to use self = str.__new__(*args, **kwargs)? I tried not using it and it worked.

class Reversed(str):
    def __new__(*args, **kwargs):
        self = str.__new__(*args, **kwargs)
        self = self[::-1]
        return self

So Kenneth called the str new method passing whatever arguments we give it. I read online that when overriding a new method you will always have to call the super class new, and that's kinda of what Kenneth does there.

Just out of curiosity, I tried not calling the super class new with a different version of the code:

class Reversed(str):
    def __new__(cls, value):
        self = value
        self = value[::-1]
        return self

And it worked fine.

>>> name = Reversed("whatever")
# returns 'revetahw'

The same goes to this code from Kenneth's example:

class FilledList(list):
    def __init__(self, count, value, *args, **kwargs):
        super().__init__()  # This creates an empty list, but is this line necessary?
        for _ in range(count):
            self.append(copy.copy(value))

I tried the SAME code but only without using super().init() to create an empty list, and it worked fine.

So why do I have to call the super class init and new in these cases even if without them the code seems to do the same?

I keep researching online and this new versus init thing isn't still very clear to me. I know I still have a lot to learn, but it would be nice to know why do I have to write the code in a certain way.

2 Answers

Chris Freeman
MOD
Chris Freeman
Treehouse Moderator 68,441 Points

Again, another great question! The advantage of using the super or str.__new__ is be able to use the automatic type converters built into the str.__new__ method and gain access to the slicing methods available to the str class.

Try using an integer such as Reversed(12345) on both of the versions above. Without the call to super you would be effectively trying to take a slice of an integer causing a TypeError: 'int' object is not subscriptable.

Edit: Adding a bit more detail. In every object creation, both the __new__ and the __init__ methods are run. The non-obvious behavior is the __new__ calls __init__. This is why __init__ methods do not include a return statement. The newly created and initialized object is returned by the __new__ method. Think of the __init__ method as merely sprucing up the newly create object with attribute values, etc.

Post back if you have more questions. Good luck!!

It makes total sense to me. Thanks so much for helping! I've learned a lot from you!

Hey, Chris! Could you answer this follow up question for me please?

I was playing around with this ReversedStr class. When I typed def __new__(, PyCharm autocompleted including a cls parameter. So far so good.

So I tried to run the same code, only including the cls parameter like this:

class ReversedStr(str):
    def __new__(cls, *args, **kwargs):   # cls included in the function parameter. Kenneth's code doesn't include cls in this case.
        self = str.__new__(*args, **kwargs)  
        return self

So when I went to the console and ran the following code, resulting in a TypeError:

>>>test = ReversedStr("Hello")
# output: TypeError: str.__new__(X): X is not a type object (str)

I kept wondering why was this happening. Then, to try to fix it, I included a cls parameter into the str.__new__

class ReversedStr(str):
    def __new__(cls, *args, **kwargs):
        self = str.__new__(cls, *args, **kwargs)  #cls included here too
        self = self[::-1]
        return self

# Console:
# test = ReversedStr("Hello")
# >>>test
# output: "olleH"

Now, the code ran perfectly.

So if I include the cls parameter inside def __new__, I also have to include inside str.__new__. The same logic goes if I DON'T include the cls parameter inside the def __new__: in this case, I have to NOT include cls inside str.__new__

Can you tell me the reason why this happens? I understood the logic of using cls in other cases, but I can't figure out on this one!

Thank you in advance!

[MOD: added ` escapes around dunder names -cf]

Chris Freeman
Chris Freeman
Treehouse Moderator 68,441 Points

Great follow-up question!: So if I include the cls parameter inside def __new__ , I also have to include inside str.__new__ . The same logic goes if I DON'T include the cls parameter inside the def __new__: in this case, I have to NOT include cls inside str.__new__? Yes, but not for the reasons you may be thinking. The short answer is cls should never be included in the __new__ method definition because __new__ is a static method not a class method! That is, the parameter lists should be *args, **kwargs, or myarg, *args, **kwargs if your __new__ method needs to use a parameter that will not be passed along in the super() call.

The reason using *args, **kwargs in both places or cls, *args, **kwargs in both places works, is that they are virtually the same. Since __new__ is a static method it does not consume the first parameter to refer to the class (cls) or a class instance (self). Since no class object exists yet (nothing new as been created to refer to), there is no meaning to this parameter as there are no other methods or attributes to be referenced. What is really happening is more obvious if I rewrite cls, *args, **kwargs as args[0], *args[1:], **kwargs. Here you can see that cls is really what would otherwise have been the first of the args list.

Update: Video Correction: In the video subclassing builtins, Kenneth says: "The __new__ method does not use "self" because it is a class method."

This is inaccurate. classmethods typically use cls as the first parameter. Since __new__ does not use cls (or self) it is a static method.

Refresher:

  • instance method uses self to refer to the class instance and it's methods. May be referenced by instance.method() or Class().method() (note use of first set of parens).
  • class method uses cls to refer to the defined class. May be referenced by Class.method() (note only method as parens). The class method should be decorated with @classmethod
  • static method (similar to regular function), does not use self or cls and does not consume first parameter to reference the instance or the class. May be referred to using instance.method() or Class.method(). Excluding __new__, the static method should be decorated with @staticmethod

Again, post back if you have more questions! Good Luck!!

Chris Freeman
Chris Freeman
Treehouse Moderator 68,441 Points

I forgot to address the error youโ€™re seeing :

TypeError: str.__new__(X): X is not a type object (str)

Looking at the code below:

class ReversedStr(str):
    def __new__(cls, *args, **kwargs): 
        self = str.__new__([], *args, **kwargs) 
        return self

In the case where cls is included in the method parameter list but not included in the super() call, the TypeError is caused by the โ€œemptyโ€ *args be equivalent to an empty list. The error you get can be seen if an empty list is passed to `super:

# Python Console:
name = ReversedStr("Ewerton")
# returns:
Traceback (most recent call last):
  File โ€œ<string>โ€, line 7, in <module>
  File โ€œ<string>โ€, line 3, in __new__
TypeError: str.__new__(X): X is not a type object (list)

If an empty *args were equivalent to None, the error would have been:

TypeError: str.__new__(X): X is not a type object (NoneType)

First of all, Thank you so much, @Chris! You are one of the reasons why learning from TeamTreehouse is so worth it. :D

I think I understand everything you said, I don't have any questions about it. The only problem is that I think I might not be connecting the dots because I still can't understand why those errors were happening.

Okay, new is a static method (I didn't know that), so it makes perfect sense not to include either "cls" or "self" in it. I only included it because Pycharm's autocomplete did it for me. So just for the heck of it, I experimented doing what I wrote on my first question.

What is really happening is more obvious if I rewrite cls, *args, **kwargs as args[0], *args[1:], **kwargs. Here you can see that cls is really what would otherwise have been the first of the args list.

From your answer, it seems to me that including "cls" in the new definition will make "cls" receive the first argument when ReversedStr is called. Any other argument besides the first one will be packed into args. In conclusion, cls would be like any other regular positional parameter. Understanding that, I think the code I wrote above should work, but it doesn't.

class ReversedStr(str):
    def __new__(cls, *args, **kwargs): 
        self = str.__new__(*args, **kwargs) 
        return self

# Python Console:
# >>> name = ReversedStr("Ewerton")
# returns: TypeError

I'll try to explain what I think should happen line by line when I run name = ReversedStr("Ewerton") on the console:

  1. "Ewerton" will be passed to the cls argument. *args and **kwargs won't receive any argument.
  2. cls is not being passed into str.new, but args and kwargs are. In this case, since args and **kwargs haven't received any arguments, str.new should return an empty string. Therefore, *self is assigned to an empty string.
  3. Since self value is an empty string, the new method will return an empty string.

In the case where I include "cls" both in the definition and inside the str.new call:

class ReversedStr(str):
    def __new__(cls, *args, **kwargs): 
        self = str.__new__(cls, *args, **kwargs)
        self = self[::-1]
        return self

# Python Console:
# >>> name = ReversedStr("Ewerton")
# returns: notrewE
  1. "Ewerton" will be passed to the cls argument. *args and **kwargs won't receive any argument.
  2. cls WILL be passed into str.new, just like args and kwargs are. In this case, str.new will receive "Ewerton" as the first argument. Since args and kwargs haven't received any argument, str.new will receive only "Ewerton".
  3. str.new("Ewerton") would return "Ewerton", therefore self is assigned to "Ewerton".
  4. The new method will return "notrewE"

I know I'm wrong on the first case, otherwise I wouldn't get an error. And I know I might be wrong on this second case too, because even though it works, It might not be from the reasons I think it works.

Chris Freeman
Chris Freeman
Treehouse Moderator 68,441 Points

Iโ€™ve added a comment to my previous answer to address the TypeError as seen in your first example. Your second example and explanation is correct.

Of course, none of the examples will actually reverse the string without the slice reversal statement, left out for brevity. ๐Ÿ™‚