Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

Why do I get this TypeError while using Python's copy.deepcopy?

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:

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

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
Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading