2011-12-11 22 views
7

Có mã này:Chuyển nhượng của các đối tượng và các loại cơ bản

# assignment behaviour for integer 
a = b = 0 
print a, b # prints 0 0 
a = 4 
print a, b # prints 4 0 - different! 

# assignment behaviour for class object 
class Klasa: 
    def __init__(self, num): 
     self.num = num 

a = Klasa(2) 
b = a 
print a.num, b.num # prints 2 2 
a.num = 3 
print a.num, b.num # prints 3 3 - the same! 

Câu hỏi:

  1. Tại sao toán tử gán công trình khác nhau cho loại hình cơ bản và đối tượng lớp (đối với loại cơ bản nó sao chép theo giá trị, cho đối tượng lớp nó sao chép theo tham chiếu)?
  2. Làm cách nào để sao chép đối tượng lớp học theo giá trị?
  3. Cách tạo tham chiếu cho các loại cơ bản như trong C++ int & b = a?
+2

Bên ngoài ngôn ngữ Python, các điều khoản theo tham chiếu/theo giá trị đã bị nhầm lẫn và gây nhầm lẫn. Bên trong Python, có mô hình dữ liệu và mô hình thực thi rất đặc biệt, các thuật ngữ này vẫn còn nhiều nhầm lẫn và khó hiểu và cần tránh. Đó là ý kiến ​​của tôi, nhưng lưu ý tôi không phải là một chuyên gia về Python. Xem (http://stackoverflow.com/a/986145/551449) và nhiều bài đăng và blog khác về chủ đề này. Có vẻ như bạn cần nghiên cứu thêm một chút về các mô hình dữ liệu và thực thi của Python – eyquem

Trả lời

11

Đây là một trở ngại cho nhiều người sử dụng Python. Các ngữ nghĩa tham chiếu đối tượng khác với ngữ nghĩa của các lập trình viên C được sử dụng.

Hãy lấy trường hợp đầu tiên. Khi bạn nói a = b = 0, đối tượng int mới được tạo với giá trị 0 và hai tham chiếu đến nó được tạo (một là a và một đối tượng khác là b). Hai biến này trỏ đến cùng một đối tượng (số nguyên mà chúng ta đã tạo). Bây giờ, chúng tôi chạy a = 4. Đối tượng int mới có giá trị 4 được tạo và a được thực hiện để trỏ đến điều đó. Điều này có nghĩa là số lượng tham chiếu đến 4 là một và số lượng tham chiếu đến 0 đã bị giảm một.

So sánh điều này với a = 4 trong C trong đó khu vực bộ nhớ mà a "điểm" được ghi vào. a = b = 4 trong C nghĩa là 4 được ghi vào hai phần bộ nhớ - một cho a và một cho b.

Bây giờ trường hợp thứ hai, a = Klass(2) tạo đối tượng thuộc loại Klass, tăng số lượng tham chiếu của nó bằng một và làm cho a trỏ tới nó. b = a chỉ cần lấy những gì a trỏ đến, làm cho b trỏ đến cùng một thứ và tăng số lượng tham chiếu của một điều. Nó giống như những gì sẽ xảy ra nếu bạn đã làm a = b = Klass(2). Cố gắng in a.numb.num giống nhau vì bạn đang dereferencing cùng một đối tượng và in một giá trị thuộc tính. Bạn có thể sử dụng hàm dựng sẵn id để xem đối tượng là như nhau (id(a)id(b) sẽ trả lại cùng một số nhận dạng). Bây giờ, bạn thay đổi đối tượng bằng cách gán một giá trị cho một trong các thuộc tính của nó. Vì ab trỏ đến cùng một đối tượng, bạn mong muốn thay đổi giá trị hiển thị khi đối tượng được truy cập qua a hoặc b. Và đó là chính xác nó như thế nào.

Bây giờ, để có câu trả lời cho câu hỏi của bạn.

  1. Toán tử gán không hoạt động khác nhau cho cả hai. Tất cả nó làm là thêm một tham chiếu đến RValue và làm cho điểm LValue đến nó. Đó là luôn là "theo tham chiếu" (mặc dù cụm từ này có ý nghĩa hơn trong ngữ cảnh tham số truyền qua các bài tập đơn giản).
  2. Nếu bạn muốn bản sao đối tượng, hãy sử dụng copy module.
  3. Như tôi đã nói ở điểm 1, khi bạn thực hiện một bài tập, bạn luôn thay đổi các tham chiếu. Sao chép không bao giờ được thực hiện trừ khi bạn yêu cầu.
+0

Tôi nghĩ rằng trong biểu thức "truyền bằng tham chiếu", từ "tham chiếu" chỉ định một số == một địa chỉ bộ nhớ. Nhưng trong phần lớn văn bản của bạn, bạn lấy từ "tham chiếu" với ý nghĩa "một phần bộ nhớ chứa một số là địa chỉ của một vị trí bộ nhớ", nghĩa là "tham chiếu" sau đó được dùng làm từ đồng nghĩa của "con trỏ". Khi "con trỏ" và "tham chiếu" là các từ có ý nghĩa nổi theo ngôn ngữ được xem xét, và Python có dữ liệu đặc biệt và các mô hình thực thi, có một chút mơ hồ khó hiểu trong văn bản của bạn, như trong phần lớn các văn bản về chủ đề này – eyquem

+0

Khi bạn viết '' a'', bạn có đại diện cho đối tượng (cấu trúc của các bit trong bộ nhớ), tham chiếu (đoạn bộ nhớ hoạt động như một hộp) với đối tượng hay mã định danh? – eyquem

+0

eyquem. Tôi đồng ý. Tôi có lẽ nên xác định các điều khoản trước khi tôi nói về điều này vì nó là một chủ đề khó hiểu. –

1

Nó không hoạt động khác. Trong ví dụ đầu tiên của bạn, bạn đã thay đổi a để ab tham chiếu các đối tượng khác nhau. Trong ví dụ thứ hai của bạn, bạn không, vì vậy, ab vẫn tham chiếu cùng một đối tượng.

Số nguyên, nhân tiện, không thay đổi. Bạn không thể sửa đổi giá trị của chúng. Tất cả những gì bạn có thể làm là tạo một số nguyên mới và rebind tham chiếu của bạn. (Như bạn đã làm trong ví dụ đầu tiên của bạn)

5

Trích dẫn từ Data Model

Đối tượng là trừu tượng Python cho dữ liệu. Tất cả dữ liệu trong một chương trình Python được đại diện bởi các đối tượng hoặc theo quan hệ giữa các đối tượng. (Trong một nghĩa nào đó, và trong sự phù hợp với mô hình của một Von Neumann “lưu trữ máy tính chương trình,” mã cũng được đại diện bởi các đối tượng.)

Từ quan điểm của Python, Fundamental data type về cơ bản là khác nhau từ C/C++ . Nó được sử dụng để ánh xạ các loại dữ liệu C/C++ tới Python. Và vì vậy chúng ta hãy để nó từ cuộc thảo luận trong thời gian này và xem xét thực tế rằng tất cả các dữ liệu là đối tượng và là biểu hiện của một số lớp. Mỗi đối tượng có một ID (giống như địa chỉ), Value và một Type.

Tất cả các đối tượng đều được sao chép theo tham chiếu. Ví dụ:

>>> x=20 
>>> y=x 
>>> id(x)==id(y) 
True 
>>> 

Cách duy nhất để có một trường hợp mới là tạo một.

>>> x=3 
>>> id(x)==id(y) 
False 
>>> x==y 
False 

Điều này nghe có vẻ phức tạp lúc đầu nhưng để đơn giản hóa một chút, Python đã thực hiện một số loại không thay đổi. Ví dụ: bạn không thể thay đổi số string. Bạn phải cắt nó và tạo một đối tượng chuỗi mới.

Thường sao chép theo tham chiếu sẽ cho kết quả không mong muốn cho ví dụ cũ.

x=[[0]*8]*8 có thể cho bạn cảm giác rằng nó tạo danh sách hai chiều là 0 s. Nhưng trên thực tế nó tạo ra một danh sách tham chiếu của cùng một đối tượng danh sách [0] s. Vì vậy, làm x [1] [1] sẽ kết thúc thay đổi tất cả các trường hợp trùng lặp cùng một lúc.

Mô-đun Copy cung cấp một phương pháp gọi là sâu để tạo ra một thể hiện mới của đối tượng chứ không phải là một cá thể cạn. Điều này có lợi khi bạn dự định có hai đối tượng riêng biệt và thao tác nó một cách riêng biệt giống như bạn dự định trong ví dụ thứ hai của mình.

Để mở rộng ví dụ của bạn

>>> class Klasa: 
    def __init__(self, num): 
     self.num = num 


>>> a = Klasa(2) 
>>> b = copy.deepcopy(a) 
>>> print a.num, b.num # prints 2 2 
2 2 
>>> a.num = 3 
>>> print a.num, b.num # prints 3 3 - different! 
3 2 
+0

+1 cho các tham chiếu đến tài liệu. –

+0

@Abhijit Khi bạn viết _ "sao chép theo tham chiếu" _ cho ví dụ '' x = 20'' thì '' y = x'', thì sao chép? Personnaly, tôi nghĩ rằng có hoàn toàn không có gì được sao chép và đó là lý do tại sao việc sử dụng "sao chép bởi" là vô nghĩa trong Python trong một số trường hợp, có thể tất cả. – eyquem

+0

Dữ liệu bên trong (một con trỏ, cụ thể là một PyObject *, trong triển khai C) được sử dụng để làm cho các biến tham chiếu tới các giá trị, được sao chép. :) –

