Trong những bài học kinh nghiệm trước đây bạn đã biết những kiểu tài liệu cơ bản của Python như list, tuple. Bạn cũng biết về hàm range ( ) và vòng lặp for. Tất cả chúng đều thực thi một mẫu phong cách thiết kế chung – mẫu iterator .Iterator là một mẫu phong cách thiết kế rất quan trọng giúp bạn thao tác với tài liệu. Ví dụ bạn hoàn toàn có thể tạo ra những cấu trúc tài liệu tuần tự mới. Các cấu trúc tài liệu dạng này tương thích cho việc tạo ra những chuỗi số, thao tác với file, hoặc tiếp xúc qua mạng .

Collection và iteration

Trong Python và các ngôn ngữ khác có thể phân ra hai loại dữ liệu: loại dữ liệu chỉ chứa một giá trị (như các loại số và bool), và loại dữ liệu chứa nhiều giá trị.

Loại dữ liệu chứa nhiều giá trị bạn đã gặp bao gồm list, tuple, dict, string, set. Chúng được gọi chung là dữ liệu collection (tập hợp) hay container (hộp chứa).

Một đặc thù phổ cập của container / collection là năng lực truy xuất lần lượt từng thành phần. Lấy ví dụ, bạn hoàn toàn có thể lần lượt duyệt qua lần lượt từng thành phần của một list ( list ) hoặc một tập hợp toán học ( set ) .

Người ta gọi quá trình truy xuất lần lượt từng phần tử của container là iteration (vòng lặp). Tại mỗi thời điểm trong iteration, bạn chỉ có thể truy xuất một bộ phận của dữ liệu.

Dữ liệu dạng container hoàn toàn có thể chia làm hai loại :

  1. Một số cho phép đánh chỉ số và truy xuất ngẫu nhiên đến từng phần tử, ví dụ như list, tuple, string. Các kiểu dữ liệu này được gọi là các kiểu dữ liệu sequence (kiểu dữ liệu tuần tự).
  2. Một số loại dữ liệu khác không cho đánh chỉ số và không thể truy xuất ngẫu nhiên như dict, set, file.

Khi thao tác với những loại tài liệu dạng tập hợp như vậy bạn hoàn toàn có thể tưởng tượng theo hai kiểu : ( 1 ) hàng loạt tài liệu đồng thời tải vào bộ nhớ, ( 2 ) tại mỗi thời gian bạn chỉ tải và truy xuất một bộ phận của tài liệu .Bạn hoàn toàn có thể so sánh thế này :

  • Khi đọc dữ liệu từ một file văn bản, bạn có thể lựa chọn tải toàn bộ văn bản vào bộ nhớ ngay lập tức, hoặc cũng có thể lựa chọn mỗi lần chỉ tải một đoạn văn bản (lấy ký tự /n làm mốc) để xử lý.
  • Khi tạo ra một dãy số (chẳng hạn chuỗi Fibonacci), bạn có thể lựa chọn tạo ra tất cả các số (trong một giới hạn nào đó) ngay lập tức, hoặc cũng có thể lựa chọn mỗi lần chỉ tạo ra một số, và chỉ tạo ra số khi chương trình cần đến (ví dụ, để in ra hoặc để tính toán).

Cách thứ nhất có lợi về vận tốc truy vấn vì mọi thứ nằm hết trong bộ nhớ nhưng đồng thời lại tốn bộ nhớ hơn. Cách này đặc biệt quan trọng không tương thích cho việc giải quyết và xử lý lượng tài liệu quá lớn hoặc tài liệu không xác lập được vị trí kết thúc ( như đọc tài liệu từ mạng hoặc tạo chuỗi số ) .Cách thứ hai có điểm yếu kém về vận tốc truy xuất, vì mỗi khi cần truy xuất, tài liệu mới được tạo ra hoặc được tải vào bộ nhớ. Tuy nhiên cách này hoàn toàn có thể giải quyết và xử lý được lượng tài liệu không số lượng giới hạn. Đây là cách Python lựa chọn để vận dụng cho những kiểu tài liệu tập hợp như list, tuple, string, cũng như vận dụng trong vòng lặp for .

Các kiểu dữ liệu tập hợp trong Python cũng được gọi chung là iterable. Để sử dụng dữ liệu iterable, bạn cần đến một iterator tương ứng.

Ví dụ về kiến thiết xây dựng Iterable và Iterator class

Hãy cùng triển khai ví dụ sau :

class FibonacciIterable:
    def __init__(self, count=10):
        self.count = count
    def __iter__(self):
        return FibonacciIterator(self.count)

class FibonacciIterator:
    def __init__(self, count=10):
        self.a, self.b = 0, 1
        self.count = count
        self.__i = 0
    def __next__(self):
        if self.__i > self.count:
            raise StopIteration()
        value = self.a
        self.a, self.b = self.b, self.a + self.b
        self.__i += 1
        return value
    # def __iter__(self):
    #     return self

def test1():
    print('Test 1:', end=' ')
    iterable = FibonacciIterable(15)
    for f in iterable:
        print(f, end=' ')
    print()

def test2():
    print('Test 2:', end=' ')
    iterator = FibonacciIterator(15)
    for f in iterator:
        print(f, end=' ')
    print()

def test3():
    print('Test 3:', end=' ')
    iterator = FibonacciIterator(15)
    while True:
        try:
            f = next(iterator)
            print(f, end=' ')
        except StopIteration:
            break
    print()

def test4():
    print('Test 4:', end=' ')
    iterable = FibonacciIterable(15)
    iterator = iter(iterable)
    while True:
        try:
            f = next(iterator)
            print(f, end=' ')
        except StopIteration:
            break
    print()

if __name__ == '__main__':
    test1()
    #test2()
    test3()
    test4()

Trong ví dụ này chúng ta xây dựng các class giúp tạo ra count phần tử đầu tiên của dãy số Fibonacci.

Nếu bạn chưa biết : dãy số Fibonacci có hai thành phần tiên phong lần lượt là 0 và 1. Phần tử thứ ba trở đi có giá trị bằng tổng giá trị hai thành phần trước nó .Kết quả của cả 3 hàm test là 15 giá trị tiên phong của chuỗi Fibonacci :

Test 1: 0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610
Test 3: 0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610
Test 4: 0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610

Trong ví dụ fibonacci.py bạn xây dựng hai class, FibonacciIterable và FibonacciIterator. Hai class này lần lượt thuộc về hai loại khác biệt: iterable và iterator.

Sự phân chia iterable và iterator như vậy tuân theo mẫu thiết kế iterator. Mẫu thiết kế này giúp phân tách thùng chứa (container) với dữ liệu thực sự chứa trong nó. Trong đó, container là iterable, còn dữ liệu thực sự chính là iterator. Nghĩa là, cùng một container nhưng bạn có thể cho nó “chứa” dữ liệu khác biệt nhau.

Iterable trong Python

Iterable trong Python là loại object container/collection.

Theo mẫu thiết kế, về hình thức thì iterable có khả năng trả lại lần lượt từng giá trị. Nghĩa là trong client code bạn có thể lần lượt duyệt từng phần tử của nó.

Tuy nhiên, iterable thực tế chỉ đơn giản là một loại thùng chứa (container). Dữ liệu thực thụ sẽ do iterator tải hoặc tạo ra. Do vậy, mỗi iterable phải có một iterator tương ứng.

Về quy định, để tạo ra một iterable, trong class phải chứa phương thức __iter__(). Khi có mặt phương thức __iter__(), class tương ứng được coi là một iterable và có thể sử dụng trực tiếp trong vòng lặp for.

Phương thức __iter__() phải trả lại một object thuộc kiểu iterator. Phương thức này sẽ được gọi tự động để lấy iterator, ví dụ, khi sử dụng trong vòng lặp for.

Hãy nhìn lại code của FibonacciIterable bạn sẽ thấy những triết lý trên đều được vận dụng :

class FibonacciIterable:
    def __init__(self, count=10):
        self.count = count
    def __iter__(self):
        return FibonacciIterator(self.count)

Hàm tạo của class này nhận giá trị count – số lượng phần tử của dãy sẽ tạo.

