Trong Python, làm cách nào để xác định xem một đối tượng có thể lặp lại không?


1084

Có một phương pháp như thế isiterablenào? Giải pháp duy nhất tôi tìm thấy cho đến nay là gọi

hasattr(myObj, '__iter__')

Nhưng tôi không chắc làm thế nào để chứng minh điều này là ngu ngốc.


18
__getitem__cũng đủ để tạo một đối tượng có thể lặp lại
Kos

4
FWIW: iter(myObj)thành công nếu isinstance(myObj, dict), vì vậy nếu bạn đang xem myObjmột chuỗi có thể là một chuỗi dicts hoặc một dict, bạn sẽ thành công trong cả hai trường hợp. Một sự tinh tế rất quan trọng nếu bạn muốn biết đâu là một chuỗi và những gì không. (trong Python 2)
Ben Mosher

7
__getitem__cũng đủ để làm cho một đối tượng có thể lặp lại ... nếu nó bắt đầu ở chỉ số 0 .
Carlos A. Gómez

Câu trả lời:


27

Gần đây tôi đã nghiên cứu vấn đề này một chút. Dựa vào đó, kết luận của tôi là ngày nay đây là cách tiếp cận tốt nhất:

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

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

Những điều trên đã được đề xuất sớm hơn, nhưng sự đồng thuận chung là việc sử dụng iter()sẽ tốt hơn:

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

Chúng tôi cũng đã sử dụng iter()mã của chúng tôi cho mục đích này, nhưng gần đây tôi đã bắt đầu thấy ngày càng khó chịu hơn bởi các đối tượng chỉ __getitem__được coi là có thể lặp lại. Có những lý do hợp lệ để có __getitem__trong một đối tượng không thể lặp lại và với chúng, đoạn mã trên không hoạt động tốt. Như một ví dụ thực tế, chúng ta có thể sử dụng Faker . Đoạn mã trên báo cáo rằng nó có thể lặp lại nhưng thực sự cố gắng lặp lại nó gây ra một AttributeError(được thử nghiệm với Faker 4.0.2):

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

Nếu chúng tôi sử dụng insinstance(), chúng tôi sẽ không vô tình coi các trường hợp Faker (hoặc bất kỳ đối tượng nào khác chỉ có __getitem__) có thể lặp lại:

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

Các câu trả lời trước đó đã nhận xét rằng việc sử dụng iter()sẽ an toàn hơn vì cách cũ để thực hiện phép lặp trong Python dựa trên __getitem__isinstance()cách tiếp cận sẽ không phát hiện ra điều đó. Điều này có thể đúng với các phiên bản Python cũ, nhưng dựa trên thử nghiệm khá toàn diện của tôi hiện đang isinstance()hoạt động rất tốt. Trường hợp duy nhất isinstance()không hoạt động nhưng iter()đã xảy ra UserDictkhi sử dụng Python 2. Nếu điều đó có liên quan, có thể sử dụng isinstance(item, (Iterable, UserDict))để bảo vệ nó.


1
Cũng typing.Dictđược coi là lặp lại bởi iter(Dict)nhưng list(Dict)không thành công với lỗi TypeError: Parameters to generic types must be types. Got 0.. Như dự kiến isinstance(Dict, Iterable)trả về sai.
Pekka Klärck

1
Tôi đi đến cùng một kết luận, nhưng vì những lý do khác nhau. Việc sử dụng itergây ra một số mã của chúng tôi đã sử dụng "bộ đệm trước" để làm chậm không cần thiết. Nếu __iter__mã chậm, vì vậy sẽ gọi iter... bất cứ lúc nào bạn chỉ muốn xem nếu có gì đó có thể lặp lại.
thorwhalen

