Python có vô dụng không

Cú pháp đơn giản, dễ học của Python có thể đánh lừa các nhà phát triển Python - đặc biệt là những người mới làm quen với ngôn ngữ này - khiến họ bỏ sót một số điểm tinh tế và đánh giá thấp sức mạnh của ngôn ngữ này. Trong bài viết này, Martin Chikilian của Toptal trình bày danh sách "10 lỗi hàng đầu" về những lỗi hơi tế nhị, khó phát hiện hơn có thể khiến ngay cả nhà phát triển Python cao cấp nhất cũng mắc phải

Chia sẻ

Chia sẻ

Cú pháp đơn giản, dễ học của Python có thể đánh lừa các nhà phát triển Python - đặc biệt là những người mới làm quen với ngôn ngữ này - khiến họ bỏ sót một số điểm tinh tế và đánh giá thấp sức mạnh của ngôn ngữ này. Trong bài viết này, Martin Chikilian của Toptal trình bày danh sách "10 lỗi hàng đầu" về những lỗi hơi tế nhị, khó phát hiện hơn có thể khiến ngay cả nhà phát triển Python cao cấp nhất cũng mắc phải

Bởi Martin Chikilian

Martin là một kỹ sư full-stack và đã làm việc với tư cách là nhà phát triển Python chuyên nghiệp từ năm 2007

Đọc phiên bản tiếng Tây Ban Nha của bài viết này được dịch bởi Marisela Ordaz

Giới thiệu về Python

Python là ngôn ngữ lập trình cấp cao, hướng đối tượng, thông dịch với ngữ nghĩa động. Các cấu trúc dữ liệu tích hợp ở mức độ cao, kết hợp với kiểu gõ động và liên kết động, khiến nó trở nên rất hấp dẫn đối với Phát triển ứng dụng nhanh, cũng như để sử dụng làm ngôn ngữ kịch bản hoặc ngôn ngữ keo để kết nối các thành phần hoặc dịch vụ hiện có. Python hỗ trợ các mô-đun và gói, do đó khuyến khích mô-đun hóa chương trình và tái sử dụng mã

Về bài viết này

Cú pháp đơn giản, dễ học của Python có thể đánh lừa các nhà phát triển Python – đặc biệt là những người mới làm quen với ngôn ngữ này – khiến họ bỏ lỡ một số nét tinh tế của nó và đánh giá thấp sức mạnh của ngôn ngữ Python đa dạng

Với ý nghĩ đó, bài viết này trình bày danh sách “10 lỗi hàng đầu” về những lỗi hơi tế nhị, khó phát hiện hơn có thể ảnh hưởng đến cả một số nhà phát triển Python cao cấp hơn ở phía sau.

[Ghi chú. Bài viết này dành cho đối tượng cao cấp hơn so với Những lỗi thường gặp của lập trình viên Python, hướng nhiều hơn đến những người mới làm quen với ngôn ngữ này. ]

Sai lầm thường gặp #1. Sử dụng sai biểu thức làm giá trị mặc định cho đối số hàm

Python cho phép bạn chỉ định rằng đối số hàm là tùy chọn bằng cách cung cấp giá trị mặc định cho nó. Mặc dù đây là một tính năng tuyệt vời của ngôn ngữ, nhưng nó có thể dẫn đến một số nhầm lẫn khi giá trị mặc định có thể thay đổi được. Ví dụ, hãy xem xét định nghĩa hàm Python này

>>> def foo[bar=[]]:        # bar is optional and defaults to [] if not specified
..    bar.append["baz"]    # but this line could be problematic, as we'll see...
..    return bar

Một sai lầm phổ biến là nghĩ rằng đối số tùy chọn sẽ được đặt thành biểu thức mặc định đã chỉ định mỗi khi hàm được gọi mà không cung cấp giá trị cho đối số tùy chọn. Ví dụ, trong đoạn mã trên, người ta có thể mong đợi rằng việc gọi

>>> class A[object]:
..     x = 1
...
>>> class B[A]:
..     pass
...
>>> class C[A]:
..     pass
...
>>> print A.x, B.x, C.x
1 1 1
0 lặp đi lặp lại [i. e. , mà không chỉ định đối số
>>> class A[object]:
..     x = 1
...
>>> class B[A]:
..     pass
...
>>> class C[A]:
..     pass
...
>>> print A.x, B.x, C.x
1 1 1
1] sẽ luôn trả về
>>> class A[object]:
..     x = 1
...
>>> class B[A]:
..     pass
...
>>> class C[A]:
..     pass
...
>>> print A.x, B.x, C.x
1 1 1
2, vì giả định là mỗi khi ____1_______0 được gọi [không chỉ định đối số ___1_______1] ____1_______1 được đặt thành ____1_______6 [i. e. , một danh sách trống mới]