Class này kiến thiết xây dựng phương pháp __iter__ ( ). Phương thức này chỉ làm trách nhiệm trả lại một object của iterator FibonacciIterator .Cũng chú ý rằng, FibonacciIterable là class sẽ được sử dụng trong client code còn FibonacciIterator sẽ ở “ hậu trường ”. Người sử dụng sẽ chỉ biết đến iterable chứ không biết đến iterator. Như vậy, mặc dầu client code cung ứng biến count cho iterable, trong thực tiễn giá trị này sẽ truyền sang cho iterator để sử dụng. Tự bản thân iterable không sử dụng .

Iterator trong Python

Iterator là loại object có nhiệm vụ tạo ra dữ liệu cho iterable. Tuy nhiên, tự bản thân iterator lại không phải là một kiểu dữ liệu container. Iterator phải phối hợp với iterable thì mới tạo ra được một container có dữ liệu và có thể duyệt được dữ liệu.

Tổ hợp iterable-iterator hoàn toàn có thể tưởng tượng giống như một chiếc thùng ( iterable ), và bạn hoàn toàn có thể bỏ vào đó gạch hoặc ngói hoặc bất kể vật gì. Việc bỏ vào thùng vật gì sẽ do iterator quyết định hành động .

Iterator object bắt buộc phải xây dựng phương thức __next__(). Nhiệm vụ của phương thức này là tạo ra phần tử cho iterable. Phương thức này cũng được gọi tự động nếu client code cần lấy dữ liệu. Trong phương thức __next__(), nếu không còn phần tử nào nữa thì cần phát ra ngoại lệ StopInteration.

Hãy xem lại code của lớp FibonacciIterator :

class FibonacciIterator:
    def __init__(self, count=10):
        self.a, self.b = 0, 1
        self.count = count
        self.__i = 0
    def __next__(self):
        if self.__i > self.count:
            raise StopIteration()
        value = self.a
        self.a, self.b = self.b, self.a + self.b
        self.__i += 1
        return value
    # def __iter__(self):
    #     return self

Khi khởi tạo object sẽ gán giá trị hai thành phần tiên phong của dãy ( a = 0, b = 1 ), nhận giá trị biến count ( số lượng thành phần cần tạo ), và gán biến đếm __i = 0 .Mỗi lần gọi phương pháp __next__ ( ) sẽ kiểm tra biến đếm. Nếu biến đếm vượt quá count sẽ phát ra ngoại lệ StopIteration. Đây là nhu yếu bắt buộc của __next__ ( ) nếu muốn kết thúc duyệt tài liệu. Nếu biến đếm trong số lượng giới hạn thì trả lại giá trị hiện tại của a, đồng thời update giá trị mới cho a, b ( a = b, còn b = a + b ). Logic tạo ra a và b trọn vẹn theo định nghĩa của chuỗi Fibonacci .Thông thường, trong iterator hoàn toàn có thể thực thi cả __iter__ ( ) giúp một iterator cũng đồng thời là một iterable. Trong trường hợp này __iter__ ( ) của iterator chỉ cần trả lại chính object đó ( self ). Tuy nhiên, để giúp bạn thuận tiện phân biệt iterable và iterator, tất cả chúng ta trong thời điểm tạm thời comment phương pháp __iter__ ( ) trong iterator .

Cách hoạt động giải trí của vòng lặp for

Iterable là loại object mà bạn hoàn toàn có thể trực tiếp sử dụng với vòng lặp for của Python .Mặc dù bạn đã học cách sử dụng vòng lặp for trước đây. Tuy nhiên, hẳn bạn đã nhận ra rằng vòng lặp for của Python không thực sự giống như vòng lặp for của C / C + + / Java hay C #. Vòng lặp for của Python, về hình thức, thì tương tự như như for của những ngôn từ kiểu C nhưng lại hoạt động giải trí giống như foreach của C # / C + + / Java .Hãy xem lại hàm Test4 :

def test4():
    print('Test 4:', end=' ')
    iterable = FibonacciIterable(15)
    iterator = iter(iterable)
    while True:
        try:
            f = next(iterator)
            print(f, end=' ')
        except StopIteration:
            break
    print()

