I’ve just run into this error with copy.deepcopy:
import copy
import datetime
class Hours(datetime.timedelta):
# Using __new__ because timedelta is immutable
# See https://stackoverflow.com/a/22531773/3358488.
def __new__(cls, hours):
return datetime.timedelta.__new__(cls, hours=hours)
h1 = Hours(2)
h2 = copy.deepcopy(h1) # TypeError: __new__() takes 2 positional arguments but 4 were given
Here’s the full traceback:
Traceback (most recent call last):
File "/Users/xxxx/Documents/PyCharmProjects/Flow/backend/deepcopy_test.py", line 11, in <module>
h2 = copy.deepcopy(h1) # TypeError: __new__() takes 2 positional arguments but 4 were given
File "/Users/xxxx/.conda/envs/qtdesktopapp/lib/python3.8/copy.py", line 172, in deepcopy
y = _reconstruct(x, memo, *rv)
File "/Users/xxxx/.conda/envs/qtdesktopapp/lib/python3.8/copy.py", line 264, in _reconstruct
y = func(*args)
TypeError: __new__() takes 2 positional arguments but 4 were given
Note that timedelta initialization‘s takes anywhere from 0 to 6 arguments:
class datetime.timedelta(days=0, seconds=0, microseconds=0, milliseconds=0, minutes=0, hours=0, weeks=0)
Have I used copy.deepcopy incorrectly or have I found a bug in it?
PS: I also tried defining __new__ as:
class Hours(datetime.timedelta):
def __new__(cls, days=0, seconds=0, microseconds=0, milliseconds=0, minutes=0, hours=0, weeks=0):
print(days, seconds, microseconds, milliseconds, minutes, hours, weeks)
return datetime.timedelta.__new__(cls, hours=hours)
and the output when I run copy.deepcopy(h1) is 0 0 0 0 0 0 0, so the information that hours=2 is not being passed along in the copy at all.
PPS: oddly, when I use *args as suggested in chosen answer, I do see the information (in the form of 7200 seconds). Not sure why the version above gets all zeros.
>Solution :
Add a *args to your __new__ and add a print so you can see what arguments it’s getting called with:
import copy
import datetime
class Hours(datetime.timedelta):
def __new__(cls, hours, *args):
print(cls, hours, *args)
return datetime.timedelta.__new__(cls, hours=hours)
h1 = Hours(2)
h2 = copy.deepcopy(h1)
prints:
<class '__main__.Hours'> 2
<class '__main__.Hours'> 0 7200 0
deepcopy doesn’t know that you modified __new__ so it’s trying to construct a copy the same way it’d copy a timedelta object, by passing the positional args days, seconds, and microseconds.
If you want deepcopy to work correcty, you need to override the __deepcopy__ method in your class to match the __new__ override:
class Hours(datetime.timedelta):
def __new__(cls, hours):
return datetime.timedelta.__new__(cls, hours=hours)
def __deepcopy__(self, _memo):
return Hours(self.seconds // 3600)
h1 = Hours(2)
h2 = copy.deepcopy(h1)
assert h1 is not h2 # different objects
assert h1 == h2 # but they're still equivalent