Tại sao Python là kiểu động?

Nếu bạn có nền tảng về các ngôn ngữ như Java, C hoặc C++ được biên dịch hoặc nhập tĩnh, bạn có thể thấy cách thức hoạt động của Python hơi khó hiểu. Chẳng hạn, khi chúng ta gán một giá trị cho một biến [giả sử

a = 1
a = 'Hello World'
a = False
4 ], làm thế quái nào mà Python biết biến đó
a = 1
a = 'Hello World'
a = False
5 là một số nguyên?

Mô hình Dynamic Typing

Trong các ngôn ngữ được nhập tĩnh, các loại biến được xác định tại thời điểm biên dịch. Trong hầu hết các ngôn ngữ hỗ trợ kiểu gõ tĩnh này, người lập trình phải chỉ định kiểu của từng biến. Ví dụ, nếu bạn muốn định nghĩa một biến số nguyên trong Java, bạn sẽ phải chỉ định rõ ràng nó trong định nghĩa

# Java Example
int a = 1;
int b;
b = 0;

Mặt khác, các kiểu trong Python được xác định trong thời gian chạy thay vì thời gian biên dịch và do đó các lập trình viên không bắt buộc phải khai báo các biến trước khi sử dụng chúng trong mã

Về mặt kỹ thuật, một biến [còn được gọi là tên trong Python] được tạo khi nó được gán giá trị lần đầu tiên trong mã. Điều này có nghĩa là một biến trước tiên phải được gán một giá trị [i. e. một tham chiếu đến một đối tượng trong bộ nhớ] trước khi nó được tham chiếu trong mã nếu không sẽ báo lỗi. Và như đã đề cập trước đó, việc gán biến không bao giờ đi kèm với định nghĩa kiểu vì kiểu được lưu trữ với các đối tượng chứ không phải với biến/tên. Mỗi khi một biến được xác định, nó sẽ tự động được thay thế bằng đối tượng trong bộ nhớ mà nó tham chiếu

Mối quan hệ giữa các đối tượng, biến và tham chiếu

Tóm lại, mỗi khi chúng ta gán biến, Python sẽ thực hiện ba bước sau

  1. Tạo một đối tượng trong bộ nhớ chứa giá trị
  2. Nếu tên biến chưa tồn tại trong không gian tên, hãy tiếp tục và tạo nó
  3. Gán tham chiếu đến đối tượng [trong bộ nhớ] cho biến

Biến, là một tên tượng trưng trong một bảng hệ thống chứa các liên kết [i. e. tham chiếu] đến các đối tượng. Nói cách khác, tham chiếu là con trỏ từ biến đến đối tượng. Tuy nhiên, trong Python, các biến không có kiểu. Do đó, có thể gán các đối tượng khác loại cho cùng một tên biến, như hình bên dưới

a = 1
a = 'Hello World'
a = False

Ở dòng đầu tiên, biến

a = 1
a = 'Hello World'
a = False
5 được gán với tham chiếu đến đối tượng số nguyên có giá trị
a = 1
a = 'Hello World'
a = False
7. Tương tự như vậy, dòng thứ hai thay đổi tham chiếu của biến
a = 1
a = 'Hello World'
a = False
5 thành một đối tượng khác của kiểu chuỗi trong khi dòng cuối cùng thay đổi tham chiếu để giờ đây
a = 1
a = 'Hello World'
a = False
5 trỏ đến một đối tượng boolean

Khi chúng ta đề cập đến các đối tượng, chúng ta thực sự muốn nói đến một phần bộ nhớ được cấp phát có khả năng biểu thị giá trị mà chúng ta muốn. Giá trị này có thể là một số nguyên, một chuỗi hoặc bất kỳ loại nào khác. Ngoài giá trị, các đối tượng cũng đi kèm với một vài trường tiêu đề. Các trường này bao gồm loại đối tượng cũng như bộ đếm tham chiếu của nó được Trình thu gom rác sử dụng để xác định xem có thể lấy lại bộ nhớ của các đối tượng không sử dụng hay không. Và vì các đối tượng Python có khả năng biết loại của chính chúng, nên các biến không cần phải nhớ đoạn thông tin này

Tài liệu tham khảo được chia sẻ

Trong Python, nhiều biến có thể tham chiếu cùng một đối tượng. Hành vi này được gọi là tham chiếu được chia sẻ. Ví dụ, hãy xem xét đoạn mã dưới đây

________số 8

Ban đầu, Python tạo một đối tượng số nguyên có giá trị

a = 1
a = 'Hello World'
a = False
7 và sau đó nó tạo biến có tên
a = 1
a = 'Hello World'
a = False
5 và cuối cùng tham chiếu trỏ đến đối tượng số nguyên trong bộ nhớ được gán cho biến

