Python'da, bir nesnenin yinelenebilir olup olmadığını nasıl belirleyebilirim?


1083

Gibi bir yöntem var mı isiterable? Şimdiye kadar bulduğum tek çözüm,

hasattr(myObj, '__iter__')

Ama bunun ne kadar aptalca olduğuna emin değilim.


18
__getitem__bir nesneyi tekrarlanabilir hale getirmek için de yeterli
Kos

4
FWIW: iter(myObj)eğer başarılı olursa isinstance(myObj, dict), bu yüzden s veya single myObjdizisi olabilirseniz , her iki durumda da başarılı olursunuz. Bir dizinin ne olduğunu ve ne olmadığını bilmek istiyorsanız önemli olan bir incelik. (Python 2'de)dictdict
Ben Mosher

7
__getitem__bir nesneyi yinelenebilir hale getirmek için de yeterlidir ... sıfır indeksinde başlıyorsa .
Carlos A. Gómez

Yanıtlar:


26

Bu sorunu birazdan inceliyorum. Buna dayanarak, bugünlerde bunun en iyi yaklaşım olduğu sonucuna varıyorum:

from collections.abc import Iterable   # drop `.abc` with Python 2.7 or lower

def iterable(obj):
    return isinstance(obj, Iterable)

Yukarıda daha önce önerilmişti, ancak genel fikir birliği, iter()kullanımın daha iyi olacağı yönündeydi:

def iterable(obj):
    try:
        iter(obj)
    except Exception:
        return False
    else:
        return True

Kodumuzda iter()bu amaç için de kullandık , ancak son zamanlarda sadece __getitem__tekrarlanabilir olarak kabul edilen nesneler tarafından daha fazla rahatsız olmaya başladım. __getitem__Yinelenemeyen bir nesneye sahip olmanın geçerli nedenleri vardır ve onlarla birlikte yukarıdaki kod iyi çalışmaz. Gerçek hayat örneği olarak Faker'i kullanabiliriz . Yukarıdaki kod, yinelenebilir olduğunu bildiriyor, ancak aslında yinelemeye çalışmak AttributeError(Faker 4.0.2 ile test edildi):

>>> from faker import Faker
>>> fake = Faker()
>>> iter(fake)    # No exception, must be iterable
<iterator object at 0x7f1c71db58d0>
>>> list(fake)    # Ooops
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/.../site-packages/faker/proxy.py", line 59, in __getitem__
    return self._factory_map[locale.replace('-', '_')]
AttributeError: 'int' object has no attribute 'replace'

Kullanacak insinstance()olsaydık, yanlışlıkla Faker örneklerinin (ya da yalnızca sahip olduğu diğer nesnelerin __getitem__) tekrarlanabilir olduğunu düşünmezdik:

>>> from collections.abc import Iterable
>>> from faker import Faker
>>> isinstance(Faker(), Iterable)
False

Daha önceki yanıtlar iter(), Python'da yinelemeyi uygulamanın eski yolunun temel alındığı __getitem__ve isinstance()yaklaşımın bunu algılamayacağı için kullanımın daha güvenli olduğunu belirtti. Bu, eski Python sürümleri için geçerli olabilir, ancak oldukça kapsamlı testlerime dayanarak isinstance()günümüzde harika çalışıyor. isinstance()İşe yaramayan ancak işe yarayan tek durum Python 2'yi kullanırken iter()oldu UserDict. Eğer bu uygunsa, bunu isinstance(item, (Iterable, UserDict))ele almak için kullanmak mümkündür .


1
Ayrıca typing.Dicttarafından tekrarlanabilir olarak kabul edilir, iter(Dict)ancak list(Dict)hata ile başarısız olur TypeError: Parameters to generic types must be types. Got 0.. Beklendiği gibi isinstance(Dict, Iterable)yanlış döndürür.
Pekka Klärck

1
Aynı sonuca vardım, ama farklı nedenlerle. Kullanılması iter, kodumuzu gereksiz yere yavaşlatmak için "önbelleğe alma" kullanılan bazı nedenlere neden oldu. Eğer __iter__kod yavaş, bu yüzden aradığını olacak iter... sadece bir şey iterable olup olmadığını görmek için istediğiniz zaman.
thorwhalen

842
  1. Denetleniyor __iter__dizi türlerinde eserler, ancak örneğin dizeleri üzerinde başarısız olur Python 2'de . Ben de doğru cevabı bilmek istiyorum, o zamana kadar, burada bir olasılık (dizelerde de işe yarayacak):

    from __future__ import print_function
    
    try:
        some_object_iterator = iter(some_object)
    except TypeError as te:
        print(some_object, 'is not iterable')

    iterİçin dahili kontroller __iter__yönteminin veya dizeleri durumunda __getitem__yöntemle.

  2. Diğer bir genel pitonik yaklaşım yinelenebilir bir varsayımdır, daha sonra verilen nesne üzerinde çalışmazsa incelikle başarısız olur. Python sözlüğü:

    Onun yöntemiyle veya nitelik imzanın muayene ile yerine bazı tip nesne ( "Bir benziyorsa için açık ilişki yoluyla bir nesnenin türünü belirler pythonic programlama tarzı ördek ve benzeri quacks ördek , bu bir olmalıdır ördek vurgulayarak arayüzleri ile.") spesifik tiplerden ziyade, iyi tasarlanmış kod polimorfik ikameye izin vererek esnekliğini artırır. Ördek yazma, type () veya isinstance () kullanarak yapılan testleri önler. Bunun yerine, tipik olarak EAFP (İzin Vermekten Affetmek Daha Kolaydır) programlama tarzını kullanır.

    ...

    try:
       _ = (e for e in my_object)
    except TypeError:
       print my_object, 'is not iterable'
  3. collectionsModül, örneğin, özel bir işlevselliği sağlamak sınıfları veya örneklerini sormak izin bazı soyut temel sınıfları sağlar:

    from collections.abc import Iterable
    
    if isinstance(e, Iterable):
        # e is iterable

    Ancak, bu yinelenebilir sınıfları kontrol etmez __getitem__.


34
[e for e in my_object]başka nedenlerle bir istisna oluşturabilir, yani my_objectuygulamada tanımlanmamış veya olası hatalardır my_object.
Nick Dandoulakis

37
Bir dizi olan (bir sekans isinstance('', Sequence) == True) ve herhangi bir dizi gibi olduğu iterable ( isinstance('', Iterable)). Gerçi hasattr('', '__iter__') == Falseve kafa karıştırıcı olabilir.
jfs

82
Eğer my_object(gibi sonsuz, diyelim çok büyük itertools.count()) Listenizi anlama zaman / çok fazla bellek kadar sürecektir. Asla (potansiyel olarak sonsuz) bir liste oluşturmaya çalışmayan bir jeneratör yapmak daha iyidir.
Chris Lutz

14
Some_object başka bir nedenden (hatalar vb.) Kaynaklanan TypeError atarsa ne olur ? Bunu "Yinelenemez TypeError" dan nasıl anlatabiliriz?
Eylül'ü

54
Python 3'te: hasattr(u"hello", '__iter__')İadeTrue
Carlos

572

Ördek yazarak

try:
    iterator = iter(theElement)
except TypeError:
    # not iterable
else:
    # iterable

# for obj in iterator:
#     pass

Tip kontrolü

Soyut Temel Sınıfları kullanın . En azından Python 2.6'ya ihtiyaç duyuyorlar ve sadece yeni stil sınıfları için çalışıyorlar.

from collections.abc import Iterable   # import directly from collections for Python < 3.3

if isinstance(theElement, Iterable):
    # iterable
else:
    # not iterable

Ancak, belgelerdeiter() açıklandığı gibi biraz daha güvenilirdir :

Denetleme isinstance(obj, Iterable), Yinelenebilir olarak kaydedilen veya bir __iter__()yöntemi olan sınıfları algılar, ancak yöntemle yinelenen sınıfları algılamaz __getitem__() . Bir nesnenin yinelenebilir olup olmadığını belirlemenin tek güvenilir yolu çağırmaktır iter(obj).


18
Luciano Ramalho'dan "Fluent Python" dan: Python 3.4'ten itibaren, x nesnesinin yinelenebilir olup olmadığını kontrol etmenin en doğru yolu iter (x) öğesini çağırmak ve değilse TypeError istisnasını işlemektir. İter (x) aynı zamanda eski dikkate çünkü bu, isinstance (x, abc.Iterable) kullanılarak daha doğru GetItem iterable ABC değil ise, yöntem.
RdB

"Ah , isinstance(x, (collections.Iterable, collections.Sequence))bunun yerine ben yapacağım" diye iter(x)düşünürseniz, bunun hala sadece uygulayan __getitem__ama değil yinelenebilir bir nesneyi algılamayacağını unutmayın __len__. İstisnayı kullanın iter(x)ve yakalayın.
Dale

İkinci cevabınız çalışmıyor. PyUNO'da yaparsam iter(slide1), her şey yolunda gidiyor, ancak isinstance(slide1, Iterable)atışlar TypeError: issubclass() arg 1 must be a class.
Hi-Angel

@ Hi-Angel, PyUNOUyarı mesajınızın issubclass()yerine hata mesajınızın söylediği bir hata gibi geliyor isinstance().
Georg Schölly

2
Bir nesne üzerinde iter () yöntemini çağırmak pahalı bir işlem olabilir (iter () üzerinde birden çok işlemi çatallayan / oluşturan, Pytorch'daki DataLoader'a bakın).
szali

126

Biraz karşılıklı etkileşimine daha fazla ışık biraz döken istiyorum iter, __iter__ve __getitem__ne perde arkasında olur. Bu bilgiyle donanmış olarak, en iyi neden yapabileceğinizi anlayabileceksiniz.

try:
    iter(maybe_iterable)
    print('iteration will probably work')
except TypeError:
    print('not iterable')

Önce gerçekleri listeleyeceğim ve daha sonra python'da bir fordöngü kullandığınızda neler olduğunu hızlı bir hatırlatma ve ardından gerçekleri göstermek için bir tartışma izleyeceğim.

Gerçekler

  1. Herhangi nesneden bir yineleyici alabilirsiniz oçağırarak iter(o): aşağıdaki koşullardan en az birinin de geçerlidir eğer

    ) bir obir sahip __iter__bir yineleyici nesnesi döndüren yöntemi. Yineleyici, __iter__ve __next__(Python 2:) nextyöntemine sahip herhangi bir nesnedir .

    b) obir __getitem__yöntemi vardır.

  2. IterableVeya öğesinin örneğini Sequencedenetlemek veya özniteliği denetlemek __iter__yeterli değildir.

  3. Bir nesne ise ouygular sadece __getitem__değil __iter__, iter(o)çalışır öğeleri getirmesi olduğunu bir yineleyici inşa edecek oindex 0 dan başlayarak tamsayı endeksine göre yineleyici herhangi yakalayacak IndexErroryükseltilmiş olması (ama başka hatalar) ve daha sonra yükseltir StopIterationkendisini.

  4. En genel anlamda, geri dönen yineleyicinin iterdenemek dışında aklı başında olup olmadığını kontrol etmenin bir yolu yoktur .

  5. Bir nesne ouygulanırsa __iter__, iterişlev, döndürülen nesnenin __iter__bir yineleyici olduğundan emin olur . Sadece bir cismin uygulanıp uygulanmadığı konusunda bir akıl sağlığı kontrolü yoktur __getitem__.

  6. __iter__kazanır. Bir nesne ise ouygular hem __iter__ve __getitem__, iter(o)arayacak __iter__.

  7. Kendi nesnelerinizi yinelenebilir hale getirmek istiyorsanız, her zaman __iter__yöntemi uygulayın.

