Logo Zephyrnet

5 vấn đề Python phổ biến (và cách tránh chúng) – KDnuggets

Ngày:

5 vấn đề Python phổ biến (và cách tránh chúng)
Hình ảnh của Tác giả
 

Python là ngôn ngữ lập trình linh hoạt và thân thiện với người mới bắt đầu, được biết đến vì tính đơn giản và dễ đọc. Tuy nhiên, cú pháp tao nhã của nó không tránh khỏi những điều kỳ quặc có thể gây ngạc nhiên ngay cả những nhà phát triển Python có kinh nghiệm. Và hiểu những điều này là điều cần thiết để viết mã không có lỗi—hoặc gỡ lỗi dễ dàng nếu bạn muốn.

Hướng dẫn này khám phá một số vấn đề sau: giá trị mặc định có thể thay đổi, phạm vi thay đổi trong vòng lặp và mức độ hiểu, gán bộ dữ liệu, v.v. Chúng ta sẽ viết mã các ví dụ đơn giản để xem tại sao mọi thứ hoạt động theo cách họ làm, và cũng nhìn vào làm thế nào chúng ta có thể tránh những điều này (nếu chúng ta thực sự có thể 🙂). 

Vậy hãy bắt đầu!

Trong Python, các giá trị mặc định có thể thay đổi là các góc nhọn phổ biến. Bạn sẽ gặp phải hành vi không mong muốn bất cứ lúc nào bạn xác định một hàm với các đối tượng có thể thay đổi, như danh sách hoặc từ điển, làm đối số mặc định. 

Giá trị mặc định chỉ được đánh giá một lần khi hàm được xác định chứ không phải mỗi lần hàm được gọi. Điều này có thể dẫn đến hành vi không mong muốn nếu bạn thay đổi đối số mặc định trong hàm.

Hãy lấy một ví dụ:

def add_to_cart(item, cart=[]):
    cart.append(item)
    return cart

 

Trong ví dụ này, add_to_cart là một hàm lấy một mục và thêm nó vào danh sách cart. Giá trị mặc định của cart là một danh sách trống. Có nghĩa là gọi hàm mà không có mặt hàng cần thêm sẽ trả về một giỏ hàng trống. 

Và đây là một vài lệnh gọi hàm:

# User 1 adds items to their cart
user1_cart = add_to_cart("Apple")
print("User 1 Cart:", user1_cart)  

 

Output >>> ['Apple']

 

Điều này hoạt động như mong đợi. Nhưng chuyện gì xảy ra bây giờ?

# User 2 adds items to their cart
user2_cart = add_to_cart("Cookies")
print("User 2 Cart:", user2_cart) 

 

Output >>>

['Apple', 'Cookies'] # User 2 never added apples to their cart!

 

Bởi vì đối số mặc định là một danh sách—một đối tượng có thể thay đổi—nó giữ nguyên trạng thái giữa các lệnh gọi hàm. Vì vậy mỗi lần bạn gọi add_to_cart, nó sẽ thêm giá trị vào cùng một đối tượng danh sách được tạo trong quá trình định nghĩa hàm. Trong ví dụ này, nó giống như tất cả người dùng chia sẻ cùng một giỏ hàng.

Làm sao để tránh

Như một giải pháp thay thế, bạn có thể đặt cart đến None và khởi tạo giỏ hàng bên trong hàm như sau:

def add_to_cart(item, cart=None):
    if cart is None:
        cart = []
    cart.append(item)
    return cart

 

Vì vậy, mỗi người dùng bây giờ có một giỏ hàng riêng. 🙂

Nếu bạn cần xem lại các hàm và đối số của hàm Python, hãy đọc Đối số hàm Python: Hướng dẫn cơ bản.

Những điều kỳ lạ về phạm vi của Python đòi hỏi phải có hướng dẫn riêng. Nhưng chúng ta sẽ xem xét một điều kỳ lạ như vậy ở đây.

Hãy xem đoạn trích sau:

x = 10
squares = []
for x in range(5):
    squares.append(x ** 2)

print("Squares list:", squares)  

# x is accessible here and is the last value of the looping var
print("x after for loop:", x)

 

Biến x được đặt thành 10. Nhưng x cũng là biến vòng lặp. Nhưng chúng ta giả định rằng phạm vi của biến vòng lặp bị giới hạn trong khối vòng lặp for, đúng không?

Hãy nhìn vào đầu ra:

Output >>>

Squares list: [0, 1, 4, 9, 16]
x after for loop: 4

 

Chúng ta thấy rằng x bây giờ là 4, giá trị cuối cùng mà nó nhận trong vòng lặp chứ không phải giá trị ban đầu là 10 mà chúng ta đặt thành.

Bây giờ hãy xem điều gì xảy ra nếu chúng ta thay thế vòng lặp for bằng một biểu thức hiểu:

x = 10
squares = [x ** 2 for x in range(5)]

print("Squares list:", squares)  

# x is 10 here
print("x after list comprehension:", x)

 

Ở đây, x là 10, giá trị chúng ta đặt trước biểu thức hiểu:

Output >>>

Squares list: [0, 1, 4, 9, 16]
x after list comprehension: 10

Làm sao để tránh

Để tránh hành vi không mong muốn: Nếu bạn đang sử dụng vòng lặp, hãy đảm bảo rằng bạn không đặt tên biến vòng lặp giống với một biến khác mà bạn muốn truy cập sau này.

Trong Python, chúng tôi sử dụng is từ khóa để kiểm tra danh tính đối tượng. Có nghĩa là nó kiểm tra xem hai biến có tham chiếu cùng một đối tượng trong bộ nhớ hay không. Và để kiểm tra sự bằng nhau, chúng tôi sử dụng == nhà điều hành. Đúng?

Bây giờ, hãy khởi động REPL Python và chạy đoạn mã sau:

>>> a = 7
>>> b = 7
>>> a == 7
True
>>> a is b
True

 

Bây giờ hãy chạy cái này:

>>> x = 280
>>> y = 280
>>> x == y
True
>>> x is y
False

 

Đợi đã, tại sao chuyện này lại xảy ra? Chà, điều này là do “bộ nhớ đệm số nguyên” hoặc “thực tập” trong CPython, cách triển khai tiêu chuẩn của Python.

CPython lưu trữ các đối tượng số nguyên trong khoảng từ -5 đến 256. Có nghĩa là mỗi khi bạn sử dụng một số nguyên trong phạm vi này, Python sẽ sử dụng cùng một đối tượng trong bộ nhớ. Vì vậy, khi bạn so sánh hai số nguyên trong phạm vi này bằng cách sử dụng is từ khóa, kết quả là True vì họ đề cập đến cùng một đối tượng trong bộ nhớ.

Đó là lý do tại sao a is b Trả về True. Bạn cũng có thể xác minh điều này bằng cách in ra id(a)id(b).

Tuy nhiên, các số nguyên nằm ngoài phạm vi này không được lưu vào bộ đệm. Và mỗi lần xuất hiện các số nguyên như vậy sẽ tạo ra một đối tượng mới trong bộ nhớ. 

Vì vậy, khi bạn so sánh hai số nguyên ngoài phạm vi được lưu trong bộ nhớ đệm bằng cách sử dụng is từ khóa (vâng, xy cả hai đều được đặt thành 280 trong ví dụ của chúng tôi), kết quả là False bởi vì chúng thực sự là hai đối tượng khác nhau trong bộ nhớ.

Làm sao để tránh

Hành vi này sẽ không thành vấn đề trừ khi bạn cố gắng sử dụng is để so sánh sự bằng nhau của hai đối tượng. Vì vậy hãy luôn sử dụng == toán tử để kiểm tra xem hai đối tượng Python có cùng giá trị hay không.

Nếu bạn quen thuộc với các cấu trúc dữ liệu có sẵn trong Python, bạn sẽ biết rằng các bộ dữ liệu bất biến. Vì vậy, bạn không thể sửa đổi chúng tại chỗ. Mặt khác, các cấu trúc dữ liệu như danh sách và từ điển lại có thể thay đổi. Có nghĩa là bạn có thể thay đổi chúng tại chỗ.

Nhưng còn các bộ chứa một hoặc nhiều đối tượng có thể thay đổi thì sao?

Thật hữu ích khi bắt đầu REPL Python và chạy ví dụ đơn giản này:

>>> my_tuple = ([1,2],3,4)
>>> my_tuple[0].append(3)
>>> my_tuple
([1, 2, 3], 3, 4)

 

Ở đây, phần tử đầu tiên của bộ dữ liệu là một danh sách có hai phần tử. Chúng tôi thử thêm 3 vào danh sách đầu tiên và nó hoạt động tốt! Chà, có phải chúng ta vừa sửa đổi một bộ dữ liệu tại chỗ không?

Bây giờ hãy thử thêm hai phần tử nữa vào danh sách, nhưng lần này sử dụng toán tử +=:

>>> my_tuple[0] += [4,5]
Traceback (most recent call last):
  File "", line 1, in 
TypeError: 'tuple' object does not support item assignment

 

Có, bạn nhận được TypeError cho biết đối tượng bộ dữ liệu không hỗ trợ việc gán mục. Đó là điều được mong đợi. Nhưng hãy kiểm tra tuple: 

>>> my_tuple
([1, 2, 3, 4, 5], 3, 4)

 

Chúng tôi thấy rằng yếu tố 4 và 5 đã được thêm vào danh sách! Có phải chương trình vừa báo lỗi và đồng thời thành công?

