変数が反復可能であるが文字列ではないことを伝える方法


88

シングルアイテムまたはダブルアイテムのいずれかである引数を取る関数があります。

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

そのため:

>>> iterable(( "f"、 "f"))
はい

>>> iterable(["f"、 "f"])
はい

>>> iterable( "ff")
番号

問題は、文字列が技術的に反復可能であるため、試行時にValueErrorをキャッチできないことarg[1]です。isinstance()を使用したくないのは、それが良い習慣ではないためです(またはそう言われています)。


1
Pythonのどのバージョンですか?答えは2. *と3で異なると思います
Kathy Van Stone

4
あなたは間違って言われました、isinstanceは悪い習慣ではありません。
Lennart Regebro 2009年

3
ああ、待ってください、多分彼はオブジェクトタイプをチェックするのは悪いことであり、これはプログラムが壊れていることを示しているという原則に言及していますか?これは原則として当てはまります(ただし、実際には常にそうとは限りません)。これは、そのような場合とそうでない場合があります。しかし、問題となるのは関数isinstanceではなく、型をチェックする習慣です。
Lennart Regebro 2009年

@Lennart:canonical.org/~kragen/isinstance それは時代遅れかもしれません
priestc 2009年

@upこれは、型ベースの関数のオーバーロードについては言及していませんisinstanceが、動的に型付けされた言語でそれを行う方法です。日常的には使わないものですが、正当な場合はOKです。
コス

回答:


50

isinstanceを使用する(なぜそれが悪い習慣なのかわかりません)

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

StringTypesの使用に注意してください。これにより、あいまいなタイプの文字列を忘れないようになります。

利点として、これは派生文字列クラスでも機能します。

class MyString(str):
    pass

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

また、この前の質問を確認することをお勧めします。

乾杯。


NB:行動としてはPython 3に変更StringTypesし、basestringもはや定義されていません。あなたのニーズに応じて、それらを置き換えることができますisinstanceすることによってstr、またはサブセットのタプル(str, bytes, unicode)Cythonユーザーのための、例えば。@Theron Luhnが述べたように、を使用することもできますsix


ニース、scvalex。-1を削除して+1にします:-)。
トム

2
悪い習慣の考えは、ダックタイピングの原則によるものだと思います。特定のクラスのメンバーであることは、それが使用できる唯一のオブジェクトであることや、期待されるメソッドが使用可能であることを意味するものではありません。しかし、メソッドが存在していても、メソッドが何をするのかを推測できないことがあると思うので、それisinstanceが唯一の方法かもしれません。
estani 2012

2
注:types.StringTypesはPython 3では使用できません。py3kには文字列型が1つしかないため、安全だと思いますdo isinstance(arg, str)。下位互換性のあるバージョンについては、pythonhosted.org / six /#six.string_types
Theron Luhn

私は厳密にPython3を使用してtypes.StringTypesいますが、Python3では使用できないことに気づきました。Python2の価値は何ですか?
kevinarpe 2015

2
2017:この回答は無効になりました。すべてのバージョンのPythonで機能するものについては、stackoverflow.com / a / 44328500/99834をご覧ください。
sorin 2017年

26

2017年の時点で、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

そこバイト文字列と2/3の間にいくつかのマイナーな矛盾だが、あなたはネイティブ「string」を使用している場合、彼らは偽の両方だ
ニック・T

16

Python 2.6以降、抽象基本クラスisinstance(具象クラスではなく、ABCで使用)が導入されたことで、完全に受け入れられるようになりました。具体的には:

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

これは、(の実装の詳細)でIterable定義されているとおりの正確なコピー(クラス名のみを変更)です...これが希望どおりに機能する理由は、そうではありませんが、後者が文字列を確実にするために余分な努力をするためですこのステートメントの直後に明示的に呼び出すことにより、反復可能と見なされます。_abcoll.pycollections.pycollections.IterableIterable.register(str)class

もちろん、定義から明確に除外したい他のクラスを呼び出す前に__subclasshook__戻ることFalseで、簡単に拡張できanyます。

いずれにせよ、あなたは、この新しいモジュールをインポートした後にmyiterisinstance('ciao', myiter.NonStringIterable)なりますFalse、とisinstance([1,2,3], myiter.NonStringIterable)なりますTrue抽象基本クラスを定義し...とPython 2.6以降ではこれが具現化、そのようなチェックに適切な方法と考えられている-あなたが要求するのと同様に、確認isinstanceしてください。