Nhưng hãy xem điều gì thực sự xảy ra khi bạn làm điều này

________số 8_______

Huh?

Câu trả lời lập trình Python nâng cao hơn là giá trị mặc định cho đối số hàm chỉ được đánh giá một lần, tại thời điểm hàm được xác định. Do đó, đối số

>>> class A[object]:
..     x = 1
...
>>> class B[A]:
..     pass
...
>>> class C[A]:
..     pass
...
>>> print A.x, B.x, C.x
1 1 1
1 được khởi tạo theo mặc định của nó [i. e. , một danh sách trống] chỉ khi
>>> class A[object]:
..     x = 1
...
>>> class B[A]:
..     pass
...
>>> class C[A]:
..     pass
...
>>> print A.x, B.x, C.x
1 1 1
0 được xác định lần đầu tiên, nhưng sau đó gọi tới
>>> class A[object]:
..     x = 1
...
>>> class B[A]:
..     pass
...
>>> class C[A]:
..     pass
...
>>> print A.x, B.x, C.x
1 1 1
0 [i. e. , không có đối số
>>> class A[object]:
..     x = 1
...
>>> class B[A]:
..     pass
...
>>> class C[A]:
..     pass
...
>>> print A.x, B.x, C.x
1 1 1
1 được chỉ định] sẽ tiếp tục sử dụng cùng một danh sách mà
>>> class A[object]:
..     x = 1
...
>>> class B[A]:
..     pass
...
>>> class C[A]:
..     pass
...
>>> print A.x, B.x, C.x
1 1 1
1 được khởi tạo ban đầu

FYI, một cách giải quyết phổ biến cho việc này như sau

>>> def foo[bar=None]:
..    if bar is None:		# or if not bar:
..        bar = []
..    bar.append["baz"]
..    return bar
...
>>> foo[]
["baz"]
>>> foo[]
["baz"]
>>> foo[]
["baz"]

Sai lầm thường gặp #2. Sử dụng biến lớp không chính xác

Xem xét ví dụ sau

>>> class A[object]:
..     x = 1
...
>>> class B[A]:
..     pass
...
>>> class C[A]:
..     pass
...
>>> print A.x, B.x, C.x
1 1 1

Có ý nghĩa

>>> B.x = 2
>>> print A.x, B.x, C.x
1 2 1

Yup, một lần nữa như mong đợi

>>> A.x = 3
>>> print A.x, B.x, C.x
3 2 3

Cái gì $%#. &?? . Tại sao

>>> B.x = 2
>>> print A.x, B.x, C.x
1 2 1
5 cũng thay đổi?

Trong Python, các biến lớp được xử lý nội bộ dưới dạng từ điển và tuân theo thứ thường được gọi là Thứ tự giải quyết phương thức [MRO]. Vì vậy, trong đoạn mã trên, vì thuộc tính

>>> B.x = 2
>>> print A.x, B.x, C.x
1 2 1
6 không được tìm thấy trong lớp
>>> B.x = 2
>>> print A.x, B.x, C.x
1 2 1
7, nên nó sẽ được tra cứu trong các lớp cơ sở của nó [chỉ có
>>> B.x = 2
>>> print A.x, B.x, C.x
1 2 1
8 trong ví dụ trên, mặc dù Python hỗ trợ nhiều kế thừa]. Nói cách khác,
>>> B.x = 2
>>> print A.x, B.x, C.x
1 2 1
7 không có tài sản
>>> B.x = 2
>>> print A.x, B.x, C.x
1 2 1
6 của riêng mình, độc lập với
>>> B.x = 2
>>> print A.x, B.x, C.x
1 2 1
8. Do đó, các tham chiếu đến
>>> B.x = 2
>>> print A.x, B.x, C.x
1 2 1
5 trên thực tế là các tham chiếu đến
>>> B.x = 2
>>> print A.x, B.x, C.x
1 2 1
4. Điều này gây ra sự cố Python trừ khi nó được xử lý đúng cách. Tìm hiểu thêm về thuộc tính lớp trong Python

Sai lầm thường gặp #3. Chỉ định tham số không chính xác cho một khối ngoại lệ

Giả sử bạn có đoạn mã sau

>>> try:
..     l = ["a", "b"]
..     int[l[2]]
.. except ValueError, IndexError:  # To catch both exceptions, right?
..     pass
...
Traceback [most recent call last]:
  File "", line 3, in 
IndexError: list index out of range

Vấn đề ở đây là câu lệnh