842
  1. Kiểm tra các __iter__tác phẩm trên các loại trình tự, nhưng nó sẽ thất bại trên các chuỗi ví dụ trong Python 2 . Tôi cũng muốn biết câu trả lời đúng, cho đến lúc đó, đây là một khả năng (cũng sẽ hoạt động trên chuỗi):

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

    Các iterbuilt-in kiểm tra cho các __iter__phương pháp hay trong trường hợp của chuỗi các __getitem__phương pháp.

  2. Một cách tiếp cận pythonic chung khác là giả định một lần lặp, sau đó thất bại một cách duyên dáng nếu nó không hoạt động trên đối tượng đã cho. Thuật ngữ Python:

    Phong cách lập trình Pythonic xác định loại đối tượng bằng cách kiểm tra chữ ký phương thức hoặc thuộc tính của nó chứ không phải bằng mối quan hệ rõ ràng với một đối tượng loại nào đó ("Nếu nó trông giống như một con vịt và quạ giống như một con vịt , thì đó phải là một con vịt .") thay vì các loại cụ thể, mã được thiết kế tốt sẽ cải thiện tính linh hoạt của nó bằng cách cho phép thay thế đa hình. Gõ vịt tránh các bài kiểm tra bằng cách sử dụng loại () hoặc isinstance (). Thay vào đó, nó thường sử dụng kiểu lập trình EAFP (Dễ dàng yêu cầu tha thứ hơn là Cấp phép).

    ...

    try:
       _ = (e for e in my_object)
    except TypeError:
       print my_object, 'is not iterable'
  3. Các collectionsmô-đun cung cấp một số lớp cơ sở trừu tượng, cho phép hỏi các lớp học hoặc trường hợp nếu họ cung cấp chức năng đặc biệt, ví dụ:

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

    Tuy nhiên, điều này không kiểm tra các lớp có thể lặp qua __getitem__.


34
[e for e in my_object]có thể đưa ra một ngoại lệ vì các lý do khác, tức my_objectlà không xác định hoặc có thể có lỗi trong my_objectquá trình thực hiện.
Nick Dandoulakis

37
Một chuỗi một chuỗi ( isinstance('', Sequence) == True) như bất kỳ chuỗi nào, nó iterable ( isinstance('', Iterable)). Mặc dù hasattr('', '__iter__') == Falsevà nó có thể gây nhầm lẫn.
jfs

82
Nếu my_objectlà rất lớn (giả sử, vô hạn như itertools.count()) việc hiểu danh sách của bạn sẽ chiếm rất nhiều thời gian / bộ nhớ. Tốt hơn để tạo một trình tạo, sẽ không bao giờ cố gắng xây dựng một danh sách (có khả năng vô hạn).
Chris Lutz

14
Điều gì sẽ xảy ra nếu some_object ném TypeError do nguyên nhân khác (lỗi, v.v.)? Làm thế nào chúng ta có thể nói với nó từ "Loại không thể lặp lại"?
Shaung

54
Lưu ý rằng trong Python 3: hasattr(u"hello", '__iter__')returnTrue
Carlos

572

Vịt gõ

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

# for obj in iterator:
#     pass

Kiểm tra loại

Sử dụng các lớp cơ sở trừu tượng . Họ cần ít nhất Python 2.6 và chỉ hoạt động cho các lớp kiểu mới.

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

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

Tuy nhiên, iter()đáng tin cậy hơn một chút như được mô tả trong tài liệu :

Kiểm tra isinstance(obj, Iterable)phát hiện các lớp được đăng ký là Iterable hoặc có một __iter__()phương thức, nhưng nó không phát hiện các lớp lặp lại với __getitem__() phương thức đó. Cách đáng tin cậy duy nhất để xác định xem một đối tượng có thể lặp được hay không là gọi iter(obj).


18
Từ "Python thông thạo" của Luciano Ramalho: Kể từ Python 3.4, cách chính xác nhất để kiểm tra xem một đối tượng x có lặp được hay không là gọi iter (x) và xử lý ngoại lệ TypeError nếu không. Điều này chính xác hơn so với sử dụng isinstance (x, abc.Iterable), bởi vì iter (x) cũng xem xét phương thức getitem kế thừa , trong khi ABC lặp lại thì không.
RdB

Trong trường hợp bạn đang nghĩ "oh tôi sẽ chỉ isinstance(x, (collections.Iterable, collections.Sequence))thay vì iter(x)", lưu ý rằng điều này vẫn sẽ không phát hiện ra một đối tượng có thể lặp lại mà chỉ thực hiện __getitem__nhưng không __len__. Sử dụng iter(x)và bắt ngoại lệ.
Dale

Câu trả lời thứ hai của bạn không hoạt động. Trong PyUNO nếu tôi làm iter(slide1), tất cả đều tốt, tuy nhiên các isinstance(slide1, Iterable)cú ném TypeError: issubclass() arg 1 must be a class.
Hi-Angel

@ Hi-Angel nghe có vẻ như là một lỗi trong PyUNOThông báo rằng thông báo lỗi của bạn nói issubclass()thay vì isinstance().
Georg Schölly

2
Gọi iter () qua một đối tượng có thể là một hoạt động đắt tiền (xem DataLoader trong Pytorch, giúp giả mạo / sinh ra nhiều quá trình trên iter ()).
szali

126

Tôi muốn đổ một chút cắn nhẹ thêm về sự tương tác của iter, __iter____getitem__và những gì xảy ra đằng sau rèm cửa. Được trang bị kiến ​​thức đó, bạn sẽ có thể hiểu tại sao điều tốt nhất bạn có thể làm là

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

