Hướng dẫn python pass by assignment - python chuyển bằng bài tập

Khi bạn gọi một chức năng trong Python và đưa ra một số đối số ... chúng có được thông qua bởi giá trị không? Không! Bằng cách tham khảo? Không! Họ được thông qua bằng cách chuyển nhượng.

Hướng dẫn python pass by assignment - python chuyển bằng bài tập

.

Giới thiệu

Nhiều ngôn ngữ lập trình truyền thống sử dụng một trong hai mô hình khi chuyển các đối số cho các chức năng:

  • Một số ngôn ngữ sử dụng mô hình qua từng giá trị; và
  • Hầu hết các loại khác sử dụng mô hình tham chiếu qua từng lần.

Phải nói rằng, điều quan trọng là phải biết mô hình mà Python sử dụng, bởi vì điều đó ảnh hưởng đến cách xử lý mã của bạn.

Trong pydon không, bạn sẽ:

  • Xem rằng Python không sử dụng các mô hình chuyển qua từng giá trị cũng như các mô hình chuyển qua;
  • Hiểu rằng Python sử dụng mô hình chuyển qua từng lần;
  • Tìm hiểu về chức năng tích hợp
    2
    6
    0;
  • Tạo sự hiểu biết tốt hơn cho mô hình đối tượng Python;
  • nhận ra rằng mỗi đối tượng có 3 tính chất rất quan trọng xác định nó;
  • hiểu sự khác biệt giữa các đối tượng đột biến và bất biến;
  • Tìm hiểu sự khác biệt giữa các bản sao nông và sâu; và
  • Tìm hiểu cách sử dụng mô -đun
    2
    6
    1 để thực hiện cả hai loại bản sao đối tượng.

Bây giờ bạn có thể nhận được bản sao MIỄN PHÍ của bạn về Sách điện tử Pydon'ts - Viết mã Python tuyệt đẹp trên Gumroad.free copy of the ebook “Pydon'ts – Write beautiful Python code” on Gumroad.

Python có qua giá trị không?

Trong mô hình chuyển qua giá trị, khi bạn gọi một hàm với một tập hợp các đối số, dữ liệu được sao chép vào hàm. Điều này có nghĩa là bạn có thể sửa đổi các đối số theo cách bạn vui lòng và bạn sẽ không thể thay đổi trạng thái của chương trình bên ngoài chức năng. Đây không phải là những gì Python làm, Python không sử dụng mô hình qua từng giá trị.

Nhìn vào đoạn mã sau đó, nó có thể trông giống như Python sử dụng giá trị qua từng giá trị:

def foo(x):
    x = 4

a = 3
foo(a)
print(a)
# 3

Điều này có vẻ như mô hình qua từng giá trị vì chúng tôi đã cho nó 3, thay đổi nó thành 4 và thay đổi không được phản ánh ở bên ngoài (

2
6
2 vẫn là 3).

Nhưng, trên thực tế, Python không sao chép dữ liệu vào chức năng.

Để chứng minh điều này, tôi sẽ chỉ cho bạn một chức năng khác:

def clearly_not_pass_by_value(my_list):
    my_list[0] = 42

l = [1, 2, 3]
clearly_not_pass_by_value(l)
print(l)
# [42, 2, 3]

Như chúng ta có thể thấy, danh sách

2
6
3, được xác định bên ngoài hàm, đã thay đổi sau khi gọi hàm
2
6
4. Do đó, Python không sử dụng mô hình qua từng giá trị.

Python có được tham chiếu không?

Trong một mô hình tham chiếu qua thực sự, hàm được gọi là truy cập vào các biến của callee! Đôi khi, nó có thể trông giống như đó là những gì Python làm, nhưng Python không sử dụng mô hình tham chiếu qua từng lần.

Tôi sẽ cố gắng hết sức để giải thích tại sao đó không phải là những gì Python làm:

def not_pass_by_reference(my_list):
    my_list = [42, 73, 0]

l = [1, 2, 3]
not_pass_by_reference(l)
print(l)
# [1, 2, 3]

Nếu Python sử dụng mô hình tham chiếu qua từng lần, chức năng sẽ thay đổi hoàn toàn giá trị của

2
6
3 bên ngoài hàm, nhưng đó không phải là những gì đã xảy ra, như chúng ta có thể thấy.

Hãy để tôi chỉ cho bạn một tình huống tham chiếu thực tế.

Đây là một số mã Pascal:

program callByReference;
var
    x: integer;

procedure foo(var a: integer);
{ create a procedure called `foo` }
begin
    a := 6            { assign 6 to `a` }
end;

begin
    x := 2;           { assign 2 to `x` }
    writeln(x);       { print `x` }
    foo(x);           { call `foo` with `x` }
    writeln(x);       { print `x` }
end.

Nhìn vào các dòng cuối cùng của mã đó:

  • Chúng tôi gán
    2
    6
    6 cho
    2
    6
    7 với
    2
    6
    8;
  • Chúng tôi in
    2
    6
    7;
  • Chúng tôi gọi
    >>> id(obj)
    2698212637504       # the identity of `obj`
    >>> type(obj)
    <class 'list'>      # the type of `obj`
    >>> obj
    [1, 2, 3]           # the contents of `obj`
    0 với
    2
    6
    7 là đối số; và
  • Chúng tôi in lại
    2
    6
    7.

Đầu ra của chương trình này là gì?

Tôi tưởng tượng rằng hầu hết các bạn sẽ không có thông dịch viên Pascal nằm xung quanh, vì vậy bạn chỉ có thể truy cập tio.run và chạy mã này trực tuyến

Nếu bạn chạy cái này, bạn sẽ thấy rằng đầu ra là

2
6

Điều này có thể khá đáng ngạc nhiên, nếu phần lớn kinh nghiệm lập trình của bạn là ở Python!

Quy trình

>>> id(obj)
2698212637504       # the identity of `obj`
>>> type(obj)
<class 'list'>      # the type of `obj`
>>> obj
[1, 2, 3]           # the contents of `obj`
0 đã nhận được một cách hiệu quả biến
2
6
7 và thay đổi giá trị mà nó chứa. Sau khi
>>> id(obj)
2698212637504       # the identity of `obj`
>>> type(obj)
<class 'list'>      # the type of `obj`
>>> obj
[1, 2, 3]           # the contents of `obj`
0 được thực hiện, biến
2
6
7 (sống bên ngoài
>>> id(obj)
2698212637504       # the identity of `obj`
>>> type(obj)
<class 'list'>      # the type of `obj`
>>> obj
[1, 2, 3]           # the contents of `obj`
0) có một giá trị khác. Bạn không thể làm bất cứ điều gì như thế này trong Python.

Mô hình đối tượng Python

Để thực sự hiểu được cách cư xử của Python khi gọi các chức năng, tốt nhất là trước tiên chúng ta hiểu các đối tượng Python là gì và làm thế nào để mô tả chúng.

Ba đặc điểm của các đối tượng

Trong Python, mọi thứ đều là một đối tượng và mỗi đối tượng được đặc trưng bởi ba điều:

  • Bản sắc của nó (một số nguyên xác định duy nhất đối tượng, giống như các số an sinh xã hội xác định con người);
  • một loại (xác định các hoạt động bạn có thể làm với đối tượng của mình); và
  • nội dung của đối tượng.

