Python: override __init__ args in __new__
I have a __new__
method as follows:
class MyClass(object):
def __new__(cls, *args):
new_args = []
args.sort()
prev = args.pop(0)
while args:
next = args.pop(0)
if prev.compare(next):
prev = prev.combine(next)
else:
new_args.append(prev)
prev = next
if some_check(prev):
return SomeOtherClass()
new_args.append(prev)
return super(MyClass, cls).__new__(cls, new_args开发者_开发知识库)
def __init__(self, *args):
...
However, this fails with a deprecation warning:
DeprecationWarning: object.__new__() takes no parameters
SomeOtherClass
can optionally get created as the args are processed, that's why they are being processed in __new__
and not in __init__
What is the best way to pass new_args
to __init__
?
Otherwise, I'll have to duplicate the processing of args in __init__
(without some_check)
The solution I went with in the end was to modify the newly created object in the __new__
and remove the __init__
method altogether:
def __new__(cls, *args):
... # as above
new_self = super(MyClass, cls).__new__(cls)
new_self.args = new_args
return new_self
#def __init__(self, *args):
# self.args = args
Since you don't even necessarily create a MyClass object, why not just put new into a separate function new(*args)
that returns a MyClass
or SomeOtherClass
object as necessary?
This will be a bit neater since you know that everywhere you put MyClass() you get a MyClass object back, and not potentially SomeOtherClass, which could be a bit confusing.
Edit: Came up with a better solution - the following wasn't behaving consistently enough.
I've solved my own question by stumbling on some unexpectedly simple behaviour:
return cls(*new_args)
instead of
return super(MyClass, cls).__new__(cls, *new_args)
It doesn't go into an infinite recursion, as I expected, so long as new_args
is not the same as the original args given to __new__
.
Well... this one works for me
>>> class B():
def __new__(cls, *args):
def is_str(x): return type(x) is str
print("__new__ parameters are: ", args)
if any(map(is_str, args)):
new_args = list(map(int, args))
ret = cls(*new_args)
print('ret args', ret.args)
ret._skip_init = True
return ret
return super().__new__(cls)
def __init__(self, *args):
if hasattr(self, '_skip_init') and self._skip_init:
print("init skipped")
return
print("__init__ parameters are: ", args)
self.args = args
>>> b = B('1', '2', '3', '4')
__new__ parameters are: ('1', '2', '3', '4')
__new__ parameters are: (1, 2, 3, 4)
__init__ parameters are: (1, 2, 3, 4)
ret args (1, 2, 3, 4)
init skipped
>>>
>>>
>>> b.args
(1, 2, 3, 4)
EDIT :
or here is a better and more general answer:
class B():
def __new__(cls, *args, run_init=False):
if not run_init:
# process your argument here
# change the argument as you want
args = [5, 7, 4, 2, 3] # changing the argument
self = cls(*args, run_init=True)
return self
return super().__new__(cls)
def __init__(self, *args, run_init=False):
if not run_init:
print("init skipped")
return
# your __init__ code goes here
print("__init__ parameters are: ", args)
self.args = args
tried on my python 3.7.3 :
>>> b = B('1', '2', '3', '4')
__init__ parameters are: (5, 7, 4, 2, 3)
init skipped
>>> b.args
(5, 7, 4, 2, 3)
精彩评论