วิธีใช้ __iter __ (ด้วยตนเอง) สำหรับวัตถุคอนเทนเนอร์ (Python)


115

ฉันได้เขียนอ็อบเจ็กต์คอนเทนเนอร์ที่กำหนดเอง

ตามหน้านี้ฉันต้องใช้วิธีนี้กับวัตถุของฉัน:

__iter__(self)

อย่างไรก็ตามในการติดตามลิงก์ไปยังIterator typesในคู่มืออ้างอิง Python ไม่มีตัวอย่างวิธีการใช้งานของคุณเอง

ใครบางคนสามารถโพสต์ตัวอย่างข้อมูล (หรือลิงก์ไปยังแหล่งข้อมูล) ที่แสดงวิธีการนี้ได้หรือไม่

คอนเทนเนอร์ที่ฉันกำลังเขียนคือแผนที่ (เช่นเก็บค่าตามคีย์ที่ไม่ซ้ำกัน) คำสั่งสามารถทำซ้ำได้ดังนี้:

for k, v in mydict.items()

ในกรณีนี้ฉันต้องสามารถส่งคืนสององค์ประกอบ (ทูเพิล?) ในตัววนซ้ำ ยังไม่ชัดเจนว่าจะใช้ตัวทำซ้ำดังกล่าวอย่างไร (แม้จะมีคำตอบหลายประการที่ให้ไว้) มีใครช่วยอธิบายเพิ่มเติมเกี่ยวกับวิธีใช้ตัววนซ้ำสำหรับวัตถุคอนเทนเนอร์แบบแผนที่ได้ไหม (คือคลาสแบบกำหนดเองที่ทำหน้าที่เหมือนคำสั่ง)?

คำตอบ:


120

ปกติฉันจะใช้ฟังก์ชันเครื่องกำเนิดไฟฟ้า ทุกครั้งที่คุณใช้ใบแจ้งยอดผลตอบแทนจะเพิ่มรายการลงในลำดับ

ต่อไปนี้จะสร้างตัววนซ้ำที่ให้ผลห้าและทุกรายการใน some_list

def __iter__(self):
   yield 5
   yield from some_list

ไม่มีอยู่ก่อน 3.3 yield fromดังนั้นคุณจะต้องทำ:

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

จำเป็นต้องเพิ่ม StopIteration หรือไม่? สิ่งนี้แยกแยะได้อย่างไรว่าเมื่อใดควรหยุด?
Jonathan

1
@JonathanLeaders เมื่อทุกองค์ประกอบในsome_listได้รับผล.
laike9m

ที่จริงแล้ว - ในกรณีการใช้งานนี้คุณจะต้องเพิ่ม StopIteration หากคุณต้องการหยุดการให้ค่าก่อนที่ some_listจะหมดลง
Tim Peoples

21
StopIterationจะถูกยกขึ้นโดย Python โดยอัตโนมัติเมื่อฟังก์ชันตัวสร้างกลับมาไม่ว่าคุณจะเรียกอย่างชัดเจนreturnหรือเมื่อถึงจุดสิ้นสุดของฟังก์ชัน (ซึ่งเช่นเดียวกับฟังก์ชันทั้งหมดจะมีนัยreturn Noneในตอนท้าย) อย่างชัดเจนยกStopIterationไม่จำเป็นและเป็นของงูใหญ่ 3.5 จริงจะไม่ทำงาน (ดูPEP 479 ) เช่นเดียวกับเครื่องกำเนิดไฟฟ้าเปิดreturnเข้าไปในStopIterationพวกเขาหันอย่างชัดเจนเป็นraise StopIteration RuntimeError
Arthur Tacca

28

