shaila
shaila

Reputation: 335

What are the other uses of __new__ method in python?

Here I have defined immutable class str. In __new__ method I am changing the values of instances such as "hello" to uppercase. Why should we do it using __new__ when we can define upper in __init__ ?

class Upperstr(str):
    def __new__(cls, value=""):
        print(cls)
        print(value)
        return str.__new__(cls, value.upper())

    # def __init__(self, m1):
    #     self.m1 = m1.upper()
    
u = Upperstr("hello")
print(u)

New is used to create class instances. What are the other uses of __new__ method?

Upvotes: 8

Views: 9737

Answers (3)

Toothpick Anemone
Toothpick Anemone

Reputation: 4644

Your question was something like,

Under what conditions is __new__ better to use than __init__ in Python?


Suppose that we have a very simple class named Point

class Point():
    def __init__(self, x:float, y:float):
        self._x = x
        self._y = y

We instantiate the Point class as follows:

# construct and initialize paula the point
paula = Point(10.2, 7.4)

In order to help explain what __new__ does, I will write a classmethod named make_point which has almost the same behavior as paula = = Point(10.2, 7.4).

class Point():
    def __new__(cls, *args, **kwargs):
        print("__new__(" + ", ".join(str(x) for x in [cls, *args]) + ")")
        obj = super().__new__(cls)
        return obj

    def __init__(self, y:float, y:float):
        args = (y, y)
        print("__init__(" + ", ".join(str(x) for x in [self, *args]) + ")")
        self._y = y
        self._y = y

    @classmethod
    def make_point(cls, *args):
        new_instance = cls.__new__(cls, *args)
        if isinstance(new_instance, cls):
            cls.__init__(new_instance, *args)
        return new_instance

Now we can instantiate the Point class as follows:

peter = Point.make_point(59.87, 5.91)

The __init__ method is unable to replace the self parameter with something new and different.

For example, people sometimes want want to put a wrapper around the self parameter. You might want something like this:

import functools
import sys

class decorator:

    def __new__(cls, kallable):
        instance = super().__new__(cls)
        instance = functools.update_wrapper(instance, kallable)
        return instance

    def __init__(self, kallable):
        self._kallable = kallable
        self._file     = sys.stdout

    def __call__(self, *args, **kwargs):
        print("__init__(" + ", ".join(str(x) for x in [self, *args]) + ")", file=self._file)
        return self._kallable(*args, **kwargs)

@decorator
def pow(base:float, exp:int):
    """
         +------------------------------------------+
         |                EXAMPLES                  |
         +------------------------------------------+
         | BASE | EXPONENT |       OUTPUT           |
         +------+----------+------------------------+
         |    2 |        5 | 2^5      |          32 |
         |  2.5 |        7 | 2.5^7    | 610.3515625 |
         |   10 |        3 | 10^3     |        1000 |
         |  0.1 |        5 | 0.1^5    |     0.00001 |
         |    7 |        0 | 7^0      |           1 |
         +------+----------+----------+-------------+
    """
    base = float(base)
    # convert `exp` to string to avoid flooring, or truncating, floats
    exp  = int(str(exp))
    if exp > 0:
        return base * pow(base, exp-1)
    else: # exp == 2
        return 1

result1 = pow(2, 5)
result2 = pow(8.1, 0)
print("pow(2, 5) == "  , result1)
print("pow(8.1, 0) == ", result2)

print("What is the documentation string? The doc-string is... ", pow.__doc__)

It will not help to use functools.update_wrapper inside of __init__. If you try to write the following...

class f:
    def __init__(self, inner):
        # Here, the wrapper callable (function o functor) is named `self`
        self = functools.update_wrapper(self, inner) 

... then self = foo(self) will be ignored.

In python, you are not allowed to replace the parameter named self with a different self

The __new__ method is nice because it allows us to replace self with a wrapper around self

import functools

class decorator:

    def __new__(cls, wrapped):
        assert(callable(wrapped))
        wrapper = super().__new__(cls)
        wrapper = functools.update_wrapper(wrapper, wrapped)
        wrapper._kallable = kallable  
        return wrapper

    def __call__(self, *args, **kwargs):  
        return self._kallable(*args, **kwargs)  

Without using functools.update_wrapper inside of __new__ the doc-string inside of the original callable will be wiped-out, ignored, not inherited, and/or shadowed.

Upvotes: 1

Daweo
Daweo

Reputation: 36570

New is used to create class instances. What are the other uses of new method?

You can use __new__ to implement singleton pattern (where pattern must be understand as thing described in Design Patterns: Elements of Reusable Object-Oriented Software), take look at example provided by geeksforgeeks.org of Classic Singleton

class SingletonClass(object):
  def __new__(cls):
    if not hasattr(cls, 'instance'):
      cls.instance = super(SingletonClass, cls).__new__(cls)
    return cls.instance
   
singleton = SingletonClass()
new_singleton = SingletonClass()
 
print(singleton is new_singleton)
 
singleton.singl_variable = "Singleton Variable"
print(new_singleton.singl_variable)

Upvotes: 11

Simon Provost
Simon Provost

Reputation: 438

new is basically a standard Python method that is invoked before init when a class instance is created. For further information, see the python new manual: https://docs.python.org/3/reference/datamodel.html#object.__new__

There is not other direct usage of it.

Regarding the difference between Init/New: The constructor function in python is called new and init is the initializer function. According to the Python documentation, new is used to control the creation of a new instance, whereas init is used to handle its initialization.

Upvotes: -3

Related Questions