Bagaimana saya bisa membuat salinan objek dengan Python?

199

Saya ingin membuat salinan suatu objek. Saya ingin objek baru memiliki semua properti objek lama (nilai bidang). Tetapi saya ingin memiliki objek independen. Jadi, jika saya mengubah nilai bidang objek baru, objek lama seharusnya tidak terpengaruh oleh itu.

Roma
sumber

Jawaban:

179

Untuk mendapatkan salinan objek yang sepenuhnya independen, Anda dapat menggunakan copy.deepcopy()fungsi ini.

Untuk perincian lebih lanjut tentang penyalinan yang dangkal dan dalam, silakan merujuk ke jawaban lain untuk pertanyaan ini dan penjelasan yang bagus dalam jawaban ini untuk pertanyaan terkait .

Sven Marnach
sumber
2
Jawaban ini ditandai sebagai "Bukan jawaban", dihapus, dan dihapus - diskusi meta di sini: meta.stackoverflow.com/questions/377844/…
Aaron Hall
@ Harun Terima kasih telah memberi tahu saya! Ini jelas bukan jawaban terhebat yang saya tulis, tetapi saya agak setuju dengan keputusan bahwa itu tidak boleh dihapus secara paksa. Saya akan menyikatnya sedikit, tetapi karena sudah ada jawaban dengan semua detail (terutama milik Anda), saya akan singkat.
Sven Marnach
70

Bagaimana saya bisa membuat salinan objek dengan Python?

Jadi, jika saya mengubah nilai bidang objek baru, objek lama seharusnya tidak terpengaruh oleh itu.

Maksudmu objek yang bisa berubah kemudian.

Di Python 3, daftar dapatkan copymetode (dalam 2, Anda akan menggunakan slice untuk membuat salinan):

>>> a_list = list('abc')
>>> a_copy_of_a_list = a_list.copy()
>>> a_copy_of_a_list is a_list
False
>>> a_copy_of_a_list == a_list
True

Salinan Dangkal

Salinan dangkal hanyalah salinan dari wadah terluar.

list.copy adalah salinan dangkal:

>>> list_of_dict_of_set = [{'foo': set('abc')}]
>>> lodos_copy = list_of_dict_of_set.copy()
>>> lodos_copy[0]['foo'].pop()
'c'
>>> lodos_copy
[{'foo': {'b', 'a'}}]
>>> list_of_dict_of_set
[{'foo': {'b', 'a'}}]

Anda tidak mendapatkan salinan objek interior. Mereka adalah objek yang sama - jadi ketika mereka bermutasi, perubahan muncul di kedua wadah.

Salinan dalam

Salinan dalam adalah salinan rekursif dari setiap objek interior.

>>> lodos_deep_copy = copy.deepcopy(list_of_dict_of_set)
>>> lodos_deep_copy[0]['foo'].add('c')
>>> lodos_deep_copy
[{'foo': {'c', 'b', 'a'}}]
>>> list_of_dict_of_set
[{'foo': {'b', 'a'}}]

Perubahan tidak tercermin dalam aslinya, hanya di salinan.

Benda yang tidak bisa berubah

Benda tidak permanen biasanya tidak perlu disalin. Bahkan, jika Anda mencoba, Python hanya akan memberi Anda objek asli:

>>> a_tuple = tuple('abc')
>>> tuple_copy_attempt = a_tuple.copy()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'tuple' object has no attribute 'copy'

Tuples bahkan tidak memiliki metode penyalinan, jadi mari kita coba dengan irisan:

>>> tuple_copy_attempt = a_tuple[:]

Tapi kita lihat itu objek yang sama:

>>> tuple_copy_attempt is a_tuple
True

Demikian pula untuk string:

>>> s = 'abc'
>>> s0 = s[:]
>>> s == s0
True
>>> s is s0
True

dan untuk frozenset, meskipun mereka memiliki copymetode:

>>> a_frozenset = frozenset('abc')
>>> frozenset_copy_attempt = a_frozenset.copy()
>>> frozenset_copy_attempt is a_frozenset
True

Kapan harus menyalin objek yang tidak dapat diubah

Objek yang tidak dapat diubah harus disalin jika Anda perlu menyalin objek interior yang dapat diubah.

>>> tuple_of_list = [],
>>> copy_of_tuple_of_list = tuple_of_list[:]
>>> copy_of_tuple_of_list[0].append('a')
>>> copy_of_tuple_of_list
(['a'],)
>>> tuple_of_list
(['a'],)
>>> deepcopy_of_tuple_of_list = copy.deepcopy(tuple_of_list)
>>> deepcopy_of_tuple_of_list[0].append('b')
>>> deepcopy_of_tuple_of_list
(['a', 'b'],)
>>> tuple_of_list
(['a'],)

Seperti yang dapat kita lihat, ketika objek interior dari salinan dimutasi, aslinya tidak berubah.

Objek Khusus

Objek khusus biasanya menyimpan data dalam suatu __dict__atribut atau dalam __slots__(struktur memori mirip tuple).

Untuk membuat objek yang dapat disalin, tentukan __copy__(untuk salinan dangkal) dan / atau __deepcopy__(untuk salinan dalam).

from copy import copy, deepcopy