for döngüler

Takip etmek için for, Python'da bir döngü kullandığınızda ne olduğunu anlamanız gerekir . Zaten biliyorsanız, bir sonraki bölüme geçmekten çekinmeyin.

for item in oBazı yinelenebilir nesne için kullandığınızda o, Python iter(o)bir yineleyici nesnesini döndürür ve dönüş değeri olarak bekler. Yineleyici, __next__(veya nextPython 2'de) bir yöntem ve yöntem uygulayan herhangi bir nesnedir __iter__.

Geleneksel olarak, __iter__bir yineleyici yöntem nesnenin kendisi (yani döndürmelidir return self). Python , yükseltilene nextkadar yineleyiciyi çağırır StopIteration. Bütün bunlar dolaylı olarak gerçekleşir, ancak aşağıdaki gösteri onu görünür kılar:

import random

class DemoIterable(object):
    def __iter__(self):
        print('__iter__ called')
        return DemoIterator()

class DemoIterator(object):
    def __iter__(self):
        return self

    def __next__(self):
        print('__next__ called')
        r = random.randint(1, 10)
        if r == 5:
            print('raising StopIteration')
            raise StopIteration
        return r

Üzerinde yineleme DemoIterable:

>>> di = DemoIterable()
>>> for x in di:
...     print(x)
...
__iter__ called
__next__ called
9
__next__ called
8
__next__ called
10
__next__ called
3
__next__ called
10
__next__ called
raising StopIteration

Tartışma ve çizimler

1. ve 2. noktalarda: yineleyici ve güvenilir olmayan kontroller almak

Aşağıdaki sınıfı düşünün:

class BasicIterable(object):
    def __getitem__(self, item):
        if item == 3:
            raise IndexError
        return item

Örneğinin çağrılması iter, uygulayıcılar BasicIterablenedeniyle sorunsuz bir yineleyici döndürür .BasicIterable__getitem__

>>> b = BasicIterable()
>>> iter(b)
<iterator object at 0x7f1ab216e320>

Ancak, o notta önemlidir byoktur __iter__özelliğini ve bir örneği olarak kabul edilmez Iterableya Sequence:

>>> from collections import Iterable, Sequence
>>> hasattr(b, '__iter__')
False
>>> isinstance(b, Iterable)
False
>>> isinstance(b, Sequence)
False

Bu nedenle Luciano Ramalho'dan Fluent Python , bir nesnenin yinelenebilir olup olmadığını kontrol etmenin en doğru yol olarak iterpotansiyelin çağrılmasını ve ele alınmasını TypeErrorönerir. Doğrudan kitaptan alıntı:

Python x3.4'ten itibaren, bir nesnenin yinelenebilir olup olmadığını kontrol etmenin en doğru yolu , değilse iter(x)bir TypeErroristisnayı çağırmak ve işlemektir . Bu, kullanmaktan daha doğrudur isinstance(x, abc.Iterable), çünkü ABC yöntemi iter(x)de eski __getitem__yöntemi dikkate alır Iterable.

3. nokta: Yalnızca sağlayan __getitem__ancak sağlamayan nesneler üzerinde yineleme__iter__

BasicIterableBeklendiği gibi bir çalışma örneği üzerinde yineleme : Python, bir IndexErroryükseltilene kadar sıfırdan başlayarak öğeleri dizine göre almaya çalışan bir yineleyici oluşturur. Demo nesnesinin __getitem__yöntemi , döndürülen yineleyici tarafından itemargüman olarak sağlanan yöntemi döndürür .__getitem__(self, item)iter

>>> b = BasicIterable()
>>> it = iter(b)
>>> next(it)
0
>>> next(it)
1
>>> next(it)
2
>>> next(it)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

Yineleyicinin bir StopIterationsonraki öğeyi döndüremediğinde IndexErroryükseldiğini ve bunun için yükseltildiğini item == 3dahili olarak ele aldığını unutmayın . Bu nedenle BasicIterable, bir fordöngü ile döngü oluşturmak beklendiği gibi çalışır:

>>> for x in b:
...     print(x)
...
0
1
2

Burada, yineleyicinin döndürdüğü kavramı iteröğelere dizine göre nasıl erişmeye çalıştığı kavramını yönlendirmek için başka bir örnek . WrappedDictmiras almaz dict, yani örneklerin bir __iter__yöntemi olmaz.

class WrappedDict(object): # note: no inheritance from dict!
    def __init__(self, dic):
        self._dict = dic

    def __getitem__(self, item):
        try:
            return self._dict[item] # delegate to dict.__getitem__
        except KeyError:
            raise IndexError

Köşeli parantez notasyonunun sadece bir steno olduğu çağrılara __getitem__delege edildiğini unutmayın dict.__getitem__.

>>> w = WrappedDict({-1: 'not printed',
...                   0: 'hi', 1: 'StackOverflow', 2: '!',
...                   4: 'not printed', 
...                   'x': 'not printed'})
>>> for x in w:
...     print(x)
... 
hi
StackOverflow
!

4. ve 5. noktalarda: iterçağırdığında yineleyici olup olmadığını kontrol eder__iter__ :

Ne zaman iter(o)bir nesne için çağrılır o, iterdönüş değeri emin olacaktır __iter__yöntem, mevcut ise, Yineleyicinin. Bu, döndürülen nesnenin __next__(veya nextPython 2'de) uygulanması gerektiği anlamına gelir __iter__. iteryalnızca sağlayan nesneler için herhangi bir sağlık kontrolü gerçekleştiremez __getitem__, çünkü nesnenin öğelerine tamsayı dizini tarafından erişilip erişilemediğini kontrol etmenin bir yolu yoktur.

class FailIterIterable(object):
    def __iter__(self):
        return object() # not an iterator

class FailGetitemIterable(object):
    def __getitem__(self, item):
        raise Exception

FailIterIterableÖrneklerden bir yineleyici oluşturmanın derhal başarısız olduğunu ve yinelemeden yineleyici oluşturmanın FailGetItemIterablebaşarılı olduğunu, ancak ilk çağrıya bir İstisna atacağını unutmayın __next__.

>>> fii = FailIterIterable()
>>> iter(fii)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: iter() returned non-iterator of type 'object'
>>>
>>> fgi = FailGetitemIterable()
>>> it = iter(fgi)
>>> next(it)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/path/iterdemo.py", line 42, in __getitem__
    raise Exception
Exception

6. noktada: __iter__kazanır

Bu çok açık. Bir nesne uygular ise __iter__ve __getitem__, iterarayacak __iter__. Aşağıdaki sınıfı düşünün

class IterWinsDemo(object):
    def __iter__(self):
        return iter(['__iter__', 'wins'])

    def __getitem__(self, item):
        return ['__getitem__', 'wins'][item]

ve bir örnek üzerinde döngü oluştururken çıktı:

>>> iwd = IterWinsDemo()
>>> for x in iwd:
...     print(x)
...
__iter__
wins

7. nokta: yinelenebilir sınıflarınız __iter__

Kendinize neden listbir __iter__yöntem uygulamak gibi çoğu yerleşik dizinin __getitem__yeterli olacağını kendinize sorabilirsiniz .

class WrappedList(object): # note: no inheritance from list!
    def __init__(self, lst):
        self._list = lst

    def __getitem__(self, item):
        return self._list[item]

Sonuçta, yineleme delegeler aramalar için, yukarıda sınıfın örneklerini üzerinde __getitem__etmek list.__getitem__(köşeli ayraç gösterimi kullanılarak), cezası çalışacaktır:

>>> wl = WrappedList(['A', 'B', 'C'])
>>> for x in wl:
...     print(x)
... 
A
B
C

Özel tekrarlarınızın uygulanması gereken nedenler __iter__şunlardır:

  1. Eğer uygularsanız __iter__, örnekler dikkate alınan Iterables olacak ve isinstance(o, collections.abc.Iterable)dönecektir True.
  2. Tarafından döndürülen nesne __iter__yineleyici değilse, iterderhal başarısız olur ve a değerini yükseltir TypeError.
  3. Özel kullanım, __getitem__geriye dönük uyumluluk nedeniyle vardır. Akıcı Python'dan tekrar alıntı:

Bu nedenle herhangi bir Python dizisi yinelenebilir: hepsi uygular __getitem__. Aslında, standart diziler de uygulanır __iter__ve sizinki de olmalıdır, çünkü __getitem__geriye dönük uyumluluk nedenleriyle özel işlemesi vardır ve gelecekte gidebilir (bunu yazarken reddedilmese de).


nedenle bir yüklemi tanımlamak için güvenlidir is_iterabledönerek Trueolarak tryblok ve Falsede except TypeErrorblok?
alancalvitti

Bu harika bir cevap. Bence getitem protokolünün kasıtsız ve talihsiz doğasını vurguluyor. Asla eklenmemeliydi.
Neil G

31

Bu yeterli değildir: tarafından döndürülen nesnenin __iter__yineleme protokolünü (yani nextyöntem) uygulaması gerekir . Belgelerdeki ilgili bölüme bakın .

Python'da iyi bir uygulama "kontrol etmek" yerine "denemek ve görmek" tir.


9
"ördek yazarak" inanıyorum? :)
willem

9
@willem: veya "izin istemeyin ama affetme" ;-)
jldupont