Trong dòng thứ hai về cơ bản là nơi các tài liệu tham khảo được chia sẻ phát huy tác dụng. Bây giờ Python sẽ tạo biến

a = 1
b = a
2 và bây giờ sẽ giữ một tham chiếu đến đối tượng
a = 1
a = 'Hello World'
a = False
7 giống với tham chiếu được gán cho biến
a = 1
a = 'Hello World'
a = False
5. Lưu ý rằng các biến
a = 1
a = 'Hello World'
a = False
5 và
a = 1
b = a
2 hoàn toàn độc lập với nhau và chúng không được liên kết theo bất kỳ cách nào. Họ chỉ chia sẻ cùng một tham chiếu trỏ đến cùng một đối tượng số nguyên trong bộ nhớ vật lý

Bây giờ hãy xem xét một ví dụ khác trong đó chúng ta có một thao tác bổ sung

a = 1
a = 'Hello World'
a = False
6

Trong ví dụ này, một đối tượng chuỗi bổ sung có giá trị

a = 1
b = a
7 được tạo trong bộ nhớ và tham chiếu của nó được gán cho biến
a = 1
a = 'Hello World'
a = False
5. Và điều quan trọng cần nhấn mạnh là trong trường hợp này, giá trị của biến
a = 1
b = a
2 không thay đổi. Hành vi tương tự được quan sát ngay cả với cùng một loại đối tượng

# Java Example
int a = 1;
int b;
b = 0;
0

Một lần nữa, câu lệnh cuối cùng sẽ kích hoạt việc tạo một đối tượng số nguyên mới có giá trị

a = 1
a = 'Hello World'
a = False
60 và cuối cùng gán tham chiếu của nó cho biến
a = 1
a = 'Hello World'
a = False
5 trong khi biến
a = 1
b = a
2 không thay đổi [i. e. nó tiếp tục tham chiếu đối tượng có giá trị
a = 1
a = 'Hello World'
a = False
7 ]

Tham chiếu được chia sẻ và các đối tượng có thể thay đổi so với bất biến

Trong Python, tất cả các biến đều là tham chiếu [i. e. con trỏ] đến một vị trí bộ nhớ cụ thể chứa đối tượng. Tuy nhiên, cách các đối tượng được tạo và sửa đổi phụ thuộc vào việc loại của chúng có thể thay đổi hay không thay đổi

Như chúng ta đã thấy trong ví dụ trước, phép gán cuối cùng

a = 1
a = 'Hello World'
a = False
64 sẽ không tự sửa đổi đối tượng vì kiểu đối tượng số nguyên là bất biến. Điều này có nghĩa là mỗi khi chúng ta muốn thay đổi giá trị của một loại đối tượng bất biến [chẳng hạn như số nguyên hoặc chuỗi], Python sẽ tạo một đối tượng mới chứa giá trị được yêu cầu. Đối với các loại không thay đổi, điều này rất đơn giản và làm cho các biến thay đổi khá an toàn vì nó không ảnh hưởng đến giá trị của các đối tượng hiện có vì các thay đổi tại chỗ không áp dụng được cho các loại đối tượng không thay đổi

Tuy nhiên, đây không phải là trường hợp đối với các loại có thể thay đổi và lập trình viên phải biết cách Python xử lý các loại đối tượng có thể thay đổi để tránh mọi hành vi và lỗi không mong muốn trong mã

Các loại đối tượng có thể thay đổi cho phép thay đổi tại chỗ, nghĩa là khi giá trị của chúng được sửa đổi, sẽ có tác động đến tất cả các biến tham chiếu đến đối tượng đó. Các loại đối tượng như vậy bao gồm danh sách, từ điển và bộ. Để minh họa khái niệm này, hãy xem xét ví dụ sau trong đó chúng ta có hai danh sách giữ tham chiếu đến cùng một đối tượng danh sách

# Java Example
int a = 1;
int b;
b = 0;
6

Bây giờ, hãy bắt đầu với trường hợp sử dụng dễ dàng khi chúng tôi muốn gán cho danh sách thứ hai một danh sách mới được tạo

# Java Example
int a = 1;
int b;
b = 0;
7

Bây giờ, hai danh sách chứa các tham chiếu khác nhau, mỗi danh sách trỏ đến hai đối tượng riêng biệt. Do đó, nếu chúng ta thực hiện thay đổi tại chỗ đối với một trong hai danh sách, thay đổi này sẽ không ảnh hưởng đến danh sách kia vì hai đối tượng khác nhau và chúng được lưu trữ ở các vị trí khác nhau trong bộ nhớ

