Bir konteyner nesnesi (Python) için __iter __ (self) nasıl uygulanır


116

Özel bir kapsayıcı nesnesi yazdım.

Bu sayfaya göre , bu yöntemi nesnem üzerinde uygulamam gerekiyor:

__iter__(self)

Bununla birlikte, Python referans kılavuzundaki Yineleyici Türlerine olan bağlantıyı izledikten sonra, kendinizinkini nasıl uygulayacağınıza dair hiçbir örnek verilmemiştir.

Birisi bunun nasıl yapılacağını gösteren bir ön bilgi (veya bir kaynağa bağlantı) gönderebilir mi?

Yazdığım konteyner bir haritadır (yani değerleri benzersiz anahtarlarla saklar). dicts şu şekilde yinelenebilir:

for k, v in mydict.items()

Bu durumda yineleyicide iki öğe (bir tuple?) Döndürebilmem gerekiyor. Böyle bir yineleyicinin nasıl uygulanacağı hala net değil (nazikçe sağlanan birkaç cevaba rağmen). Birisi lütfen harita benzeri bir konteyner nesnesi için bir yineleyicinin nasıl uygulanacağına biraz daha ışık tutabilir mi? (yani bir dikte görevi gören özel bir sınıf)?

Yanıtlar:


120

Normalde bir jeneratör işlevi kullanırdım. Bir getiri ifadesini her kullandığınızda, diziye bir öğe ekleyecektir.

Aşağıdaki, beş ve sonra bir_listedeki her öğeyi veren bir yineleyici oluşturacaktır.

def __iter__(self):
   yield 5
   yield from some_list

3.3 öncesi, yield frommevcut değildi, bu yüzden yapmanız gerekecek:

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

StopIteration'ı yükseltmek gerekiyor mu? Bu ne zaman duracağını nasıl ayırt ediyor?
Jonathan

1
@JonathanLeaders içindeki tüm öğeler some_listverildiğinde.
laike9m

Aslında - Bu kullanma dosyası ile - değerlerini veren durdurmak isterseniz sadece zam StopIteration gerek önce some_list dışarı atılır.
Tim Peoples

21
StopIterationOluşturucu işlevi döndüğünde, ya siz açıkça çağırarak returnya da işlevin sonuna ulaşarak (tüm işlevlerin return Nonesonunda örtük bir yere sahip olduğu gibi) Python tarafından otomatik olarak yükseltilir . Açıkça yükselterek StopIterationgerekli değildir ve Python 3.5 itibariyle aslında işi (göreceksiniz PEP 479 ): jeneratörler açmak gibi returniçine StopIteration, bunlar açık çevirmek raise StopIterationbir içine RuntimeError.
Arthur Tacca

28

Diğer bir seçenek de, burada belgelendiği gibi koleksiyonlar modülünden uygun soyut temel sınıftan miras almaktır .

Konteynerin kendi yineleyicisi olması durumunda, 'den devralabilirsiniz collections.Iterator. O zaman sadece nextyöntemi uygulamanız gerekir .

Bir örnek:

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

Baktığınız iken collectionsmodül, devralan düşünün Sequence, Mappingo daha uygun olup olmadığını veya başka bir soyut temel sınıfı. Aşağıda bir Sequencealt sınıf için bir örnek verilmiştir :

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

Not : Glenn Maynard'a, bir yandan yineleyiciler ile diğer yandan yineleyicilerden ziyade yinelenebilen kapsayıcılar arasındaki farkı netleştirme ihtiyacına dikkatimi çektiği için teşekkür ederim.


13
Yinelenebilir nesneleri ve yineleyicileri karıştırmayın - yineleyici olmayan yinelenebilir bir nesne için Yineleyici'den miras almak istemezsiniz (örneğin, bir kap).
Glenn Maynard

@Glenn: Tipik bir konteynerin yineleyici olmadığı konusunda haklısınız. Yineleyici türlerinden bahseden soruyu takip ettim. Cevabın sonunda söylediğim gibi daha uygun bir seçenekten miras almanın daha uygun olduğunu düşünüyorum. Cevapta bu noktayı açıklığa kavuşturacağım.
Muhammad Alkarouri

13

__iter__()next () yöntemini (oluşturucu nesnesi) zaten tanımladıysanız genellikle sadece self döndürür:

İşte bir jeneratörün Sahte bir örneği:

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

ancak __iter__()şu şekilde de kullanılabilir: http://mail.python.org/pipermail/tutor/2006-January/044455.html


7
Yineleyici sınıfları için yaptığınız şey budur, ancak soru konteyner nesneleriyle ilgilidir.
Glenn Maynard

11

Nesneniz, nesnenizin yinelemesini bağlamak istediğiniz bir dizi veri içeriyorsa, hile yapabilir ve bunu yapabilirsiniz:

>>> 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
Kontrol etmek yerine şunu hasattrkullanıntry/except AttributeError
IceArdor

10

Python'daki "yinelenebilir arayüz" iki yöntemden oluşur __next__()ve __iter__(). __next__O yineleyici davranış tanımlar olarak işlev, en önemli olan - bir fonksiyon değeri sonraki döndürülmesi gerektiğini belirler. __iter__()Yöntem, yineleme başlangıç noktasını sıfırlamak için kullanılır. Çoğunlukla, başlangıç ​​noktasını ayarlamak için kullanıldığında __iter__()sadece kendine dönebileceğini göreceksiniz __init__().

"Yinelenebilir arabirimi" uygulayan ve herhangi bir sıra sınıfından herhangi bir örnek üzerinde bir yineleyici tanımlayan bir Sınıf Tersini tanımlamak için aşağıdaki koda bakın. __next__()Yöntem dizisinin ters sırayla sekansı ve döner değerlerinin sonunda başlar. "Sıra arabirimini" uygulayan bir sınıfın örneklerinin bir __len__()ve bir __getitem__()yöntemi tanımlaması gerektiğini unutmayın .

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

İle ilgili soruyu cevaplamak için eşleştirmeleri : senin sağlanan __iter__üzerinde yineleme gereken anahtarları eşleme. Aşağıdaki, bir eşleme oluşturan x -> x * xve ABC eşlemesini genişleten Python3 üzerinde çalışan basit bir örnektir .

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

dictBaşkalarının önerdiği gibi miras almak istemiyorsanız , işte __iter__kaba bir özel dikt örneği için nasıl uygulanacağı sorusuna doğrudan cevap :

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

Bu, burada iyi anlatılan bir jeneratör kullanır .

Biz devralan çalıştığından beri Mapping, ayrıca uygulamak gerekir __getitem__ve __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

Bazı durumlarda işe yarayabilecek bir seçenek, özel sınıfınızın miras alınmasını sağlamaktır dict. Bu bir kural gibi davranıyorsa mantıklı bir seçim gibi görünüyor; belki de bir buyruk olmalı . Bu şekilde, ücretsiz olarak dikte benzeri yineleme elde edersiniz.

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

Çıktı:

Some name
a => 1
b => 2

2

Örneğin, dikteden gizlemek için, onu değiştirin, iterörneğin, 2for döngüsündeyken anahtarı atla

# 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
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.