Ian
Ian

Reputation: 3709

Any ideas about the best work around for __new__ losing its arguments?

So, I only realised today that __new__ is deprecated for receiving arguments, as of python 2.6 (it isn't mentioned in the documentation, which is also not true in terms of the behavior of __new__ calling __init__ as far as I can see). This means my functional code has started raising warnings, and I want to rid myself of them. But I can't see an elegant way to work around.

I have a bunch of classes that perform optimizations when they are constructed. So I have

class Conjunction(Base):
    def __new__(cls, a, b):
       if a == True: 
          return b
       elif b == True
          return a
       else:
          return super(Conjunction,cls).__new__(cls, a, b)

And so on (real versions cover lots more cases). So unlike what Guido says in this response (the only reference to it I can find), my __new__ method does use its arguments, and cannot be replaced by an overridden __init__ function.

The best I can do is to split this in two:

def Conjunction(a, b):
   if a == True: 
      return b
   elif b == True
      return a
   else:
      return ConjunctionImpl(a, b)

class ConjunctionImpl(Base):
   # ...

But that is plain ugly and stinks to high heaven. Am I missing an elegant way to have a class constructor return some arbitrary object based on the constructor parameters it is given?

Upvotes: 7

Views: 1958

Answers (3)

Dan
Dan

Reputation: 23677

Well this made me curious because I did not see the deprecation in the documentation so I gave it a try myself.

class Foo(object):
    def __new__(cls, a, b):
        if a:
            return a
        elif b:
            return b
        else:
            return super(Foo, cls).__new__(cls, a, b)

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

class Bar(Foo):
    def __new__(cls, x, y):
        if x:
            return x
        if y:
            return y
        else:
            return super(Bar, cls).__new__(cls, x, y)


foo = Bar(False, False)

As you can see in this example I overrode the init in Foo because any args passed to new will be forwarded to the cls instance that __new__ attempts to create. The instance of foo with be of a Bar class but it will have members a and b. I caused the super class's __init__ to be called by not overriding it. The method __new__ always passes its args on to __init__. If you don't override the __init__ for object it will fail since that method takes no args.

That's my take on the usage of new in Python 2.7. According to the docs 2.6 is similar.

Upvotes: 0

Ian
Ian

Reputation: 3709

Thomas put me right in his answer, but I should add that the solution in my case was trivial: add a __new__ method to my base class with the lines:

class Base(object):
    def __new__(cls, *args, **kws):
        instance = super(Base, cls).__new__(cls)
        instance.__init__(*args, **kws)
        return instance

Upvotes: 5

Thomas Wouters
Thomas Wouters

Reputation: 133405

__new__ is not "deprecated for receiving arguments". What changed in Python 2.6 is that object.__new__, the __new__ method of the object class, no longer ignores any arguments it's passed. (object.__init__ also doesn't ignore the arguments anymore, but that's just a warning in 2.6.) You can't use object as the terminating class for your inheritance if you want to pass arguments to __new__ or __init__.

In order for any code to rely on that behaviour to work in 2.6, you just have to replace object as the baseclass, using a baseclass that properly accepts the extra arguments and does not pass them along in the calls it makes (using super().)

Upvotes: 13

Related Questions