14
@willem Hem "izin" hem de "affetme" stilleri ördek yazımı olarak nitelendirilir. Eğer bir nesne ne sorarsanız yapmak yerine o olandan ise , 's ördek yazarak söyledi. İçgözlem kullanırsanız, bu "izin" tir; sadece yapmaya çalışırsanız, işe yarayıp yaramadığını görürseniz, bu "af" dır.
Mark Reed

22

Python <= 2.5'de yapamazsınız ve yapmamalısınız - tekrarlanabilir bir "gayri resmi" arayüzdü.

Ancak Python 2.6 ve 3.0'dan bu yana, koleksiyon modülünde bulunan bazı yerleşik ABC'lerle birlikte yeni ABC (soyut temel sınıf) altyapısından yararlanabilirsiniz:

from collections import Iterable

class MyObject(object):
    pass

mo = MyObject()
print isinstance(mo, Iterable)
Iterable.register(MyObject)
print isinstance(mo, Iterable)

print isinstance("abc", Iterable)

Şimdi, bu arzu edilir mi yoksa gerçekten işe yarıyorsa, sadece bir konuyla ilgilidir. Gördüğünüz gibi, olabilir iterable olarak olmayan bir iterable nesneyi kayıt - ve zamanında bir istisna yükseltecektir. Bu nedenle, isinstance "yeni" bir anlam kazanır - sadece "beyan edilen" tip uyumluluğunu kontrol eder, bu da Python'a gitmek için iyi bir yoldur.

