Một ngôn ngữ được coi là ngôn ngữ được nhập động nếu loại biến của ngôn ngữ đó được kiểm tra trong thời gian chạy của quá trình biên dịch mã hoặc diễn giải mã. Trong các loại ngôn ngữ lập trình như vậy, chúng ta không cần khởi tạo một biến với kiểu của nó. Chúng ta có thể khai báo một biến bằng cách viết tên ở bên trái và giá trị ở bên trái của tên biến, Ex Var = 90. Một số ngôn ngữ gõ động là
Vì việc cấp phát bộ nhớ và kiểm tra biến được thực hiện trong thời gian chạy mã, nên các loại ngôn ngữ này không được coi là kém tối ưu hơn so với ngôn ngữ được nhập tĩnh
Ngôn ngữ lập trình có thể được phân loại thành hai loại Dynamic typing và static typing. Python là một ngôn ngữ được gõ động. Điều này có nghĩa là trình thông dịch Python chỉ kiểm tra kiểu khi mã chạy. Tuy nhiên, trong ngôn ngữ kiểu tĩnh, việc kiểm tra các biến/đối tượng
được thực hiện tại thời điểm biên dịch. Vì vậy, việc xử lý hoặc sửa lỗi trở nên dễ dàng nếu bạn có thể tìm thấy chúng trước khi chạy chương trình
Khi bạn viết chương trình bằng ngôn ngữ gõ động, không bắt buộc phải xác định loại biến. Nó có nguồn gốc dựa trên các giá trị bạn gán cho chúng. Nhưng việc duy trì mã ngày càng khó hơn vì nó tạo ra sự nhầm lẫn và mất nhiều thời gian hơn để hiểu mã
Một số ví dụ về ngôn ngữ gõ động là
- con trăn
- PHP
- JavaScript
Một số ví dụ về ngôn ngữ gõ tĩnh là
- Java
- C
- C++
Ví dụ
Hãy tạo một biến trong python và xem nó có thể thay đổi kiểu như thế nào
con trăn
Java. Bạn có thể thấy trong trường hợp java bạn không thể gán một loại giá trị khác với loại biến
Python sẽ luôn. Tuy nhiên, PEP 484 đã giới thiệu các gợi ý về kiểu, cho phép thực hiện kiểm tra kiểu tĩnh của mã Python
Tuy nhiên, không giống như ngôn ngữ kiểu tĩnh, chỉ xác định gợi ý kiểu trong mã python không thực thi kiểu. Chúng tôi có một số công cụ để thực hiện các kiểm tra này như mypy
Trong ví dụ sau, chúng ta có hai hàm, một hàm không có gợi ý kiểu và một hàm có gợi ý kiểu. Khi chúng tôi chạy chương trình, nó chỉ hoạt động và không phàn nàn vì chúng tôi đang chuyển các giá trị chuỗi thay vì các kiểu int được xác định cho hàm thứ hai
Nhưng hãy nghĩ xem nếu hàm hello_world_with_type_hint thực hiện phép chia cho tham số được truyền, Trong trường hợp đó, nó sẽ thông qua ngoại lệ
Sẽ thật tuyệt nếu chúng ta có một số kiểu kiểm tra trước khi thực thi mã để chúng ta có thể sửa nó phải không?
Vì vậy, ở đây đến mypy. Nó sẽ giúp chúng tôi thực hiện kiểm tra loại nếu chúng tôi có gợi ý loại được xác định trong mã
Bạn có thể cài đặt mypy bằng pip
Bây giờ hãy thực hiện kiểm tra kiểu với mypy
Bạn có thể thấy rằng nó hiển thị lỗi cho kiểu trả về và kiểu đối số
Hãy thay đổi kiểu trả về thành float theo lời khuyên của mypy
Bây giờ hãy gọi hàm với kiểu đối số hợp lệ [ int]. Bạn có thể thấy thông báo thành công ngay bây giờ
Bạn đã thấy cách mypy giúp chúng tôi tìm ra vấn đề trước khi thực sự chạy chương trình. Nó cũng đề xuất các tùy chọn chính xác
Nhưng chúng tôi không cần chạy mypy cho từng tệp mã của mình. Tuy nhiên chúng ta có thể cấu hình mypy với pre-commit để nó thực thi bất cứ khi nào bạn muốn commit code
Phần kết luận
Chúng tôi đã thấy một ví dụ về một trong những lợi thế của việc thêm các loại vào mã của bạn. gõ gợi ý giúp bắt lỗi nhất định. Nó cũng giúp ghi lại mã của bạn
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
- Tạo một đối tượng trong bộ nhớ chứa giá trị
- 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ó
- 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 booleanKhi 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 đối tượng đượ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ố 8Ban đầ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ếnTrong 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
6Trong 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;
0Mộ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 các 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 đổiTuy 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;
6Bâ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;
7Bâ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ỗ cho 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;
8Bâ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
66a = 1
a = 'Hello World'
a = False
1Trong 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ìnhTrong 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áchA 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
8Tuy 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;
05a = 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épa = 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;
62a = 1
a = 'Hello World'
a = False
2Nhượ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
3Phầ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ẽ cho các ngôn ngữ lập trình, tuy nhiên, các lập trình viên cần phải 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.