Tôi sẽ liệt kê các sự kiện trước và sau đó theo dõi nhanh chóng về những gì xảy ra khi bạn sử dụng một forvòng lặp trong python, sau đó là một cuộc thảo luận để minh họa các sự kiện.

Sự kiện

  1. Bạn có thể nhận được một trình vòng lặp từ bất kỳ đối tượng nào obằng cách gọi iter(o)nếu ít nhất một trong các điều kiện sau đây đúng:

    a) ocó một __iter__phương thức trả về một đối tượng lặp. Một iterator là bất kỳ đối tượng nào có phương thức __iter____next__(Python 2 next:).

    b) o__getitem__phương pháp.

  2. Kiểm tra một thể hiện của Iterablehoặc Sequence, hoặc kiểm tra thuộc tính __iter__là không đủ.

  3. Nếu một đối tượng ochỉ thực hiện __getitem__, nhưng không __iter__, iter(o)sẽ xây dựng một trình vòng lặp cố gắng tìm nạp các mục từ ochỉ số nguyên, bắt đầu từ chỉ số 0. Trình lặp sẽ bắt bất kỳ IndexError(nhưng không có lỗi nào khác) được đưa ra và sau đó StopIterationtự tăng .

  4. Theo nghĩa chung nhất, không có cách nào để kiểm tra xem trình lặp được trả về itercó lành mạnh hay không ngoài việc dùng thử.

  5. Nếu một đối tượng othực hiện __iter__, iterhàm sẽ đảm bảo rằng đối tượng được trả về __iter__là một trình vòng lặp. Không có kiểm tra độ tỉnh táo nếu một đối tượng chỉ thực hiện __getitem__.

  6. __iter__chiến thắng Nếu một đối tượng othực hiện cả hai __iter____getitem__, iter(o)sẽ gọi __iter__.

  7. Nếu bạn muốn làm cho các đối tượng của riêng mình lặp lại, luôn luôn thực hiện __iter__phương thức.

for vòng lặp

Để làm theo, bạn cần hiểu về những gì xảy ra khi bạn sử dụng một forvòng lặp trong Python. Hãy bỏ qua ngay phần tiếp theo nếu bạn đã biết.

Khi bạn sử dụng for item in ocho một số đối tượng lặp lại o, Python gọi iter(o)và mong đợi một đối tượng lặp là giá trị trả về. Trình lặp là bất kỳ đối tượng nào thực hiện phương thức __next__(hoặc nexttrong Python 2) và __iter__phương thức.

Theo quy ước, __iter__phương thức của một trình vòng lặp sẽ trả về chính đối tượng đó (tức là return self). Python sau đó gọi nextiterator cho đến khi StopIterationđược nâng lên. Tất cả điều này xảy ra ngầm, nhưng trình diễn sau đây cho thấy nó:

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

Lặp lại qua một 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

Thảo luận và minh họa

Ở điểm 1 và 2: nhận được một vòng lặp và kiểm tra không đáng tin cậy

Hãy xem xét các lớp sau:

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

Gọi itervới một thể hiện BasicIterablesẽ trả về một iterator mà không có bất kỳ vấn đề nào vì BasicIterablethực hiện __getitem__.

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

Tuy nhiên, điều quan trọng cần lưu ý là bkhông có __iter__thuộc tính và không được coi là một thể hiện của Iterablehoặc Sequence:

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

Đây là lý do tại sao Fluent Python của Luciano Ramalho khuyên bạn nên gọi itervà xử lý tiềm năng TypeErrorlà cách chính xác nhất để kiểm tra xem một đối tượng có thể lặp lại được không. Trích dẫn trực tiếp từ cuốn sách:

Kể từ Python 3.4, cách chính xác nhất để kiểm tra xem một đối tượng xcó thể lặp được hay không là gọi iter(x)và xử lý một TypeErrorngoại lệ nếu không. Điều này chính xác hơn so với sử dụng isinstance(x, abc.Iterable), vì iter(x)cũng xem xét __getitem__phương pháp kế thừa , trong khi IterableABC thì không.

Ở điểm 3: Lặp lại các đối tượng chỉ cung cấp __getitem__, nhưng không__iter__

Lặp lại một thể hiện của các BasicIterablecông việc như mong đợi: Python xây dựng một trình vòng lặp cố gắng tìm nạp các mục theo chỉ mục, bắt đầu từ 0, cho đến khi IndexErrorđược nâng lên. __getitem__Phương thức của đối tượng demo chỉ đơn giản trả về itemcái được cung cấp làm đối số cho __getitem__(self, item)iterator trả về 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