>>> A.x = 3
>>> print A.x, B.x, C.x
3 2 3
4 không có danh sách các trường hợp ngoại lệ được chỉ định theo cách này. Thay vào đó, trong Python 2. x, cú pháp
>>> A.x = 3
>>> print A.x, B.x, C.x
3 2 3
5 được sử dụng để liên kết ngoại lệ với tham số tùy chọn thứ hai được chỉ định [trong trường hợp này là
>>> A.x = 3
>>> print A.x, B.x, C.x
3 2 3
6], để sẵn sàng kiểm tra thêm. Kết quả là, trong đoạn mã trên, ngoại lệ
>>> A.x = 3
>>> print A.x, B.x, C.x
3 2 3
7 không bị bắt bởi câu lệnh
>>> A.x = 3
>>> print A.x, B.x, C.x
3 2 3
4;

Cách thích hợp để bắt nhiều ngoại lệ trong câu lệnh

>>> A.x = 3
>>> print A.x, B.x, C.x
3 2 3
4 là chỉ định tham số đầu tiên là a chứa tất cả các ngoại lệ sẽ bị bắt. Ngoài ra, để có tính di động tối đa, hãy sử dụng từ khóa
>>> try:
..     l = ["a", "b"]
..     int[l[2]]
.. except ValueError, IndexError:  # To catch both exceptions, right?
..     pass
...
Traceback [most recent call last]:
  File "", line 3, in 
IndexError: list index out of range
1, vì cú pháp đó được hỗ trợ bởi cả Python 2 và Python 3

>>> try:
..     l = ["a", "b"]
..     int[l[2]]
.. except [ValueError, IndexError] as e:  
..     pass
...
>>>

Sai lầm thường gặp #4. Hiểu sai quy tắc phạm vi Python

Độ phân giải phạm vi Python dựa trên cái được gọi là quy tắc LEGB, viết tắt của Cục bộ, Kèm theo, Toàn cầu, Tích hợp. Có vẻ đủ đơn giản, phải không? . Hãy xem xét những điều sau đây

>>> x = 10
>>> def foo[]:
..     x += 1
..     print x
...
>>> foo[]
Traceback [most recent call last]:
  File "", line 1, in 
  File "", line 2, in foo
UnboundLocalError: local variable 'x' referenced before assignment

Vấn đề là gì?

Lỗi trên xảy ra bởi vì, khi bạn gán một biến trong một phạm vi, biến đó được Python tự động coi là cục bộ của phạm vi đó và che khuất mọi biến có tên tương tự trong bất kỳ phạm vi bên ngoài nào

Do đó, nhiều người ngạc nhiên khi nhận được một mã

>>> try:
..     l = ["a", "b"]
..     int[l[2]]
.. except ValueError, IndexError:  # To catch both exceptions, right?
..     pass
...
Traceback [most recent call last]:
  File "", line 3, in 
IndexError: list index out of range
2 trong mã đang hoạt động trước đó khi nó được sửa đổi bằng cách thêm một câu lệnh gán vào đâu đó trong phần thân của hàm. [Bạn có thể đọc thêm về điều này. ]

Điều này đặc biệt phổ biến đối với các nhà phát triển khi sử dụng danh sách. Xem xét ví dụ sau

>>> lst = [1, 2, 3]
>>> def foo1[]:
..     lst.append[5]   # This works ok...
...
>>> foo1[]
>>> lst
[1, 2, 3, 5]

>>> lst = [1, 2, 3]
>>> def foo2[]:
..     lst += [5]      # .. but this bombs!
...
>>> foo2[]
Traceback [most recent call last]:
  File "", line 1, in 
  File "", line 2, in foo
UnboundLocalError: local variable 'lst' referenced before assignment

Huh?

Câu trả lời giống như trong bài toán ví dụ trước nhưng được thừa nhận là tinh tế hơn.

>>> try:
..     l = ["a", "b"]
..     int[l[2]]
.. except ValueError, IndexError:  # To catch both exceptions, right?
..     pass
...
Traceback [most recent call last]:
  File "", line 3, in 
IndexError: list index out of range
4 không giao nhiệm vụ cho
>>> try:
..     l = ["a", "b"]
..     int[l[2]]
.. except ValueError, IndexError:  # To catch both exceptions, right?
..     pass
...
Traceback [most recent call last]:
  File "", line 3, in 
IndexError: list index out of range
6, trong khi
>>> try:
..     l = ["a", "b"]
..     int[l[2]]
.. except ValueError, IndexError:  # To catch both exceptions, right?
..     pass
...
Traceback [most recent call last]:
  File "", line 3, in 
IndexError: list index out of range
3 thì. Hãy nhớ rằng
>>> try:
..     l = ["a", "b"]
..     int[l[2]]
.. except ValueError, IndexError:  # To catch both exceptions, right?
..     pass
...
Traceback [most recent call last]:
  File "", line 3, in 
