làm thế nào để nói một biến là có thể lặp lại nhưng không phải là một chuỗi


88

Tôi có một hàm nhận đối số có thể là một mục đơn hoặc một mục kép:

def iterable(arg)
    if #arg is an iterable:
        print "yes"
    else:
        print "no"

vậy nên:

>>> có thể lặp lại (("f", "f"))
Đúng

>>> có thể lặp lại (["f", "f"])
Đúng

>>> có thể lặp lại ("ff")
Không

Vấn đề là chuỗi đó có thể lặp lại về mặt kỹ thuật, vì vậy tôi không thể chỉ bắt được ValueError khi thử arg[1]. Tôi không muốn sử dụng isinstance (), bởi vì đó không phải là phương pháp hay (hoặc vì vậy tôi đã nói).


1
Phiên bản Python nào? Tôi tin rằng câu trả lời là khác nhau giữa 2. * và 3
Kathy Van Đá

4
Bạn đã nói không chính xác, suy nghĩ không phải là thực hành xấu.
Lennart Regebro

3
Ồ, khoan đã, có thể anh ta nói đến nguyên tắc rằng việc kiểm tra kiểu đối tượng là tệ hại, và đây là dấu hiệu cho thấy chương trình bị hỏng? Điều này đúng về nguyên tắc (nhưng không phải lúc nào cũng đúng trong thực tế). Đây có thể là một trường hợp như vậy hoặc không. Nhưng vấn đề không phải là sự suy nghĩ của chức năng mà là do thói quen kiểm tra các kiểu.
Lennart Regebro

@Lennart: canonical.org/~kragen/isinstance nó có thể là lỗi thời mặc dù
priestc

@up Tuy nhiên, điều này không đề cập đến việc nạp chồng hàm dựa trên kiểu và isinstancelà cách để thực hiện điều đó trong các ngôn ngữ được nhập động. Một thứ không được sử dụng hàng ngày, nhưng OK trong những trường hợp hợp lý.
Kos

Câu trả lời:


50

Sử dụng isinstance (tôi không hiểu tại sao nó lại là phương pháp không tốt)

import types
if not isinstance(arg, types.StringTypes):

Lưu ý việc sử dụng StringTypes. Nó đảm bảo rằng chúng ta không quên một số loại chuỗi khó hiểu.

Mặt khác, điều này cũng hoạt động đối với các lớp chuỗi dẫn xuất.

class MyString(str):
    pass

isinstance(MyString("  "), types.StringTypes) # true

Ngoài ra, bạn có thể muốn xem câu hỏi trước này .

Chúc mừng.


NB: hành vi đã thay đổi trong Python 3 như StringTypesbasestringkhông còn được xác định. Tùy thuộc vào nhu cầu của bạn, bạn có thể thay thế chúng isinstancebằng str, hoặc một bộ con (str, bytes, unicode), ví dụ như dành cho người dùng Cython. Như @Theron Luhn đã đề cập, bạn cũng có thể sử dụng six.


Đẹp, scvalex. Tôi đang xóa -1 của mình và đặt nó thành +1 :-).
Tom

2
Tôi nghĩ ý tưởng thực hành tồi là do nguyên tắc gõ vịt . Là thành viên của một lớp cụ thể không có nghĩa là nó là đối tượng duy nhất có thể được sử dụng cũng như không có sẵn các phương thức mong đợi. Nhưng tôi nghĩ rằng đôi khi bạn không thể suy ra phương pháp này hoạt động gì ngay cả khi nó hiện diện, vì vậy isinstancecó thể là cách duy nhất.
estani

2
Lưu ý: styles.StringTypes không có sẵn trong Python 3. Vì chỉ có một loại chuỗi trong py3k, tôi nghĩ rằng nó an toàn do isinstance(arg, str). Đối với phiên bản tương thích ngược, hãy cân nhắc sử dụng pythonhosted.org/six/#six.string_types
Theron Luhn

Tôi hoàn toàn sử dụng Python3 và nhận thấy types.StringTypeskhông có sẵn trong Python3. Giá trị trong Python2 là gì?
kevinarpe

2
2017 : Câu trả lời này không còn hợp lệ nữa, hãy xem stackoverflow.com/a/44328500/99834 để biết câu trả lời hoạt động với tất cả các phiên bản Python.
sorin

26

Kể từ năm 2017, đây là một giải pháp di động hoạt động với tất cả các phiên bản của Python:

#!/usr/bin/env python
import collections
import six


def iterable(arg):
    return (
        isinstance(arg, collections.Iterable) 
        and not isinstance(arg, six.string_types)
    )


# non-string iterables    
assert iterable(("f", "f"))    # tuple
assert iterable(["f", "f"])    # list
assert iterable(iter("ff"))    # iterator
assert iterable(range(44))     # generator
assert iterable(b"ff")         # bytes (Python 2 calls this a string)

# strings or non-iterables
assert not iterable(u"ff")     # string
assert not iterable(44)        # integer
assert not iterable(iterable)  # function

Có một số mâu thuẫn nhỏ giữa 2/3 với bytestrings, nhưng nếu bạn sử dụng "string" gốc thì cả hai đều sai
Nick T.

16

Kể từ Python 2.6, với sự ra đời của các lớp cơ sở trừu tượng, isinstance(được sử dụng trên ABC, không phải các lớp cụ thể) hiện được coi là hoàn toàn chấp nhận được. Đặc biệt:

from abc import ABCMeta, abstractmethod

class NonStringIterable:
    __metaclass__ = ABCMeta

    @abstractmethod
    def __iter__(self):
        while False:
            yield None

    @classmethod
    def __subclasshook__(cls, C):
        if cls is NonStringIterable:
            if any("__iter__" in B.__dict__ for B in C.__mro__):
                return True
        return NotImplemented

Đây là một bản sao chính xác (chỉ thay đổi tên lớp) của Iterablenhư được định nghĩa trong _abcoll.py(chi tiết triển khai của collections.py) ... lý do mà điều này hoạt động như bạn muốn, trong khi collections.Iterablekhông, là bản sao sau đi xa hơn để đảm bảo các chuỗi được coi là có thể lặp lại, bằng cách gọi Iterable.register(str)rõ ràng ngay sau classcâu lệnh này .

Tất nhiên, thật dễ dàng để tăng cường __subclasshook__bằng cách quay lại Falsetrước lệnh anygọi cho các lớp khác mà bạn muốn loại trừ cụ thể khỏi định nghĩa của mình.

Trong bất kỳ trường hợp nào, sau khi bạn đã nhập mô-đun mới này myiter, isinstance('ciao', myiter.NonStringIterable)sẽ Falseisinstance([1,2,3], myiter.NonStringIterable)sẽ Truenhư bạn yêu cầu - và trong Python 2.6 trở lên, đây được coi là cách thích hợp để thể hiện các kiểm tra như vậy ... xác định một lớp cơ sở trừu tượng và kiểm tra isinstancenó.


Trong Python 3 isinstance('spam', NonStringIterable)trả về True.
Nick T

1
(...) và trong Python 2.6 và sau này, đây được coi là cách thích hợp để thể hiện các kiểm tra như vậy (...) Việc lạm dụng khái niệm lớp trừu tượng nổi tiếng theo cách như vậy có thể được coi là cách thích hợp như thế nào nằm ngoài khả năng hiểu của tôi. Cách thích hợp sẽ là giới thiệu một số toán tử trông giống như thay thế.
Piotr Dobrogost

Alex, bạn có thể giải quyết khẳng định của Nick rằng điều này không hoạt động trong Python 3 không? Tôi thích câu trả lời, nhưng muốn đảm bảo rằng tôi đang viết mã chống lại tương lai.
Merlyn Morgan-Graham

@ MerlynMorgan-Graham, đó là đúng, bởi vì __iter__ tại thực hiện trong chuỗi bằng Python 3. Vì vậy, tôi "dễ dàng để tăng thêm" đoạn trở thành áp dụng và ví dụ if issublass(cls, str): return Falsenhu cầu để được thêm vào lúc bắt đầu __subclasshook__(cũng như bất kỳ các lớp khác mà xác định __iter__nhưng trong bạn mindset không được chấp nhận là "lặp lại không phải chuỗi").
Alex Martelli

@AlexMartelli Đối với Python 3, không phải ý bạn là if issublass(C, str): return Falsenên thêm vào?
Rob Smallshire

4

Tôi nhận ra đây là một bài viết cũ nhưng tôi nghĩ rằng nó đáng để bổ sung cách tiếp cận của tôi cho hậu thế Internet. Hàm bên dưới dường như hoạt động đối với tôi trong hầu hết các trường hợp với cả Python 2 và 3:

def is_collection(obj):
    """ Returns true for any iterable which is not a string or byte sequence.
    """
    try:
        if isinstance(obj, unicode):
            return False
    except NameError:
        pass
    if isinstance(obj, bytes):
        return False
    try:
        iter(obj)
    except TypeError:
        return False
    try:
        hasattr(None, obj)
    except TypeError:
        return True
    return False

Điều này kiểm tra một chuỗi không phải là có thể lặp lại bởi (mis) bằng cách sử dụng tích hợp sẵn hasattrsẽ nâng lên một TypeErrorkhi đối số thứ hai của nó không phải là một chuỗi hoặc chuỗi unicode.