Đây là một đối tượng và ba đặc điểm của nó:

>>> id(obj)
2698212637504       # the identity of `obj`
>>> type(obj)
<class 'list'>      # the type of `obj`
>>> obj
[1, 2, 3]           # the contents of `obj`

Như chúng ta có thể thấy ở trên,

2
6
0 là chức năng tích hợp mà bạn sử dụng để truy vấn danh tính của một đối tượng và
>>> id(obj)
2698212637504       # the identity of `obj`
>>> type(obj)
<class 'list'>      # the type of `obj`
>>> obj
[1, 2, 3]           # the contents of `obj`
9 là chức năng tích hợp mà bạn sử dụng để truy vấn loại đối tượng.

(Im)mutability

Khả năng đột biến (IM) của một đối tượng phụ thuộc vào loại của nó. Nói cách khác, (IM) biến đổi là một đặc điểm của các loại, không phải của các đối tượng cụ thể!

Nhưng chính xác thì nó có nghĩa là gì đối với một đối tượng có thể thay đổi? Hoặc cho một đối tượng là bất biến?

Hãy nhớ lại rằng một đối tượng được đặc trưng bởi danh tính, loại của nó và nội dung của nó. Một loại có thể thay đổi nếu bạn có thể thay đổi nội dung của các đối tượng mà không thay đổi danh tính và loại của nó.

Danh sách là một ví dụ tuyệt vời của một loại dữ liệu có thể thay đổi. Tại sao? Bởi vì danh sách là container: Bạn có thể đặt những thứ bên trong danh sách và bạn có thể xóa nội dung từ bên trong cùng danh sách đó.

Dưới đây, bạn có thể thấy làm thế nào nội dung của danh sách

>>> obj = []
>>> id(obj)
2287844221184
>>> obj.append(0); obj.extend([1, 2, 3]); obj
[42, 0, 1, 2, 3]
>>> id(obj)
2287844221184
>>> obj.pop(0); obj.pop(0); obj.pop(); obj
42
0
3
[1, 2]
>>> id(obj)
2287844221184
0 thay đổi khi chúng tôi thực hiện các cuộc gọi phương thức, nhưng danh tính của danh sách vẫn giữ nguyên:

>>> obj = []
>>> id(obj)
2287844221184
>>> obj.append(0); obj.extend([1, 2, 3]); obj
[42, 0, 1, 2, 3]
>>> id(obj)
2287844221184
>>> obj.pop(0); obj.pop(0); obj.pop(); obj
42
0
3
[1, 2]
>>> id(obj)
2287844221184

Tuy nhiên, khi đối phó với các vật thể bất biến, đó là một câu chuyện hoàn toàn khác. Nếu chúng ta kiểm tra một từ điển tiếng Anh, thì đây là những gì chúng ta nhận được cho định nghĩa của những người khác không thể tin được:

tính từ: bất biến - không thay đổi theo thời gian hoặc không thể thay đổi.

Nội dung của đối tượng bất biến không bao giờ thay đổi. Lấy một chuỗi làm ví dụ:

>>> obj = "Hello, world!"

Chuỗi là một ví dụ tốt cho cuộc thảo luận này bởi vì, đôi khi, chúng có thể trông có thể thay đổi. Nhưng họ thì không!

Một chỉ số rất tốt cho thấy một đối tượng là bất biến là khi tất cả các phương thức của nó trả về một cái gì đó. Đây không phải là phương thức

>>> obj = []
>>> id(obj)
2287844221184
>>> obj.append(0); obj.extend([1, 2, 3]); obj
[42, 0, 1, 2, 3]
>>> id(obj)
2287844221184
>>> obj.pop(0); obj.pop(0); obj.pop(); obj
42
0
3
[1, 2]
>>> id(obj)
2287844221184
1 của danh sách, ví dụ! Nếu bạn sử dụng
>>> obj = []
>>> id(obj)
2287844221184
>>> obj.append(0); obj.extend([1, 2, 3]); obj
[42, 0, 1, 2, 3]
>>> id(obj)
2287844221184
>>> obj.pop(0); obj.pop(0); obj.pop(); obj
42
0
3
[1, 2]
>>> id(obj)
2287844221184
1 trong danh sách, bạn không nhận được giá trị trả về. Mặt khác, bất kỳ phương pháp nào bạn sử dụng trên chuỗi, kết quả sẽ được trả về cho bạn:

>>> [].append(0)    # No return.
>>> obj.upper()     # A string is returned.
'HELLO, WORLD!"

Lưu ý cách

>>> obj = []
>>> id(obj)
2287844221184
>>> obj.append(0); obj.extend([1, 2, 3]); obj
[42, 0, 1, 2, 3]
>>> id(obj)
2287844221184
>>> obj.pop(0); obj.pop(0); obj.pop(); obj
42
0
3
[1, 2]
>>> id(obj)
2287844221184
0 không được cập nhật tự động lên
>>> obj = []
>>> id(obj)
2287844221184
>>> obj.append(0); obj.extend([1, 2, 3]); obj
[42, 0, 1, 2, 3]
>>> id(obj)
2287844221184
>>> obj.pop(0); obj.pop(0); obj.pop(); obj
42
0
3
[1, 2]
>>> id(obj)
2287844221184
4. Thay vào đó, chuỗi mới đã được tạo và trả lại cho bạn.

Một gợi ý tuyệt vời khác về thực tế là các chuỗi là bất biến là bạn không thể gán cho các chỉ số của nó:

>>> obj[0]
'H'
>>> obj[0] = "h"
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'str' object does not support item assignment

Điều này cho thấy rằng, khi một chuỗi được tạo, nó vẫn giữ nguyên. Nó có thể được sử dụng để xây dựng các chuỗi khác, nhưng bản thân chuỗi luôn luôn. ở lại. không thay đổi.

Để tham khảo,

>>> obj = []
>>> id(obj)
2287844221184
>>> obj.append(0); obj.extend([1, 2, 3]); obj
[42, 0, 1, 2, 3]
>>> id(obj)
2287844221184
>>> obj.pop(0); obj.pop(0); obj.pop(); obj
42
0
3
[1, 2]
>>> id(obj)
2287844221184
5,
>>> obj = []
>>> id(obj)
2287844221184
>>> obj.append(0); obj.extend([1, 2, 3]); obj
[42, 0, 1, 2, 3]
>>> id(obj)
2287844221184
>>> obj.pop(0); obj.pop(0); obj.pop(); obj
42
0
3
[1, 2]
>>> id(obj)
2287844221184
6,
>>> obj = []
>>> id(obj)
2287844221184
>>> obj.append(0); obj.extend([1, 2, 3]); obj
[42, 0, 1, 2, 3]
>>> id(obj)
2287844221184
>>> obj.pop(0); obj.pop(0); obj.pop(); obj
42
0
3
[1, 2]
>>> id(obj)
2287844221184
7,
>>> obj = []
>>> id(obj)
2287844221184
>>> obj.append(0); obj.extend([1, 2, 3]); obj
[42, 0, 1, 2, 3]
>>> id(obj)
2287844221184
>>> obj.pop(0); obj.pop(0); obj.pop(); obj
42
0
3
[1, 2]
>>> id(obj)
2287844221184
8,
>>> obj = []
>>> id(obj)
2287844221184
>>> obj.append(0); obj.extend([1, 2, 3]); obj
[42, 0, 1, 2, 3]
>>> id(obj)
2287844221184
>>> obj.pop(0); obj.pop(0); obj.pop(); obj
42
0
3
[1, 2]
>>> id(obj)
2287844221184
9 và
>>> obj = "Hello, world!"
0 là các loại đối tượng bất biến phổ biến nhất;
>>> obj = "Hello, world!"
1,
>>> obj = "Hello, world!"
2 và
>>> obj = "Hello, world!"
3 là các loại đối tượng có thể thay đổi phổ biến nhất.

Tên biến làm nhãn

Một điều quan trọng khác cần hiểu là một tên biến có rất ít liên quan đến chính đối tượng.

Trên thực tế, tên

>>> obj = []
>>> id(obj)
2287844221184
>>> obj.append(0); obj.extend([1, 2, 3]); obj
[42, 0, 1, 2, 3]
>>> id(obj)
2287844221184
>>> obj.pop(0); obj.pop(0); obj.pop(); obj
42
0
3
[1, 2]
>>> id(obj)
2287844221184
0 chỉ là một nhãn mà tôi quyết định gắn vào đối tượng có danh tính 2698212637504, có loại danh sách và nội dung 1, 2, 3.

Giống như tôi đã đính kèm nhãn

>>> obj = []
>>> id(obj)
2287844221184
>>> obj.append(0); obj.extend([1, 2, 3]); obj
[42, 0, 1, 2, 3]
>>> id(obj)
2287844221184
>>> obj.pop(0); obj.pop(0); obj.pop(); obj
42
0
3
[1, 2]
>>> id(obj)
2287844221184
0 vào đối tượng đó, tôi có thể đính kèm nhiều tên khác vào nó:

def clearly_not_pass_by_value(my_list):
    my_list[0] = 42

l = [1, 2, 3]
clearly_not_pass_by_value(l)
print(l)
# [42, 2, 3]
0

Một lần nữa, những cái tên này chỉ là nhãn. Nhãn mà tôi quyết định bám vào cùng một đối tượng. Làm thế nào chúng ta có thể biết đó là cùng một đối tượng? Chà, tất cả các số an sinh xã hội của họ (IDS) phù hợp, vì vậy chúng phải là cùng một đối tượng:

def clearly_not_pass_by_value(my_list):
    my_list[0] = 42

l = [1, 2, 3]
clearly_not_pass_by_value(l)
print(l)
# [42, 2, 3]
1

Do đó, chúng tôi kết luận rằng

>>> id(obj)
2698212637504       # the identity of `obj`
>>> type(obj)
<class 'list'>      # the type of `obj`
>>> obj
[1, 2, 3]           # the contents of `obj`
0,
>>> obj = "Hello, world!"
7,
>>> obj = "Hello, world!"
8 và
>>> obj = []
>>> id(obj)
2287844221184
>>> obj.append(0); obj.extend([1, 2, 3]); obj
[42, 0, 1, 2, 3]
>>> id(obj)
2287844221184
>>> obj.pop(0); obj.pop(0); obj.pop(); obj
42
0
3
[1, 2]
>>> id(obj)
2287844221184
0, là các tên biến mà tất cả đều đề cập đến cùng một đối tượng.

Nhà điều hành >>> [].append(0) # No return. >>> obj.upper() # A string is returned. 'HELLO, WORLD!"0

Đây chính xác là những gì người vận hành

>>> [].append(0)    # No return.
>>> obj.upper()     # A string is returned.
'HELLO, WORLD!"
0 làm: nó kiểm tra xem hai đối tượng có giống nhau không.

Đối với hai đối tượng giống nhau, chúng phải có cùng một danh tính:

def clearly_not_pass_by_value(my_list):
    my_list[0] = 42

l = [1, 2, 3]
clearly_not_pass_by_value(l)
print(l)
# [42, 2, 3]
2

Nó không đủ để có cùng loại và nội dung! Chúng ta có thể tạo một danh sách mới với nội dung

>>> [].append(0)    # No return.
>>> obj.upper()     # A string is returned.
'HELLO, WORLD!"
2 và đó sẽ không giống đối tượng như
>>> obj = []
>>> id(obj)
2287844221184
>>> obj.append(0); obj.extend([1, 2, 3]); obj
[42, 0, 1, 2, 3]
>>> id(obj)
2287844221184
>>> obj.pop(0); obj.pop(0); obj.pop(); obj
42
0
3
[1, 2]
>>> id(obj)
2287844221184
0:

def clearly_not_pass_by_value(my_list):
    my_list[0] = 42

l = [1, 2, 3]
clearly_not_pass_by_value(l)
print(l)
# [42, 2, 3]
3

Hãy nghĩ về nó về mặt cặp song sinh hoàn hảo. Khi hai anh chị em là cặp song sinh hoàn hảo, họ trông giống hệt nhau. Tuy nhiên, họ là những người khác nhau!

>>> [].append(0) # No return. >>> obj.upper() # A string is returned. 'HELLO, WORLD!"4

Chỉ là một lưu ý phụ, nhưng là một điều quan trọng, bạn nên biết về nhà điều hành

>>> [].append(0)    # No return.
>>> obj.upper()     # A string is returned.
'HELLO, WORLD!"
4.

Nói chung, khi bạn muốn phủ nhận một điều kiện, bạn đặt một

>>> [].append(0)    # No return.
>>> obj.upper()     # A string is returned.
'HELLO, WORLD!"
6 trước nó:

def clearly_not_pass_by_value(my_list):
    my_list[0] = 42

l = [1, 2, 3]
clearly_not_pass_by_value(l)
print(l)
# [42, 2, 3]
4

Vì vậy, nếu bạn muốn kiểm tra xem hai biến chỉ ra các đối tượng khác nhau, bạn có thể bị cám dỗ để viết

def clearly_not_pass_by_value(my_list):
    my_list[0] = 42

l = [1, 2, 3]
clearly_not_pass_by_value(l)
print(l)
# [42, 2, 3]
5

Tuy nhiên, Python có nhà điều hành

>>> [].append(0)    # No return.
>>> obj.upper()     # A string is returned.
'HELLO, WORLD!"
4, tương tự như một câu tiếng Anh thích hợp, mà tôi nghĩ là thực sự tuyệt vời!

Do đó, ví dụ trên thực sự nên được viết

def clearly_not_pass_by_value(my_list):
    my_list[0] = 42

l = [1, 2, 3]
clearly_not_pass_by_value(l)
print(l)
# [42, 2, 3]
6

Python làm một điều tương tự cho toán tử

>>> [].append(0)    # No return.
>>> obj.upper()     # A string is returned.
'HELLO, WORLD!"
8, cũng cung cấp toán tử
>>> [].append(0)    # No return.
>>> obj.upper()     # A string is returned.
'HELLO, WORLD!"
9 ... Thật tuyệt vời ?!

Nhiệm vụ là biệt danh