Lưu ý rằng iterator tăng StopIterationkhi nó không thể trả về mục tiếp theo và mục IndexErrorđược nâng lên item == 3được xử lý bên trong. Đây là lý do tại sao việc lặp qua a BasicIterablevới một forvòng lặp hoạt động như mong đợi:

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

Đây là một ví dụ khác để lái xe về nhà khái niệm về cách trình lặp được trả về bằng cách itercố gắng truy cập các mục theo chỉ mục. WrappedDictkhông kế thừa từ dictđó, có nghĩa là các trường hợp sẽ không có __iter__phương thức.

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

Lưu ý rằng các cuộc gọi __getitem__được ủy quyền dict.__getitem__mà ký hiệu dấu ngoặc vuông chỉ đơn giản là một tốc ký.

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

Trên điểm 4 và 5: iterkiểm tra một trình vòng lặp khi nó gọi__iter__ :

Khi iter(o)được gọi cho một đối tượng o, itersẽ đảm bảo rằng giá trị trả về của __iter__, nếu phương thức có mặt, là một trình vòng lặp. Điều này có nghĩa là đối tượng trả về phải thực hiện __next__(hoặc nexttrong Python 2) và __iter__. iterkhông thể thực hiện bất kỳ kiểm tra độ tỉnh táo nào cho các đối tượng chỉ cung cấp __getitem__, vì nó không có cách nào để kiểm tra xem các mục của đối tượng có thể truy cập được bằng chỉ số nguyên hay không.

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

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

Lưu ý rằng việc xây dựng một trình vòng lặp từ các FailIterIterabletrường hợp không thành công ngay lập tức, trong khi việc xây dựng một trình vòng lặp từ FailGetItemIterablethành công, nhưng sẽ ném Ngoại lệ vào lệnh gọi đầu tiê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

Trên điểm 6: __iter__thắng

Điều này là đơn giản. Nếu một đối tượng thực hiện __iter____getitem__, itersẽ gọi __iter__. Hãy xem xét các lớp sau

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

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

và đầu ra khi lặp qua một thể hiện:

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

Ở điểm 7: các lớp lặp của bạn sẽ thực hiện __iter__

Bạn có thể tự hỏi tại sao hầu hết các chuỗi dựng sẵn như listtriển khai một __iter__phương thức khi __getitem__nào là đủ.

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

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

Rốt cuộc, việc lặp lại qua các thể hiện của lớp ở trên, mà ủy nhiệm các cuộc gọi __getitem__đến list.__getitem__(sử dụng ký hiệu dấu ngoặc vuông), sẽ hoạt động tốt:

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

Những lý do lặp lại tùy chỉnh của bạn nên thực hiện __iter__như sau:

  1. Nếu bạn thực hiện __iter__, các thể hiện sẽ được coi là lặp lại và isinstance(o, collections.abc.Iterable)sẽ trả về True.
  2. Nếu đối tượng được trả về __iter__không phải là một trình vòng lặp, itersẽ thất bại ngay lập tức và tăng a TypeError.
  3. Việc xử lý đặc biệt __getitem__tồn tại vì lý do tương thích ngược. Trích dẫn một lần nữa từ Python thông thạo:

Đó là lý do tại sao bất kỳ chuỗi Python nào cũng có thể lặp lại: tất cả chúng đều thực hiện __getitem__. Trong thực tế, các trình tự tiêu chuẩn cũng thực hiện__iter__ và bạn cũng vậy, vì việc xử lý đặc biệt __getitem__tồn tại vì lý do tương thích ngược và có thể không còn trong tương lai (mặc dù nó không bị phản đối khi tôi viết bài này).


Vì vậy, có an toàn để xác định một vị ngữ is_iterablebằng cách trở lại Truetrong trykhối và Falsetrong except TypeErrorkhối?
alancalvitti

Đây là một câu trả lời tuyệt vời. Tôi nghĩ rằng nó làm nổi bật bản chất không trực quan và không may của giao thức getitem. Nó không bao giờ nên được thêm vào.
Neil G

31

Điều này là không đủ: đối tượng được trả về __iter__phải thực hiện giao thức lặp (tức là nextphương thức). Xem phần liên quan trong tài liệu .

Trong Python, một thực hành tốt là "thử và xem" thay vì "kiểm tra".


9
"Gõ vịt" Tôi tin không? :)
willem

9
@willem: hoặc "đừng xin phép mà hãy tha thứ" ;-)
jldupont

