Cara mengimplementasikan __iter __ (self) untuk objek kontainer (Python)

115

Saya telah menulis objek kontainer khusus.

Menurut halaman ini , saya perlu menerapkan metode ini pada objek saya:

__iter__(self)

Namun, setelah menindaklanjuti tautan ke Jenis Iterator di manual referensi Python, tidak ada contoh yang diberikan tentang cara mengimplementasikan milik Anda sendiri.

Dapatkah seseorang memposting cuplikan (atau menautkan ke sumber daya), yang menunjukkan cara melakukan ini?

Wadah yang saya tulis, adalah peta (yaitu, menyimpan nilai dengan kunci unik). penis bisa diulang seperti ini:

for k, v in mydict.items()

Dalam hal ini saya harus bisa mengembalikan dua elemen (tuple?) Di iterator. Masih belum jelas bagaimana menerapkan iterator semacam itu (terlepas dari beberapa jawaban yang telah diberikan dengan baik hati). Bisakah seseorang menjelaskan lebih lanjut tentang cara menerapkan iterator untuk objek kontainer seperti peta? (yaitu kelas khusus yang bertindak seperti dikt)?

skyeagle
sumber

Jawaban:

120

Saya biasanya menggunakan fungsi generator. Setiap kali Anda menggunakan pernyataan hasil, itu akan menambahkan item ke urutan.

Berikut ini akan membuat iterator yang menghasilkan lima, dan kemudian setiap item di some_list.

def __iter__(self):
   yield 5
   yield from some_list

Sebelum 3.3, yield fromtidak ada, jadi Anda harus melakukan:

def __iter__(self):
   yield 5
   for x in some_list:
      yield x
mikerobi
sumber
Apakah seseorang perlu menaikkan StopIteration? Bagaimana ini membedakan kapan harus berhenti?
Jonathan
1
@JonathanLeaders Saat semua elemen di some_listtelah dihasilkan.
laike9m
Sebenarnya - dengan kasus penggunaan ini - Anda hanya perlu menaikkan StopIteration jika Anda ingin berhenti menghasilkan nilai sebelum some_list habis.
Tim Peoples
21
StopIterationsecara otomatis dimunculkan oleh Python saat fungsi generator kembali, baik oleh Anda secara eksplisit memanggil returnatau dengan mencapai akhir fungsi (yang seperti semua fungsi memiliki implisit return Nonedi bagian akhir). Secara eksplisit meningkatkan StopIterationtidak diperlukan dan sebagai Python 3.5 akan benar-benar tidak bekerja (lihat PEP 479 ): seperti generator berubah returnmenjadi StopIteration, mereka berpaling eksplisit raise StopIterationmenjadi RuntimeError.
Arthur Tacca
28

Opsi lainnya adalah mewarisi dari kelas dasar abstrak yang sesuai dari modul `koleksi seperti yang didokumentasikan di sini .

Jika penampung adalah iteratornya sendiri, Anda dapat mewarisi dari collections.Iterator. Anda hanya perlu mengimplementasikan nextmetode itu.

Contohnya adalah:

>>> 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

Saat Anda melihat collectionsmodul, pertimbangkan untuk mewarisi dari Sequence, Mappingatau kelas dasar abstrak lainnya jika itu lebih sesuai. Berikut adalah contoh untuk Sequencesubclass:

>>> 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 : Terima kasih kepada Glenn Maynard karena telah menarik perhatian saya pada kebutuhan untuk mengklarifikasi perbedaan antara iterator di satu sisi dan container yang iterable daripada iterator di sisi lain.

Muhammad Alkarouri
sumber
13
Jangan mencampur objek dan iterator yang dapat diulang - Anda tidak ingin mewarisi dari Iterator untuk objek yang dapat diulang yang bukan merupakan iterator itu sendiri (mis. Wadah).
Glenn Maynard
@ Glenn: Anda benar bahwa container biasa bukanlah iterator. Saya hanya mengikuti pertanyaan, yang menyebutkan tipe iterator. Saya pikir lebih tepat untuk mewarisi dari opsi yang lebih cocok seperti yang saya katakan di akhir jawaban. Saya akan menjelaskan poin ini dalam jawaban.
Muhammad Alkarouri
13

biasanya __iter__()hanya mengembalikan diri jika Anda sudah mendefinisikan metode next () (objek generator):

berikut adalah contoh Dummy generator:

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

tetapi __iter__()juga dapat digunakan seperti ini: http://mail.python.org/pipermail/tutor/2006-January/044455.html

mouad
sumber
7
Itulah yang Anda lakukan untuk kelas iterator, tetapi pertanyaannya adalah tentang objek kontainer.
Glenn Maynard
11

Jika objek Anda berisi sekumpulan data yang ingin Anda kaitkan dengan iter objek, Anda dapat menipu dan melakukan ini:

>>> 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
Squirrelsama
sumber
2
Alih-alih mencentang hasattr, gunakantry/except AttributeError
IceArdor
9

The "iterable interface" di python terdiri dari dua metode __next__()dan__iter__() . The __next__fungsi yang paling penting, karena mendefinisikan perilaku iterator - yaitu, fungsi menentukan nilai apa yang harus dikembalikan berikutnya. The __iter__()metode yang digunakan untuk me-reset titik awal dari iterasi. Seringkali, Anda akan menemukan bahwa __iter__()dapat kembali ke diri sendiri ketika __init__()digunakan untuk menetapkan titik awal.

Lihat kode berikut untuk menentukan Pembalikan Kelas yang mengimplementasikan "antarmuka iterable" dan mendefinisikan iterator pada setiap instance dari kelas urutan apa pun. The __next__()Metode dimulai pada akhir urutan dan kembali nilai-nilai dalam urutan terbalik dari urutan. Perhatikan bahwa instance dari kelas yang menerapkan "antarmuka urutan" harus menentukan __len__()dan __getitem__()metode.

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
FredAKA
sumber
7

Untuk menjawab pertanyaan tentang pemetaan : yang Anda berikan __iter__harus mengulangi kunci pemetaan. Berikut ini adalah contoh sederhana yang membuat pemetaan x -> x * xdan bekerja pada Python3 yang memperluas pemetaan 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
Juan A. Navarro
sumber
4

Jika Anda tidak ingin mewarisi dictseperti yang disarankan orang lain, berikut adalah jawaban langsung untuk pertanyaan tentang bagaimana menerapkan __iter__untuk contoh kasar dari custom dict:

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

Itu menggunakan generator, yang dijelaskan dengan baik di sini .

Karena kita mewarisi dari Mapping, Anda juga perlu menerapkan __getitem__dan __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)
jfritz42.dll
sumber
2

Salah satu opsi yang mungkin berhasil untuk beberapa kasus adalah membuat kelas kustom Anda diturunkan dari dict. Ini sepertinya pilihan yang logis jika bertindak seperti dikt; mungkin itu harus menjadi sebuah dikt. Dengan cara ini, Anda mendapatkan iterasi seperti dikt secara gratis.

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

Keluaran:

Some name
a => 1
b => 2
Eddified
sumber
2

contoh untuk mewarisi dari dict, modifikasi iter, misalnya, lewati kunci 2saat di for loop

# 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
WeizhongTu
sumber