如何在Python中复制元组等不可变对象?

问题描述 投票:0回答:6

copy.copy()
copy.deepcopy()
只是复制不可变对象(如元组)的引用。 如何在不同的内存位置创建第一个不可变对象的重复副本?

python types
6个回答
49
投票

您正在寻找

deepcopy

from copy import deepcopy

tup = (1, 2, 3, 4, 5)
put = deepcopy(tup)

不可否认,这两个元组的ID将指向同一个地址。因为元组是不可变的,所以实际上没有理由创建它的另一个完全相同的副本。但是,请注意,元组可以包含可变元素,并且 deepcopy/id 的行为正如您预期的那样:

from copy import deepcopy
tup = (1, 2, [])
put = deepcopy(tup)
tup[2].append('hello')
print tup # (1, 2, ['hello'])
print put # (1, 2, [])

25
投票

向其中添加空元组:

>>> a = (1, 2, 3)
>>> a is a+tuple()  
False

连接元组总是返回一个新的不同元组,即使结果相等。


7
投票

试试这个:

tup = (1,2,3)
nt = tuple(list(tup))

0
投票

这个答案:

  • 取决于 cPython 实现细节(这意味着它有点 特定于版本的(虽然在实践中,它已经很长时间没有改变了, 并且可能不会很快改变))
  • 调用 C 未定义行为
  • 取决于您机器上指针的大小

这个答案不应该以任何方式在生产代码中使用。现在与 警告/免责声明让我们开始吧。

充满毫无意义的迂腐的前言

首先,我要迂腐地回答你的问题。 (我很抱歉,但是学究 我内心无法自拔)。让我们从这个想法开始 不变性。在 cPython 中,没有什么是不可变的。这是因为cPython 让您可以访问一个名为

ctypes
的方便的 lil' 模块。这可以让你 做各种很酷的事情,例如修改
tuple

>>> 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)

即使没有

ctypes
,人们总是可以在某些东西中编写扩展模块 像C一样完成同样的事情。然后总是有能力 直接写入进程内存(因为
/proc/self/mem
存在), 你可以使用普通的 Python 来做到这一点(没有
ctypes
或扩展模块或 任何这样的东西)。所有这一切都表明,不变性并不真实。什么时候 在 Python 中,某些东西是“不可变的”,这意味着它将成为 如果你想变异的话有点不方便。

答案

像往常一样,处理 Python 的愚蠢的不变性和 安全就是忽略它们。

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 = }
""")

这只是一个浅拷贝,但可以用它构建一个真正的深拷贝。 (我说“真实”是因为Python的deepcopy不是真实的:它不复制所谓的 不可变对象,或者对象的类型信息(中的

ob_type
成员)
PyObject
结构),以及其他一些类似的东西。)

享受吧!


0
投票

较新的 python 版本不会轻易被欺骗。然而,切片与 splat 运算符相结合为您提供了一条出路。使用 Python v3.12 进行测试。

>>> test = (558, 3, 4, 64)
>>> test2 = (558, *test[1:])
>>> test == test2
True
>>> test is test2
False
>>> id(test), id(test2)
(4345565632, 4345564832)

-4
投票

如果您想复制元组,请尝试

tup = ('a',2,obj)
dup_tup = tuple(tup)

在本例中,我们创建了一个新元组:我们可以更改它而不更改第一个元组。

如果我们在这种情况下使用

sec_tup = tup
,我们有两个元组,但更改一个元组也会在另一个元组中完成

© www.soinside.com 2019 - 2024. All rights reserved.