2009-09-30 14 views
60

Vì vậy, chỉ để thiết lập, tôi cảm thấy như tôi hiểu sự khác biệt giữa copy vs deepcopy trong module sao chép và tôi đã sử dụng copy.copycopy.deepcopy trước khi thành công, nhưng đây là lần đầu tiên tôi thực sự đi về quá tải __copy____deepcopy__ phương pháp. Tôi đã google và xem qua các mô-đun Python tích hợp để tìm các phiên bản của các hàm __copy____deepcopy__ (ví dụ: sets.py, decimal.pyfractions.py), nhưng tôi vẫn không chắc chắn 100%. .Cách đúng để ghi đè lên các hoạt động sao chép/in sâu trên một đối tượng bằng Python là gì?

Dưới đây là kịch bản của tôi:

Tôi có một đối tượng cấu hình mà chủ yếu chỉ bao gồm các thuộc tính đơn giản (mặc dù nó có khả năng sẽ có danh sách các đối tượng phi nguyên thủy khác trong nó). Ban đầu tôi sẽ khởi tạo một đối tượng cấu hình với một bộ giá trị mặc định. Cấu hình này sẽ được chuyển giao cho nhiều đối tượng khác (để đảm bảo tất cả các đối tượng bắt đầu với cùng một cấu hình). Tuy nhiên, khi sự tương tác của người dùng bắt đầu, mỗi đối tượng sẽ cần phải có khả năng tinh chỉnh các cấu hình một cách độc lập mà không ảnh hưởng đến cấu hình của nhau (mà nói với tôi rằng tôi sẽ cần phải thực hiện bản vẽ sâu của cấu hình ban đầu của mình).

Dưới đây là một đối tượng mẫu:

class ChartConfig(object): 

    def __init__(self): 

     #Drawing properties (Booleans/strings) 
     self.antialiased = None 
     self.plot_style = None 
     self.plot_title = None 
     self.autoscale = None 

     #X axis properties (strings/ints) 
     self.xaxis_title = None 
     self.xaxis_tick_rotation = None 
     self.xaxis_tick_align = None 

     #Y axis properties (strings/ints) 
     self.yaxis_title = None 
     self.yaxis_tick_rotation = None 
     self.yaxis_tick_align = None 

     #A list of non-primitive objects 
     self.trace_configs = [] 

    def __copy__(self): 
     pass 

    def __deepcopy__(self, memo): 
     pass 

đúng cách để thực hiện copydeepcopy phương pháp trên đối tượng này để đảm bảo copy.copycopy.deepcopy cho tôi các hành vi thích hợp là gì? Tôi hiện đang sử dụng Python 2.6.2.

Cảm ơn trước!

+0

Tính năng này có hoạt động không? Có vấn đề gì không? –

+0

Tôi nghĩ rằng tôi vẫn gặp sự cố với các tham chiếu được chia sẻ, nhưng hoàn toàn có thể là tôi đã làm rối tung lên ở nơi khác. Tôi sẽ kiểm tra lại dựa trên bài đăng của @ MortenSiebuhr khi tôi có cơ hội và cập nhật kết quả. –

+0

Từ hiểu biết hạn chế hiện tại của tôi, tôi mong đợi copy.deepcopy (ChartConfigInstance) trả về một cá thể mới không có bất kỳ tham chiếu được chia sẻ nào với bản gốc (mà không tự mình thực hiện bản thân sâu). Điều này có đúng không? – emschorsch

Trả lời

52

Các khuyến nghị cho tùy biến đang ở cuối của docs page:

Lớp học có thể sử dụng các giao diện tương tự để sao chép điều khiển mà họ sử dụng để kiểm soát tẩy. Xem mô tả của mô-đun dưa để biết thông tin về các phương pháp này. Mô-đun sao chép thực hiện không sử dụng mô-đun đăng ký copy_reg .

Để cho một lớp học để xác định riêng thực hiện bản sao của nó, nó có thể xác định phương pháp đặc biệt __copy__()__deepcopy__(). Trước đây được gọi là để thực hiện các bản sao nông hoạt động; không có đối số bổ sung nào được thông qua . Sau này được gọi là thực hiện thao tác sao chép sâu; nó được chuyển một đối số, ghi nhớ từ điển. Nếu triển khai __deepcopy__() cần thực hiện một bản sao sâu của một thành phần, cần gọi chức năng deepcopy() với thành phần làm đối số đầu tiên và từ ghi nhớ thứ hai làm đối số thứ hai.

Vì bạn dường như không quan tâm đến việc tùy biến tẩy, xác định __copy____deepcopy__ chắc chắn có vẻ đúng cách để đi cho bạn.

Cụ thể, __copy__ (bản sao cạn) là khá dễ dàng trong trường hợp của bạn ...:

def __copy__(self): 
    newone = type(self)() 
    newone.__dict__.update(self.__dict__) 
    return newone 

