Làm thế nào tôi có thể tạo một bản sao của một đối tượng trong Python?


199

Tôi muốn tạo một bản sao của một đối tượng. Tôi muốn đối tượng mới sở hữu tất cả các thuộc tính của đối tượng cũ (giá trị của các trường). Nhưng tôi muốn có những đối tượng độc lập. Vì vậy, nếu tôi thay đổi giá trị của các trường của đối tượng mới, đối tượng cũ sẽ không bị ảnh hưởng bởi điều đó.

Câu trả lời:


179

Để có được một bản sao hoàn toàn độc lập của một đối tượng, bạn có thể sử dụng copy.deepcopy()hàm.

Để biết thêm chi tiết về sao chép nông và sâu, vui lòng tham khảo các câu trả lời khác cho câu hỏi này và lời giải thích hay trong câu trả lời này cho một câu hỏi liên quan .


2
Câu trả lời này được gắn cờ là "Không phải là câu trả lời", đã bị xóa và không bị xóa - thảo luận meta tại đây: meta.stackoverflow.com/questions/377844/ Lỗi
Aaron Hall

@AaronHall Cảm ơn đã cho tôi biết! Đây chắc chắn không phải là câu trả lời lớn nhất mà tôi đã viết, nhưng tôi đồng ý với quyết định rằng nó không nên bị xóa. Tôi sẽ xem lại một chút, nhưng vì đã có câu trả lời với tất cả các chi tiết (đáng chú ý là của bạn), tôi sẽ giữ nó ngắn gọn.
Sven Marnach

70

Làm thế nào tôi có thể tạo một bản sao của một đối tượng trong Python?

Vì vậy, nếu tôi thay đổi giá trị của các trường của đối tượng mới, đối tượng cũ sẽ không bị ảnh hưởng bởi điều đó.

Bạn có nghĩa là một đối tượng đột biến sau đó.

Trong Python 3, danh sách có một copyphương thức (trong 2, bạn sẽ sử dụng một lát để tạo một bản sao):

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

Bản sao nông

Bản sao nông chỉ là bản sao của thùng chứa ngoài cùng.

list.copy là một bản sao nông:

>>> 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'}}]

Bạn không nhận được một bản sao của các đối tượng bên trong. Chúng là cùng một đối tượng - vì vậy khi chúng bị đột biến, sự thay đổi sẽ xuất hiện trong cả hai container.

Bản sao sâu

Bản sao sâu là bản sao đệ quy của từng đối tượng nội thất.

>>> 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'}}]

Thay đổi không được phản ánh trong bản gốc, chỉ trong bản sao.

Đối tượng bất biến

Đối tượng bất biến thường không cần phải được sao chép. Trong thực tế, nếu bạn cố gắng, Python sẽ chỉ cung cấp cho bạn đối tượng ban đầu:

>>> 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 thậm chí không có phương pháp sao chép, vì vậy hãy thử với một lát:

>>> tuple_copy_attempt = a_tuple[:]

Nhưng chúng ta thấy đó là cùng một đối tượng:

>>> tuple_copy_attempt is a_tuple
True

Tương tự cho chuỗi:

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

và đối với hàng chục người, mặc dù họ có một copyphương pháp:

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

Khi nào cần sao chép các đối tượng bất biến

Các đối tượng bất biến nên được sao chép nếu bạn cần một đối tượng bên trong có thể thay đổi được sao chép.

>>> 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'],)

Như chúng ta có thể thấy, khi đối tượng bên trong của bản sao bị đột biến, bản gốc không thay đổi.

Đối tượng tùy chỉnh

Các đối tượng tùy chỉnh thường lưu trữ dữ liệu trong một __dict__thuộc tính hoặc trong __slots__(cấu trúc bộ nhớ giống như tuple.)

Để tạo một đối tượng có thể __copy__sao chép , xác định (đối với các bản sao nông) và / hoặc __deepcopy__(đối với các bản sao sâu).

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