3

Bằng cách kết hợp các câu trả lời trước đó, tôi đang sử dụng:

import types
import collections

#[...]

if isinstance(var, types.StringTypes ) \
    or not isinstance(var, collections.Iterable):

#[Do stuff...]

Không phải là bằng chứng ngu ngốc 100%, nhưng nếu một đối tượng không phải là một đối tượng có thể lặp lại, bạn vẫn có thể để nó trôi qua và quay trở lại gõ vịt.


Chỉnh sửa: Python3

types.StringTypes == (str, unicode). Tương đương với Phython3 là:

if isinstance(var, str ) \
    or not isinstance(var, collections.Iterable):

Báo cáo nhập của bạn phải là 'loại' chứ không phải 'loại'
PaulR

3

2.x

Tôi sẽ đề nghị:

hasattr(x, '__iter__')

hoặc theo nhận xét của David Charles điều chỉnh điều này cho Python3, thì sao:

hasattr(x, '__iter__') and not isinstance(x, (str, bytes))

3.x

basestringkiểu tóm tắt nội trang đã bị xóa . Sử dụng strthay thế. Các strbytesloại không có đủ chức năng chung để đảm bảo một lớp cơ sở được chia sẻ.


3
Có lẽ vì __iter__có trên chuỗi trong Python 3?
davidrmcharles

@DavidCharles Ồ, vậy à? Lỗi của tôi. Tôi là một người dùng Jython và Jython hiện không có phiên bản 3.
mike động vật gặm nhấm

Đây thực sự không phải là một câu trả lời, nhiều hơn một bình luận / câu hỏi, và nó sai cho 3.x. Bạn có thể làm sạch nó? Bạn có thể thêm lý do cho tuyên bố "Loại 'str' và 'byte' không có đủ chức năng chung để đảm bảo một lớp cơ sở được chia sẻ." Một trong những điểm chính của 3.x là biến các byte Unicode trở thành công dân hạng nhất.
smci

Tôi không biết tại sao tôi lại viết bất kỳ điều nào ở trên. Tôi đề xuất xóa tất cả văn bản trong "3.x" ... mặc dù bạn đã chỉnh sửa câu trả lời của tôi. Chỉnh sửa nó nhiều hơn nếu bạn thích.
mike gặm nhấm

0

Như bạn đã chỉ ra một cách chính xác, một chuỗi đơn là một chuỗi ký tự.

Vì vậy, điều bạn thực sự muốn làm là tìm ra loại trình tự argbằng cách sử dụng isinstance hoặc type (a) == str.

Nếu bạn muốn nhận ra một hàm có một lượng tham số thay đổi, bạn nên làm như sau:

def function(*args):
    # args is a tuple
    for arg in args:
        do_something(arg)

function ("ff") và function ("ff", "ff") sẽ hoạt động.

Tôi không thể thấy trường hợp cần một hàm isiterable () như hàm của bạn. Không phải isinstance () là kiểu xấu mà là những tình huống bạn cần sử dụng isinstance ().


4
Việc sử dụng type(a) == strphải được tránh. Đó là một thực tiễn tồi vì nó không tính đến các loại tương tự hoặc các loại có nguồn gốc từ str. typekhông leo lên hệ thống phân cấp kiểu, trong khi isinstancecó, do đó tốt hơn là sử dụng isinstance.
AkiRoss

0

Để mở rộng một cách rõ ràng về bản hack tuyệt vời của Alex Martelli collections.pyvà giải quyết một số câu hỏi xung quanh nó: Giải pháp hoạt động hiện tại trong python 3.6+ là

import collections
import _collections_abc as cabc
import abc


class NonStringIterable(metaclass=abc.ABCMeta):

    __slots__ = ()

    @abc.abstractmethod
    def __iter__(self):
        while False:
            yield None

    @classmethod
    def __subclasshook__(cls, c):
        if cls is NonStringIterable:
            if issubclass(c, str):
                return False
            return cabc._check_methods(c, "__iter__")
        return NotImplemented

và chứng minh

>>> typs = ['string', iter(''), list(), dict(), tuple(), set()]
>>> [isinstance(o, NonStringIterable) for o in typs]
[False, True, True, True, True, True]

iter('')Ví dụ: nếu bạn muốn thêm vào các loại trừ, hãy sửa đổi dòng

            if issubclass(c, str):
                return False

được

            # `str_iterator` is just a shortcut for `type(iter(''))`*
            if issubclass(c, (str, cabc.str_iterator)):
                return False

để có được

[False, False, True, True, True, True]
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.