How can a Python subclass control what data is stored in an immutable instance?

When subclassing an immutable type like int, str, or tuple, you need to override the __new__() method instead of __init__() to control what data gets stored in the instance.

The __new__ method gets called when an object is created, whereas __init__() initializes the object after creation. For immutable types, the data must be set during object creation, not initialization.

Understanding Magic Methods

Magic methods (also called dunder methods) are identified by double underscores as prefix and suffix. They allow us to customize object behavior in Python ?

print(dir(int))
['__abs__', '__add__', '__and__', '__bool__', '__ceil__', '__class__', '__delattr__', '__dir__', '__divmod__', '__doc__', '__eq__', '__float__', '__floor__', '__floordiv__', '__format__', '__ge__', '__getattribute__', '__getnewargs__', '__gt__', '__hash__', '__index__', '__init__', '__init_subclass__', '__int__', '__invert__', '__le__', '__lshift__', '__lt__', '__mod__', '__mul__', '__ne__', '__neg__', '__new__', '__or__', '__pos__', '__pow__', '__radd__', '__rand__', '__rdivmod__', '__reduce__', '__reduce_ex__', '__repr__', '__rfloordiv__', '__rlshift__', '__rmod__', '__rmul__', '__ror__', '__round__', '__rpow__', '__rrshift__', '__rshift__', '__rsub__', '__rtruediv__', '__rxor__', '__setattr__', '__sizeof__', '__str__', '__sub__', '__subclasshook__', '__truediv__', '__trunc__', '__xor__', 'as_integer_ratio', 'bit_length', 'conjugate', 'denominator', 'from_bytes', 'imag', 'numerator', 'real', 'to_bytes']

The __init__() Method Example

Here's how __init__() works with mutable objects ?

class String:
    # The magic method to initiate object
    def __init__(self, string):
        self.string = string

# Driver Code
if __name__ == '__main__':
    # object creation
    myStr = String('Demo')
    # print object location
    print(myStr)
<__main__.String object at 0x7f34c97799d0>

Subclassing Immutable Types with __new__()

To control data in immutable instances, override __new__() method. Here are three practical examples ?

from datetime import date

class FirstOfMonthDate(date):
    "Always choose the first day of the month"
    def __new__(cls, year, month, day):
        return super().__new__(cls, year, month, 1)

class NamedInt(int):
    "Allow text names for some numbers"
    xlat = {'zero': 0, 'one': 1, 'ten': 10, 'fifteen': 15}
    def __new__(cls, value):
        value = cls.xlat.get(value, value)
        return super().__new__(cls, value)

class TitleStr(str):
    "Convert str to name suitable for a URL path"
    def __new__(cls, s):
        s = s.lower().replace(' ', '-')
        s = ''.join([c for c in s if c.isalnum() or c == '-'])
        return super().__new__(cls, s)

# Testing the subclasses
print(FirstOfMonthDate(2022, 9, 8))
print(NamedInt('fifteen'))
print(NamedInt(18))

# Create a URL path
print(TitleStr('course for beginners'))
2022-09-01
15
18
course-for-beginners

How It Works

FirstOfMonthDate: Forces any date to use day 1, regardless of the input day.

NamedInt: Translates string names to integers using a lookup dictionary.

TitleStr: Converts text to URL-friendly format by lowercasing, replacing spaces with hyphens, and removing special characters.

Key Points

? Use __new__() for immutable types because data must be set at creation time

? Use __init__() for mutable types to initialize after creation

? Always call super().__new__() with modified data to create the instance

Conclusion

Override __new__() when subclassing immutable types to control the data stored in instances. This allows you to create specialized versions of built-in immutable types with custom behavior while maintaining their immutable nature.

Updated on: 2026-03-26T21:47:09+05:30

342 Views

Kickstart Your Career

Get certified by completing the course

Get Started
Advertisements