Cách triển khai __iter __ (self) cho một đối tượng vùng chứa (Python)


115

Tôi đã viết một đối tượng vùng chứa tùy chỉnh.

Theo trang này , tôi cần triển khai phương thức này trên đối tượng của mình:

__iter__(self)

Tuy nhiên, khi theo liên kết đến Các loại lặp lại trong sổ tay tham chiếu Python, không có ví dụ nào được đưa ra về cách triển khai của riêng bạn.

Ai đó có thể đăng một đoạn mã (hoặc liên kết đến một tài nguyên), hướng dẫn cách thực hiện điều này không?

Vùng chứa mà tôi đang viết, là một bản đồ (tức là lưu trữ các giá trị bằng các khóa duy nhất). các phái có thể được lặp lại như thế này:

for k, v in mydict.items()

Trong trường hợp này, tôi cần có thể trả về hai phần tử (một bộ giá trị?) Trong trình lặp. Vẫn chưa rõ làm thế nào để triển khai một trình lặp như vậy (mặc dù một số câu trả lời đã được cung cấp). Ai đó có thể vui lòng làm sáng tỏ thêm về cách triển khai trình lặp cho một đối tượng vùng chứa giống như bản đồ không? (tức là một lớp tùy chỉnh hoạt động giống như một dict)?

Câu trả lời:


120

Tôi thường sử dụng một hàm máy phát điện. Mỗi lần bạn sử dụng câu lệnh lợi nhuận, nó sẽ thêm một mục vào chuỗi.

Phần sau sẽ tạo một trình lặp cho ra năm, sau đó là mọi mục trong some_list.

def __iter__(self):
   yield 5
   yield from some_list

Trước 3.3, yield fromkhông tồn tại, vì vậy bạn sẽ phải làm:

def __iter__(self):
   yield 5
   for x in some_list:
      yield x

Có cần tăng StopIteration không? Làm thế nào để phân biệt điều này khi dừng lại?
Jonathan

1
@JonathanLeaders Khi tất cả các phần tử trong some_listđã được tạo ra.
laike9m

Trên thực tế - với trường hợp sử dụng này - bạn chỉ cần tăng StopIteration nếu bạn muốn dừng các giá trị mang lại trước khi some_list hết.
Tim

21
StopIterationđược Python tự động nâng lên khi hàm trình tạo trả về, do bạn gọi một cách rõ ràng returnhoặc bằng cách đến cuối hàm (giống như tất cả các hàm đều có ẩn return Noneở cuối). Việc nâng cấp rõ ràng StopIterationlà không cần thiết và kể từ Python 3.5 sẽ thực sự không hoạt động (xem PEP 479 ): giống như trình tạo biến returnthành StopIteration, chúng biến rõ ràng raise StopIterationthành a RuntimeError.
Arthur Tacca

28

Một tùy chọn khác là kế thừa từ lớp cơ sở trừu tượng thích hợp từ mô-đun bộ sưu tập như được nêu ở đây .

Trong trường hợp vùng chứa là trình lặp của riêng nó, bạn có thể kế thừa từ collections.Iterator. Bạn chỉ cần thực hiện nextphương pháp sau đó.

Một ví dụ là:

>>> from collections import Iterator
>>> class MyContainer(Iterator):
...     def __init__(self, *data):
...         self.data = list(data)
...     def next(self):
...         if not self.data:
...             raise StopIteration
...         return self.data.pop()
...         
...     
... 
>>> c = MyContainer(1, "two", 3, 4.0)
>>> for i in c:
...     print i
...     
... 
4.0
3
two
1

Trong khi bạn đang nhìn vào collectionsmô-đun, hãy xem xét kế thừa từ Sequence, Mappinghay cách khác lớp cơ sở trừu tượng nếu đó là thích hợp hơn. Đây là một ví dụ cho một Sequencelớp con:

>>> from collections import Sequence
>>> class MyContainer(Sequence):
...     def __init__(self, *data):
...         self.data = list(data)
...     def __getitem__(self, index):
...         return self.data[index]
...     def __len__(self):
...         return len(self.data)
...         
...     
... 
>>> c = MyContainer(1, "two", 3, 4.0)
>>> for i in c:
...     print i
...     
... 
1
two
3
4.0

NB : Cảm ơn Glenn Maynard đã thu hút sự chú ý của tôi đến sự cần thiết phải làm rõ sự khác biệt giữa các trình vòng lặp một mặt và các thùng chứa là các tệp lặp hơn là các trình vòng lặp.


13
Không trộn lẫn các đối tượng có thể lặp lại và trình vòng lặp - bạn không muốn kế thừa từ Iterator cho một đối tượng có thể lặp lại không phải là bản thân trình vòng lặp (ví dụ: một vùng chứa).
Glenn Maynard

@Glenn: Bạn nói đúng rằng một vùng chứa điển hình không phải là một trình lặp. Tôi vừa theo dõi câu hỏi, đề cập đến các loại trình lặp. Tôi nghĩ rằng nó là thích hợp hơn để kế thừa từ một lựa chọn phù hợp hơn như tôi đã nói ở phần cuối của câu trả lời. Tôi sẽ làm rõ điểm này trong câu trả lời.
Muhammad Alkarouri

13

thường __iter__()chỉ trả về self nếu bạn đã xác định phương thức next () (đối tượng máy tạo):

đây là một ví dụ Dummy của một máy phát điện:

class Test(object):

    def __init__(self, data):
       self.data = data

    def next(self):
        if not self.data:
           raise StopIteration
        return self.data.pop()

    def __iter__(self):
        return self

nhưng __iter__()cũng có thể được sử dụng như sau: http://mail.python.org/pipermail/tutor/2006-January/044455.html


7
Đó là những gì bạn làm đối với các lớp trình lặp, nhưng câu hỏi là về các đối tượng vùng chứa.
Glenn Maynard

11

Nếu đối tượng của bạn chứa một tập hợp dữ liệu mà bạn muốn liên kết với đối tượng của mình, bạn có thể gian lận và thực hiện điều này:

>>> class foo:
    def __init__(self, *params):
           self.data = params
    def __iter__(self):
        if hasattr(self.data[0], "__iter__"):
            return self.data[0].__iter__()
        return self.data.__iter__()
>>> d=foo(6,7,3,8, "ads", 6)
>>> for i in d:
    print i
6
7
3
8
ads
6

2
Thay vì kiểm tra hasattr, hãy sử dụngtry/except AttributeError
IceArdor

9

"Giao diện có thể lặp lại" trong python bao gồm hai phương thức __next__()__iter__(). Các __next__chức năng là quan trọng nhất, vì nó xác định hành vi iterator - có nghĩa là, chức năng xác định những gì giá trị nên được trả lại sau. Các __iter__()phương pháp được sử dụng để thiết lập lại điểm bắt đầu của lần lặp. Thông thường, bạn sẽ thấy rằng __iter__()chỉ có thể tự trả lại khi __init__()được sử dụng để đặt điểm bắt đầu.

Xem đoạn mã sau để xác định Đảo ngược lớp thực hiện "giao diện có thể lặp lại" và xác định một trình lặp trên bất kỳ trường hợp nào từ bất kỳ lớp trình tự nào. Các __next__()phương pháp bắt đầu vào cuối những giá trị chuỗi và trả về theo thứ tự ngược của dãy. Lưu ý rằng các trường hợp từ một lớp thực hiện "giao diện trình tự" phải xác định một __len__()và một __getitem__()phương thức.

class Reverse:
    """Iterator for looping over a sequence backwards."""
    def __init__(self, seq):
        self.data = seq
        self.index = len(seq)

    def __iter__(self):
        return self

    def __next__(self):
        if self.index == 0:
            raise StopIteration
        self.index = self.index - 1
        return self.data[self.index]

>>> rev = Reverse('spam')
>>> next(rev)   # note no need to call iter()
'm'
>>> nums = Reverse(range(1,10))
>>> next(nums)
9

7

Để trả lời câu hỏi về ánh xạ : bạn đã cung cấp __iter__phải lặp lại các khóa của ánh xạ. Sau đây là một ví dụ đơn giản tạo ánh xạ x -> x * xvà hoạt động trên Python3 mở rộng ánh xạ ABC.

import collections.abc

class MyMap(collections.abc.Mapping):
    def __init__(self, n):
        self.n = n

    def __getitem__(self, key): # given a key, return it's value
        if 0 <= key < self.n:
            return key * key
        else:
            raise KeyError('Invalid key')

    def __iter__(self): # iterate over all keys
        for x in range(self.n):
            yield x

    def __len__(self):
        return self.n

m = MyMap(5)
for k, v in m.items():
    print(k, '->', v)
# 0 -> 0
# 1 -> 1
# 2 -> 4
# 3 -> 9
# 4 -> 16

4

Trong trường hợp bạn không muốn kế thừa từ dictnhững người khác đã đề xuất, đây là câu trả lời trực tiếp cho câu hỏi về cách triển khai __iter__cho một ví dụ thô về một câu lệnh tùy chỉnh:

class Attribute:
    def __init__(self, key, value):
        self.key = key
        self.value = value

class Node(collections.Mapping):
    def __init__(self):
        self.type  = ""
        self.attrs = [] # List of Attributes

    def __iter__(self):
        for attr in self.attrs:
            yield attr.key

Điều đó sử dụng một máy phát điện, được mô tả kỹ ở đây .

Vì chúng tôi kế thừa từ đó Mapping, bạn cũng cần phải triển khai __getitem____len__:

    def __getitem__(self, key):
        for attr in self.attrs:
            if key == attr.key:
                return attr.value
        raise KeyError

    def __len__(self):
        return len(self.attrs)

2

Một tùy chọn có thể hoạt động đối với một số trường hợp là làm cho lớp tùy chỉnh của bạn kế thừa từ dict. Đây có vẻ là một lựa chọn hợp lý nếu nó hoạt động giống như một mệnh lệnh; có lẽ nó phải một mệnh lệnh. Bằng cách này, bạn nhận được sự lặp lại giống như dict miễn phí.

class MyDict(dict):
    def __init__(self, custom_attribute):
        self.bar = custom_attribute

mydict = MyDict('Some name')
mydict['a'] = 1
mydict['b'] = 2

print mydict.bar
for k, v in mydict.items():
    print k, '=>', v

Đầu ra:

Some name
a => 1
b => 2

2

ví dụ cho inhert từ dict, sửa đổi nó iter, ví dụ, bỏ qua phím 2khi trong vòng lặp for

# method 1
class Dict(dict):
    def __iter__(self):
        keys = self.keys()
        for i in keys:
            if i == 2:
                continue
            yield i

# method 2
class Dict(dict):
    def __iter__(self):
        for i in super(Dict, self).__iter__():
            if i == 2:
                continue
            yield i
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.