__deepcopy__ sẽ tương tự (chấp nhận một arg memo quá) nhưng trước sự trở lại nó sẽ phải gọi self.foo = deepcopy(self.foo, memo) đối với bất kỳ thuộc tính nào self.foo cần sao chép sâu (về cơ bản các thuộc tính là vùng chứa - danh sách, dấu gạch ngang, đối tượng không nguyên thủy giữ các nội dung khác thông qua số __dict__ của chúng).

+0

Tôi nghĩ rằng tôi đã nghe nói rằng nó tốt hơn để ghi đè lên '__getstate__' /' __setstate__' để thực hiện sao chép. Hay tôi bối rối? – u0b34a0f6ae

+1

@kaizer, chúng ta có thể tùy chỉnh tẩy/tháo ghim cũng như sao chép, nhưng nếu bạn không quan tâm đến việc tẩy, thì việc sử dụng '__copy__' /' __deepcopy__' đơn giản hơn và trực tiếp hơn. –

+2

Điều đó dường như không phải là bản dịch trực tiếp của bản sao/sâu. Cả bản sao lẫn bản in sâu đều không gọi hàm tạo của đối tượng đang được sao chép. Hãy xem xét ví dụ này. lớp Test1 (object): def __init __ (tự): print "% s% s" % (tự .__ lớp __.__ name__, "__init__") lớp Test2 (Test1): def __copy __ (tự): mới = loại (tự)() trở lại mới t1 = Test1() copy.copy (t1) t2 = Test2() copy.copy (t2) –

5

Tôi có thể là một chút về các chi tiết cụ thể, nhưng ở đây đi;

Từ số copy docs;

  • Một bản sao cạn xây dựng một đối tượng hợp chất mới và sau đó (trong phạm vi có thể) chèn tài liệu tham khảo vào nó để các đối tượng được tìm thấy trong bản gốc.
  • Một bản sao sâu xây dựng một đối tượng hợp chất mới và sau đó, đệ quy, chèn các bản sao vào nó của các đối tượng được tìm thấy trong bản gốc.

Nói cách khác: copy() sẽ sao chép chỉ là yếu tố hàng đầu và để lại phần còn lại như con trỏ vào cấu trúc ban đầu. deepcopy() sẽ sao chép đệ quy tất cả mọi thứ.

Tức là, deepcopy() là những gì bạn cần.

Nếu bạn cần làm điều gì đó thực sự cụ thể, bạn có thể ghi đè __copy__() hoặc __deepcopy__(), như được mô tả trong sách hướng dẫn. Cá nhân, tôi có thể triển khai một hàm đơn giản (ví dụ: config.copy_config() hoặc như vậy) để làm cho nó rõ ràng rằng nó không phải là hành vi chuẩn của Python.

+2

* Để cho một lớp xác định việc thực hiện bản sao của riêng nó, nó có thể định nghĩa các phương thức đặc biệt '__copy __ (') và '__deepcopy __()'. * Http://docs.python.org/library/copy.html – SilentGhost

+0

I ' sẽ kiểm tra lại mã của tôi, cảm ơn. Tôi sẽ cảm thấy câm nếu đây là một lỗi đơn giản ở nơi khác :-P –

+0

@MortenSiebuhr Bạn là chính xác. Tôi đã không hoàn toàn rõ ràng rằng bản sao/deepcopy sẽ làm bất cứ điều gì theo mặc định mà không có tôi ghi đè những chức năng. Tôi đã tìm mã thực tế mặc dù tôi có thể chỉnh sửa sau (ví dụ: nếu tôi không muốn sao chép tất cả các thuộc tính), vì vậy tôi đã cho bạn một cuộc bỏ phiếu nhưng tôi sẽ đi với câu trả lời của @ AlexMartinelli. Cảm ơn! –

52

Đưa cùng câu trả lời Alex Martelli và bình luận Rob Young bạn sẽ có được đoạn mã sau:

from copy import copy, deepcopy 

class A(object): 
    def __init__(self): 
     print 'init' 
     self.v = 10 
     self.z = [2,3,4] 

    def __copy__(self): 
     cls = self.__class__ 
     result = cls.__new__(cls) 
     result.__dict__.update(self.__dict__) 
     return result 

    def __deepcopy__(self, memo): 
     cls = self.__class__ 
     result = cls.__new__(cls) 
     memo[id(self)] = result 
     for k, v in self.__dict__.items(): 
      setattr(result, k, deepcopy(v, memo)) 
     return result 

a = A() 
a.v = 11 
b1, b2 = copy(a), deepcopy(a) 
a.v = 12 
a.z.append(5) 
print b1.v, b1.z 
print b2.v, b2.z 

in

init 
11 [2, 3, 4, 5] 
11 [2, 3, 4] 

đây __deepcopy__ điền vào memo dict để tránh sao chép dư thừa trong trường hợp đối tượng tự được tham chiếu từ thành viên của nó.

+2