Öte yandan, nesneniz ihtiyaç duyduğunuz arabirimi karşılamıyorsa ne yapacaksınız? Aşağıdaki örneği alın:

from collections import Iterable
from traceback import print_exc

def check_and_raise(x):
    if not isinstance(x, Iterable):
        raise TypeError, "%s is not iterable" % x
    else:
        for i in x:
            print i

def just_iter(x):
    for i in x:
        print i


class NotIterable(object):
    pass

if __name__ == "__main__":
    try:
        check_and_raise(5)
    except:
        print_exc()
        print

    try:
        just_iter(5)
    except:
        print_exc()
        print

    try:
        Iterable.register(NotIterable)
        ni = NotIterable()
        check_and_raise(ni)
    except:
        print_exc()
        print

Nesne beklediğiniz şeyi karşılamıyorsa, sadece bir TypeError atarsınız, ancak uygun ABC kaydedilmişse, çekiniz işe yaramaz. Aksine, __iter__yöntem mevcutsa Python bu sınıftaki nesneyi tekrarlanabilir olarak otomatik olarak tanıyacaktır.

Yani, sadece bir tekrarlanabilir beklerseniz, onu tekrarlayın ve unutun. Öte yandan, giriş türüne bağlı olarak farklı şeyler yapmanız gerekiyorsa, ABC altyapısını oldukça yararlı bulabilirsiniz.