IndexError: list index out of range
8 thực sự chỉ là viết tắt của
>>> try:
..     l = ["a", "b"]
..     int[l[2]]
.. except ValueError, IndexError:  # To catch both exceptions, right?
..     pass
...
Traceback [most recent call last]:
  File "", line 3, in 
IndexError: list index out of range
9, chúng tôi thấy rằng chúng tôi đang cố gắng gán một giá trị cho
>>> try:
..     l = ["a", "b"]
..     int[l[2]]
.. except ValueError, IndexError:  # To catch both exceptions, right?
..     pass
...
Traceback [most recent call last]:
  File "", line 3, in 
IndexError: list index out of range
6 [do đó Python cho rằng nó nằm trong phạm vi cục bộ]. Tuy nhiên, giá trị mà chúng tôi muốn gán cho
>>> try:
..     l = ["a", "b"]
..     int[l[2]]
.. except ValueError, IndexError:  # To catch both exceptions, right?
..     pass
...
Traceback [most recent call last]:
  File "", line 3, in 
IndexError: list index out of range
6 dựa trên chính
>>> try:
..     l = ["a", "b"]
..     int[l[2]]
.. except ValueError, IndexError:  # To catch both exceptions, right?
..     pass
...
Traceback [most recent call last]:
  File "", line 3, in 
IndexError: list index out of range
6 [một lần nữa, hiện được cho là thuộc phạm vi cục bộ], giá trị này vẫn chưa được xác định. bùng nổ

Sai lầm thường gặp #5. Sửa đổi một danh sách trong khi lặp lại nó

Vấn đề với đoạn mã sau khá rõ ràng

>>> foo[]
["baz"]
>>> foo[]
["baz", "baz"]
>>> foo[]
["baz", "baz", "baz"]
0

Xóa một mục khỏi danh sách hoặc mảng trong khi lặp lại nó là một vấn đề Python mà bất kỳ nhà phát triển phần mềm có kinh nghiệm nào cũng biết. Nhưng trong khi ví dụ trên có thể khá rõ ràng, ngay cả những nhà phát triển tiên tiến cũng có thể vô tình bị cắn bởi mã này phức tạp hơn nhiều

May mắn thay, Python kết hợp một số mô hình lập trình tao nhã, khi được sử dụng đúng cách, có thể dẫn đến mã được đơn giản hóa và hợp lý hóa đáng kể. Một lợi ích phụ của điều này là mã đơn giản hơn ít có khả năng bị cắn bởi lỗi vô tình xóa danh sách mục trong khi lặp đi lặp lại qua nó. Một mô hình như vậy là của. Hơn nữa, việc hiểu danh sách đặc biệt hữu ích để tránh vấn đề cụ thể này, như được thể hiện bằng cách triển khai thay thế mã trên hoạt động hoàn hảo này

>>> foo[]
["baz"]
>>> foo[]
["baz", "baz"]
>>> foo[]
["baz", "baz", "baz"]
1

Sai lầm thường gặp #6. Nhầm lẫn cách Python liên kết các biến trong bao đóng

Xét ví dụ sau

>>> foo[]
["baz"]
>>> foo[]
["baz", "baz"]
>>> foo[]
["baz", "baz", "baz"]
2

Bạn có thể mong đợi đầu ra sau

>>> foo[]
["baz"]
>>> foo[]
["baz", "baz"]
>>> foo[]
["baz", "baz", "baz"]
3

Nhưng bạn thực sự nhận được

>>> foo[]
["baz"]
>>> foo[]
["baz", "baz"]
>>> foo[]
["baz", "baz", "baz"]
4

Sự ngạc nhiên

Điều này xảy ra do hành vi ràng buộc muộn của Python nói rằng giá trị của các biến được sử dụng trong bao đóng được tra cứu tại thời điểm hàm bên trong được gọi. Vì vậy, trong đoạn mã trên, bất cứ khi nào bất kỳ hàm trả về nào được gọi, giá trị của