Nếu chúng ta tiếp tục đẩy phép ẩn dụ này về phía trước, việc gán các biến cũng giống như đặt một biệt danh mới cho ai đó.

Bạn bè của tôi từ trường cấp hai gọi tôi là Rojer Rojer. Bạn bè của tôi từ trường đại học gọi tôi là Gir Giraoo. Những người tôi không gần gũi gọi tôi bằng tên của tôi - Hồi Rodrigo. Tuy nhiên, bất kể họ gọi tôi là gì, tôi vẫn là tôi, phải không?

Nếu một ngày tôi quyết định thay đổi kiểu tóc của mình, mọi người sẽ thấy kiểu tóc mới, bất kể họ gọi tôi là gì!

Theo cách tương tự, nếu tôi sửa đổi nội dung của một đối tượng, tôi có thể sử dụng bất kỳ biệt danh nào tôi thích thấy rằng những thay đổi đó đã xảy ra. Ví dụ: chúng ta có thể thay đổi phần tử giữa của danh sách chúng ta đã chơi xung quanh với:

def clearly_not_pass_by_value(my_list):
    my_list[0] = 42

l = [1, 2, 3]
clearly_not_pass_by_value(l)
print(l)
# [42, 2, 3]
7

Chúng tôi đã sử dụng biệt danh

>>> id(obj)
2698212637504       # the identity of `obj`
>>> type(obj)
<class 'list'>      # the type of `obj`
>>> obj
[1, 2, 3]           # the contents of `obj`
0 để sửa đổi phần tử giữa, nhưng sự thay đổi đó cũng có thể nhìn thấy từ tất cả các biệt danh khác.

Why?

Bởi vì tất cả đều chỉ vào cùng một đối tượng danh sách.

Python là thông qua

Đã đặt ra tất cả những điều này, bây giờ chúng tôi đã sẵn sàng để hiểu làm thế nào Python chuyển các lập luận cho các chức năng.

Khi chúng ta gọi một hàm, mỗi tham số của hàm được gán cho đối tượng chúng được truyền vào. Về bản chất, mỗi tham số bây giờ trở thành một biệt danh mới cho các đối tượng được đưa ra.

Lập luận bất biến

Nếu chúng ta vượt qua các lập luận bất biến, thì chúng ta không có cách nào để tự sửa đổi các đối số. Rốt cuộc, đó là những gì có nghĩa là bất biến: Không phải thay đổi.

Đó là lý do tại sao nó có thể trông giống như Python sử dụng mô hình qua từng giá trị. Bởi vì cách duy nhất mà chúng ta có thể có tham số giữ một cái gì đó khác là bằng cách gán nó cho một điều hoàn toàn khác. Khi chúng tôi làm điều đó, chúng tôi đang sử dụng lại cùng một biệt danh cho một đối tượng khác:

def clearly_not_pass_by_value(my_list):
    my_list[0] = 42

l = [1, 2, 3]
clearly_not_pass_by_value(l)
print(l)
# [42, 2, 3]
8

Trong ví dụ trên, khi chúng ta gọi

>>> id(obj)
2698212637504       # the identity of `obj`
>>> type(obj)
<class 'list'>      # the type of `obj`
>>> obj
[1, 2, 3]           # the contents of `obj`
0 với đối số
>>> obj[0]
'H'
>>> obj[0] = "h"
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'str' object does not support item assignment
2, như thể chúng ta đang thực hiện
>>> obj[0]
'H'
>>> obj[0] = "h"
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'str' object does not support item assignment
3 khi bắt đầu chức năng.

Ngay sau đó, chúng tôi có

>>> obj[0]
'H'
>>> obj[0] = "h"
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'str' object does not support item assignment
4. Điều này có nghĩa là, hãy lấy biệt danh "thanh" và trỏ nó vào số nguyên
>>> obj[0]
'H'
>>> obj[0] = "h"
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'str' object does not support item assignment
5. Python không quan tâm rằng
>>> obj = "Hello, world!"
7, như một biệt danh (như một tên biến) đã được sử dụng. Bây giờ nó đang chỉ vào đó
>>> obj[0]
'H'
>>> obj[0] = "h"
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'str' object does not support item assignment
5!

Lập luận thay đổi

Mặt khác, các đối số có thể thay đổi có thể được thay đổi. Chúng tôi có thể sửa đổi nội dung nội bộ của họ. Một ví dụ điển hình của một đối tượng có thể thay đổi là một danh sách: các yếu tố của nó có thể thay đổi (và độ dài của nó cũng vậy).

Đó là lý do tại sao nó có thể trông giống như Python sử dụng mô hình tham chiếu qua từng lần. Tuy nhiên, khi chúng ta thay đổi nội dung của một đối tượng, chúng ta đã không thay đổi danh tính của chính đối tượng. Tương tự, khi bạn thay đổi kiểu tóc hoặc quần áo, số an sinh xã hội của bạn không thay đổi:

def clearly_not_pass_by_value(my_list):
    my_list[0] = 42

l = [1, 2, 3]
clearly_not_pass_by_value(l)
print(l)
# [42, 2, 3]
9

Bạn có hiểu những gì tôi đang cố gắng nói không? Nếu không, hãy bỏ một bình luận bên dưới và tôi sẽ cố gắng giúp đỡ.

Cẩn thận khi gọi các chức năng

Điều này cho thấy bạn nên cẩn thận khi xác định các chức năng của bạn. Nếu chức năng của bạn mong đợi các đối số có thể thay đổi, bạn nên thực hiện một trong hai:

  • Không làm thay đổi lập luận theo bất kỳ cách nào; hoặc
  • Tài liệu rõ ràng rằng đối số có thể bị đột biến.

Cá nhân, tôi thích đi theo cách tiếp cận đầu tiên: không thay đổi lập luận; Nhưng có những lúc và địa điểm cho cách tiếp cận thứ hai.

Đôi khi, bạn cần phải lấy lập luận làm cơ sở cho một loại chuyển đổi nào đó, điều đó có nghĩa là bạn sẽ muốn thay đổi lập luận. Trong những trường hợp đó, bạn có thể nghĩ về việc thực hiện một bản sao của đối số (được thảo luận trong phần tiếp theo), nhưng việc thực hiện bản sao đó có thể tốn nhiều tài nguyên. Trong những trường hợp đó, đột biến đối số có thể là lựa chọn hợp lý duy nhất.

Tạo bản sao

Bản sao nông so với sâu

Sao chép một đối tượng, có nghĩa là tạo một đối tượng thứ hai có một danh tính khác (do đó, là một đối tượng khác) nhưng có cùng nội dung. Nói chung, chúng tôi sao chép một đối tượng để chúng tôi có thể làm việc với nó và biến đổi nó, đồng thời bảo tồn đối tượng đầu tiên.

Khi sao chép các đối tượng, có một vài sắc thái cần được thảo luận.

Sao chép các đối tượng bất biến

Điều đầu tiên cần được nói là, đối với các đối tượng bất biến, không có ý nghĩa gì khi nói về các bản sao.

Bản sao của người Viking chỉ có ý nghĩa đối với các đối tượng có thể thay đổi. Nếu đối tượng của bạn là bất biến và nếu bạn muốn bảo tồn một tham chiếu đến nó, bạn chỉ có thể thực hiện một nhiệm vụ thứ hai và làm việc với nó:

def not_pass_by_reference(my_list):
    my_list = [42, 73, 0]

l = [1, 2, 3]
not_pass_by_reference(l)
print(l)
# [1, 2, 3]
0

Hoặc, đôi khi, bạn chỉ có thể gọi các phương thức và các chức năng khác trực tiếp trên bản gốc, bởi vì bản gốc không đi đâu cả:

def not_pass_by_reference(my_list):
    my_list = [42, 73, 0]

l = [1, 2, 3]
not_pass_by_reference(l)
print(l)
# [1, 2, 3]
1

Vì vậy, chúng ta chỉ cần lo lắng về các đối tượng có thể thay đổi.

Bản sao nông

Nhiều đối tượng có thể thay đổi có thể chứa, chính chúng, các đối tượng có thể thay đổi. Do đó, hai loại bản sao tồn tại:

  • bản sao nông; và
  • bản sao sâu.

Sự khác biệt nằm ở những gì xảy ra với các đối tượng có thể thay đổi bên trong các đối tượng có thể thay đổi.

Danh sách và từ điển có một phương thức

>>> obj[0]
'H'
>>> obj[0] = "h"
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'str' object does not support item assignment
8 trả về một bản sao nông của đối tượng tương ứng.

Chúng ta hãy xem một ví dụ với một danh sách:

def not_pass_by_reference(my_list):
    my_list = [42, 73, 0]

l = [1, 2, 3]
not_pass_by_reference(l)
print(l)
# [1, 2, 3]
2

Đầu tiên, chúng tôi tạo một danh sách bên trong danh sách và chúng tôi sao chép danh sách bên ngoài. Bây giờ, vì nó là một bản sao, danh sách được sao chép không phải là cùng một đối tượng với danh sách bên ngoài gốc:

def not_pass_by_reference(my_list):
    my_list = [42, 73, 0]

l = [1, 2, 3]
not_pass_by_reference(l)
print(l)
# [1, 2, 3]
3

Nhưng nếu chúng không phải là cùng một đối tượng, thì chúng ta có thể sửa đổi nội dung của một trong các danh sách và người kia sẽ không phản ánh sự thay đổi:

def not_pass_by_reference(my_list):
    my_list = [42, 73, 0]

l = [1, 2, 3]
not_pass_by_reference(l)
print(l)
# [1, 2, 3]
4

Đó là những gì chúng tôi đã thấy: Chúng tôi đã thay đổi yếu tố đầu tiên của

>>> obj[0]
'H'
>>> obj[0] = "h"
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'str' object does not support item assignment
9 và
def clearly_not_pass_by_value(my_list):
    my_list[0] = 42

l = [1, 2, 3]
clearly_not_pass_by_value(l)
print(l)
# [42, 2, 3]
00 vẫn không thay đổi.

Bây giờ, chúng tôi cố gắng sửa đổi nội dung của

def clearly_not_pass_by_value(my_list):
    my_list[0] = 42

l = [1, 2, 3]
clearly_not_pass_by_value(l)
print(l)
# [42, 2, 3]
01 và đó là khi niềm vui bắt đầu!

def not_pass_by_reference(my_list):
    my_list = [42, 73, 0]

l = [1, 2, 3]
not_pass_by_reference(l)
print(l)
# [1, 2, 3]
5

Khi chúng tôi sửa đổi nội dung của

def clearly_not_pass_by_value(my_list):
    my_list[0] = 42

l = [1, 2, 3]
clearly_not_pass_by_value(l)
print(l)
# [42, 2, 3]
01, cả
def clearly_not_pass_by_value(my_list):
    my_list[0] = 42

l = [1, 2, 3]
clearly_not_pass_by_value(l)
print(l)
# [42, 2, 3]
00 và
>>> obj[0]
'H'
>>> obj[0] = "h"
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'str' object does not support item assignment
9 đều phản ánh những thay đổi đó ...

Nhưng không phải bản sao được cho là cho tôi một danh sách thứ hai mà tôi có thể thay đổi mà không ảnh hưởng đến danh sách đầu tiên? Đúng! Và đó là những gì đã xảy ra!

Trên thực tế, việc sửa đổi nội dung của

def clearly_not_pass_by_value(my_list):
    my_list[0] = 42

l = [1, 2, 3]
clearly_not_pass_by_value(l)
print(l)
# [42, 2, 3]
01 không thực sự sửa đổi nội dung của cả
>>> obj[0]
'H'
>>> obj[0] = "h"
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'str' object does not support item assignment
9 cũng như
def clearly_not_pass_by_value(my_list):
    my_list[0] = 42

l = [1, 2, 3]
clearly_not_pass_by_value(l)
print(l)
# [42, 2, 3]
00: Rốt cuộc, yếu tố thứ ba của cả hai đã chỉ vào một đối tượng danh sách, và nó vẫn là! Đó là nội dung (bên trong) của đối tượng mà chúng ta đang chỉ ra đã thay đổi.

Đôi khi, chúng tôi không muốn điều này xảy ra: Đôi khi, chúng tôi không muốn các đối tượng có thể thay đổi để chia sẻ các đối tượng có thể thay đổi bên trong.

Kỹ thuật sao chép nông phổ biến

Khi làm việc với danh sách, người ta thường sử dụng việc cắt để tạo ra một bản sao nông của danh sách:

def not_pass_by_reference(my_list):
    my_list = [42, 73, 0]

l = [1, 2, 3]
not_pass_by_reference(l)
print(l)
# [1, 2, 3]
6

Sử dụng chức năng tích hợp cho loại tương ứng, trên chính đối tượng, cũng xây dựng các bản sao nông. Điều này hoạt động cho các danh sách và từ điển, và có khả năng làm việc cho các loại có thể thay đổi khác.

Dưới đây là một ví dụ với danh sách bên trong danh sách:

def not_pass_by_reference(my_list):
    my_list = [42, 73, 0]

l = [1, 2, 3]
not_pass_by_reference(l)
print(l)
# [1, 2, 3]
7

Và đây là một ví dụ với một danh sách bên trong từ điển:

def not_pass_by_reference(my_list):
    my_list = [42, 73, 0]

l = [1, 2, 3]
not_pass_by_reference(l)
print(l)
# [1, 2, 3]
8

Sao chép sâu

Khi bạn muốn sao chép một đối tượng, kỹ lưỡng và bạn không muốn bản sao chia sẻ tham chiếu đến các đối tượng bên trong, bạn cần thực hiện một bản sao sâu của đối tượng. Bạn có thể nghĩ về một bản sao sâu như một thuật toán đệ quy.

Bạn sao chép các yếu tố của cấp độ đầu tiên và, bất cứ khi nào bạn tìm thấy một yếu tố có thể thay đổi ở cấp độ đầu tiên, bạn sẽ tái sử dụng và sao chép nội dung của các yếu tố đó.

Để hiển thị ý tưởng này, đây là một triển khai đệ quy đơn giản của một bản sao sâu cho các danh sách có chứa các danh sách khác:

def not_pass_by_reference(my_list):
    my_list = [42, 73, 0]

l = [1, 2, 3]
not_pass_by_reference(l)
print(l)
# [1, 2, 3]
9

