5

Python version: "'2.7.3 (default, Apr 10 2013, 06:20:15) \n[GCC 4.6.3]'"

I have this:

>>> class testclass1(object):
    ...     pass
    ... 

>>> class testclass2(object):
    ...     def __init__(self,param):
    ...             pass
    ... 

>>> a = object.__new__(testclass1, 56)
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    TypeError: object.__new__() takes no parameters

>>> b = object.__new__(testclass2, 56)

>>> b
    <__main__.testclass2 object at 0x276a5d0>

Some more fun! Compare with results of testclass1 above.

>>> class testclass3(object):
    ...     def __init__(self):
    ...             pass
    ... 

>>> c = object.__new__(testclass3, 56)

>>> c
    <__main__.testclass3 object at 0x276a790>

>>> c1 = object.__new__(testclass3)

>>> c1
    <__main__.testclass3 object at 0x276a810>

My question is how does (not why does) object__new__ behave differently in these two cases? Also notice the error is kind of misleading in the first case because in the second case object.__new__ does end up taking an argument!.

Ankur Agarwal
  • 23,692
  • 41
  • 137
  • 208
  • 2
    Which Python version are you using? I cannot repeat your first error message; what I get instead in both 2.7 and 3.3 is `TypeError: object() takes no parameters`, which is less misleading because it doesn't claim anything about `__new__`, only about the constructor. – user4815162342 Sep 08 '13 at 06:31
  • @user4815162342 I am on 2.7 and can reliably repro the error. – Ankur Agarwal Sep 08 '13 at 06:37
  • Ah, they changed it between 2.7.3 and 2.7.4, probably to reflect a similar change in 3.x sources. – user4815162342 Sep 08 '13 at 06:40
  • the answers given below do not answer why : 1. it should be object.__init__ takes no parameters instead of object.__new__ takes no parameter (testclass1) 2. why no error is raised for testclass3 ? (as it takes no arguments other than self) – ychaouche Oct 09 '13 at 16:12

3 Answers3

6

Both object.__new__ and object.__init__ go through a carefully constructed maze of conditions that allow excess arguments in some cases, raise an error in others, and raise a warning in a very specific one. The code that implements the checks is easy enough to follow, but the reasoning behind it would likely remain inscrutable without this elucidating comment:

You may wonder why object.__new__() only complains about arguments when object.__init__() is not overridden, and vice versa.

Consider the use cases:

  1. When neither is overridden, we want to hear complaints about excess (i.e., any) arguments, since their presence could indicate there's a bug.

  2. When defining an Immutable type, we are likely to override only __new__(), since __init__() is called too late to initialize an Immutable object. Since __new__() defines the signature for the type, it would be a pain to have to override __init__() just to stop it from complaining about excess arguments.

  3. When defining a Mutable type, we are likely to override only __init__(). So here the converse reasoning applies: we don't want to have to override __new__() just to stop it from complaining.

  4. When __init__() is overridden, and the subclass __init__() calls object.__init__(), the latter should complain about excess arguments; ditto for __new__().

Use cases 2 and 3 make it unattractive to unconditionally check for excess arguments. The best solution that addresses all four use cases is as follows: __init__() complains about excess arguments unless __new__() is overridden and __init__() is not overridden (IOW, if __init__() is overridden or __new__() is not overridden); symmetrically, __new__() complains about excess arguments unless __init__() is overridden and __new__() is not overridden (IOW, if __new__() is overridden or __init__() is not overridden).

However, for backwards compatibility, this breaks too much code. Therefore, in 2.6, we'll warn about excess arguments when both methods are overridden; for all other cases we'll use the above rules.

user4815162342
  • 141,790
  • 18
  • 296
  • 355
3

The class that you are creating has its member __init__() is called by new() to handle any creation parameters but in the fist case you have no __init__ so can not pass any parameters.

Steve Barnes
  • 27,618
  • 6
  • 63
  • 73
  • Right. I was more interested in how does this happen and not why does this happen. – Ankur Agarwal Sep 08 '13 at 06:07
  • 2
    The `object.new()` code checks for an init method and raises an error if it is not possible to call it with the supplied parameters – Steve Barnes Sep 08 '13 at 06:33
  • 2
    Interestingly, both `object.__new__` and `object.__init__` go through a carefully constructed maze of conditions that allow excess arguments in some cases, raise an error in others, and raise a warning in a specific one. It would be quite hard to understand without [this elucidating comment](http://hg.python.org/cpython/file/44ed0cd3dc6d/Objects/typeobject.c#l2818) in the code. – user4815162342 Sep 08 '13 at 06:54
  • @user4815162342 If you make your comment as an answer I would be inclined to accept that as the answer to my question. – Ankur Agarwal Sep 08 '13 at 07:09
  • @abc I've added an answer. – user4815162342 Sep 08 '13 at 14:45
3

The __new__ static method takes a class as its first argument. The other arguments will be passed into that class's __init__ method. Since your class has no __init__ method, __new__ won't accept the other arguments.

Take a look at the documentation for more info.

As for the "how", it's implemented in C (Objects/typeobject.c), but you could perform that same check with pure Python:

def __new__(cls, *args, **kwargs):
    ...

    if not hasattr(cls, '__init__') and (args or kwargs):
        raise TypeError("object.__init__() takes no parameters")

    ...
Blender
  • 289,723
  • 53
  • 439
  • 496
  • Right. I was more interested in how does this happen and not why does this happen. – Ankur Agarwal Sep 08 '13 at 06:07
  • @abc: I'm not really sure what you mean by "how". It's implemented in C. – Blender Sep 08 '13 at 06:20
  • There is a bug in the code. `not` has higher precedence than `and` and it is not so much the args to `__new__` that matter its the args to `__init__` that lead ( or do not lead to the error). – Ankur Agarwal Sep 08 '13 at 06:32
  • @abc: Well, yeah. The error is thrown only if you supply extra arguments to `__new__` and the class has no `__init__` method. Like I said, if you're interested in the actual implementation, look at the C source. – Blender Sep 08 '13 at 06:35