Vâng, toán tử += hoạt động nội bộ bằng cách gọi __iadd__() phương thức thực hiện phép cộng tại chỗ và sửa đổi danh sách tại chỗ. Nhiệm vụ này gây ra ngoại lệ TypeError, nhưng việc thêm các phần tử vào cuối danh sách đã thành công. += có lẽ là góc nhọn nhất!

Làm sao để tránh

Để tránh những điều kỳ quặc như vậy trong chương trình của bạn, hãy thử sử dụng bộ dữ liệu có thể cho các bộ sưu tập bất biến. Và tránh sử dụng các đối tượng có thể thay đổi làm phần tử tuple càng nhiều càng tốt.

Tính biến đổi đã là một chủ đề được lặp đi lặp lại trong cuộc thảo luận của chúng ta cho đến nay. Vì vậy, đây là một phần khác để kết thúc hướng dẫn này.

Đôi khi bạn có thể cần tạo các bản sao danh sách độc lập. Nhưng điều gì xảy ra khi bạn tạo một bản sao bằng cú pháp tương tự như list2 = list1 Ở đâu list1 là danh sách ban đầu?

Đó là một bản sao nông được tạo ra. Vì vậy, nó chỉ sao chép các tham chiếu đến các phần tử gốc của danh sách. Sửa đổi các yếu tố thông qua bản sao nông sẽ ảnh hưởng cả hai danh sách ban đầu bản sao nông cạn. 

Hãy lấy ví dụ này:

original_list = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]

# Shallow copy of the original list
shallow_copy = original_list

# Modify the shallow copy
shallow_copy[0][0] = 100

# Print both the lists
print("Original List:", original_list)
print("Shallow Copy:", shallow_copy)

 

Chúng tôi thấy rằng những thay đổi đối với bản sao nông cũng ảnh hưởng đến danh sách gốc:

Output >>>

Original List: [[100, 2, 3], [4, 5, 6], [7, 8, 9]]
Shallow Copy: [[100, 2, 3], [4, 5, 6], [7, 8, 9]]

 

Ở đây, chúng tôi sửa đổi phần tử đầu tiên của danh sách lồng nhau đầu tiên trong bản sao nông: shallow_copy[0][0] = 100. Nhưng chúng tôi thấy rằng việc sửa đổi ảnh hưởng đến cả danh sách gốc và bản sao nông. 

Làm sao để tránh

Để tránh điều này, bạn có thể tạo một bản sao sâu như sau:

import copy

original_list = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]

# Deep copy of the original list
deep_copy = copy.deepcopy(original_list)

# Modify an element of the deep copy
deep_copy[0][0] = 100

# Print both lists
print("Original List:", original_list)
print("Deep Copy:", deep_copy)

 

Bây giờ, bất kỳ sửa đổi nào đối với bản sao sâu đều không thay đổi danh sách ban đầu.

Output >>>

Original List: [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
Deep Copy: [[100, 2, 3], [4, 5, 6], [7, 8, 9]]

Và đó là một bọc! Trong hướng dẫn này, chúng ta đã khám phá một số điểm kỳ lạ trong Python: từ hành vi đáng ngạc nhiên của các giá trị mặc định có thể thay đổi cho đến sự tinh tế của danh sách sao chép nông. Đây chỉ là phần giới thiệu về những điều kỳ lạ của Python và không phải là danh sách đầy đủ. Bạn có thể tìm thấy tất cả các ví dụ mã trên GitHub.

Khi bạn tiếp tục viết mã bằng Python lâu hơn—và hiểu ngôn ngữ này tốt hơn—có thể bạn sẽ gặp phải nhiều vấn đề như vậy hơn. Vì vậy, hãy tiếp tục viết mã, tiếp tục khám phá!

Ồ, và hãy cho chúng tôi biết trong phần nhận xét nếu bạn muốn đọc phần tiếp theo của hướng dẫn này.
 
 

Bala Priya C là một nhà phát triển và nhà văn kỹ thuật đến từ Ấn Độ. Cô ấy thích làm việc ở lĩnh vực giao thoa giữa toán học, lập trình, khoa học dữ liệu và sáng tạo nội dung. Các lĩnh vực chuyên môn và quan tâm của cô bao gồm DevOps, khoa học dữ liệu và xử lý ngôn ngữ tự nhiên. Cô ấy thích đọc, viết, viết mã và cà phê! Hiện tại, cô ấy đang nỗ lực học hỏi và chia sẻ kiến ​​thức của mình với cộng đồng nhà phát triển bằng cách viết các hướng dẫn, hướng dẫn cách thực hiện, các ý kiến, v.v. Bala cũng tạo ra các hướng dẫn viết mã và tổng quan tài nguyên hấp dẫn.

tại chỗ_img

Tin tức mới nhất

tại chỗ_img