Chúng ta có thể sử dụng chức năng này để sao chép

def clearly_not_pass_by_value(my_list):
    my_list[0] = 42

l = [1, 2, 3]
clearly_not_pass_by_value(l)
print(l)
# [42, 2, 3]
00 trước đó và xem điều gì sẽ xảy ra:

program callByReference;
var
    x: integer;

procedure foo(var a: integer);
{ create a procedure called `foo` }
begin
    a := 6            { assign 6 to `a` }
end;

begin
    x := 2;           { assign 2 to `x` }
    writeln(x);       { print `x` }
    foo(x);           { call `foo` with `x` }
    writeln(x);       { print `x` }
end.
0

Như bạn có thể thấy ở đây, sửa đổi nội dung của

def clearly_not_pass_by_value(my_list):
    my_list[0] = 42

l = [1, 2, 3]
clearly_not_pass_by_value(l)
print(l)
# [42, 2, 3]
01 chỉ ảnh hưởng gián tiếp; Nó không ảnh hưởng đến
>>> obj[0]
'H'
>>> obj[0] = "h"
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'str' object does not support item assignment
9.

Đáng buồn thay, phương pháp

def clearly_not_pass_by_value(my_list):
    my_list[0] = 42

l = [1, 2, 3]
clearly_not_pass_by_value(l)
print(l)
# [42, 2, 3]
12 mà tôi đã triển khai không mạnh mẽ, cũng không linh hoạt, nhưng thư viện tiêu chuẩn Python đã giúp chúng tôi được bảo hiểm!

Mô -đun 2 61 và Phương pháp def clearly_not_pass_by_value(my_list): my_list[0] = 42 l = [1, 2, 3] clearly_not_pass_by_value(l) print(l) # [42, 2, 3]14

Mô -đun

2
6
1 chính xác là những gì chúng ta cần. Mô -đun cung cấp hai chức năng hữu ích:

  • def clearly_not_pass_by_value(my_list):
        my_list[0] = 42
    
    l = [1, 2, 3]
    clearly_not_pass_by_value(l)
    print(l)
    # [42, 2, 3]
    16 cho các bản sao nông; và
  • def clearly_not_pass_by_value(my_list):
        my_list[0] = 42
    
    l = [1, 2, 3]
    clearly_not_pass_by_value(l)
    print(l)
    # [42, 2, 3]
    17 cho các bản sao sâu.

Và đó là nó! Và, hơn thế nữa, phương pháp

def clearly_not_pass_by_value(my_list):
    my_list[0] = 42

l = [1, 2, 3]
clearly_not_pass_by_value(l)
print(l)
# [42, 2, 3]
17 đủ thông minh để xử lý các vấn đề có thể phát sinh với các định nghĩa tròn, ví dụ! Đó là, khi một đối tượng chứa một đối tượng khác có chứa tên đầu tiên: Việc thực hiện đệ quy ngây thơ của một thuật toán sao chép sâu sẽ vào một vòng lặp vô hạn!

Nếu bạn viết các đối tượng tùy chỉnh của riêng mình và bạn muốn chỉ định các bản sao nông và sâu của chúng nên được tạo như thế nào, bạn chỉ cần thực hiện lần lượt

def clearly_not_pass_by_value(my_list):
    my_list[0] = 42

l = [1, 2, 3]
clearly_not_pass_by_value(l)
print(l)
# [42, 2, 3]
19 và
def clearly_not_pass_by_value(my_list):
    my_list[0] = 42

l = [1, 2, 3]
clearly_not_pass_by_value(l)
print(l)
# [42, 2, 3]
20!

Đó là một mô -đun tuyệt vời, theo ý kiến ​​của tôi.

Ví dụ trong mã

Bây giờ chúng tôi đã đi sâu vào lý thuyết - ý định chơi chữ & nbsp; -, đã đến lúc cho bạn thấy một số mã thực tế chơi với các khái niệm này.

Đối số mặc định có thể thay đổi

Hãy bắt đầu với một yêu thích Twitter:

Python là một ngôn ngữ đáng kinh ngạc nhưng đôi khi dường như có những điều kỳ quặc 🤪

Ví dụ: một điều thường gây nhầm lẫn cho người mới bắt đầu là lý do tại sao bạn không nên sử dụng danh sách làm giá trị mặc định 👇

Đây là một chủ đề sẽ giúp bạn hiểu 💯 pic.twitter.com/hvhpjs2psh này

- Rodrigo 🐍📝 (@mathsppblog) ngày 5 tháng 10 năm 2021

Rõ ràng, đó là một ý tưởng tồi để sử dụng các đối tượng có thể thay đổi làm đối số mặc định. Đây là một đoạn trích cho bạn thấy lý do tại sao:

program callByReference;
var
    x: integer;

procedure foo(var a: integer);
{ create a procedure called `foo` }
begin
    a := 6            { assign 6 to `a` }
end;

begin
    x := 2;           { assign 2 to `x` }
    writeln(x);       { print `x` }
    foo(x);           { call `foo` with `x` }
    writeln(x);       { print `x` }
end.
1

Hàm trên nối một phần tử vào danh sách và, nếu không có danh sách nào được đưa ra, hãy nối nó vào một danh sách trống theo mặc định.

Tuyệt vời, hãy đưa chức năng này vào sử dụng tốt:

program callByReference;
var
    x: integer;

procedure foo(var a: integer);
{ create a procedure called `foo` }
begin
    a := 6            { assign 6 to `a` }
end;

begin
    x := 2;           { assign 2 to `x` }
    writeln(x);       { print `x` }
    foo(x);           { call `foo` with `x` }
    writeln(x);       { print `x` }
end.
2

Chúng tôi sử dụng nó một lần với

def clearly_not_pass_by_value(my_list):
    my_list[0] = 42

l = [1, 2, 3]
clearly_not_pass_by_value(l)
print(l)
# [42, 2, 3]
21 và chúng tôi nhận được một danh sách với
def clearly_not_pass_by_value(my_list):
    my_list[0] = 42

l = [1, 2, 3]
clearly_not_pass_by_value(l)
print(l)
# [42, 2, 3]
21 bên trong. Sau đó, chúng tôi sử dụng nó để nối một
def clearly_not_pass_by_value(my_list):
    my_list[0] = 42

l = [1, 2, 3]
clearly_not_pass_by_value(l)
print(l)
# [42, 2, 3]
21 vào danh sách khác mà chúng tôi có. Và cuối cùng, chúng tôi sử dụng nó để nối một
>>> obj[0]
'H'
>>> obj[0] = "h"
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'str' object does not support item assignment
5 vào một danh sách trống ... ngoại trừ đó không phải là những gì xảy ra!

Hóa ra, khi chúng ta xác định một hàm, các đối số mặc định được tạo và lưu trữ ở một nơi đặc biệt:

program callByReference;
var
    x: integer;

procedure foo(var a: integer);
{ create a procedure called `foo` }
begin
    a := 6            { assign 6 to `a` }
end;

begin
    x := 2;           { assign 2 to `x` }
    writeln(x);       { print `x` }
    foo(x);           { call `foo` with `x` }
    writeln(x);       { print `x` }
end.
3