อีกตัวเลือกหนึ่งคือการสืบทอดจากคลาสฐานนามธรรมที่เหมาะสมจาก `คอลเลกชันโมดูลเป็นเอกสารที่นี่

ในกรณีที่ภาชนะที่เป็น iterator collections.Iteratorของตัวเองคุณสามารถรับมรดกจาก คุณจะต้องใช้nextวิธีนี้เท่านั้น

ตัวอย่างคือ:

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

ในขณะที่คุณกำลังมองหาที่collectionsโมดูลพิจารณาสืบทอดจากSequence, Mappingหรืออีกชั้นฐานนามธรรมว่ามีความเหมาะสมมากขึ้น นี่คือตัวอย่างสำหรับSequenceคลาสย่อย:

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

หมายเหตุ : ขอบคุณ Glenn Maynard ที่ดึงดูดความสนใจของฉันให้ต้องชี้แจงความแตกต่างระหว่างตัวทำซ้ำในมือข้างหนึ่งและคอนเทนเนอร์ที่วนซ้ำแทนที่จะเป็นตัวทำซ้ำในอีกด้านหนึ่ง


13
อย่าผสมวัตถุที่ทำซ้ำได้และตัวทำซ้ำ - คุณไม่ต้องการสืบทอดจาก Iterator สำหรับวัตถุที่ทำซ้ำได้ที่ไม่ใช่ตัววนซ้ำ (เช่นคอนเทนเนอร์)
เกล็นเมย์นาร์ด

@Glenn: คุณถูกต้องที่คอนเทนเนอร์ทั่วไปไม่ใช่ตัวทำซ้ำ ฉันเพิ่งทำตามคำถามซึ่งกล่าวถึงประเภทตัววนซ้ำ ฉันคิดว่ามันเหมาะสมกว่าที่จะสืบทอดจากตัวเลือกที่เหมาะสมกว่าดังที่ฉันได้กล่าวไว้ในตอนท้ายของคำตอบ ฉันจะชี้แจงประเด็นนี้ในคำตอบ
Muhammad Alkarouri

13

โดยปกติแล้วจะ__iter__()คืนค่าตัวเองหากคุณได้กำหนดวิธีการ () ถัดไป (วัตถุกำเนิด) แล้ว:

นี่คือตัวอย่าง Dummy ของเครื่องกำเนิดไฟฟ้า:

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

แต่__iter__()ยังสามารถใช้เช่นนี้: http://mail.python.org/pipermail/tutor/2006- มกราคม/044455.html


7
นั่นคือสิ่งที่คุณทำสำหรับคลาส iterator แต่คำถามเกี่ยวกับออบเจ็กต์คอนเทนเนอร์
Glenn Maynard

11

หากออบเจ็กต์ของคุณมีชุดข้อมูลที่คุณต้องการเชื่อมโยงกับวัตถุของคุณคุณสามารถโกงและทำสิ่งนี้ได้:

>>> 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
แทนที่จะตรวจสอบhasattrให้ใช้try/except AttributeError
IceArdor

10

"อินเทอร์เฟซที่ทำซ้ำได้" ใน python ประกอบด้วยสองวิธี__next__()และ__iter__(). __next__ฟังก์ชั่นเป็นสิ่งสำคัญที่สุดในขณะที่มันกำหนดพฤติกรรม iterator - นั่นคือฟังก์ชั่นกำหนดสิ่งที่มีค่าควรจะกลับต่อไป __iter__()วิธีการที่ใช้ในการตั้งค่าจุดเริ่มต้นของการทำซ้ำ บ่อยครั้งคุณจะพบว่า __iter__()สามารถคืนตัวเองได้เมื่อ__init__()ถูกใช้เพื่อกำหนดจุดเริ่มต้น

ดูรหัสต่อไปนี้สำหรับการกำหนดคลาสย้อนกลับซึ่งใช้ "อินเทอร์เฟซที่ทำซ้ำได้" และกำหนดตัววนซ้ำบนอินสแตนซ์ใด ๆ จากคลาสลำดับใด ๆ __next__()วิธีการเริ่มต้นในตอนท้ายของลำดับและผลตอบแทนค่าในลำดับที่กลับของลำดับ โปรดทราบว่าอินสแตนซ์จากคลาสที่ใช้ "ลำดับอินเทอร์เฟซ" ต้องกำหนด a __len__()และ__getitem__()วิธีการ

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

ในการตอบคำถามเกี่ยวกับการแมป : ที่คุณให้มา__iter__ควรย้ำบนคีย์ของการแมป ต่อไปนี้เป็นตัวอย่างง่ายๆที่สร้างการแมปx -> x * xและทำงานบน Python3 เพื่อขยายการแมป 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

ในกรณีที่คุณไม่ต้องการรับช่วงต่อจากdictที่คนอื่นแนะนำนี่คือคำตอบโดยตรงสำหรับคำถามเกี่ยวกับวิธีใช้__iter__สำหรับตัวอย่างคร่าวๆของคำสั่งที่กำหนดเอง:

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

ที่ใช้เครื่องกำเนิดไฟฟ้าซึ่งอธิบายไว้อย่างดีที่นี่ที่นี่

เนื่องจากเราได้รับมรดกจากMappingคุณจึงต้องใช้__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

ทางเลือกหนึ่งที่อาจทำงานสำหรับบางกรณีคือการทำให้ระดับของคุณเองที่สืบทอดdictจาก นี่ดูเหมือนจะเป็นทางเลือกที่สมเหตุสมผลหากทำหน้าที่เหมือนคำสั่ง บางทีมันควรจะเป็นคำสั่ง ด้วยวิธีนี้คุณจะได้รับคำสั่งซ้ำฟรี

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

เอาท์พุท:

Some name
a => 1
b => 2

2

ตัวอย่างสำหรับการสืบทอดจาก dict แก้ไขมันiterเช่นข้ามคีย์2เมื่ออยู่ในลูป

# 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
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.