Lưu ý rằng deepcopygiữ một từ điển ghi nhớ của id(original)(hoặc số nhận dạng) cho các bản sao. Để tận hưởng hành vi tốt với các cấu trúc dữ liệu đệ quy, hãy đảm bảo bạn chưa tạo một bản sao và nếu có, hãy trả lại.

Vì vậy, hãy tạo một đối tượng:

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

copytạo một bản sao nông:

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

deepcopybây giờ tạo một bản sao sâu sắc:

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

10

Bản sao nông với 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]

Bản sao sâu với 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]

Tài liệu: https://docs.python.org/3/l Library / copy.html

Đã thử nghiệm trên Python 3.6.5.


-2

Tôi tin rằng những điều sau đây sẽ hoạt động với nhiều lớp được xử lý tốt trong Python:

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

(Tất nhiên, tôi không nói ở đây về "bản sao sâu sắc", đó là một câu chuyện khác, và có thể không phải là một khái niệm rất rõ ràng - đủ sâu đến mức nào?)

Theo thử nghiệm của tôi với Python 3, đối với các đối tượng không thay đổi, như bộ dữ liệu hoặc chuỗi, nó trả về cùng một đối tượng (vì không cần tạo một bản sao nông của một đối tượng bất biến), nhưng đối với danh sách hoặc từ điển, nó tạo ra một bản sao nông độc lập .

Tất nhiên phương thức này chỉ hoạt động đối với các lớp có hàm tạo hành xử tương ứng. Các trường hợp sử dụng có thể: tạo một bản sao nông của lớp chứa Python chuẩn.


Đó là gọn gàng và tất cả, nhưng không trả lời câu hỏi vì chức năng sao chép của bạn không thành công cho các lớp tùy chỉnh và câu hỏi là về các đối tượng .
Jared Smith

@JaredSmith, không nói rõ rằng câu hỏi là về tất cả các đối tượng. Nó thậm chí không rõ ràng nếu đó là về bản sao sâu hay nông (tôi sẽ giả sử bản sao nông thông thường, nhưng câu trả lời được chấp nhận là về bản sâu). Đối với các lớp tùy chỉnh, nếu chúng là của bạn, bạn có thể chỉ tôn trọng loại quy ước này trong __init__phương thức của chúng . Vì vậy, tôi nghĩ rằng phương pháp này có thể đủ tốt cho các mục đích nhất định. Trong mọi trường hợp, tôi sẽ quan tâm đến ý kiến ​​thông tin về đề nghị này.
Alexey

Hãy xem xét class Foo(object): def __init__(self, arg): super(Foo, self).__init__() self.arg = argcơ bản như nó được. Nếu tôi có foo = Foo(3) bar = copy(foo) print(foo.arg) # 3 print(bar.arg) # <__main__.Foo object at ...>nghĩa là copychức năng của bạn bị hỏng ngay cả những lớp cơ bản nhất. Một lần nữa, đó là một mẹo gọn gàng (do đó không có DV), nhưng không phải là một câu trả lời.
Jared Smith

@JaredSmith, tôi thấy rằng có một copy.copyphương pháp để tạo các bản sao nông, nhưng, có thể ngây thơ, đối với tôi, đó là trách nhiệm của lớp để cung cấp một "trình tạo bản sao nông". Trong trường hợp như vậy tại sao không cung cấp cùng một giao diện cho nó dictlistlàm gì? Vì vậy, nếu lớp của bạn muốn chịu trách nhiệm sao chép các đối tượng của nó, tại sao không thêm một if isinstance(arg, type(self))mệnh đề vào __init__?
Alexey

1
Bởi vì bạn không luôn có quyền kiểm soát các lớp bạn sử dụng theo cách bạn thực hiện. Chúng có thể, giống như một ví dụ, là các chương trình C có các ràng buộc Python (ví dụ GTK, openalpr, các phần của lõi). Chưa kể rằng ngay cả khi bạn lấy một thư viện bên thứ ba và thêm các phương thức sao chép vào mỗi lớp, bạn sẽ dệt nó vào quản lý phụ thuộc của bạn như thế nào?
Jared Smith
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.