8: Inside Class

Summary

  • __new__ is automatically called to create the instance

  • __init__ is called after __new__ to add instance attributes to the new instance

  • __new__ generally works by calling the superclass __new__ to create an instance, but you can use any mechanism that creates an object

  • As long as the object is of the same type as the class or is subtype the __init__ method is called

  • One standard example of how __new__ can be used is to create a singleton, a class that can only be initialized once

  • Another use of __new__ is to customize immutable data objects

  • Adding a custom __call__ method can be used to convert any object into a callable
    • This is how class is converted to a callable

Program

"""
Demonstration of:
- __new__
- __init__
- Singleton pattern using __new__
- Custom immutable object using __new__
- Custom __call__ method
"""

# --------------------------------------------------
# 1. Basic Example Showing __new__ and __init__
# --------------------------------------------------

class BasicExample:
    def __new__(cls, *args, **kwargs):
        print("__new__ called: Creating instance")
        instance = super().__new__(cls)  # Call superclass __new__
        return instance

    def __init__(self, name):
        print("__init__ called: Initializing instance")
        self.name = name


print("---- Basic Example ----")
obj1 = BasicExample("Brayden")
print("Name:", obj1.name)
print()


# --------------------------------------------------
# 2. Singleton Pattern Using __new__
# --------------------------------------------------

class Singleton:
    _instance = None

    def __new__(cls, value):
        if cls._instance is None:
            print("Creating new Singleton instance")
            cls._instance = super().__new__(cls)
        else:
            print("Using existing Singleton instance")
        return cls._instance

    def __init__(self, value):
        # This still runs every time unless guarded
        self.value = value


print("---- Singleton Example ----")
s1 = Singleton(10)
print("Value:", s1.value)

s2 = Singleton(20)
print("Value:", s2.value)

print("Are s1 and s2 the same object?", s1 is s2)
print()


# --------------------------------------------------
# 3. Custom Immutable Object Using __new__
# --------------------------------------------------

class PositiveInt(int):
    def __new__(cls, value):
        print("Customizing immutable int")
        if value < 0:
            value = 0  # Force non-negative
        return super().__new__(cls, value)


print("---- Immutable Customization Example ----")
num1 = PositiveInt(5)
num2 = PositiveInt(-10)

print("num1:", num1)
print("num2 (negative converted to 0):", num2)
print()


# --------------------------------------------------
# 4. Making an Object Callable with __call__
# --------------------------------------------------

class Multiplier:
    def __init__(self, factor):
        self.factor = factor

    def __call__(self, value):
        return value * self.factor


print("---- __call__ Example ----")
double = Multiplier(2)

print("Calling object like a function:")
print("double(5) =", double(5))
print("double(10) =", double(10))

Program Output

(docs-env) root@BMitchellLTOP:~/git/sphinx_students/source/programming_lang/book/programs# python3 chapterEight.py
---- Basic Example ----
__new__ called: Creating instance
__init__ called: Initializing instance
Name: Brayden

---- Singleton Example ----
Creating new Singleton instance
Value: 10
Using existing Singleton instance
Value: 20
Are s1 and s2 the same object? True

---- Immutable Customization Example ----
Customizing immutable int
Customizing immutable int
num1: 5
num2 (negative converted to 0): 0

---- __call__ Example ----
Calling object like a function:
double(5) = 10
double(10) = 20