14
@willem Cả hai kiểu "cho phép" và "tha thứ" đều đủ điều kiện là kiểu gõ vịt. Nếu bạn hỏi một đối tượng có thể làm gì hơn là nó gì , thì đó là gõ vịt. Nếu bạn sử dụng hướng nội, đó là "sự cho phép"; nếu bạn chỉ cố gắng làm nó và xem nó có hoạt động hay không, đó là "sự tha thứ".
Mark Reed

22

Trong Python <= 2.5, bạn không thể và không nên - iterable là giao diện "không chính thức".

Nhưng vì Python 2.6 và 3.0, bạn có thể tận dụng cơ sở hạ tầng ABC (lớp cơ sở trừu tượng) mới cùng với một số ABC dựng sẵn có trong mô-đun bộ sưu tập:

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)

Bây giờ, cho dù điều này là mong muốn hoặc thực sự hoạt động, chỉ là một vấn đề của quy ước. Như bạn thấy, bạn có thể đăng ký một đối tượng không thể lặp lại dưới dạng Iterable - và nó sẽ đưa ra một ngoại lệ khi chạy. Do đó, isinstance có được ý nghĩa "mới" - nó chỉ kiểm tra tính tương thích của loại "khai báo", đây là một cách hay để sử dụng Python.

Mặt khác, nếu đối tượng của bạn không thỏa mãn giao diện bạn cần, bạn sẽ làm gì? Lấy ví dụ sau:

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

Nếu đối tượng không thỏa mãn những gì bạn mong đợi, bạn chỉ cần ném TypeError, nhưng nếu ABC thích hợp đã được đăng ký, séc của bạn sẽ không đáng tin. Ngược lại, nếu__iter__ phương thức khả dụng thì Python sẽ tự động nhận ra đối tượng của lớp đó là Iterable.

Vì vậy, nếu bạn chỉ mong đợi một lần lặp, lặp đi lặp lại và quên nó đi. Mặt khác, nếu bạn cần làm những việc khác nhau tùy thuộc vào loại đầu vào, bạn có thể thấy cơ sở hạ tầng ABC khá hữu ích.


13
không sử dụng mã trần except:trong ví dụ cho người mới bắt đầu. Nó thúc đẩy thực hành xấu.
JFS

JFS: Tôi sẽ không, nhưng tôi cần phải trải qua nhiều mã tăng ngoại lệ và tôi không muốn bắt ngoại lệ cụ thể ... Tôi nghĩ mục đích của mã này khá rõ ràng.
Alan Franzoni

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

Đừng chạy kiểm tra xem con vịt của bạn có thực sự là một con vịt để xem nó có lặp lại được hay không, hãy coi nó như thể nó và phàn nàn nếu nó không.


3
Về mặt kỹ thuật, trong quá trình lặp lại, tính toán của bạn có thể ném TypeErrorvà ném bạn ra đây, nhưng về cơ bản là có.
Chris Lutz

6
@willem: Vui lòng sử dụng timeit để thực hiện điểm chuẩn. Các ngoại lệ của Python thường nhanh hơn các câu lệnh if. Họ có thể đi một con đường ngắn hơn một chút thông qua trình thông dịch.
S.Lott

2
@willem: IronPython có ngoại lệ chậm (so với CPython).
jfs

2
Một thử làm việc: tuyên bố là thực sự nhanh chóng. Vì vậy, nếu bạn có một vài ngoại lệ, hãy thử - ngoại trừ nhanh. Nếu bạn mong đợi nhiều trường hợp ngoại lệ, thì nếu if có thể nhanh hơn.
Arne Babenhauserheide

2
Không nên bắt đối tượng ngoại lệ bằng cách thêm " as e" sau TypeErrorthay vì thêm " , e"?
HelloGoodbye

21

Python 3.5, bạn có thể sử dụng mô-đun từ thư viện chuẩn cho các thứ liên quan đến loại:

from typing import Iterable

...

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

18

Giải pháp tốt nhất tôi tìm thấy cho đến nay:

hasattr(obj, '__contains__')

về cơ bản kiểm tra nếu đối tượng thực hiện intoán tử.

Ưu điểm (không có giải pháp nào khác có cả ba):

  • nó là một biểu thức (hoạt động như một lambda , trái ngược với thử ... ngoại trừ biến thể)
  • nó được (nên) được thực hiện bởi tất cả các iterables, bao gồm cả các chuỗi (trái ngược với __iter__)
  • hoạt động trên mọi Python> = 2.5