1

Giả sử bạn và tôi có một người bạn chung. Nếu tôi quyết định rằng tôi không còn thích cô ấy nữa, cô ấy vẫn là bạn của bạn. Mặt khác, nếu tôi tặng cô ấy một món quà, bạn của bạn nhận được một món quà.

Bài tập không sao chép bất kỳ nội dung nào bằng Python và "sao chép theo tham chiếu" ở đâu đó giữa khó xử và vô nghĩa (như bạn thực sự chỉ ra trong một trong các nhận xét của bạn). Chuyển nhượng gây ra một biến để bắt đầu đề cập đến một giá trị. Không có "loại cơ bản" riêng biệt trong Python; trong khi một số trong số đó được tích hợp sẵn, thì int vẫn là một lớp.

Trong các trường hợp cả hai trường hợp, việc gán sẽ khiến biến tham chiếu đến bất kỳ biến nào mà bên phải đánh giá. Hành vi bạn nhìn thấy chính xác là những gì bạn nên mong đợi trong môi trường đó, theo ẩn dụ. Cho dù "người bạn" của bạn là một số int hoặc một số Klasa, việc gán cho một thuộc tính về cơ bản khác với việc gán lại biến đó cho một trường hợp hoàn toàn khác, với hành vi khác nhau tương ứng.

