4

In Python 3, the following code throws a TypeError because object() takes no parameters. In Python 2.7, no error is thrown. Why isn't it an error in 2.7?

class A(object):

    def __new__(self, **kwargs):
        return super(A, self).__new__(self, **kwargs)

    def __init__(self, **kwargs):
        super(A, self).__init__(**kwargs)


A(x=3)
markw
  • 620
  • 3
  • 14
  • Possible duplicate of https://stackoverflow.com/questions/47602278/correctly-override-new-in-python3 – DeepSpace Jun 03 '18 at 13:15
  • @DeepSpace I don't see how that answers the question? – Aran-Fey Jun 03 '18 at 13:16
  • @chepner: perhaps you're thinking of Python 3? In 2.7, arguments are required for super. Edit: also, you get an error in Python 3.6.4 regardless. – markw Jun 03 '18 at 13:26
  • Oh, right. I spun myself in a circle looking for the difference. Python 3 is the one the correctly fails. – chepner Jun 03 '18 at 13:28
  • @chepner I don't see anything wrong with either `super` call. What's a little misleading though is that the first parameter of `__new__` is named `self` instead of `cls`. – Aran-Fey Jun 03 '18 at 13:28

1 Answers1

2

Well, it looks like someone decided to deprecate in 2.6, but didn't raise an exception until somewhere between 2.7.9 and 3.6.4.

from typeobject.c:

/* 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.
*/

2.7 does give a warning though (similar code in object_init):

static PyObject *
object_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
    int err = 0;
    if (excess_args(args, kwds)) {
        if (type->tp_new != object_new &&
            type->tp_init != object_init)
        {
            err = PyErr_WarnEx(PyExc_DeprecationWarning,
                       "object() takes no parameters",
                       1);
        }
        else if (type->tp_new != object_new ||
                 type->tp_init == object_init)
        {
            PyErr_SetString(PyExc_TypeError,
                "object() takes no parameters");
            err = -1;
        }
    }
...
}

but you only see it if you enable deprecation warnings (option -Wd on python command line).

markw
  • 620
  • 3
  • 14