13
except:yeni başlayanlar için örnek kodda çıplak kullanmayın . Kötü uygulamayı teşvik eder.
jfs

JFS: Yapmazdım, ancak birden fazla özel durum yükseltme kodundan geçmem gerekiyordu ve belirli bir özel durumu yakalamak istemedim ... Bence bu kodun amacı oldukça açık.
Alan Franzoni

21
try:
  #treat object as iterable
except TypeError, e:
  #object is not actually iterable

Ördekinizin gerçekten tekrarlanabilir olup olmadığını görmek için bir ördek olup olmadığını görmek için kontrolleri çalıştırmayın , sanki sanki öyle davranın ve değilse şikayet edin.


3
Teknik olarak, yineleme sırasında hesaplamanız TypeErrorsizi buraya atabilir ve buraya atabilir, ancak temel olarak evet.
Chris Lutz

6
@willem: Bir kıyaslama yapmak için lütfen timeit kullanın. Python istisnaları genellikle if ifadelerinden daha hızlıdır. Tercümandan biraz daha kısa bir yol izleyebilirler.
S.Lott

2
@willem: IronPython'un (CPython ile karşılaştırıldığında) yavaş istisnaları vardır.
jfs

2
Çalışan bir try: ifadesi gerçekten hızlı. Bu nedenle, birkaç istisnanız varsa, try-hariç hızlıdır. Pek çok istisna bekliyorsanız, “if” daha hızlı olabilir.
Arne Babenhauserheide

2
İstisna nesnesi, " as e" TypeErroryerine " " eklenerek yakalanmamalı , emı?
HelloGoodbye

21

Python 3.5'ten beri, türle ilgili şeyler için standart kitaplıktaki yazma modülünü kullanabilirsiniz :

from typing import Iterable

...

if isinstance(my_item, Iterable):
    print(True)

18

Şimdiye kadar bulduğum en iyi çözüm:

hasattr(obj, '__contains__')

temel olarak nesnenin inoperatörü uygulayıp uygulamadığını kontrol eder .

Avantajları (diğer çözümlerin hiçbirinin üçü de yoktur):

  • bir ifade ( denemenin aksine lambda gibi çalışır ... varyant hariç )
  • (aksine ) dizeler de dahil olmak üzere tüm yinelenebilirler tarafından uygulanır (uygulanmalıdır __iter__)
  • herhangi bir Python üzerinde çalışır> = 2.5

