-
-
Notifications
You must be signed in to change notification settings - Fork 34.3k
Open
Labels
stdlibStandard Library Python modules in the Lib/ directoryStandard Library Python modules in the Lib/ directorytype-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or error
Description
Bug report
Bug description:
Just found a weird behavior with objects interned via caching __new__ result: __new__ is called with no additional arguments during copying which leads to unexpected behavior (all copies become the same object)
import copy
from functools import cache
class Dummy:
def __init__(self, field):
self.field = field
@cache
def __new__(cls, *args, **kwargs):
print(f'__new__ is called with {args=} {kwargs=}')
return super(Dummy, cls).__new__(cls)
a, b, c = Dummy('a'), Dummy('b'), Dummy('c')
# __new__ is called with args=('a',) kwargs={}
# __new__ is called with args=('b',) kwargs={}
# __new__ is called with args=('c',) kwargs={}
print(vars(a), vars(b), vars(c))
# {'field': 'a'} {'field': 'b'} {'field': 'c'}
dc_a = copy.deepcopy(a)
# __new__ is called with args=() kwargs={}
print(vars(dc_a))
# {'field': 'a'}
dc_b = copy.deepcopy(b)
print(vars(dc_a), vars(dc_b))
# {'field': 'b'} {'field': 'b'}
dc_a is dc_b
# True
dc_c = copy.deepcopy(c)
print(vars(dc_a), vars(dc_b), vars(dc_c))
# {'field': 'c'} {'field': 'c'} {'field': 'c'}
dc_c is dc_b
# True
# with copy.copy works too
c_a = copy.copy(a)
c_b = copy.copy(b)
c_c = copy.copy(c)
print(vars(c_a), vars(c_b), vars(c_c))
# {'field': 'c'} {'field': 'c'} {'field': 'c'}
c_a is dc_a
# True
c_c is c
# FalseIt works with frozen sloted dataclasses as well
@dataclass(slots=True, frozen=True)
class Dummy:
str_field: str
int_field: int
@cache
def __new__(cls, *args, **kwargs):
print(f'__new__ called with {args=} {kwargs=}')
return super(Dummy, cls).__new__(cls)
Dummy('a', 1) is Dummy('a', 1)
# __new__ called with args=('a', 1) kwargs={}
# True
Dummy('a', 1) is not Dummy('b', 1)
# __new__ called with args=('b', 1) kwargs={}
# True
Dummy('a', 1) is copy.deepcopy(Dummy('a', 1))
# __new__ called with args=() kwargs={}
# False
copy.deepcopy(Dummy('a', 1)) is copy.deepcopy(Dummy('b', 1))
# True
Yes, there is a workaround requiring to define something like this in the class
def __deepcopy__(self, memo=None):
return self
But it still looks like a bug.
If it is somehow an intended behavior it would be useful to point that out here: https://docs.python.org/3/library/copy.html
CPython versions tested on:
3.14
Operating systems tested on:
macOS
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
stdlibStandard Library Python modules in the Lib/ directoryStandard Library Python modules in the Lib/ directorytype-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or error
Projects
Status
No status