Mọi thứ trở nên phức tạp hơn một chút khi chúng ta cố gắng sửa đổi một đối tượng danh sách được tham chiếu bởi các biến khác nhau. Để minh họa điều này, hãy xem xét thêm một ví dụ hiển thị bên dưới

# Java Example
int a = 1;
int b;
b = 0;
8

Bây giờ nếu chúng tôi in cả hai danh sách, chúng tôi sẽ nhận thấy rằng mặc dù chúng tôi chỉ thay đổi

a = 1
a = 'Hello World'
a = False
65 trên thực tế, điều này cũng đã ảnh hưởng đến nội dung của
a = 1
a = 'Hello World'
a = False
66

a = 1
a = 'Hello World'
a = False
1

Trong ví dụ này, chúng tôi đã không thay đổi danh sách

a = 1
a = 'Hello World'
a = False
66, tuy nhiên, thay đổi trong
a = 1
a = 'Hello World'
a = False
65 đã gây ra thay đổi trong
a = 1
a = 'Hello World'
a = False
66. Như chúng tôi đã giải thích, điều này xảy ra do cả hai
a = 1
a = 'Hello World'
a = False
66 đều tham chiếu đến cùng một đối tượng như
a = 1
a = 'Hello World'
a = False
65. Do đó, một thay đổi tại chỗ đối với một đối tượng tác động đến tất cả các biến tham chiếu đến nó. Và một lần nữa, điều quan trọng cần nhấn mạnh là hành vi này chỉ được quan sát đối với các loại đối tượng có thể thay đổi và bạn phải biết cách các thay đổi tại chỗ hoạt động trong Python để bạn không vô tình đưa lỗi vào mã của mình

Trong hầu hết các trường hợp, hành vi này là điều mà các lập trình viên thực sự muốn thấy trong mã của họ. Tuy nhiên, cũng có nhiều trường hợp sử dụng không mong muốn điều này và các giải pháp thay thế cần được xem xét để thay đổi trong một biến không ảnh hưởng đến các biến khác tham chiếu cùng một đối tượng. Nếu đúng như vậy thì sao chép các đối tượng đó là con đường phía trước

Sao chép các đối tượng trong Python

Python đi kèm với một gói tích hợp có tên là

# Java Example
int a = 1;
int b;
b = 0;
02 cung cấp chức năng sao chép các đối tượng. Hai loại bản sao là nông và sâu và sự khác biệt của chúng liên quan đến việc bạn có phải xử lý các đối tượng phức hợp hay không, đó là các đối tượng chứa các đối tượng khác — ví dụ: danh sách từ điển hoặc danh sách danh sách

A xây dựng một đối tượng phức hợp mới và sau đó [trong phạm vi có thể] chèn vào đó các tham chiếu tới các đối tượng được tìm thấy trong bản gốc

Chẳng hạn, hãy xem xét một ví dụ mà chúng ta cần lấy một bản sao của danh sách và sau đó sửa đổi một trong hai biến. Trong trường hợp này, mỗi biến giờ đây sẽ trỏ đến một đối tượng khác và do đó, thay đổi tại chỗ ở một trong hai đối tượng sẽ không ảnh hưởng đến đối tượng kia

a = 1
a = 'Hello World'
a = False
8

Tuy nhiên, các bản sao nông sẽ không thực hiện được thủ thuật khi bạn có một đối tượng phức hợp với các loại có thể thay đổi được lồng vào nhau - ví dụ: danh sách các danh sách. Trong ví dụ bên dưới, chúng ta có thể thấy rằng nếu chúng ta lấy một bản sao nông của một danh sách các danh sách, một thay đổi của danh sách gốc

a = 1
a = 'Hello World'
a = False
5 hoặc đối tượng ghép ban đầu
# Java Example
int a = 1;
int b;
b = 0;
04, thì kết quả sẽ có hiệu lực đối với danh sách đã sao chép
# Java Example
int a = 1;
int b;
b = 0;
05

a = 1
a = 'Hello World'
a = False
0

Điều này là do bản sao nông không tạo đối tượng mới cho các phiên bản lồng nhau mà thay vào đó, nó sao chép tham chiếu của chúng sang đối tượng ban đầu. Trong hầu hết các trường hợp, chúng ta thường cần tạo một đối tượng mới ngay cả đối với các trường hợp lồng nhau để đối tượng ghép được sao chép hoàn toàn độc lập với đối tượng cũ. Trong Python, đây được gọi là bản sao sâu

A xây dựng một đối tượng phức hợp mới và sau đó, theo cách đệ quy, chèn các bản sao của các đối tượng được tìm thấy trong bản gốc vào đó.