Ghi chú:

  • triết lý "cầu xin tha thứ, không cho phép" của Python không hoạt động tốt khi ví dụ trong danh sách bạn có cả iterables và non-iterables và bạn cần đối xử với từng yếu tố khác nhau tùy theo loại của nó (đối xử với iterables khi thử và không lặp đi lặp lại ngoại trừ sẽ hoạt động, nhưng nó sẽ trông xấu xí và gây hiểu lầm)
  • các giải pháp cho vấn đề này cố gắng thực sự lặp lại đối tượng (ví dụ: [x for x in obj]) để kiểm tra xem nó có thể tạo ra các hình phạt hiệu suất đáng kể cho các lần lặp lớn hay không (đặc biệt là nếu bạn chỉ cần một vài yếu tố đầu tiên của lần lặp, cho ví dụ) và nên tránh

3
Đẹp, nhưng tại sao không sử dụng mô-đun bộ sưu tập như được đề xuất trong stackoverflow.com/questions/1952464/ Khăn ? Có vẻ biểu cảm hơn với tôi.
Dave Abrahams

1
Nó ngắn hơn (và không yêu cầu nhập thêm) mà không mất đi sự rõ ràng: có phương pháp "chứa" cảm giác giống như một cách tự nhiên để kiểm tra xem có thứ gì đó là một bộ sưu tập các đối tượng hay không.
Vlad

46
Chỉ vì thứ gì đó có thể chứa thứ gì đó không nhất thiết có nghĩa là nó có thể lặp lại. Ví dụ: người dùng có thể kiểm tra xem một điểm có trong khối 3D hay không, nhưng bạn sẽ lặp qua đối tượng này như thế nào?
Casey Kuball

13
Điều này là không chính xác. Bản thân một iterable không hỗ trợ chứa , ít nhất là với Python 3.4.
Peter Shinners

15

Bạn có thể thử điều này:

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

Nếu chúng ta có thể tạo một trình tạo lặp đi lặp lại trên nó (nhưng không bao giờ sử dụng trình tạo để nó không chiếm dung lượng), thì đó là lần lặp. Có vẻ như một loại "duh". Tại sao bạn cần xác định xem một biến có thể lặp lại ở vị trí đầu tiên không?


Thế còn iterable(itertools.repeat(0))? :)
badp

5
@badp, (x for x in a)chỉ cần tạo một trình tạo, nó không thực hiện bất kỳ phép lặp nào trên a.
Catchmeifyoutry

5
Là cố gắng (x for x in a)chính xác tương đương với cố gắng iterator = iter(a)? Hoặc có một số trường hợp hai là khác nhau?
tối đa

Không for _ in a: breakđơn giản hơn sao? Có chậm hơn không?
Mr_and_Mrs_D

2
@Mr_and_Mrs_D thật tệ nếu đối tượng được kiểm tra là một trình vòng lặp được lặp đi lặp lại sau đó (nó sẽ là 1 mục ngắn vì vị trí của nó không thể được đặt lại), tạo ra các trình tạo rác không lặp lại đối tượng vì chúng không được lặp lại, mặc dù tôi không chắc chắn rằng nó sẽ tăng 100% TypeError nếu không lặp lại được.
Tcll

13

Tôi tìm thấy một giải pháp tốt đẹp ở đây :

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

11

Theo Thuật ngữ Python 2 , các lần lặp là

tất cả các loại chuỗi (ví dụ như list, strtuple) và một số loại phi tự như dictfilevà các đối tượng của bất kỳ lớp bạn xác định với một __iter__()hoặc __getitem__()phương pháp. Lặp lại có thể được sử dụng trong một vòng lặp for và ở nhiều nơi khác, nơi cần một chuỗi (zip (), map (), ...). Khi một đối tượng lặp được truyền dưới dạng đối số cho hàm dựng sẵn iter (), nó sẽ trả về một iterator cho đối tượng.

Tất nhiên, với phong cách mã hóa chung cho Python dựa trên thực tế là nó dễ dàng hơn để yêu cầu sự tha thứ hơn là sự cho phép. Đây, kỳ vọng chung là sử dụng

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

Nhưng nếu bạn cần kiểm tra nó một cách rõ ràng, bạn có thể kiểm tra một lần lặp bằng cách hasattr(object_in_question, "__iter__") or hasattr(object_in_question, "__getitem__"). Bạn cần kiểm tra cả hai, vì strchúng không có __iter__phương thức (ít nhất là không có trong Python 2, trong Python 3 chúng làm) và vì generatorcác đối tượng không có __getitem__phương thức.


8

Tôi thường thấy thuận tiện, bên trong các tập lệnh của tôi, để xác định một iterablechức năng. (Bây giờ kết hợp đơn giản hóa đề xuất của Alfe):