Sự khác biệt thực sự duy nhất là int không có bất kỳ thuộc tính nào bạn có thể gán cho. (Đó là phần mà việc triển khai thực sự phải làm một chút phép thuật để hạn chế bạn.)

Bạn đang bối rối hai khái niệm khác nhau về "tham chiếu". C++ T& là một điều kỳ diệu khi được chỉ định, cập nhật đối tượng được đề cập tại chỗ và không phải là tham chiếu; mà không bao giờ có thể được "reseated" một khi tham chiếu được khởi tạo. Điều này rất hữu ích trong một ngôn ngữ mà hầu hết mọi thứ là giá trị. Trong Python, mọi thứ đều là một tham chiếu để bắt đầu. Tham chiếu Pythonic giống như một con trỏ luôn luôn hợp lệ, không có giá trị, không thể sử dụng-cho-số học, tự động tham chiếu. Nhiệm vụ làm cho tham chiếu bắt đầu đề cập đến một điều hoàn toàn khác. Bạn không thể "cập nhật đối tượng được đề cập tại chỗ" bằng cách thay thế nó bán buôn, bởi vì các đối tượng của Python không hoạt động như thế. Bạn có thể, tất nhiên, cập nhật trạng thái nội tại của nó bằng cách chơi với các thuộc tính của nó (nếu có bất kỳ thuộc tính nào có thể truy cập), nhưng các thuộc tính đó là chính chúng cũng là tất cả các tham chiếu.

Các vấn đề liên quan