Bây giờ nếu đối tượng

# Java Example
int a = 1;
int b;
b = 0;
05 là bản sao nông của
# Java Example
int a = 1;
int b;
b = 0;
04 , thay đổi đối với
a = 1
a = 'Hello World'
a = False
5 ,
a = 1
b = a
2 hoặc
# Java Example
int a = 1;
int b;
b = 0;
04 sẽ không ảnh hưởng đến danh sách đã sao chép

a = 1
a = 'Hello World'
a = False
1

Đối tượng bình đẳng giải thích

Do cách thức hoạt động của mô hình tham chiếu Python, nó cũng có hai cách khả thi để thực hiện kiểm tra sự bằng nhau giữa các đối tượng. Nhưng nó luôn phụ thuộc vào chính xác những gì bạn muốn kiểm tra. Để kiểm tra xem hai đối tượng có cùng giá trị hay không, bạn có thể sử dụng toán tử

# Java Example
int a = 1;
int b;
b = 0;
61. Mặt khác, nếu bạn cần kiểm tra xem hai biến có trỏ đến cùng một đối tượng hay không, bạn cần sử dụng toán tử
# Java Example
int a = 1;
int b;
b = 0;
62

a = 1
a = 'Hello World'
a = False
2

Nhược điểm lớn của ngôn ngữ gõ động

Mặc dù mô hình gõ động mang lại sự linh hoạt nhưng nó cũng có thể gây rắc rối khi tạo ứng dụng Python. Ưu điểm chính của các ngôn ngữ được nhập tĩnh như Java, là việc kiểm tra được thực hiện tại thời điểm biên dịch và do đó có thể xác định sớm các lỗi.

Mặt khác, các ngôn ngữ được nhập động như Python giúp tăng năng suất của các nhà phát triển, tuy nhiên người dùng cần hết sức cẩn thận do thực tế là các lỗi sẽ chỉ được báo cáo trong thời gian chạy. Ví dụ: hãy xem xét tình huống trong đó cùng một biến trỏ đến các loại đối tượng khác nhau trong toàn bộ mã [và lưu ý rằng điều này thậm chí không được phép trong hầu hết các ngôn ngữ được nhập tĩnh]. Bạn phải đảm bảo rằng chỉ các thao tác có liên quan mới được thực hiện trên biến/đối tượng đó tại một số điểm nhất định trong mã để bạn tránh sử dụng các hàm hoặc phương thức không hợp lệ không áp dụng cho loại đối tượng cụ thể đó

a = 1
a = 'Hello World'
a = False
3

Phần kết luận

Trong bài viết này, chúng ta đã khám phá một trong những đặc điểm cơ bản của Python, đó là mô hình gõ động của nó. Chúng ta đã thảo luận về mối quan hệ giữa các biến, đối tượng và tham chiếu cũng như chi tiết về mô hình tham chiếu của Python. Chúng ta đã thấy cách hoạt động của các tham chiếu được chia sẻ và cách tránh các hành vi không mong muốn khi xử lý các loại đối tượng có thể thay đổi

Mô hình gõ động là một đặc điểm mạnh mẽ đối với các ngôn ngữ lập trình, tuy nhiên, các lập trình viên cần hết sức cẩn thận khi viết các ứng dụng bằng các ngôn ngữ gõ động như Python vì các lỗi rất dễ mắc phải do nhầm lẫn.

Tại sao Python là ngôn ngữ được gõ động?

Python không có vấn đề gì ngay cả khi chúng ta không khai báo kiểu biến. Nó cho biết loại biến trong thời gian chạy của chương trình. Python cũng đảm nhiệm việc quản lý bộ nhớ, điều rất quan trọng trong lập trình . Vì vậy, Python là một ngôn ngữ được gõ động.

Tại sao Python động và gõ mạnh?

Python vừa là ngôn ngữ được gõ mạnh vừa là ngôn ngữ được gõ động. Nhập mạnh có nghĩa là các biến có một loại và loại đó quan trọng khi thực hiện các thao tác trên một biến . Nhập động có nghĩa là loại biến chỉ được xác định trong thời gian chạy.

Tại sao ngôn ngữ được gõ động?

Một ngôn ngữ được nhập động nếu loại được liên kết với các giá trị thời gian chạy và không được đặt tên biến/trường/v.v. . Điều này có nghĩa là bạn với tư cách là một lập trình viên có thể viết nhanh hơn một chút vì bạn không phải chỉ định các loại mỗi lần [trừ khi sử dụng ngôn ngữ được nhập tĩnh với suy luận kiểu].

Chủ Đề