cảm ơn vì câu trả lời, các tài liệu thực sự thiếu điểm này! có lý do nào bạn 'từ sao chép nhập khẩu sâu 'cả ở đầu tệp và bên trong phương thức' __deepcopy__' không? – Anentropic

+0

Tôi gặp lỗi với việc thực hiện ở trên 'lớp Transporter không có thuộc tính '__new __'' (python 2.7). Tôi đang cố gắng ghi đè '__deepcopy__' – bytestorm

+0

@bytestorm 'Transporter' là gì? –

4

Không rõ ràng từ vấn đề của bạn tại sao bạn cần ghi đè các phương pháp này, vì bạn không muốn thực hiện bất kỳ tùy chỉnh nào cho phương pháp sao chép.

Nhưng dù sao, nếu bạn muốn tuỳ chỉnh các bản sao sâu (ví dụ như bằng cách chia sẻ một số thuộc tính và sao chép những người khác), đây là một giải pháp:

from copy import deepcopy 


def deepcopy_with_sharing(obj, shared_attribute_names, memo=None): 
    ''' 
    Deepcopy an object, except for a given list of attributes, which should 
    be shared between the original object and its copy. 

    obj is some object 
    shared_attribute_names: A list of strings identifying the attributes that 
     should be shared between the original and its copy. 
    memo is the dictionary passed into __deepcopy__. Ignore this argument if 
     not calling from within __deepcopy__. 
    ''' 
    assert isinstance(shared_attribute_names, (list, tuple)) 
    shared_attributes = {k: getattr(obj, k) for k in shared_attribute_names} 

    if hasattr(obj, '__deepcopy__'): 
     # Do hack to prevent infinite recursion in call to deepcopy 
     deepcopy_method = obj.__deepcopy__ 
     obj.__deepcopy__ = None 

    for attr in shared_attribute_names: 
     del obj.__dict__[attr] 

    clone = deepcopy(obj) 

    for attr, val in shared_attributes.iteritems(): 
     setattr(obj, attr, val) 
     setattr(clone, attr, val) 

    if hasattr(obj, '__deepcopy__'): 
     # Undo hack 
     obj.__deepcopy__ = deepcopy_method 
     del clone.__deepcopy__ 

    return clone 



class A(object): 

    def __init__(self): 
     self.copy_me = [] 
     self.share_me = [] 

    def __deepcopy__(self, memo): 
     return deepcopy_with_sharing(self, shared_attribute_names = ['share_me'], memo=memo) 

a = A() 
b = deepcopy(a) 
assert a.copy_me is not b.copy_me 
assert a.share_me is b.share_me 

c = deepcopy(b) 
assert c.copy_me is not b.copy_me 
assert c.share_me is b.share_me 
+0

Không phải bản sao cũng cần thiết lập lại phương thức '__deepcopy__' vì nó sẽ có' __deepcopy__' = Không? – flutefreak7

+0

Không. Nếu không tìm thấy phương thức '__deepcopy__' (hoặc' obj .__ deepcopy__' trả về None), thì 'deepcopy' sẽ quay trở lại chức năng sao chép sâu tiêu chuẩn. Điều này có thể được nhìn thấy [ở đây] (https://github.com/python/cpython/blob/3.6/Lib/copy.py#L159) – Peter

+0

Nhưng sau đó b sẽ không có khả năng đọc sâu với chia sẻ? c = deepcopy (a) sẽ khác với d = deepcopy (b) vì d sẽ là một deepcopy mặc định trong đó c sẽ có một số attrs chia sẻ với a. – flutefreak7

4

Sau Peter's excellent answer, để thực hiện một deepcopy tùy chỉnh, với thay đổi tối thiểu để thực hiện mặc định (ví dụchỉ sửa đổi một lĩnh vực như tôi cần thiết):

class Foo(object): 
    def __deepcopy__(self, memo): 
     deepcopy_method = self.__deepcopy__ 
     self.__deepcopy__ = None 
     cp = deepcopy(self, memo) 
     self.__deepcopy__ = deepcopy_method 

     # custom treatments 
     # for instance: cp.id = None 

     return cp 
0

Xây dựng về câu trả lời sạch Antony Hatchkins', đây là phiên bản của tôi, nơi lớp trong câu hỏi xuất phát từ một lớp tùy chỉnh (st chúng ta cần phải gọi super):

class Foo(FooBase): 
    def __init__(self, param1, param2): 
     self._base_params = [param1, param2] 
     super(Foo, result).__init__(*self._base_params) 

    def __copy__(self): 
     cls = self.__class__ 
     result = cls.__new__(cls) 
     result.__dict__.update(self.__dict__) 
     super(Foo, result).__init__(*self._base_params) 
     return result 

    def __deepcopy__(self, memo): 
     cls = self.__class__ 
     result = cls.__new__(cls) 
     memo[id(self)] = result 
     for k, v in self.__dict__.items(): 
      setattr(result, k, copy.deepcopy(v, memo)) 
     super(Foo, result).__init__(*self._base_params) 
     return result 
Các vấn đề liên quan