class Copyable:
    __slots__ = 'a', '__dict__'
    def __init__(self, a, b):
        self.a, self.b = a, b
    def __copy__(self):
        return type(self)(self.a, self.b)
    def __deepcopy__(self, memo): # memo is a dict of id's to copies
        id_self = id(self)        # memoization avoids unnecesary recursion
        _copy = memo.get(id_self)
        if _copy is None:
            _copy = type(self)(
                deepcopy(self.a, memo), 
                deepcopy(self.b, memo))
            memo[id_self] = _copy 
        return _copy

Catatan yang deepcopymenyimpan kamus memoisasi id(original)(atau nomor identitas) untuk salinan. Untuk menikmati perilaku yang baik dengan struktur data rekursif, pastikan Anda belum membuat salinan, dan jika sudah, kembalikan.

Jadi mari kita membuat objek:

>>> c1 = Copyable(1, [2])

Dan copybuat salinan yang dangkal:

>>> c2 = copy(c1)
>>> c1 is c2
False
>>> c2.b.append(3)
>>> c1.b
[2, 3]

Dan deepcopysekarang membuat salinan yang dalam:

>>> c3 = deepcopy(c1)
>>> c3.b.append(4)
>>> c1.b
[2, 3]
Aaron Hall
sumber
10

Salin dangkal dengan copy.copy()

#!/usr/bin/env python3

import copy

class C():
    def __init__(self):
        self.x = [1]
        self.y = [2]

# It copies.
c = C()
d = copy.copy(c)
d.x = [3]
assert c.x == [1]
assert d.x == [3]

# It's shallow.
c = C()
d = copy.copy(c)
d.x[0] = 3
assert c.x == [3]
assert d.x == [3]

Salinan dalam dengan copy.deepcopy()

#!/usr/bin/env python3
import copy
class C():
    def __init__(self):
        self.x = [1]
        self.y = [2]
c = C()
d = copy.deepcopy(c)
d.x[0] = 3
assert c.x == [1]
assert d.x == [3]

Dokumentasi: https://docs.python.org/3/library/copy.html

Diuji pada Python 3.6.5.

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
sumber
-2

Saya percaya yang berikut ini harus bekerja dengan banyak perilakunya yang baik diklasifikasikan dalam Python:

def copy(obj):
    return type(obj)(obj)

(Tentu saja, saya tidak berbicara di sini tentang "salinan dalam," yang merupakan cerita yang berbeda, dan yang mungkin bukan konsep yang sangat jelas - seberapa dalam cukup dalam?)

Menurut pengujian saya dengan Python 3, untuk objek yang tidak dapat diubah, seperti tupel atau string, ia mengembalikan objek yang sama (karena tidak perlu membuat salinan dangkal dari objek yang tidak dapat diubah), tetapi untuk daftar atau kamus ia membuat salinan dangkal independen .

Tentu saja metode ini hanya berfungsi untuk kelas yang konstruktornya berperilaku sesuai. Kasing penggunaan yang memungkinkan: membuat salinan dangkal kelas kontainer Python standar.

Alexey
sumber
Itu rapi dan semuanya, tetapi tidak menjawab pertanyaan karena fungsi salin Anda gagal untuk kelas khusus dan pertanyaannya adalah tentang objek .
Jared Smith
@ JaredSmith, tidak disebutkan bahwa pertanyaannya adalah tentang semua objek. Bahkan tidak jelas apakah itu tentang salinan yang dalam atau dangkal (saya akan menganggap yang biasa dangkal, tetapi jawaban yang diterima adalah tentang yang dalam). Mengenai kelas khusus, jika kelas itu milik Anda, Anda mungkin hanya menghargai konvensi semacam ini dalam __init__metode mereka . Jadi, saya pikir metode ini mungkin cukup baik untuk tujuan tertentu. Bagaimanapun, saya akan tertarik pada komentar informatif tentang saran ini.
Alexey
Pertimbangkan class Foo(object): def __init__(self, arg): super(Foo, self).__init__() self.arg = argDasar saat mendapat. If I do foo = Foo(3) bar = copy(foo) print(foo.arg) # 3 print(bar.arg) # <__main__.Foo object at ...>Meaning bahwa copyfungsi Anda rusak bahkan untuk kelas paling dasar sekalipun. Sekali lagi, ini adalah trik yang rapi (karenanya tidak ada DV), tetapi bukan jawaban.
Jared Smith
@JaredSmith, saya melihat bahwa ada copy.copymetode untuk membuat salinan yang dangkal, tetapi, mungkin secara naif, menurut saya seharusnya menjadi tanggung jawab kelas untuk menyediakan "konstruktor salinan yang dangkal." Dalam kasus seperti itu, mengapa tidak menyediakan antarmuka yang sama dengannya dictdan listmelakukan? Jadi, jika kelas Anda ingin bertanggung jawab untuk menyalin objeknya, mengapa tidak menambahkan if isinstance(arg, type(self))klausa __init__?
Alexey
1
Karena Anda tidak selalu memiliki kendali atas kelas yang Anda gunakan dengan cara yang Anda definisikan. Mereka mungkin, hanya sebagai salah satu contoh, menjadi program C yang memiliki binding Python (misalnya GTK, openalpr, bagian inti). Belum lagi bahkan jika Anda mengambil perpustakaan pihak ketiga dan menambahkan metode salin ke setiap kelas, bagaimana Anda akan menenunnya ke dalam manajemen ketergantungan Anda?
Jared Smith