Điều này có nghĩa là đối số mặc định luôn là cùng một đối tượng. Do đó, vì nó là một đối tượng có thể thay đổi, nội dung của nó có thể thay đổi theo thời gian. Đó là lý do tại sao, trong mã ở trên,

def clearly_not_pass_by_value(my_list):
    my_list[0] = 42

l = [1, 2, 3]
clearly_not_pass_by_value(l)
print(l)
# [42, 2, 3]
25 hiển thị một danh sách với hai mục đã có.

Nếu chúng ta xác định lại chức năng, thì

def clearly_not_pass_by_value(my_list):
    my_list[0] = 42

l = [1, 2, 3]
clearly_not_pass_by_value(l)
print(l)
# [42, 2, 3]
25 của nó hiển thị một danh sách trống:

program callByReference;
var
    x: integer;

procedure foo(var a: integer);
{ create a procedure called `foo` }
begin
    a := 6            { assign 6 to `a` }
end;

begin
    x := 2;           { assign 2 to `x` }
    writeln(x);       { print `x` }
    foo(x);           { call `foo` with `x` }
    writeln(x);       { print `x` }
end.
4

Đây là lý do tại sao, nói chung, các đối tượng có thể thay đổi không nên được sử dụng làm đối số mặc định.

Thực tiễn tiêu chuẩn, trong những trường hợp này, là sử dụng

def clearly_not_pass_by_value(my_list):
    my_list[0] = 42

l = [1, 2, 3]
clearly_not_pass_by_value(l)
print(l)
# [42, 2, 3]
27 và sau đó sử dụng đường ngắn mạch Boolean để gán giá trị mặc định:

program callByReference;
var
    x: integer;

procedure foo(var a: integer);
{ create a procedure called `foo` }
begin
    a := 6            { assign 6 to `a` }
end;

begin
    x := 2;           { assign 2 to `x` }
    writeln(x);       { print `x` }
    foo(x);           { call `foo` with `x` }
    writeln(x);       { print `x` }
end.
5

Với việc triển khai này, chức năng hiện hoạt động như mong đợi:

program callByReference;
var
    x: integer;

procedure foo(var a: integer);
{ create a procedure called `foo` }
begin
    a := 6            { assign 6 to `a` }
end;

begin
    x := 2;           { assign 2 to `x` }
    writeln(x);       { print `x` }
    foo(x);           { call `foo` with `x` }
    writeln(x);       { print `x` }
end.
6

def clearly_not_pass_by_value(my_list): my_list[0] = 42 l = [1, 2, 3] clearly_not_pass_by_value(l) print(l) # [42, 2, 3]28

Tìm kiếm thông qua Thư viện tiêu chuẩn Python cho thấy toán tử

>>> [].append(0)    # No return.
>>> obj.upper()     # A string is returned.
'HELLO, WORLD!"
4 được sử dụng hơn 5.000 lần. Đó là rất nhiều.

Và, cho đến nay và lớn, nhà điều hành đó hầu như luôn luôn được theo sau bởi

def clearly_not_pass_by_value(my_list):
    my_list[0] = 42

l = [1, 2, 3]
clearly_not_pass_by_value(l)
print(l)
# [42, 2, 3]
27. Trên thực tế,
def clearly_not_pass_by_value(my_list):
    my_list[0] = 42

l = [1, 2, 3]
clearly_not_pass_by_value(l)
print(l)
# [42, 2, 3]
28 xuất hiện 3169 lần trong thư viện tiêu chuẩn!

def clearly_not_pass_by_value(my_list):
    my_list[0] = 42

l = [1, 2, 3]
clearly_not_pass_by_value(l)
print(l)
# [42, 2, 3]
32 thực hiện chính xác những gì nó được viết: nó kiểm tra xem
2
6
7 có phải là
def clearly_not_pass_by_value(my_list):
    my_list[0] = 42

l = [1, 2, 3]
clearly_not_pass_by_value(l)
print(l)
# [42, 2, 3]
27 hay không.

Dưới đây là một ví dụ đơn giản sử dụng điều đó, từ mô -đun

def clearly_not_pass_by_value(my_list):
    my_list[0] = 42

l = [1, 2, 3]
clearly_not_pass_by_value(l)
print(l)
# [42, 2, 3]
35 để tạo giao diện dòng lệnh:

program callByReference;
var
    x: integer;

procedure foo(var a: integer);
{ create a procedure called `foo` }
begin
    a := 6            { assign 6 to `a` }
end;

begin
    x := 2;           { assign 2 to `x` }
    writeln(x);       { print `x` }
    foo(x);           { call `foo` with `x` }
    writeln(x);       { print `x` }
end.
7

Ngay cả khi không có nhiều bối cảnh, chúng ta có thể thấy những gì đang xảy ra: khi hiển thị lệnh trợ giúp cho một phần nhất định, chúng ta có thể muốn thụt vào nó (hoặc không) để hiển thị các phụ thuộc phân cấp.

Nếu

def clearly_not_pass_by_value(my_list):
    my_list[0] = 42

l = [1, 2, 3]
clearly_not_pass_by_value(l)
print(l)
# [42, 2, 3]
36 của một phần là
def clearly_not_pass_by_value(my_list):
    my_list[0] = 42

l = [1, 2, 3]
clearly_not_pass_by_value(l)
print(l)
# [42, 2, 3]
27, thì phần đó không có cha mẹ và không cần phải thụt vào. Nói cách khác, nếu cha mẹ của một phần
def clearly_not_pass_by_value(my_list):
    my_list[0] = 42

l = [1, 2, 3]
clearly_not_pass_by_value(l)
print(l)
# [42, 2, 3]
28, thì chúng tôi muốn thụt vào nó. Lưu ý cách tiếng Anh của tôi phù hợp chính xác với mã!

Bản sao sâu của môi trường hệ thống

Phương pháp

def clearly_not_pass_by_value(my_list):
    my_list[0] = 42

l = [1, 2, 3]
clearly_not_pass_by_value(l)
print(l)
# [42, 2, 3]
17 được sử dụng một vài lần trong thư viện tiêu chuẩn và ở đây tôi muốn hiển thị một ví dụ sử dụng trong đó một từ điển được sao chép.

Mô -đun

def clearly_not_pass_by_value(my_list):
    my_list[0] = 42

l = [1, 2, 3]
clearly_not_pass_by_value(l)
print(l)
# [42, 2, 3]
40 cung cấp thuộc tính
def clearly_not_pass_by_value(my_list):
    my_list[0] = 42

l = [1, 2, 3]
clearly_not_pass_by_value(l)
print(l)
# [42, 2, 3]
41, tương tự như từ điển, chứa các biến môi trường được xác định.

Dưới đây là một vài ví dụ từ máy (Windows) của tôi:

program callByReference;
var
    x: integer;

procedure foo(var a: integer);
{ create a procedure called `foo` }
begin
    a := 6            { assign 6 to `a` }
end;

begin
    x := 2;           { assign 2 to `x` }
    writeln(x);       { print `x` }
    foo(x);           { call `foo` with `x` }
    writeln(x);       { print `x` }
end.
8

Mô -đun

def clearly_not_pass_by_value(my_list):
    my_list[0] = 42

