Reputation:
copy.copy()
and copy.deepcopy()
just copy the reference for an immutable object like a tuple.
How can I create a duplicate copy of the first immutable object at a different memory location?
Upvotes: 50
Views: 97756
Reputation: 29
In addition to Wiki Zhao answer (https://stackoverflow.com/a/15214661/24155535):
Instead of
tup = (1,2,3)
nt = tuple(list(tup))
it's also possible to replace list
with iter
which should be less overhead
tup = (1,2,3)
nt = tuple(iter(tup))
.
Both return False
by the following check of the memory location
tup is nt
.
Concatenation as discribet by Ivo (https://stackoverflow.com/a/15214570/24155535) with + tuple()
or + ()
does not work anymore and return True
!
Upvotes: 1
Reputation: 3039
Newer python versions will not get tricked easily. However, slicing combined with splat operator gives you a way out. Tested with Python v3.12.
>>> test = (558, 3, 4, 64)
>>> test2 = (test[0], *test[1:])
>>> test2
(558, 3, 4, 64)
>>> test == test2
True
>>> test is test2
False
>>> id(test), id(test2)
(4345565632, 4345564832)
Like many others, the following is still caught by python:
>>> test3 = (test[:])
>>> test3 == test
True
>>> test3 is test
True
>>> id(test3), id(test)
(4345565632, 4345565632)
Upvotes: 0
Reputation: 11
This answer:
This answer should NOT be used in production code in any way. Now with the warning/disclaimer out of the way, let's get into it.
First, I'm going to be pedantic about your question. (I'm sorry, but the pedant
inside me just can't help themselves). Let's start with the idea of
immutability. In cPython, nothing is immutable. This is because cPython
gives you access to a convenient lil' module called ctypes
. This allows you
to do all sorts of cool things, such as modify tuple
s in place:
>>> from ctypes import py_object as PyObject_p
>>> mutator = PyObject_p.from_address
>>> victim = (0, 1, 2, 3, 4)
>>> print(victim)
(0, 1, 2, 3, 4)
>>> mutator(id(victim) + 40).value = "not so immutable now, are ye'?"
>>> print(victim)
(0, 1, "not so immutable now, are ye'?", 3, 4)
Even without ctypes
, one could always write an extension module in something
like C and accomplish the same thing. And then there's always just the ability
to write into the process memory directly (because /proc/self/mem
exists),
and you can do that using plain Python (without ctypes
or extension modules or
any of that stuff). All of this is to say, immutability isn't real. When
something is "immutable" in Python, all that means is that it's going to be
slightly inconvenient if you want to mutate it.
As per usual, the way to deal with Python's silly ideas of immutability and safety is to ignore them.
from ctypes import (
byref as _addrof,
sizeof as _sizeof,
create_string_buffer as _malloc,
cast,
memmove as memcpy,
c_void_p as void_p,
py_object as PyObject_p,
)
# get the base class, since it's not publicly exposed
_ctypes_base_cls = void_p
while (_next_base := _ctypes_base_cls.__bases__[0]) is not object:
_ctypes_base_cls = _next_base
## some utility functions that work with PyObjects as well as ctypes objects
# sizeof
def sizeof(obj):
if isinstance(obj, _ctypes_base_cls):
return _sizeof(obj)
else:
return obj.__sizeof__()
# does the same thing as `&obj` in C
def addrof(obj):
if not isinstance(obj, _ctypes_base_cls):
return void_p(id(obj))
return cast(_addrof(obj), void_p)
# allocate memory
def malloc(size):
return cast(_malloc(size), void_p)
# treat something as a `PyObject *`
def as_pyobj_p(obj):
if not isinstance(obj, _ctypes_base_cls):
what = addrof(obj)
return PyObject_p.from_address(addrof(obj).value)
## now for the magic...
# returns a (shallow) copy of the object
def copyobj(obj):
copy_addr = malloc(sizeof(obj))
memcpy(copy_addr, addrof(obj), sizeof(obj))
return as_pyobj_p(copy_addr).value
orig = (0, 1, 2, 3, 4)
copy = copyobj(orig)
# Show the details of the objects:
print(f"""
original:
{orig!r}
{sizeof(orig)} bytes at address {hex(addrof(orig).value)}
copy:
{copy!r}
{sizeof(copy)} bytes at address {hex(addrof(copy).value)}
{orig is copy = }
""")
This is just a shallow copy, but one could construct a real deepcopy with this.
(I say "real" because Python's deepcopy is not real: it doesn't copy so-called
immutable objects, or the type information of objects (the ob_type
member in
the PyObject
struct), and a few other things like that.)
Enjoy!
Upvotes: 0
Reputation: 455
If You wish duplicate a tuple try
tup = ('a',2,obj)
dup_tup = tuple(tup)
in this case we created a new tuple: we can change it without changing the first.
If we use sec_tup = tup
in this case we have two tuple but changing one the change is done also in the other
Upvotes: -4
Reputation: 106470
You're looking for deepcopy
.
from copy import deepcopy
tup = (1, 2, 3, 4, 5)
put = deepcopy(tup)
Admittedly, the ID of these two tuples will point to the same address. Because a tuple is immutable, there's really no rationale to create another copy of it that's the exact same. However, note that tuples can contain mutable elements to them, and deepcopy/id behaves as you anticipate it would:
from copy import deepcopy
tup = (1, 2, [])
put = deepcopy(tup)
tup[2].append('hello')
print tup # (1, 2, ['hello'])
print put # (1, 2, [])
Upvotes: 51
Reputation: 35089
Add the empty tuple to it:
>>> a = (1, 2, 3)
>>> a is a+tuple()
False
Concatenating tuples always returns a new distinct tuple, even when the result turns out to be equal.
Upvotes: 26