Mặc dù ở đây sử dụng vòng lặp while, đó chính là mô phỏng hoạt động giải trí của vòng lặp for trên trong thực tiễn .Logic hoạt động giải trí của vòng lặp for như sau :

  • Gọi hàm tích hợp iter(). Hàm này thực tế sẽ gọi __iter__() của iterable để lấy object iterator tương ứng.
  • Trên iterator liên tục gọi hàm next() để lấy dữ liệu cụ thể. Hàm này sẽ gọi tới __next__() của iterator.
  • Thực hiện các xử lý cần thiết trên dữ liệu vừa lấy được.
  • Nếu gặp ngoại lệ StopIteration thì dừng vòng lặp.

Bằng cách này bạn hoàn toàn có thể tự mình tạo ra một ‘ vòng lặp ’ riêng thay cho for .Dĩ nhiên, cách sử dụng iterable này rất cồng kệnh. Chúng ta đưa ra để bạn hiểu nguyên tắc hoạt động giải trí của vòng for .Trên trong thực tiễn, vòng lặp for của Python tự động hóa triển khai toàn bộ những thao tác trên nếu bạn phân phối cho nó một iterable. Đây là trường hợp của hàm test1 ( ) :

def test1():
    print('Test 1:', end=' ')
    iterable = FibonacciIterable(15)
    for f in iterable:
        print(f, end=' ')
    print()

Bạn tạo ra một object iterable từ class FibonacciIterable và cấp thẳng cho lệnh for. Lệnh for sẽ tự động hóa triển khai theo logic tất cả chúng ta đã chỉ ở trên. Mỗi lần gọi next ( ) sẽ trả giá trị về cho biến tạm f để tất cả chúng ta sử dụng .Trong trường hợp bạn không thiết kế xây dựng iterable mà chỉ có iterator, bạn không hề trực tiếp sử dụng iterator trong vòng lặp for. Khi đó bạn phải sử dụng theo kiểu của hàm test3 :

def test3():
    print('Test 3:', end=' ')
    iterator = FibonacciIterator(15)
    while True:
        try:
            f = next(iterator)
            print(f, end=' ')
        except StopIteration:
            break
    print()

Ở đây bạn phải làm thủ công bằng tay với vòng lặp while theo đúng logic đã trình diễn ở trên .Như đã nói, iterator cũng thường kiến thiết xây dựng luôn cả phương pháp __iter__ ( ) giúp cho iterator đồng thời đóng vai trò của iterable. Nếu bạn bỏ dấu comment của phương pháp __iter__ ( ) trong FibonacciIterator, bạn hoàn toàn có thể sử dụng hàm test2 :

def test2():
    print('Test 2:', end=' ')
    iterator = FibonacciIterator(15)
    for f in iterator:
        print(f, end=' ')
    print()

Ở đây object iterator đồng thời đóng luôn vai trò của iterable. Do vậy bạn dùng được nó trong vòng lặp for.

Kết luận

Trong bài học kinh nghiệm này tất cả chúng ta đã học về iterable và iterator. Đây là một chủ đề khó và rất hay gây nhầm lẫn. Không có nhiều tài liệu trên mạng lý giải được rõ ràng yếu tố này .

  • Iterable và iterator là hai bộ phận của mẫu thiết kế iterator giúp phân tách thùng chứa (iterable) và dữ liệu (iterator).
  • Iterable được sử dụng bởi client code trong vòng lặp for; Iterator được vòng lặp này tự động tạo ra từ iterable và sử dụng để lấy dữ liệu.
  • Iterable phải thực thư phương thức __iter__() nhằm chỉ định iterator.
  • Iterator phải thực thi phương thức __nex__() để lấy dữ liệu.
  • Thông thường iterator cũng thực thi luôn cả __iter__(), do vậy iterator có thể đồng thời đóng vai trò iterable.

+ Nếu bạn thấy site hữu ích, trước khi rời đi hãy giúp đỡ site bằng một hành động nhỏ để site có thể phát triển và phục vụ bạn tốt hơn.
+ Nếu bạn thấy bài viết hữu ích, hãy giúp chia sẻ tới mọi người.
+ Nếu có thắc mắc hoặc cần trao đổi thêm, mời bạn viết trong phần thảo luận cuối trang.
Cảm ơn bạn!

Để lại một bình luận

Email của bạn sẽ không được hiển thị công khai. Các trường bắt buộc được đánh dấu *