l = [1, 2, 3]
clearly_not_pass_by_value(l)
print(l)
# [42, 2, 3]
42 cung cấp một số lớp cho các máy chủ HTTP cơ bản.

Một trong những lớp đó,

def clearly_not_pass_by_value(my_list):
    my_list[0] = 42

l = [1, 2, 3]
clearly_not_pass_by_value(l)
print(l)
# [42, 2, 3]
43, thực hiện một máy chủ HTTP cũng có thể chạy các tập lệnh CGI và, theo phương thức
def clearly_not_pass_by_value(my_list):
    my_list[0] = 42

l = [1, 2, 3]
clearly_not_pass_by_value(l)
print(l)
# [42, 2, 3]
44 của nó, nó cần thiết lập một loạt các biến môi trường.

Các biến môi trường này được đặt để đưa ra bối cảnh cần thiết cho tập lệnh CGI sẽ được chạy. Tuy nhiên, chúng tôi không muốn thực sự sửa đổi môi trường hiện tại!

Vì vậy, những gì chúng tôi làm là tạo ra một bản sao sâu của môi trường, và sau đó chúng tôi sửa đổi nó thành nội dung trái tim của chúng tôi! Sau khi chúng tôi hoàn thành, chúng tôi bảo Python thực thi tập lệnh CGI và chúng tôi cung cấp môi trường bị thay đổi như một đối số.

Cách chính xác mà điều này được thực hiện có thể không tầm thường để hiểu. Tôi, đối với một người, đừng nghĩ rằng tôi có thể giải thích nó cho bạn. Nhưng điều đó không có nghĩa là chúng ta không thể suy ra các phần của nó:

Đây là mã:

program callByReference;
var
    x: integer;

procedure foo(var a: integer);
{ create a procedure called `foo` }
begin
    a := 6            { assign 6 to `a` }
end;

begin
    x := 2;           { assign 2 to `x` }
    writeln(x);       { print `x` }
    foo(x);           { call `foo` with `x` }
    writeln(x);       { print `x` }
end.
9

Như chúng ta có thể thấy, chúng tôi đã sao chép môi trường và xác định một số biến. Cuối cùng, chúng tôi đã tạo ra một quy trình con mới có được môi trường sửa đổi.

Sự kết luận

Đây là điểm chính của Pydon không, đối với bạn, trên một đĩa bạc:

Cấm Python sử dụng mô hình chuyển giao từng lần và hiểu nó đòi hỏi bạn phải nhận ra tất cả các đối tượng được đặc trưng bởi một số nhận dạng, loại của chúng và nội dung của chúng.

Điều này không cho bạn thấy rằng:

  • Python không sử dụng mô hình chuyển qua từng giá trị, cũng như mô hình vượt qua;
  • Python sử dụng một mô hình chuyển đổi qua từng lần (sử dụng biệt danh trực tuyến);
  • Mỗi đối tượng được đặc trưng bởi
    • bản sắc của nó;
    • loại của nó; và
    • Nội dung của nó.
  • Hàm
    2
    6
    0 được sử dụng để truy vấn định danh của đối tượng;
  • Hàm
    >>> id(obj)
    2698212637504       # the identity of `obj`
    >>> type(obj)
    <class 'list'>      # the type of `obj`
    >>> obj
    [1, 2, 3]           # the contents of `obj`
    9 được sử dụng để truy vấn loại đối tượng;
  • Loại đối tượng xác định liệu nó có thể thay đổi hay bất biến;
  • Bản sao nông sao chép tham chiếu của các đối tượng có thể thay đổi lồng nhau;
  • Các bản sao sâu thực hiện các bản sao cho phép một đối tượng và các yếu tố bên trong của nó, được thay đổi mà không bao giờ ảnh hưởng đến bản sao;
  • def clearly_not_pass_by_value(my_list):
        my_list[0] = 42
    
    l = [1, 2, 3]
    clearly_not_pass_by_value(l)
    print(l)
    # [42, 2, 3]
    16 và
    def clearly_not_pass_by_value(my_list):
        my_list[0] = 42
    
    l = [1, 2, 3]
    clearly_not_pass_by_value(l)
    print(l)
    # [42, 2, 3]
    17 có thể được sử dụng để thực hiện các bản sao nông/sâu; và
  • Bạn có thể triển khai
    def clearly_not_pass_by_value(my_list):
        my_list[0] = 42
    
    l = [1, 2, 3]
    clearly_not_pass_by_value(l)
    print(l)
    # [42, 2, 3]
    19 và
    def clearly_not_pass_by_value(my_list):
        my_list[0] = 42
    
    l = [1, 2, 3]
    clearly_not_pass_by_value(l)
    print(l)
    # [42, 2, 3]
    20 nếu bạn muốn các đối tượng của riêng mình có thể giải quyết được.

Xem thêm

Nếu bạn thích nội dung video, bạn có thể kiểm tra video YouTube này, được lấy cảm hứng từ bài viết này.

Nếu bạn thích Pydon này, đừng chắc chắn để lại phản ứng bên dưới và chia sẻ điều này với bạn bè và đồng nghiệp của bạn. Ngoài ra, hãy đăng ký nhận bản tin để bạn không bỏ lỡ một Pydon nào!

Tôi hy vọng bạn đã học được điều gì đó mới! Nếu bạn đã làm, hãy xem xét theo bước chân của những độc giả đã mua cho tôi một lát bánh pizza. Đóng góp nhỏ của bạn giúp tôi sản xuất nội dung này miễn phí và không spam bạn với quảng cáo khó chịu.

Người giới thiệu

  • Python 3 Docs, FAQ lập trình, Cách tôi viết một hàm với các tham số đầu ra (gọi theo tham chiếu)? -a-function-with-output-parameters-call-by-preference [truy cập lần cuối 04-10-2021];
  • Python 3 Docs, Thư viện tiêu chuẩn Python,
    2
    6
    1, https://docs.python.org/3/l Library/copy.html [truy cập lần cuối 05-10-2021];
  • effbot.org, cuộc gọi của đối tượng (thông qua arquivo.pt,), https://arquivo.pt/wayback/20160516131553/http://effbot.org/zone/call-by-byject.htm 04-10-2021];
  • effbot.org, đối tượng của Python (thông qua arquivo.pt,), https://arquivo.pt/wayback/20191115002033/http://effbot.org/zone/python-objects.htm -2021];
  • Robert Heaton, Hồi là Python Pass-By-Reference hoặc Pass-by-Value, https://robertheaton.com/2014/02/09/pythons-pass-by-bject-bectre- as-as-as-as-by-philip -K-dick/ [truy cập lần cuối 04-10-2021];
  • Câu hỏi và câu trả lời của StackoverFlow, Làm cách nào để tôi vượt qua một biến bằng cách tham khảo ?,, https://stackoverflow.com/q/986006/2828287 [truy cập lần cuối 04-10-2021];
  • Câu hỏi và câu trả lời của stackoverflow, các giá trị vượt qua trong python [trùng lặp], https://stackoverflow.com/q/534375/2828287 [truy cập lần cuối 04-10-2021];
  • Chủ đề Twitter của @mathsppblog, https://twitter.com/mathsppblog/status/1445148566721335298 [truy cập lần cuối 20-10-2021];