Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

__new__ method giving error object.__new__() takes exactly one argument (the type to instantiate)

why the following code is giving error?

class Foo:
    def __new__(cls, *args, **kwargs):
        print("Creating Instance")
        instance = super(Foo, cls).__new__(cls,*args, **kwargs)
        return instance

    def __init__(self, a, b):
        self.a = a
        self.b = b

z= Foo(2,3)

it is giving the following error

TypeError: object.__new__() takes exactly one argument (the type to instantiate)
like image 249
Danish Mahmood Avatar asked Dec 06 '19 17:12

Danish Mahmood


4 Answers

    instance = super(Foo, cls).__new__(cls,*args, **kwargs)

is correct. However, you are responsible for first removing arguments that your class introduces, so that when object.__new__ is ultimately called, both *args and **kwargs are empty.

Your code should be something like

class Foo:
    def __new__(cls, a, b, *args, **kwargs):
        print("Creating Instance")
        instance = super(Foo, cls).__new__(cls, *args, **kwargs)
        return instance

    def __init__(self, a, b, *args, **kwargs):
            super().__init__(*args, **kwargs)
            self.a = a
            self.b = b

This definition removes your new parameters a and b from args before passing it on to whoever is next on the MRO. Likewise for __init__.

like image 149
chepner Avatar answered Sep 25 '22 09:09

chepner


object.__new__() signature is (*args, **kwargs), you may check this by using inspect.signature function.

But why then you have this error? TLDR: because you defined custom __new__ method.

Small research

All tests were done on Python 3.9.1.


Consider next class.

class MyClass:
    def __init__(self): pass

Let's call object.__new__() on it:

>>> object.__new__(MyClass, *range(10), **{f'a{i}': i for i in range(10)})
<__main__.MyClass object at 0x000001E7B15B3550>

No problems at all. This class has only custom __init__ and no custom __new__. Now try to do the same call for your Foo:

>>> object.__new__(Foo, *range(10), **{f'a{i}': i for i in range(10)})
Traceback (most recent call last):
  File "<input>", line 1, in <module>
TypeError: object.__new__() takes exactly one argument (the type to instantiate)

An exception about object.__new__(). This class has both custom __init__ and __new__. You will see the same error when only custom __new__ is defined:

>>> class OnlyNew:
...     def __new__(cls, *args, **kwargs): return super().__new__(cls)
>>> object.__new__(OnlyNew, *range(10), **{f'a{i}': i for i in range(10)})
Traceback (most recent call last):
  File "<input>", line 1, in <module>
TypeError: object.__new__() takes exactly one argument (the type to instantiate)

Let's check a class with no custom __init__ and __new__.

>>> class A: pass
>>> object.__new__(A, *range(10), **{f'a{i}': i for i in range(10)})
Traceback (most recent call last):
  File "<input>", line 1, in <module>
TypeError: A() takes no arguments

Absolutely different error.


Let's check how it works with inheritance. Derive from A and define __init__.

>>> class B(A):
...     def __init__(self): pass
>>> object.__new__(B, *range(10), **{f'a{i}': i for i in range(10)})
<__main__.B object at 0x000001E7B15D23A0>

Derive from MyClass and define nothing.

>>> class MC(MyClass): pass
>>> object.__new__(MC, *range(10), **{f'a{i}': i for i in range(10)})
<__main__.MC object at 0x000001E7B15D2CA0>

Derive from MyClass and define __new__.

>>> class Sub(MyClass):
    def __new__(cls, *args, **kwargs): return super().__new__(cls)
>>> object.__new__(Sub, *range(10), **{f'a{i}': i for i in range(10)})
Traceback (most recent call last):
  File "<input>", line 1, in <module>
TypeError: object.__new__() takes exactly one argument (the type to instantiate)

Derive from Foo and define nothing.

>>> class F(Foo): pass
>>> object.__new__(F, *range(10), **{f'a{i}': i for i in range(10)})
Traceback (most recent call last):
  File "<input>", line 1, in <module>
TypeError: object.__new__() takes exactly one argument (the type to instantiate)

And now let's look on an absolutely exotic case:

class Base:
    def __init__(self): pass
    def __new__(cls, *args, **kwargs): return super().__new__(cls)


class Sub(Base):
    def __init__(self): pass
    __new__ = object.__new__


class Free:
    def __init__(self): pass
    __new__ = object.__new__
>>> object.__new__(Free, *range(10), **{f'a{i}': i for i in range(10)})
<__main__.Free object at 0x000001E7B15C5A90>
>>> object.__new__(Sub, *range(10), **{f'a{i}': i for i in range(10)})
Traceback (most recent call last):
  File "<input>", line 1, in <module>
TypeError: object.__new__() takes exactly one argument (the type to instantiate)

Both Sub and Free do not have a custom __new__ method - in both classes __new__ is object.__new__(). But creating Sub raises an error while creating Free does not. Seems like object.__new__() checks not getattr(A_Class, '__new__', object.__new__) is object.__new__ but all(getattr(cls, '__new__', object.__new__) is object.__new__ for cls in A_Class.mro()).


Conclusion

  1. If a class has custom __new__ in its MRO, calling object.__new__() with >1 arguments raises TypeError.
  2. If a class has only custom __init__ and does not have custom __new__ in its MRO, calling object.__new__() with >1 arguments creates a proper instance.
  3. If a class does not have both custom __init__ and __new__ in its MRO, calling object.__new__() with >1 arguments raises TypeError.
like image 22
Svyatoslav Avatar answered Sep 27 '22 09:09

Svyatoslav


In addition to this and this answer, It's good to have this response from Guido van Rossum here.

This addresses the behavior of object.__new__ when overriding or not overriding __new__ in subclasses and what happens to the extra arguments which passed that method.

There's no point in calling object.__new__() with more than a class parameter, and any code that did so was just dumping those args into a black hole.

The only time when it makes sense for object.__new__() to ignore extra arguments is when it's not being overridden, but __init__ is being overridden -- then you have a completely default __new__ and the checking of constructor arguments is relegated to __init__.

The purpose of all this is to catch the error in a call like object(42) which (again) passes an argument that is not used. This is often a symptom of a bug in your program.

from inspect import signature

print(signature(object.__new__))
print('------------------------------')
print(signature(object.__init__))
print('------------------------------')
object(42)

output:

(*args, **kwargs)
------------------------------
(self, /, *args, **kwargs)
------------------------------
Traceback (most recent call last):
  File "<>", line 7, in <module>
    object(42)
TypeError: object() takes no arguments
like image 37
SorousH Bakhtiary Avatar answered Sep 24 '22 09:09

SorousH Bakhtiary


This should work:

class Foo:
    def __new__(cls, a, b):
        print("Creating Instance")
        instance = super(Foo, cls).__new__(cls)
        return instance


    def __init__(self, a, b):
        self.a = a
        self.b = b

foo_1 = Foo(a=1, b=2)
foo_2 = Foo(a=1, b=3)

if you want to use Singleton design pattern, and create instance of class with params in constructor, this should work:

class FooSingleton:
    _instances = {}

    def __new__(cls, a, b):
        if cls not in cls._instances:
            print('creating new FooSingleton instance')
            cls._instances[cls] = super(FooSingleton, cls).__new__(cls)
        else:
            print('using FooSingleton instance')
        return cls._instances[cls]

    def __init__(self, a, b):
        self.a = a
        self.b = b

foo_s1 = FooSingleton(a=1, b=2)
foo_s2 = FooSingleton(a=1, b=3)
like image 4
Peter Trcka Avatar answered Sep 26 '22 09:09

Peter Trcka