Trong hướng dẫn này, chúng ta sẽ bàn về hàm enumerate() trong Python là gì, khi nào dùng, nó tạo ra kiểu đối tượng nào, cú pháp ra sao và hoạt động thế nào. Sau đó, chúng ta sẽ xem một số ví dụ liệt kê (enumerate) các kiểu đối tượng khác nhau trong Python, các phương án thay thế cho hàm enumerate() và vì sao chúng kém phù hợp hơn cho cùng tác vụ.
Enumerate trong Python là gì và khi nào dùng?
Hàm enumerate() là một hàm dựng sẵn của Python nhận vào một iterable (một tập hợp phần tử hoặc bất kỳ đối tượng Python nào hỗ trợ lặp, như tuple, list, set hoặc string), lặp qua các phần tử của nó ở tầng dưới và trả về một đối tượng enumerate. Nói cách khác, hàm này gán một bộ đếm tăng dần 1 cho mỗi phần tử của iterable và giúp chúng ta theo dõi số vòng lặp khi duyệt qua đối tượng đó. Sau khi tạo đối tượng enumerate, ta có thể chuyển nó thành list, tuple hoặc dictionary (lần lượt dùng các hàm list(), tuple() hoặc dict()), hoặc lặp trực tiếp qua đối tượng này để truy cập cả phần tử và chỉ số tương ứng.
Dùng hàm enumerate() hữu ích về mặt hiệu năng bộ nhớ và tính toán so với dùng for-loop, vì nó trả về chỉ số và phần tử tương ứng trong một lần.
Ở đây, iterable là đối tượng có thể lặp mà chúng ta muốn enumerate, và start là tham số tùy chọn xác định chỉ số bắt đầu của bộ đếm (mặc định là chỉ số 0 theo phong cách Python).
Lưu ý rằng áp dụng hàm enumerate() lên một iterable không làm thay đổi chính đối tượng ban đầu. Thay vào đó, đối tượng enumerate kết quả cần được gán vào một biến nếu chúng ta muốn tiếp tục dùng nó.
Bây giờ, hãy xem hàm này hoạt động thế nào trên các kiểu iterable khác nhau của Python.
Enumerate một List
Đầu tiên, chúng ta áp dụng hàm enumerate() lên một list Python:
drinks =['tea','coffee','cappuccino','lemonade']
enumerated_drinks =enumerate(drinks)print(type(enumerated_drinks))print(enumerated_drinks)Được hỗ trợ bởi
Kết quả:
<class'enumerate'><enumerateobject at 0x000002DC3A1145C0>Được hỗ trợ bởi
Kiểu của đối tượng thu được là enumerate, và khi in nó ra, chúng ta chỉ xác nhận lại kiểu đối tượng và nơi nó được lưu trong bộ nhớ máy tính. Hãy thử truy cập phần tử đầu tiên của đối tượng này theo cách giống như với list hoặc tuple Python:
Chúng ta nhận lỗi cho biết các thành phần của một đối tượng enumerate không thể truy cập theo cách này. Thay vào đó, để làm việc này, cần chuyển nó thành list, tuple hoặc dictionary, hoặc lặp trực tiếp qua đối tượng enumerate. Hãy chuyển đối tượng enumerate của chúng ta thành list:
list_enumerated_drinks =list(enumerated_drinks)print(list_enumerated_drinks)Được hỗ trợ bởi
Kết quả:
[(0,'tea'),(1,'coffee'),(2,'cappuccino'),(3,'lemonade')]Được hỗ trợ bởi
Bây giờ, chúng ta thấy rõ cấu trúc bên trong của list kết quả: mỗi phần tử là một tuple 2 phần tử, trong đó phần tử thứ nhất là chỉ số của phần tử tương ứng trong list gốc và phần tử thứ hai là chính phần tử đó. Ta có thể dùng cách đánh chỉ số thông thường để truy cập các phần tử của list này:
print(list_enumerated_drinks[0])print(list_enumerated_drinks[0][1])Được hỗ trợ bởi
Giờ hãy thử chuyển một đối tượng enumerate được tạo từ list gốc thành một tuple. Ngoài ra, lần này chúng ta sẽ đặt tham số tùy chọn start là 1:
tuple_enumerated_drinks =tuple(enumerate(drinks, start=1))print(tuple_enumerated_drinks)Được hỗ trợ bởi
Kết quả:
((1,'tea'),(2,'coffee'),(3,'cappuccino'),(4,'lemonade'))Được hỗ trợ bởi
Cấu trúc bên trong của tuple trên giống hệt với list đã tạo trước đó. Tuy nhiên, lần này chúng ta cũng đã thay đổi bộ đếm cho “gần gũi với con người” hơn thay vì kiểu chỉ số bắt đầu từ 0 của Python.
Có một phương thức thú vị của đối tượng enumerate đáng nhắc đến ở đây: phương thức next(). Hãy tạo lại đối tượng enumerated_drinks và thử phương thức này:
enumerated_drinks =enumerate(drinks)print(next(enumerated_drinks))print(next(enumerated_drinks))print(next(enumerated_drinks))print(next(enumerated_drinks))print(next(enumerated_drinks))Được hỗ trợ bởi
Như thấy, mỗi lần gọi phương thức này trên một đối tượng enumerate, nó trả về phần tử kế tiếp (và ta có thể xác nhận lại rằng mỗi phần tử là một tuple 2 phần tử chứa cặp đếm—phần tử). Khi tất cả phần tử đã hết, đối tượng enumerate bị “cạn”, và việc gọi next() trên nó sẽ ném lỗi StopIteration. Thật vậy, nếu bây giờ ta thử chuyển đối tượng enumerate này thành list, sẽ thấy nó rỗng
Điều này có nghĩa là mỗi lần áp dụng next() lên một đối tượng enumerate, một phần tử được lấy ra khỏi đối tượng, nên mỗi lần nó lại nhỏ đi cho đến khi cạn hẳn. Chúng ta cũng sẽ thấy tính chất này của đối tượng enumerate khi bàn về việc áp dụng for-loop lên chúng.
Nếu bạn muốn tìm hiểu thêm về list và các khái niệm cơ bản khác của Python, bạn có thể học khóa Introduction to Python của DataCamp.
Enumerate một Tuple
Áp dụng hàm enumerate() lên một tuple là y hệt như với list:
drinks =('tea','coffee','cappuccino','lemonade')
enumerated_drinks =enumerate(drinks)print(enumerated_drinks)Được hỗ trợ bởi
Một lần nữa, chúng ta thu được một đối tượng enumerate mà các phần tử của nó không thể truy cập bằng cách đánh chỉ số thông thường. Hãy biến đổi đối tượng này sang một đối tượng khác, lần này là dictionary:
dict_enumerated_drinks =dict(enumerated_drinks)print(dict_enumerated_drinks)Được hỗ trợ bởi
Kết quả:
{0:'tea',1:'coffee',2:'cappuccino',3:'lemonade'}Được hỗ trợ bởi
Mỗi cặp key:value của dictionary thu được đại diện cho cặp đếm—phần tử tương ứng. Ta có thể truy cập các giá trị của dictionary theo cách Pythonic thông thường:
print(dict_enumerated_drinks[0])print(dict_enumerated_drinks[3])Được hỗ trợ bởi
list_enumerated_hello_world =list(enumerated_hello_world)print(list_enumerated_hello_world)Được hỗ trợ bởi
Kết quả:
[(10,'H'),(11,'e'),(12,'l'),(13,'l'),(14,'o'),(15,','),(16,' '),(17,'W'),(18,'o'),(19,'r'),(20,'l'),(21,'d'),(22,'!')]Được hỗ trợ bởi
Chúng ta thấy các tuple 2 phần tử đã quen thuộc, trong đó phần tử thứ nhất là số đếm của ký tự tương ứng trong chuỗi (trong trường hợp này bắt đầu từ 10), và phần tử thứ hai là chính ký tự đó. Khoảng trắng và dấu câu cũng được tính.
Hãy tạo lại đối tượng enumerated_hello_world, lần này không có start=10, và áp dụng phương thức next lên nó:
enumerated_hello_world =enumerate('Hello, World!')print(next(enumerated_hello_world))print(next(enumerated_hello_world))print(next(enumerated_hello_world))print(next(enumerated_hello_world))print(next(enumerated_hello_world))print(next(enumerated_hello_world))
# Creating a list from the remaining enumerate objectprint(list(enumerated_hello_world))Được hỗ trợ bởi
Kết quả:
(0,'H')(1,'e')(2,'l')(3,'l')(4,'o')(5,',')[(6,' '),(7,'W'),(8,'o'),(9,'r'),(10,'l'),(11,'d'),(12,'!')]Được hỗ trợ bởi
Ở dòng cuối cùng của đoạn mã trên, chúng ta dừng việc gọi phương thức next() và chuyển đối tượng enumerated_hello_world thành list. List kết quả chứa các ký tự còn lại của đối tượng enumerate ban đầu sau nhiều lần áp dụng next() lên nó.
Enumerate trong For-Loop
Cuối cùng, hãy xem hàm enumerate() hoạt động như thế nào khi kết hợp với for-loop:
for item inenumerate(['tea','coffee','cappuccino','lemonade']):print(item)Được hỗ trợ bởi
Kết quả:
(0,'tea')(1,'coffee')(2,'cappuccino')(3,'lemonade')Được hỗ trợ bởi
Mỗi vòng lặp, tuple đếm—phần tử tương ứng được trả về, giống như khi chúng ta dùng phương thức next(). Dù không hiện rõ trong đoạn mã trên, đối tượng enumerate cũng mất dần các phần tử (tức các tuple 2 phần tử) từng cái một ở mỗi vòng lặp. Để minh họa, hãy viết lại đoạn mã trên theo dạng khác và kiểm tra độ dài của list kết quả sau khi kết thúc các vòng lặp:
drinks =['tea','coffee','cappuccino','lemonade']
enumerated_drinks =enumerate(drinks)
for item in enumerated_drinks:print(item)print(list(enumerated_drinks))Được hỗ trợ bởi
Kết quả:
(0,'tea')(1,'coffee')(2,'cappuccino')(3,'lemonade')[]Được hỗ trợ bởi
Giống như khi áp dụng phương thức next(), lặp qua một đối tượng enumerate sẽ làm nó cạn dần.
Tuy nhiên, hãy quay lại đoạn mã trước. Làm sao truy cập riêng từng phần tử từ mỗi tuple 2 phần tử? Để làm vậy, chúng ta sẽ thêm một biến lặp nữa và “unpack” các tuple:
for count, drink inenumerate(['tea','coffee','cappuccino','lemonade']):print(count, drink)Được hỗ trợ bởi
Ở trên, chúng ta trả về hai biến cho mỗi tuple: một cho số đếm của phần tử và một cho chính phần tử đó.
Lưu ý rằng chúng ta cũng có thể thực hiện cùng thao tác như trên mà không dùng hàm enumerate(). Ví dụ, có thể khai báo một biến đếm bên ngoài vòng lặp và tăng nó lên 1 ở mỗi vòng:
count =0for drink in['tea','coffee','cappuccino','lemonade']:print(count, drink)
count +=1Được hỗ trợ bởi
Tuy nhiên, so với việc dùng hàm enumerate(), cả hai cách trên đều cồng kềnh hơn, dễ lỗi hơn (ví dụ, có thể quên cập nhật biến đếm thủ công ở mỗi vòng), và cần nhiều mã hơn. Vì vậy, dùng hàm enumerate() là cách Pythonic nhất bất cứ khi nào chúng ta cần truy cập cả số đếm và phần tử của một iterable ở mỗi vòng lặp.
Kết luận
Trong hướng dẫn này, chúng ta đã học nhiều điều về hàm enumerate() trong Python, bao gồm các cách dùng, những phương án thay thế tiềm năng và vì sao cuối cùng nó hiệu quả hơn các phương án đó. Hàm enumerate() có thể không quen thuộc với phần lớn người mới học Python, và thậm chí lập trình viên nâng cao cũng có thể chưa biết đến hàm cực kỳ hữu ích này.
Nếu bạn muốn học thêm về enumerate(), hãy cân nhắc khóa Python Data Science Toolbox (Phần 2) khám phá chi tiết iterator và iterable, đồng thời cung cấp bài tập tương tác để luyện kỹ năng mới. Ngoài ra, nếu bạn muốn thành thạo các khái niệm Python khác, bạn có thể quan tâm đến Learn Python with DataCamp với nhiều lộ trình kỹ năng và nghề nghiệp, cheat sheet, hướng dẫn, chứng chỉ Python và bài kiểm tra đánh giá.
Trong hướng dẫn này, chúng ta sẽ bàn về hàm enumerate() trong Python là gì, khi nào dùng, nó tạo ra kiểu đối tượng nào, cú pháp ra sao và hoạt động thế nào. Sau đó, chúng ta sẽ xem một số ví dụ liệt kê (enumerate) các kiểu đối tượng khác nhau trong Python, các phương án thay thế cho hàm enumerate() và vì sao chúng kém phù hợp hơn cho cùng tác vụ.
Enumerate trong Python là gì và khi nào dùng?
Hàm enumerate() là một hàm dựng sẵn của Python nhận vào một iterable (một tập hợp phần tử hoặc bất kỳ đối tượng Python nào hỗ trợ lặp, như tuple, list, set hoặc string), lặp qua các phần tử của nó ở tầng dưới và trả về một đối tượng enumerate. Nói cách khác, hàm này gán một bộ đếm tăng dần 1 cho mỗi phần tử của iterable và giúp chúng ta theo dõi số vòng lặp khi duyệt qua đối tượng đó. Sau khi tạo đối tượng enumerate, ta có thể chuyển nó thành list, tuple hoặc dictionary (lần lượt dùng các hàm list(), tuple() hoặc dict()), hoặc lặp trực tiếp qua đối tượng này để truy cập cả phần tử và chỉ số tương ứng.
Dùng hàm enumerate() hữu ích về mặt hiệu năng bộ nhớ và tính toán so với dùng for-loop, vì nó trả về chỉ số và phần tử tương ứng trong một lần.
Cú pháp
Cú pháp của hàm enumerate() rất đơn giản:
Ở đây, iterable là đối tượng có thể lặp mà chúng ta muốn enumerate, và start là tham số tùy chọn xác định chỉ số bắt đầu của bộ đếm (mặc định là chỉ số 0 theo phong cách Python).
Lưu ý rằng áp dụng hàm enumerate() lên một iterable không làm thay đổi chính đối tượng ban đầu. Thay vào đó, đối tượng enumerate kết quả cần được gán vào một biến nếu chúng ta muốn tiếp tục dùng nó.
Bây giờ, hãy xem hàm này hoạt động thế nào trên các kiểu iterable khác nhau của Python.
Enumerate một List
Đầu tiên, chúng ta áp dụng hàm enumerate() lên một list Python:
Kết quả:
Kiểu của đối tượng thu được là enumerate, và khi in nó ra, chúng ta chỉ xác nhận lại kiểu đối tượng và nơi nó được lưu trong bộ nhớ máy tính. Hãy thử truy cập phần tử đầu tiên của đối tượng này theo cách giống như với list hoặc tuple Python:
Kết quả:
Chúng ta nhận lỗi cho biết các thành phần của một đối tượng enumerate không thể truy cập theo cách này. Thay vào đó, để làm việc này, cần chuyển nó thành list, tuple hoặc dictionary, hoặc lặp trực tiếp qua đối tượng enumerate. Hãy chuyển đối tượng enumerate của chúng ta thành list:
Kết quả:
Bây giờ, chúng ta thấy rõ cấu trúc bên trong của list kết quả: mỗi phần tử là một tuple 2 phần tử, trong đó phần tử thứ nhất là chỉ số của phần tử tương ứng trong list gốc và phần tử thứ hai là chính phần tử đó. Ta có thể dùng cách đánh chỉ số thông thường để truy cập các phần tử của list này:
Kết quả:
Giờ hãy thử chuyển một đối tượng enumerate được tạo từ list gốc thành một tuple. Ngoài ra, lần này chúng ta sẽ đặt tham số tùy chọn start là 1:
Kết quả:
Cấu trúc bên trong của tuple trên giống hệt với list đã tạo trước đó. Tuy nhiên, lần này chúng ta cũng đã thay đổi bộ đếm cho “gần gũi với con người” hơn thay vì kiểu chỉ số bắt đầu từ 0 của Python.
Có một phương thức thú vị của đối tượng enumerate đáng nhắc đến ở đây: phương thức next(). Hãy tạo lại đối tượng enumerated_drinks và thử phương thức này:
Kết quả:
Như thấy, mỗi lần gọi phương thức này trên một đối tượng enumerate, nó trả về phần tử kế tiếp (và ta có thể xác nhận lại rằng mỗi phần tử là một tuple 2 phần tử chứa cặp đếm—phần tử). Khi tất cả phần tử đã hết, đối tượng enumerate bị “cạn”, và việc gọi next() trên nó sẽ ném lỗi StopIteration. Thật vậy, nếu bây giờ ta thử chuyển đối tượng enumerate này thành list, sẽ thấy nó rỗng
Kết quả:
Điều này có nghĩa là mỗi lần áp dụng next() lên một đối tượng enumerate, một phần tử được lấy ra khỏi đối tượng, nên mỗi lần nó lại nhỏ đi cho đến khi cạn hẳn. Chúng ta cũng sẽ thấy tính chất này của đối tượng enumerate khi bàn về việc áp dụng for-loop lên chúng.
Nếu bạn muốn tìm hiểu thêm về list và các khái niệm cơ bản khác của Python, bạn có thể học khóa Introduction to Python của DataCamp.
Enumerate một Tuple
Áp dụng hàm enumerate() lên một tuple là y hệt như với list:
Kết quả:
Một lần nữa, chúng ta thu được một đối tượng enumerate mà các phần tử của nó không thể truy cập bằng cách đánh chỉ số thông thường. Hãy biến đổi đối tượng này sang một đối tượng khác, lần này là dictionary:
Kết quả:
Mỗi cặp key:value của dictionary thu được đại diện cho cặp đếm—phần tử tương ứng. Ta có thể truy cập các giá trị của dictionary theo cách Pythonic thông thường:
Kết quả:
Nếu bạn muốn đào sâu vào dictionary của Python và pandas, hãy khám phá khóa Intermediate Python.
Enumerate một String
Vì string trong Python là chuỗi ký tự và do đó cũng là đối tượng có thể lặp, chúng ta cũng có thể tạo đối tượng enumerate cho một string:
Kết quả:
Như trước, hãy chuyển đối tượng này thành list:
Kết quả:
Chúng ta thấy các tuple 2 phần tử đã quen thuộc, trong đó phần tử thứ nhất là số đếm của ký tự tương ứng trong chuỗi (trong trường hợp này bắt đầu từ 10), và phần tử thứ hai là chính ký tự đó. Khoảng trắng và dấu câu cũng được tính.
Hãy tạo lại đối tượng enumerated_hello_world, lần này không có start=10, và áp dụng phương thức next lên nó:
Kết quả:
Ở dòng cuối cùng của đoạn mã trên, chúng ta dừng việc gọi phương thức next() và chuyển đối tượng enumerated_hello_world thành list. List kết quả chứa các ký tự còn lại của đối tượng enumerate ban đầu sau nhiều lần áp dụng next() lên nó.
Enumerate trong For-Loop
Cuối cùng, hãy xem hàm enumerate() hoạt động như thế nào khi kết hợp với for-loop:
Kết quả:
Mỗi vòng lặp, tuple đếm—phần tử tương ứng được trả về, giống như khi chúng ta dùng phương thức next(). Dù không hiện rõ trong đoạn mã trên, đối tượng enumerate cũng mất dần các phần tử (tức các tuple 2 phần tử) từng cái một ở mỗi vòng lặp. Để minh họa, hãy viết lại đoạn mã trên theo dạng khác và kiểm tra độ dài của list kết quả sau khi kết thúc các vòng lặp:
Kết quả:
Giống như khi áp dụng phương thức next(), lặp qua một đối tượng enumerate sẽ làm nó cạn dần.
Tuy nhiên, hãy quay lại đoạn mã trước. Làm sao truy cập riêng từng phần tử từ mỗi tuple 2 phần tử? Để làm vậy, chúng ta sẽ thêm một biến lặp nữa và “unpack” các tuple:
Kết quả:
Ở trên, chúng ta trả về hai biến cho mỗi tuple: một cho số đếm của phần tử và một cho chính phần tử đó.
Lưu ý rằng chúng ta cũng có thể thực hiện cùng thao tác như trên mà không dùng hàm enumerate(). Ví dụ, có thể khai báo một biến đếm bên ngoài vòng lặp và tăng nó lên 1 ở mỗi vòng:
Kết quả:
Hoặc, ta có thể dùng kết hợp hàm range() và len() trên một list (hoặc tuple, string, v.v.):
Kết quả:
Hơn nữa, với cả hai phương án thay thế cho hàm enumerate() này, chúng ta vẫn có thể điều chỉnh biến đếm để không bắt đầu từ 0:
Kết quả:
Tuy nhiên, so với việc dùng hàm enumerate(), cả hai cách trên đều cồng kềnh hơn, dễ lỗi hơn (ví dụ, có thể quên cập nhật biến đếm thủ công ở mỗi vòng), và cần nhiều mã hơn. Vì vậy, dùng hàm enumerate() là cách Pythonic nhất bất cứ khi nào chúng ta cần truy cập cả số đếm và phần tử của một iterable ở mỗi vòng lặp.
Kết luận
Trong hướng dẫn này, chúng ta đã học nhiều điều về hàm enumerate() trong Python, bao gồm các cách dùng, những phương án thay thế tiềm năng và vì sao cuối cùng nó hiệu quả hơn các phương án đó. Hàm enumerate() có thể không quen thuộc với phần lớn người mới học Python, và thậm chí lập trình viên nâng cao cũng có thể chưa biết đến hàm cực kỳ hữu ích này.
Nếu bạn muốn học thêm về enumerate(), hãy cân nhắc khóa Python Data Science Toolbox (Phần 2) khám phá chi tiết iterator và iterable, đồng thời cung cấp bài tập tương tác để luyện kỹ năng mới. Ngoài ra, nếu bạn muốn thành thạo các khái niệm Python khác, bạn có thể quan tâm đến Learn Python with DataCamp với nhiều lộ trình kỹ năng và nghề nghiệp, cheat sheet, hướng dẫn, chứng chỉ Python và bài kiểm tra đánh giá.