Python 3では、をisinstance('spam', NonStringIterable)返しますTrue
ニックT

1
(...)そしてPython 2.6以降では、これはそのようなチェックを具体化する適切な方法と見なされます(...)このような方法で抽象クラスのよく知られた概念を悪用することが適切な方法と見なされる方法は私の理解を超えています。適切な方法は、代わりにいくつかの似たような演算子を導入することです。
Piotr Dobrogost 2014年

アレックス、これはPython 3では機能しないというニックの主張に対処できますか?私は答えが好きですが、将来を見据えたコードを書いていることを確認したいと思います。
マーリンモーガン-グラハム

ので、正しいですMerlynMorgan-グラハム、@__iter__ されて私の「簡単に補強するために、」段落が適用となり例えばので、今はPython 3内の文字列で実装if issublass(cls, str): return Falseの開始時に追加するニーズを__subclasshook__うまく定義する任意の他のクラスと同様に(__iter__しかし、あなたの中に考え方を「文字列以外の反復可能」として受け入れてはなりません)。
Alex Martelli 2015

@AlexMartelli Python 3の場合、それif issublass(C, str): return Falseを追加する必要があるという意味ではありませんか?
ロブスモールシャー2018

4

これは古い投稿だと思いますが、インターネットの後世へのアプローチを追加する価値があると思いました。以下の関数は、Python2と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

これは、2番目の引数が文字列またはUnicode文字列でない場合にhasattrを発生させる組み込みを(誤って)使用することにより、文字列以外の反復可能性をチェックしTypeErrorます。


3

以前の返信を組み合わせて、私は使用しています:

import types
import collections

#[...]

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

#[Do stuff...]

100%ばかげた証拠ではありませんが、オブジェクトが反復可能でない場合でも、オブジェクトを通過させてダックタイピングにフォールバックすることができます。


編集:Python3

types.StringTypes == (str, unicode)。Phython3に相当するものは次のとおりです。

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

インポートステートメントは「タイプ」ではなく「タイプ」である必要があります
PaulR 2015

3

2.x

私は提案したでしょう:

hasattr(x, '__iter__')

または、Python3用にこれを微調整したDavid Charlesのコメントを考慮して、どうでしょうか。

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

3.x

組み込みのbasestring抽象型が削除されましたstr代わりに使用してください。タイプは、共有ベースクラスを保証するために共通して機能を十分に持っていません。strbytes


3
おそらくPython3の__iter__文字列にあるからですか?
davidrmcharles 2016年

@DavidCharlesああ、本当に?私の悪い。私は、JythonのユーザーだとJythonのは、現在、バージョン3がありません
マイクげっ歯類

これは実際には答えではなく、コメント/質問であり、3.xでは間違っています。片付けてもらえますか?「「str」タイプと「bytes」タイプには、共有基本クラスを保証するのに十分な共通の機能がありません」と主張する理由を追加できますか3.xの重要なポイントの1つは、Unicodeバイトを第一級市民にすることでした。
smci

上記のいずれかを書いた理由がわかりません。「3.x」の下のすべてのテキストを削除することを提案します...あなたはすでに私の答えを編集していますが。必要に応じてさらに編集してください。
マイクげっ歯類

0

正しく指摘しているように、単一の文字列は文字シーケンスです。

したがって、本当にやりたいことargは、isinstanceまたはtype(a)== strを使用して、シーケンスの種類を見つけることです。

可変量のパラメーターを受け取る関数を実現したい場合は、次のようにする必要があります。

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

function( "ff")とfunction( "ff"、 "ff")は機能します。

あなたのようなisiterable()関数が必要なシナリオは見当たりません。悪いスタイルはisinstance()ではなく、isinstance()を使用する必要がある状況です。


4
使用type(a) == strは避けてください。類似のタイプまたはから派生しstrたタイプが考慮されていないため、これは悪い習慣です。typeは型階層を登りませんが、登りisinstanceますisinstance。したがって、を使用することをお勧めします。
AkiRoss 2015年

0

AlexMartelliの優れたハックを明示的に拡張する collections.py、その周りのいくつかの質問に対処するには:python3.6 +で現在機能しているソリューションは次のとおりです。

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

と実証

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

追加したい場合 iter('')たとえば、除外する場合は、行を変更します

            if issubclass(c, str):
                return False

することが

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

取得するため

[False, False, True, True, True, True]
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.