Notlar:

  • Örneğin bir listede hem yinelenebilir hem de yinelenemediğiniz ve her öğeye türüne göre farklı şekilde davranmanız gerektiğinde (affetme, izin değil, Python felsefesi) iyi çalışmıyor dışında üzerinde Iterables olur ) faydalı olur ancak çirkin ve yanıltıcı olmazdı
  • yinelenebilir olup olmadığını kontrol etmek için nesne üzerinde gerçekten yineleme girişiminde bulunan bu soruna yönelik çözümler (örn. [obj için x için x]) örnek) ve kaçınılmalıdır

3
Güzel, ama neden koleksiyon modülünü stackoverflow.com/questions/1952464/… ' de önerildiği gibi kullanmıyorsunuz ? Bana daha etkileyici geliyor.
Dave Abrahams

1
Herhangi bir netlik kaybetmeden daha kısadır (ve ek ithalat gerektirmez): "içerir" yöntemine sahip olmak, bir şeyin bir nesne koleksiyonu olup olmadığını kontrol etmenin doğal bir yolu gibi gelir.
Vlad

46
Bir şeyin bir şey içerebilmesi, mutlaka yinelenebileceği anlamına gelmez. Örneğin, bir kullanıcı bir noktanın 3D küp içinde olup olmadığını kontrol edebilir, ancak bu nesneyi nasıl tekrarlarsınız?
Casey Kuball

13
Bu yanlış. Kendisi bir iterable destek vermez içeren en az Python 3.4 ile.
Peter Shinners

15

Bunu deneyebilirsiniz:

def iterable(a):
    try:
        (x for x in a)
        return True
    except TypeError:
        return False

Üzerinde yinelenen bir jeneratör yapabilirsek (ancak jeneratörü asla yer kaplamayacak şekilde kullanmayın), bu yinelenebilir. Bir tür "duh" gibi görünüyor. Bir değişkenin ilk etapta tekrarlanabilir olup olmadığını neden belirlemeniz gerekiyor?


Ne olmuş iterable(itertools.repeat(0))? :)
badp

5
@badp, (x for x in a)sadece bir jeneratör oluşturur, bir üzerinde yineleme yapmaz.
catchmeifyoutry

5
Denemek (x for x in a)kesinlikle denemeye eşit iterator = iter(a)midir? Yoksa ikisinin farklı olduğu bazı durumlar var mı?
maksimum

for _ in a: breakDaha kolay değil mi? Daha yavaş mı?
Mr_and_Mrs_D

2
@Mr_and_Mrs_D, test edilen nesne daha sonra tekrarlanan bir yineleyiciyse (konum sıfırlanamayacağı için 1 öğe kısa olacaktır) kötüdür, çöp üreteçleri oluşturmak, yinelemedikleri için nesne üzerinde yinelenmez, ancak% 100 yinelenemezse bir TypeError hatası oluşturacağından emin değilim.
Tcll

13

Burada güzel bir çözüm buldum :

isiterable = lambda obj: isinstance(obj, basestring) \
    or getattr(obj, '__iter__', False)

10

Göre Python 2 Sözlüğü , Iterables vardır

Tüm sekans tipleri (örneğin list, strve tuple) ve bu gibi bazı non-dizi tipleri dictve fileve bir ile tanımlayan bir sınıf nesneleri __iter__()ya da __getitem__()bir yöntem. Yinelenebilirler bir for döngüsünde ve bir dizinin gerekli olduğu birçok yerde kullanılabilir (zip (), map (), ...). Yinelenebilir bir nesne, yerleşik işlev iter () öğesine bağımsız değişken olarak iletildiğinde, nesne için bir yineleyici döndürür.

Tabii ki, “izin vermekten affetmeyi istemek daha kolay” gerçeğine dayanarak Python için genel kodlama stili göz önüne alındığında, genel beklenti kullanmaktır.

try:
    for i in object_in_question:
        do_something
except TypeError:
    do_something_for_non_iterable

Ancak açık bir şekilde kontrol etmeniz gerekiyorsa, tarafından bir tekrarlanabilirlik testi yapabilirsiniz hasattr(object_in_question, "__iter__") or hasattr(object_in_question, "__getitem__"). S'nin strbir __iter__yöntemi olmadığı için (en azından Python 2'de, Python 3'te yaptıkları) ve generatornesnelerin bir __getitem__yöntemi olmadığı için her ikisini de kontrol etmeniz gerekir .


8