>>> try:
..     l = ["a", "b"]
..     int[l[2]]
.. except [ValueError, IndexError] as e:  
..     pass
...
>>>
3 sẽ được tra cứu trong phạm vi xung quanh tại thời điểm nó được gọi [và khi đó, vòng lặp đã hoàn thành, vì vậy,
>>> try:
..     l = ["a", "b"]
..     int[l[2]]
.. except [ValueError, IndexError] as e:  
..     pass
...
>>>
3 đã được gán giá trị cuối cùng của nó

Giải pháp cho vấn đề Python phổ biến này hơi phức tạp một chút

>>> foo[]
["baz"]
>>> foo[]
["baz", "baz"]
>>> foo[]
["baz", "baz", "baz"]
5

thì đấy. Chúng tôi đang tận dụng các đối số mặc định ở đây để tạo các hàm ẩn danh nhằm đạt được hành vi mong muốn. Một số sẽ gọi đây là thanh lịch. Một số sẽ gọi nó là tinh tế. Một số ghét nó. Nhưng nếu bạn là nhà phát triển Python, điều quan trọng là phải hiểu trong mọi trường hợp

Sai lầm thường gặp #7. Tạo các phụ thuộc mô-đun tròn

Giả sử bạn có hai tệp,

>>> try:
..     l = ["a", "b"]
..     int[l[2]]
.. except [ValueError, IndexError] as e:  
..     pass
...
>>>
5 và
>>> try:
..     l = ["a", "b"]
..     int[l[2]]
.. except [ValueError, IndexError] as e:  
..     pass
...
>>>
6, mỗi tệp này nhập tệp kia, như sau

Trong

>>> try:
..     l = ["a", "b"]
..     int[l[2]]
.. except [ValueError, IndexError] as e:  
..     pass
...
>>>
5

>>> foo[]
["baz"]
>>> foo[]
["baz", "baz"]
>>> foo[]
["baz", "baz", "baz"]
6

Và trong

>>> try:
..     l = ["a", "b"]
..     int[l[2]]
.. except [ValueError, IndexError] as e:  
..     pass
...
>>>
6

>>> foo[]
["baz"]
>>> foo[]
["baz", "baz"]
>>> foo[]
["baz", "baz", "baz"]
7

Trước tiên, hãy thử nhập

>>> try:
..     l = ["a", "b"]
..     int[l[2]]
.. except [ValueError, IndexError] as e:  
..     pass
...
>>>
5

>>> foo[]
["baz"]
>>> foo[]
["baz", "baz"]
>>> foo[]
["baz", "baz", "baz"]
8

làm việc tốt. Có lẽ điều đó làm bạn ngạc nhiên. Rốt cuộc, chúng tôi có một lần nhập vòng tròn ở đây, điều này có lẽ là một vấn đề, phải không?

Câu trả lời là bản thân sự hiện diện của một lần nhập vòng tròn không phải là một vấn đề trong Python. Nếu một mô-đun đã được nhập, Python đủ thông minh để không cố nhập lại nó. Tuy nhiên, tùy thuộc vào thời điểm mà mỗi mô-đun đang cố gắng truy cập các hàm hoặc biến được xác định trong mô-đun kia, bạn thực sự có thể gặp sự cố

Vì vậy, quay trở lại ví dụ của chúng tôi, khi chúng tôi nhập

>>> try:
..     l = ["a", "b"]
..     int[l[2]]
.. except [ValueError, IndexError] as e:  
..     pass
...
>>>
5, không có vấn đề gì khi nhập
>>> try:
..     l = ["a", "b"]
..     int[l[2]]
.. except [ValueError, IndexError] as e:  
..     pass
...
>>>
6, vì
>>> try:
..     l = ["a", "b"]
..     int[l[2]]
.. except [ValueError, IndexError] as e:  
..     pass
...
>>>
6 không yêu cầu bất kỳ điều gì từ
>>> try:
..     l = ["a", "b"]
..     int[l[2]]
.. except [ValueError, IndexError] as e:  
..     pass
...
>>>
5 được xác định tại thời điểm nó được nhập. Tham chiếu duy nhất trong
>>> try:
..     l = ["a", "b"]
..     int[l[2]]
.. except [ValueError, IndexError] as e:  
..     pass
...
>>>
6 đến
>>> x = 10
>>> def foo[]:
..     x += 1
..     print x
...
>>> foo[]
Traceback [most recent call last]:
  File "", line 1, in 
  File "", line 2, in foo
UnboundLocalError: local variable 'x' referenced before assignment
5 là cuộc gọi tới
>>> x = 10
>>> def foo[]:
..     x += 1
..     print x
...
>>> foo[]
Traceback [most recent call last]:
  File "", line 1, in 
  File "", line 2, in foo
UnboundLocalError: local variable 'x' referenced before assignment
6. Nhưng cuộc gọi đó là trong
>>> x = 10
>>> def foo[]:
..     x += 1
..     print x
...
>>> foo[]
Traceback [most recent call last]:
  File "", line 1, in 
  File "", line 2, in foo
UnboundLocalError: local variable 'x' referenced before assignment
7 và không có gì trong
>>> try:
..     l = ["a", "b"]
..     int[l[2]]
.. except [ValueError, IndexError] as e:  
..     pass
...
>>>
5 hoặc
>>> try:
..     l = ["a", "b"]
..     int[l[2]]
.. except [ValueError, IndexError] as e:  
..     pass
...
>>>
6 gọi
>>> x = 10
>>> def foo[]:
..     x += 1
..     print x
...
>>> foo[]
Traceback [most recent call last]:
  File "", line 1, in 
  File "", line 2, in foo
UnboundLocalError: local variable 'x' referenced before assignment
7. Vì vậy, cuộc sống là tốt

Nhưng điều gì sẽ xảy ra nếu chúng tôi cố gắng nhập

>>> try:
..     l = ["a", "b"]
..     int[l[2]]
.. except [ValueError, IndexError] as e:  
..     pass
...
>>>
6 [nghĩa là trước đó chưa nhập
>>> try:
..     l = ["a", "b"]
..     int[l[2]]
.. except [ValueError, IndexError] as e:  
..     pass
...
>>>
5]

>>> foo[]
["baz"]
>>> foo[]
["baz", "baz"]
>>> foo[]
["baz", "baz", "baz"]
9

Uh-oh. Điều đó không tốt. Vấn đề ở đây là, trong quá trình nhập

>>> try:
..     l = ["a", "b"]
..     int[l[2]]
.. except [ValueError, IndexError] as e:  
..     pass
...
>>>
6, nó cố gắng nhập
>>> try:
..     l = ["a", "b"]
..     int[l[2]]
.. except [ValueError, IndexError] as e:  
..     pass
...
>>>
5, đến lượt nó gọi
>>> lst = [1, 2, 3]
>>> def foo1[]:
..     lst.append[5]   # This works ok...
...
>>> foo1[]
>>> lst
[1, 2, 3, 5]

>>> lst = [1, 2, 3]
>>> def foo2[]:
..     lst += [5]      # .. but this bombs!
...
>>> foo2[]
Traceback [most recent call last]:
  File "", line 1, in 
  File "", line 2, in foo
UnboundLocalError: local variable 'lst' referenced before assignment
5, nó cố gắng truy cập
>>> lst = [1, 2, 3]
>>> def foo1[]:
..     lst.append[5]   # This works ok...
...
>>> foo1[]
>>> lst
[1, 2, 3, 5]

>>> lst = [1, 2, 3]
>>> def foo2[]:
..     lst += [5]      # .. but this bombs!
...
>>> foo2[]
Traceback [most recent call last]:
  File "", line 1, in 
  File "", line 2, in foo
UnboundLocalError: local variable 'lst' referenced before assignment
6. Nhưng
>>> lst = [1, 2, 3]
>>> def foo1[]:
..     lst.append[5]   # This works ok...
...
>>> foo1[]
>>> lst
[1, 2, 3, 5]

>>> lst = [1, 2, 3]
>>> def foo2[]:
..     lst += [5]      # .. but this bombs!
...
>>> foo2[]
Traceback [most recent call last]:
  File "", line 1, in 
  File "", line 2, in foo
UnboundLocalError: local variable 'lst' referenced before assignment
6 vẫn chưa được xác định. Do đó, ngoại lệ
>>> lst = [1, 2, 3]
>>> def foo1[]:
..     lst.append[5]   # This works ok...
...
>>> foo1[]
>>> lst
[1, 2, 3, 5]

>>> lst = [1, 2, 3]
>>> def foo2[]:
..     lst += [5]      # .. but this bombs!
...
>>> foo2[]
Traceback [most recent call last]:
  File "", line 1, in 
  File "", line 2, in foo
UnboundLocalError: local variable 'lst' referenced before assignment
8

Ít nhất một giải pháp cho điều này là khá tầm thường. Chỉ cần sửa đổi

>>> try:
..     l = ["a", "b"]
..     int[l[2]]
.. except [ValueError, IndexError] as e:  
..     pass
...
>>>
6 để nhập
>>> try:
..     l = ["a", "b"]
..     int[l[2]]
.. except [ValueError, IndexError] as e:  
..     pass
...
>>>
5 trong vòng
>>> x = 10
>>> def foo[]:
..     x += 1
..     print x
...
>>> foo[]
Traceback [most recent call last]:
  File "", line 1, in 
  File "", line 2, in foo
UnboundLocalError: local variable 'x' referenced before assignment
7

>>> def foo[bar=None]:
..    if bar is None:		# or if not bar:
..        bar = []
..    bar.append["baz"]
..    return bar
...
>>> foo[]
["baz"]
>>> foo[]
["baz"]
>>> foo[]
["baz"]
0

Không khi chúng tôi nhập nó, mọi thứ đều ổn

>>> def foo[bar=None]:
..    if bar is None:		# or if not bar:
..        bar = []
..    bar.append["baz"]
..    return bar
...
>>> foo[]
["baz"]
>>> foo[]
["baz"]
>>> foo[]
["baz"]
1

Sai lầm thường gặp #8. Xung đột tên với các mô-đun Thư viện chuẩn Python

Một trong những nét đẹp của Python là sự phong phú của các mô-đun thư viện mà nó đi kèm “có sẵn”. Nhưng kết quả là, nếu bạn không cố ý tránh nó, sẽ không khó để gặp phải xung đột tên giữa tên của một trong các mô-đun của bạn và một mô-đun có cùng tên trong thư viện chuẩn đi kèm với Python [ví dụ:

Điều này có thể dẫn đến các sự cố nghiêm trọng, chẳng hạn như nhập một thư viện khác, thư viện này lại cố gắng nhập phiên bản Thư viện chuẩn Python của một mô-đun, nhưng vì bạn có một mô-đun có cùng tên nên gói khác nhập nhầm phiên bản của bạn thay vì phiên bản bên trong . Đây là nơi xảy ra lỗi Python xấu

Do đó, cần cẩn thận để tránh sử dụng cùng tên với tên trong các mô-đun Thư viện chuẩn Python. Bạn có thể dễ dàng thay đổi tên của mô-đun trong gói của mình hơn là gửi Đề xuất cải tiến Python [PEP] để yêu cầu thay đổi tên ngược dòng và thử và được chấp thuận

Sai lầm thường gặp #9. Không giải quyết được sự khác biệt giữa Python 2 và Python 3

Xem xét tệp sau

>>> foo[]
["baz"]
>>> foo[]
["baz", "baz"]
>>> foo[]
["baz", "baz", "baz"]
03

>>> def foo[bar=None]:
..    if bar is None:		# or if not bar:
..        bar = []
..    bar.append["baz"]
..    return bar
...
>>> foo[]
["baz"]
>>> foo[]
["baz"]
>>> foo[]
["baz"]
2

Trên Python 2, điều này chạy tốt

>>> def foo[bar=None]:
..    if bar is None:		# or if not bar:
..        bar = []
..    bar.append["baz"]
..    return bar
...
>>> foo[]
["baz"]
>>> foo[]
["baz"]
>>> foo[]
["baz"]
3

Nhưng bây giờ, hãy thử sức với Python 3

>>> def foo[bar=None]:
..    if bar is None:		# or if not bar:
..        bar = []
..    bar.append["baz"]
..    return bar
...
>>> foo[]
["baz"]
>>> foo[]
["baz"]
>>> foo[]
["baz"]
4

Điều gì vừa xảy ra ở đây? . [Lý do cho điều này là, nếu không, nó sẽ giữ một chu kỳ tham chiếu với khung ngăn xếp trong bộ nhớ cho đến khi trình thu gom rác chạy và xóa các tham chiếu khỏi bộ nhớ. Thêm chi tiết kỹ thuật về điều này có sẵn]

Một cách để tránh vấn đề này là duy trì một tham chiếu đến đối tượng ngoại lệ bên ngoài phạm vi của khối

>>> A.x = 3
>>> print A.x, B.x, C.x
3 2 3
4 để nó vẫn có thể truy cập được. Đây là phiên bản của ví dụ trước sử dụng kỹ thuật này, do đó tạo ra mã thân thiện với cả Python 2 và Python 3

>>> def foo[bar=None]:
..    if bar is None:		# or if not bar:
..        bar = []
..    bar.append["baz"]
..    return bar
...
>>> foo[]
["baz"]
>>> foo[]
["baz"]
>>> foo[]
["baz"]
5

Chạy cái này trên Py3k

>>> def foo[bar=None]:
..    if bar is None:		# or if not bar:
..        bar = []
..    bar.append["baz"]
..    return bar
...
>>> foo[]
["baz"]
>>> foo[]
["baz"]
>>> foo[]
["baz"]
6

dippee

[Nhân tiện, chúng tôi thảo luận về một số điểm khác biệt quan trọng khác cần lưu ý khi di chuyển mã từ Python 2 sang Python 3. ]

Sai lầm thường gặp #10. Lạm dụng phương pháp
>>> foo[]
["baz"]
>>> foo[]
["baz", "baz"]
>>> foo[]
["baz", "baz", "baz"]
06

Giả sử bạn có cái này trong một tệp có tên là

>>> foo[]
["baz"]
>>> foo[]
["baz", "baz"]
>>> foo[]
["baz", "baz", "baz"]
07

>>> def foo[bar=None]:
..    if bar is None:		# or if not bar:
..        bar = []
..    bar.append["baz"]
..    return bar
...
>>> foo[]
["baz"]
>>> foo[]
["baz"]
>>> foo[]
["baz"]
7

Và sau đó bạn đã cố gắng làm điều này từ

>>> foo[]
["baz"]
>>> foo[]
["baz", "baz"]
>>> foo[]
["baz", "baz", "baz"]
08

>>> def foo[bar=None]:
..    if bar is None:		# or if not bar:
..        bar = []
..    bar.append["baz"]
..    return bar
...
>>> foo[]
["baz"]
>>> foo[]
["baz"]
>>> foo[]
["baz"]
8

Bạn sẽ nhận được một ngoại lệ

>>> lst = [1, 2, 3]
>>> def foo1[]:
..     lst.append[5]   # This works ok...
...
>>> foo1[]
>>> lst
[1, 2, 3, 5]

>>> lst = [1, 2, 3]
>>> def foo2[]:
..     lst += [5]      # .. but this bombs!
...
>>> foo2[]
Traceback [most recent call last]:
  File "", line 1, in 
  File "", line 2, in foo
UnboundLocalError: local variable 'lst' referenced before assignment
8 xấu xí

Tại sao? . Kết quả là, trong ví dụ trên, tại thời điểm được gọi, tên

>>> foo[]
["baz"]
>>> foo[]
["baz", "baz"]
>>> foo[]
["baz", "baz", "baz"]
12 đã được đặt thành
>>> foo[]
["baz"]
>>> foo[]
["baz", "baz"]
>>> foo[]
["baz", "baz", "baz"]
10

Một giải pháp cho vấn đề lập trình Python nâng cao hơn một chút này là sử dụng

>>> foo[]
["baz"]
>>> foo[]
["baz", "baz"]
>>> foo[]
["baz", "baz", "baz"]
14 thay thế. Theo cách đó, khi chương trình của bạn thực thi xong [tức là khi thoát bình thường], các trình xử lý đã đăng ký của bạn sẽ được khởi động trước khi trình thông dịch bị tắt

Với sự hiểu biết đó, một bản sửa lỗi cho mã

>>> foo[]
["baz"]
>>> foo[]
["baz", "baz"]
>>> foo[]
["baz", "baz", "baz"]
07 ở trên có thể giống như thế này

>>> def foo[bar=None]:
..    if bar is None:		# or if not bar:
..        bar = []
..    bar.append["baz"]
..    return bar
...
>>> foo[]
["baz"]
>>> foo[]
["baz"]
>>> foo[]
["baz"]
9

Việc triển khai này cung cấp một cách rõ ràng và đáng tin cậy để gọi bất kỳ chức năng dọn dẹp cần thiết nào khi chấm dứt chương trình thông thường. Rõ ràng, việc quyết định phải làm gì với đối tượng gắn với cái tên

>>> foo[]
["baz"]
>>> foo[]
["baz", "baz"]
>>> foo[]
["baz", "baz", "baz"]
17 là tùy thuộc vào
>>> foo[]
["baz"]
>>> foo[]
["baz", "baz"]
>>> foo[]
["baz", "baz", "baz"]
16, nhưng bạn hiểu rồi đấy.

Gói [lại

Python là một ngôn ngữ mạnh mẽ và linh hoạt với nhiều cơ chế và mô hình có thể cải thiện đáng kể năng suất. Tuy nhiên, như với bất kỳ công cụ phần mềm hoặc ngôn ngữ nào, việc hiểu biết hoặc đánh giá hạn chế về các khả năng của nó đôi khi có thể là một trở ngại hơn là lợi ích, khiến một người rơi vào tình trạng “biết đủ là nguy hiểm”

Tự làm quen với các sắc thái chính của Python, chẳng hạn như [nhưng không giới hạn ở] các vấn đề lập trình nâng cao vừa phải được nêu trong bài viết này, sẽ giúp tối ưu hóa việc sử dụng ngôn ngữ trong khi tránh một số lỗi phổ biến hơn của nó

Bạn cũng có thể muốn xem các đề xuất của chúng tôi về các câu hỏi phỏng vấn có thể giúp xác định các chuyên gia Python

Python có thực sự hữu ích không?

Python đã trở thành một yếu tố chính trong khoa học dữ liệu , cho phép các nhà phân tích dữ liệu và các chuyên gia khác sử dụng ngôn ngữ này để tiến hành các phép tính thống kê phức tạp, tạo trực quan hóa dữ liệu, xây dựng các thuật toán học máy, thao tác và .

Python đang mất dần tính phổ biến?

Đó là lý do tại sao một số người sống trong bóng tối cho rằng ngôn ngữ lập trình Python có thể mất đi tính phổ biến ngay sau năm 2023. Tuy nhiên, với sự thành công của Python trong học máy và các trường hợp sử dụng chung như phát triển web, dự án khoa học dữ liệu, v.v. , còn quá sớm để nói Python sẽ sớm biến mất

Tôi nên học C++ hay Python?

Tuy nhiên, nếu bạn đang tìm cách phát triển phần mềm hoặc mã chạy đặc biệt nhanh, hãy sử dụng C++ thay vì Python vì cái trước nhanh hơn nhiều phải không . Khi nói đến việc chọn ngôn ngữ lập trình, bạn cũng nên sử dụng ngôn ngữ phù hợp nhất với trình độ chuyên môn của mình.

Chủ Đề