import collections

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

vì vậy bạn có thể kiểm tra xem có đối tượng nào có thể lặp lại ở dạng rất dễ đọc không

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

như bạn sẽ làm với callablechức năng

EDIT: nếu bạn đã cài đặt numpy, bạn có thể thực hiện một cách đơn giản: từ numpy import iterable, đơn giản là một cái gì đó như

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

Nếu bạn không có numpy, bạn chỉ cần thực hiện mã này hoặc mã ở trên.


3
Bất cứ khi nào bạn làm sth như if x: return True else: return False(với xboolean), bạn có thể viết này như return x. Trong trường hợp của bạn return isinstance(…)mà không có bất kỳ if.
Alfe

Vì bạn thừa nhận rằng giải pháp của Alfe tốt hơn, tại sao bạn không chỉnh sửa câu trả lời của mình để nói đơn giản như vậy? Thay vào đó, bây giờ bạn có phiên bản CẢ HAI trong câu trả lời của bạn. Độ dài không cần thiết. Gửi một chỉnh sửa để sửa lỗi này.
ToolmakerSteve 12/12/13

2
Bạn nên bắt "TypeError" trong dòng `trừ: return false`. Nắm bắt mọi thứ là một mô hình xấu.
Mariusz Jamro

Biết rằng. Tôi đã dịch đoạn mã đó từ thư viện NumPy, sử dụng ngoại lệ chung.
fmonegaglia

Chỉ vì một mã được lấy từ NumPy không có nghĩa là nó tốt ... mẫu hay không, thời gian duy nhất nắm bắt mọi thứ nên được thực hiện là nếu bạn xử lý lỗi rõ ràng trong chương trình của mình.
Tcll

5

có chức năng tích hợp như thế:

from pandas.util.testing import isiterable

tuy nhiên điều này chỉ xem có __iter__và không thực sự quan tâm đến trình tự và tương tự.
ead

4

Nó luôn lảng tránh tôi về lý do tại sao trăn có callable(obj) -> boolnhưng không iterable(obj) -> bool...
chắc chắn sẽ dễ thực hiện hơn hasattr(obj,'__call__')ngay cả khi nó chậm hơn.

Vì mọi câu trả lời khác đều khuyên bạn nên sử dụng try/ except TypeError, trong đó việc kiểm tra ngoại lệ thường được coi là thực hành xấu trong bất kỳ ngôn ngữ nào, đây là cách triển khai iterable(obj) -> booltôi đã yêu thích và sử dụng thường xuyên hơn:

Vì lợi ích của python 2, tôi sẽ sử dụng lambda chỉ để tăng hiệu suất bổ sung đó ...
(trong python 3 không quan trọng bạn sử dụng gì để xác định chức năng, defcó tốc độ gần bằng lambda)

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

Lưu ý rằng hàm này thực thi nhanh hơn cho các đối tượng __iter__vì nó không kiểm tra __getitem__.

Hầu hết các đối tượng có thể lặp lại nên dựa vào __iter__nơi các đối tượng trong trường hợp đặc biệt rơi trở lại __getitem__, mặc dù hoặc là bắt buộc đối với một đối tượng có thể lặp lại được.
(và vì đây là tiêu chuẩn, nó cũng ảnh hưởng đến các đối tượng C)


anh ta không cung cấp mã làm việc, chứ đừng nói đến hiệu suất của trăn ... mặc dù câu trả lời này thực sự chỉ để thuận tiện như tôi đã thấy nhiều lần ở đây.
Tcll

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

Điều này sẽ nói có với tất cả các cách của các đối tượng lặp lại, nhưng nó sẽ nói không với các chuỗi trong Python 2 . (Đó là điều tôi muốn ví dụ khi một hàm đệ quy có thể lấy một chuỗi hoặc một chuỗi các chuỗi. Trong tình huống đó, yêu cầu sự tha thứ có thể dẫn đến obfuscode, và tốt hơn là nên xin phép trước.)

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)

Nhiều chiến lược khác ở đây sẽ nói có với chuỗi. Sử dụng chúng nếu đó là những gì bạn muố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__')

Lưu ý: is_iterable () sẽ nói có với các chuỗi loại bytesbytearray.

  • bytesCác đối tượng trong Python 3 đều có thể lặp lại True == is_iterable(b"string") == is_iterable("string".encode('utf-8'))Không có loại nào như vậy trong Python 2.
  • bytearray các đối tượng trong Python 2 và 3 có thể lặp lại True == is_iterable(bytearray(b"abc"))