Komut dosyalarımın içinde bir iterableişlevi tanımlamak için genellikle uygun buluyorum . (Şimdi Alfe'nin önerilen sadeleştirmesini içeriyor):

import collections

def iterable(obj):
    return isinstance(obj, collections.Iterable):

böylece herhangi bir nesnenin çok okunabilir formda yinelenebilir olup olmadığını test edebilirsiniz

if iterable(obj):
    # act on iterable
else:
    # not iterable

Eğer ile yaptığımız gibi callableişlevi

EDIT: Eğer numpy yüklüyse, şunları yapabilirsiniz: from numpy import iterable, hangi gibi bir şey

def iterable(obj):
    try: iter(obj)
    except: return False
    return True

Numpy'niz yoksa, bu kodu veya yukarıdaki kodu uygulayabilirsiniz.


3
Ne zaman if x: return True else: return False( xboolean olmak gibi) sth bunu bunu olarak yazabilirsiniz return x. return isinstance(…)Hiçbir durumda if.
Alfe

Alfe'nin çözümünün daha iyi olduğunu bildiğiniz için, cevabınızı neden basitçe söylemek için düzenlemediniz? Bunun yerine, artık cevabınızda BOTH sürümleri var. Gereksiz ayrıntı. Bunu düzeltmek için bir düzenleme gönderme.
ToolmakerSteve

2
`Return: False 'satırında“ TypeError ”yazmalısınız. Her şeyi yakalamak kötü bir modeldir.
Mariusz Jamro

Biliyorum. Bu kod parçasını, genel istisnayı kullanan NumPy kütüphanesinden çevirdim.
fmonegaglia

NumPy'den bir kod alınması iyi olduğu anlamına gelmez ... desen ya da değil, her şeyin yakalanması gereken tek zaman, programınızın içinde açıkça hata işleme alıyorsanız.
Tcll

5

yerleşik bir işlevi vardır:

from pandas.util.testing import isiterable

ancak bu sadece __iter__diziler ve benzeri şeylerle gerçekten ilgilenip ilgilenmediğine bakar .
ead

4

Python'un neden var callable(obj) -> boololmasına rağmen her zaman beni kaçırdı iterable(obj) -> bool...
elbette hasattr(obj,'__call__')yavaş olsa bile yapmak daha kolay .

Hemen hemen her diğer yanıt , istisnalar için testin genellikle herhangi bir dil arasında kötü uygulama olarak kabul edildiği try/ kullanılmasını önerdiğinden except TypeError,iterable(obj) -> bool daha çok sevdiğim ve sık kullandığım :

Python 2 uğruna, sadece ekstra performans artışı için bir lambda kullanacağım ...
(python 3'te işlevi tanımlamak için ne kullandığınız önemli değil def, kabaca aynı hıza sahip lambda)

iterable = lambda obj: hasattr(obj,'__iter__') or hasattr(obj,'__getitem__')

Bu işlevin __iter__, test etmediği için nesneler için daha hızlı çalıştığını unutmayın.__getitem__ .

Çoğu yinelenebilir nesne , bir nesnenin yinelenebilir olması için gerekli olsa da, __iter__özel durumdaki nesnelerin nereye düştüğüne __getitem__güvenmelidir.
(ve bu standart olduğu için C nesnelerini de etkiler)


Bu kod gerçekten burada sadece defalarca yapıldığını gördüğüm gibi kolaylık sağlamak için olsa da, çalışma kodu sağlamıyor.
19'da Tcll

3
def is_iterable(x):
    try:
        0 in x
    except TypeError:
        return False
    else:
        return True

Bu, her türlü yinelenebilir nesneye evet diyecektir, ancak Python 2'deki dizelere hayır diyecektir . (Örneğin, özyinelemeli bir işlev bir dize veya bir dize kabı alabildiğinde bunu istiyorum. Bu durumda, affetmeyi istemek obfuscode'a yol açabilir ve önce izin istemek daha iyidir.)

import numpy

class Yes:
    def __iter__(self):
        yield 1;
        yield 2;
        yield 3;

class No:
    pass

class Nope:
    def __iter__(self):
        return 'nonsense'

assert is_iterable(Yes())
assert is_iterable(range(3))
assert is_iterable((1,2,3))   # tuple
assert is_iterable([1,2,3])   # list
assert is_iterable({1,2,3})   # set
assert is_iterable({1:'one', 2:'two', 3:'three'})   # dictionary
assert is_iterable(numpy.array([1,2,3]))
assert is_iterable(bytearray("not really a string", 'utf-8'))

assert not is_iterable(No())
assert not is_iterable(Nope())
assert not is_iterable("string")
assert not is_iterable(42)
assert not is_iterable(True)
assert not is_iterable(None)

Buradaki diğer birçok strateji dizelere evet diyecektir. İstediğiniz bu ise onları kullanın.

import collections
import numpy

assert isinstance("string", collections.Iterable)
assert isinstance("string", collections.Sequence)
assert numpy.iterable("string")
assert iter("string")
assert hasattr("string", '__getitem__')

Not: is_iterable () Çeşidi dizeleri evet diyecek bytesve bytearray.

  • bytesPython 3'teki nesneler yinelenebilir True == is_iterable(b"string") == is_iterable("string".encode('utf-8'))Python 2'de böyle bir tür yoktur.
  • bytearray Python 2 ve 3'teki nesneler yinelenebilir True == is_iterable(bytearray(b"abc"))

OP hasattr(x, '__iter__')yaklaşımı Python 3'teki dizelere evet, Python 2'deki dizilere hayır diyecektir ( ''ya b''da ya da olsun u''). @LuisMasuelli sayesinde farkettiğiniz için buggy de sizi yarı yolda bırakacaktır __iter__.


2

Python'un ördek yazımına saygı duymanın en kolay yolu hatayı yakalamaktır (Python, bir nesneden bir yineleyici haline gelmesini beklediğini mükemmel bir şekilde bilir):

class A(object):
    def __getitem__(self, item):
        return something

class B(object):
    def __iter__(self):
        # Return a compliant iterator. Just an example
        return iter([])

class C(object):
    def __iter__(self):
        # Return crap
        return 1

class D(object): pass

def iterable(obj):
    try:
        iter(obj)
        return True
    except:
        return False

assert iterable(A())
assert iterable(B())
assert iterable(C())
assert not iterable(D())

Notlar :

  1. Nesnenin yinelenebilir veya bir arabası olup olmadığı ayrımı ile ilgisizdir. __iter__İstisna türü uygulanmış olması : yine de nesneyi tekrarlayamazsınız.
  2. Endişenizi anlıyorum: Nesnem için tanımlanmamış callablebir AttributeErrorif yükseltmek için ördek __call__yazımına da güvenebilirsem nasıl bir kontrol olarak var olur , ancak tekrarlanabilir kontrol için durum böyle değil mi?

    Cevabı bilmiyorum, ama verdiğim işlevi (ve diğer kullanıcıları) uygulayabilir veya sadece kodunuzdaki istisnayı yakalayabilirsiniz (bu bölümdeki uygulamanız yazdığım işlev gibi olacak - sadece istisnayı yakalayıp başka birinden ayırt edebilmeniz için kodun geri kalanından yineleyici oluşturma TypeError.


1

isiterableAşağıdaki kod getirileri de işlev Truenesne iterable ise. eğer tekrarlanabilir değilseFalse

def isiterable(object_):
    return hasattr(type(object_), "__iter__")

misal

fruits = ("apple", "banana", "peach")
isiterable(fruits) # returns True

num = 345
isiterable(num) # returns False

isiterable(str) # returns False because str type is type class and it's not iterable.

hello = "hello dude !"
isiterable(hello) # returns True because as you know string objects are iterable

2
yukarıda birçok upvotes ile çok ayrıntılı cevaplar ve açıklanamayan bir cevap atmak ... meh
Nrzonline

Lütfen çıplak kod göndermeyin. Bunun ne yaptığına dair bir açıklama da ekleyin.
Jonathan Mee

1

__iter__Özniteliği denetlemek yerine, __len__dizeler de dahil olmak üzere yinelenebilir yerleşik her python tarafından uygulanan özniteliği kontrol edebilirsiniz .

>>> hasattr(1, "__len__")
False
>>> hasattr(1.3, "__len__")
False
>>> hasattr("a", "__len__")
True
>>> hasattr([1,2,3], "__len__")
True
>>> hasattr({1,2}, "__len__")
True
>>> hasattr({"a":1}, "__len__")
True
>>> hasattr(("a", 1), "__len__")
True

Yinelenemez nesneler bunu bariz nedenlerle uygulamaz. Bununla birlikte, onu uygulamayan kullanıcı tanımlı yinelemeleri veya iterbaşa çıkabilecek jeneratör ifadelerini yakalamaz . Ancak, bu bir satırda yapılabilir ve orjeneratörler için basit bir ifade kontrolü eklemek bu sorunu çözecektir. ( type(my_generator_expression) == generatorYazmanın a atacağını unutmayın NameError. Bunun yerine bu cevaba bakınız .)

GeneratorType'ı aşağıdaki türlerden kullanabilirsiniz:

>>> import types
>>> types.GeneratorType
<class 'generator'>
>>> gen = (i for i in range(10))
>>> isinstance(gen, types.GeneratorType)
True

--- utdemir tarafından kabul edilen cevap

(Bu len, nesneyi çağırabileceğinizi kontrol etmek için yararlı kılar .)


ne yazık ki tüm yinelenebilir nesneler kullanmaz __len__... bu durumda, genellikle 2 nesne arasındaki mesafeyi hesaplamanın yanlış kullanımıdır. burada obj.dist()kolayca ikame edilebilir.
Tcll

Evet. Çoğu kullanıcı tanımlı yineleme, iter ve getitem uygular ancak len uygular. Bununla birlikte, yerleşik türler yapar ve üzerinde len fonksiyonunu çağırabileceğinizi kontrol etmek istiyorsanız, len'i kontrol etmek daha güvenlidir. Fakat haklısın.
DarthCadeus

0

Gerçekten "doğru" değil ama dizeler, tuples, şamandıralar, vb gibi en yaygın türleri hızlı kontrol olarak hizmet verebilir ...

>>> '__iter__' in dir('sds')
True
>>> '__iter__' in dir(56)
False
>>> '__iter__' in dir([5,6,9,8])
True
>>> '__iter__' in dir({'jh':'ff'})
True
>>> '__iter__' in dir({'jh'})
True
>>> '__iter__' in dir(56.9865)
False

0

Partiye geç kaldım ama kendime bu soruyu sordum ve bunun bir cevap düşündüğünü gördüm. Birinin bunu zaten paylaşıp yayınlamadığını bilmiyorum. Ama aslında, tüm yinelenebilir türlerin kendi kararlarında __getitem __ () olduğunu fark ettim . Bu, bir nesnenin denemeden bile tekrarlanabilir olup olmadığını nasıl kontrol edeceğinizdir. (Pun amaçlı)

def is_attr(arg):
    return '__getitem__' in dir(arg)

Ne yazık ki, bu güvenilmez. Örnek
timgeb

1
Küme nesneleri başka bir karşı örnek.
Raymond Hettinger
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.