Cách hasattr(x, '__iter__')tiếp cận OP sẽ nói có với các chuỗi trong Python 3 và không trong Python 2 (bất kể có ''hay b''không u''). Cảm ơn @LuisMasuelli vì đã nhận thấy nó cũng sẽ khiến bạn thất vọng __iter__.


2

Cách dễ nhất, tôn trọng cách gõ vịt của Python , là bắt lỗi (Python biết hoàn toàn những gì nó mong đợi từ một đối tượng để trở thành một trình vòng lặp):

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

Ghi chú :

  1. Việc phân biệt đối tượng cho dù đối tượng không thể lặp lại hay lỗi __iter__đã được thực hiện là không liên quan , nếu loại ngoại lệ là như nhau: dù sao bạn cũng sẽ không thể lặp lại đối tượng.
  2. Tôi nghĩ rằng tôi hiểu mối quan tâm của bạn: Làm thế nào callabletồn tại dưới dạng kiểm tra nếu tôi cũng có thể dựa vào gõ vịt để nâng cao AttributeErrornếu __call__không được xác định cho đối tượng của mình, nhưng đó không phải là trường hợp kiểm tra lặp lại?

    Tôi không biết câu trả lời, nhưng bạn có thể thực hiện chức năng mà tôi (và những người dùng khác) đã đưa ra hoặc chỉ cần bắt ngoại lệ trong mã của bạn (việc triển khai của bạn trong phần đó sẽ giống như chức năng tôi đã viết - chỉ cần đảm bảo bạn tách biệt tạo iterator từ phần còn lại của mã để bạn có thể nắm bắt ngoại lệ và phân biệt nó với mã khác TypeError.


1

Các isiterableFunc tại trở về đoạn mã sau Truenếu đối tượng là iterable. nếu nó không lặp lạiFalse

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

thí dụ

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
rất nhiều câu trả lời chi tiết ở trên với nhiều câu trả lời và bạn ném vào một câu trả lời không giải thích được ... meh
Nrzonline

Xin vui lòng không đăng mã trần. Cũng bao gồm một lời giải thích về những gì đang làm.
Jonathan Mee

1

Thay vì kiểm tra __iter__thuộc tính, bạn có thể kiểm tra __len__thuộc tính, được thực hiện bởi mọi python dựng sẵn, bao gồm cả chuỗi.

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

Các đối tượng không thể lặp lại sẽ không thực hiện điều này vì lý do rõ ràng. Tuy nhiên, nó không bắt được các iterables do người dùng định nghĩa không thực hiện nó, cũng như các biểu thức trình tạo itercó thể xử lý. Tuy nhiên, điều này có thể được thực hiện trong một dòng và thêm một orbiểu thức kiểm tra đơn giản cho các trình tạo sẽ khắc phục vấn đề này. (Lưu ý rằng viết type(my_generator_expression) == generatorsẽ ném một NameError. Thay vào đó hãy tham khảo câu trả lời này .)

Bạn có thể sử dụng GeneratorType từ các loại:

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

--- chấp nhận câu trả lời của utdemir

(Điều này làm cho nó hữu ích để kiểm tra nếu bạn có thể gọi lenđối tượng mặc dù.)


thật không may, không phải tất cả các đối tượng có thể lặp lại đều sử dụng __len__... trong trường hợp này, đó thường là việc sử dụng khoảng cách tính toán không chính xác giữa 2 đối tượng. nơi obj.dist()có thể dễ dàng thay thế.
Tcll

Vâng. Hầu hết người dùng định nghĩa iterables thực hiện iter và getitem nhưng không len. Tuy nhiên, các kiểu dựng sẵn làm và nếu bạn muốn kiểm tra xem bạn có thể gọi hàm len trên nó hay không, kiểm tra len có an toàn hơn không. Nhưng bạn đúng.
DarthCadeus

0

Không thực sự "chính xác" nhưng có thể đóng vai trò kiểm tra nhanh hầu hết các loại phổ biến như chuỗi, bộ dữ liệu, số float, v.v ...

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

Đến bữa tiệc muộn nhưng tôi đã tự hỏi mình câu hỏi này và thấy điều này sau đó nghĩ đến một câu trả lời. Tôi không biết nếu ai đó đã đăng này. Nhưng về cơ bản, tôi đã nhận thấy rằng tất cả các loại có thể lặp lại có __getitem __ () trong chính tả của chúng. Đây là cách bạn sẽ kiểm tra nếu một đối tượng là một lần lặp mà không cần thử. (Pun dự định)

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

Thật không may, điều này là không đáng tin cậy. Ví dụ
gian

1
Đặt đối tượng là một ví dụ khác.
Raymond Hettinger
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.