как сказать, что переменная является итерируемой, но не является строкой

88

У меня есть функция, которая принимает аргумент, который может быть либо одиночным, либо двойным элементом:

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

так что:

>>> iterable (("е", "е"))
да

>>> iterable (["f", "f"])
да

>>> iterable ("ff")
нет

Проблема в том, что строка технически итеративна, поэтому я не могу просто поймать ValueError при попытке arg[1]. Я не хочу использовать isinstance (), потому что это не очень хорошая практика (по крайней мере, мне так сказали).

священник
источник
1
Какая версия Python? Я считаю, что ответ между 2 * и 3 отличается
Кэти Ван Стоун
4
Вам неправильно сказали, что это не плохая практика.
Lennart Regebro
3
Ой, подождите, может, он ссылается на принцип, что проверять тип объекта - это плохо, и что это показатель того, что программа не работает? В принципе это так (но не всегда на практике). Это может быть так, а может и не быть. Но проблема не в функции, а в привычке проверять типы.
Lennart Regebro
@Lennart: canonical.org/~kragen/isinstance, хотя он может быть устаревшим
priestc
@up Здесь не упоминается перегрузка функций на основе типов, и isinstanceэто способ сделать это на языках с динамической типизацией. Вещь не для повседневного использования, но годится в обоснованных случаях.
Кос

Ответы:

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.

сквалекс
источник
Отлично, сквалекс. Сейчас я удаляю свой -1 и делаю +1 :-).
Tom
2
Я думаю, что плохая практика связана с принципом утиного набора текста . Быть членом определенного класса не означает, что это единственный объект, который можно использовать, и что ожидаемые методы доступны. Но я думаю, что иногда вы просто не можете сделать вывод, что делает метод, даже если он присутствует, так что это isinstanceможет быть единственный способ.
estani 05
2
Примечание. Types.StringTypes недоступны в Python 3. Поскольку в py3k есть только один строковый тип, я думаю, что это безопасно do isinstance(arg, str). Для версии с обратной совместимостью рассмотрите возможность использования pythonhosted.org/six/#six.string_types
Терон
Я строго использую Python3 и заметил, что types.StringTypesон недоступен в Python3. Какое значение имеет Python2?
kevinarpe
2
2017 : Этот ответ больше не действителен, см. Stackoverflow.com/a/44328500/99834 , чтобы узнать , как работает со всеми версиями Python.
sorin
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 строками байтов, но если вы используете родную «строку», они оба ложны
Ник Т.
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.py(детали реализации collections.py) ... причина, по которой это работает так, как вы хотите, а collections.Iterableне нет, заключается в том, что последний делает все возможное, чтобы гарантировать, что строки считается повторяемым, вызывая Iterable.register(str)явным образом сразу после этого classоператора.

Конечно, это легко дополнить __subclasshook__, вернув Falseперед anyвызовом другие классы, которые вы хотите специально исключить из своего определения.

В любом случае, после того, как вы импортировали этот новый модуль, как myiter, isinstance('ciao', myiter.NonStringIterable)будет Falseи isinstance([1,2,3], myiter.NonStringIterable)будет True, как вы запрашиваете - и в Python 2.6 и более поздних версиях это считается правильным способом реализации таких проверок ... определить абстрактный базовый класс и проверьте isinstanceэто.

Алекс Мартелли
источник
В Python 3 isinstance('spam', NonStringIterable)возвращается True.
Nick T
1
(...) и в Python 2.6 и более поздних версиях это считается правильным способом реализации таких проверок (...) Каким образом злоупотребление хорошо известной концепцией абстрактного класса может считаться правильным способом , вне моего понимания. Правильный способ - вместо этого ввести какой-нибудь похожий на оператора оператор.
Петр Доброгост
Алекс, не могли бы вы ответить на утверждение Ника, что это не работает в Python 3? Мне нравится ответ, но я хочу убедиться, что пишу код, соответствующий требованиям завтрашнего дня.
Мерлин Морган-Грэм
@ MerlynMorgan-Graham, это правильно, потому что __iter__ в настоящее время осуществляется в строках в Python 3. Так что мой «легко дополнить» пункт становится применимым и , например , if issublass(cls, str): return Falseпотребности , которые будут добавлены в начале __subclasshook__(а также любые другие классы , которые определяют , __iter__но в вашем мышление не должно восприниматься как «нестроковые итерации»).
Алекс Мартелли
@AlexMartelli Для Python 3, разве вы не имеете в виду, что это if issublass(C, str): return Falseнужно добавить?
Роб Смоллшир,
4

Я понимаю, что это старый пост, но подумал, что стоит добавить свой подход для потомков Интернета. Приведенная ниже функция, похоже, работает для меня в большинстве случаев как с Python 2, так и с Python 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

Это проверяет наличие нестроковой итерации (неверно) с использованием встроенной функции, hasattrкоторая TypeErrorвызывает a, когда ее второй аргумент не является строкой или строкой Unicode.

Найджел Смолл
источник
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):
xvan
источник
Ваш оператор импорта должен быть "типы", а не "тип"
PaulR
3

2.x

Я бы предложил:

hasattr(x, '__iter__')

или с учетом комментария Дэвида Чарльза о настройке этого для Python3, как насчет:

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

3.x

встроенный basestringабстрактный тип был удален . strВместо этого используйте . В strи bytesтипах не имеют функциональность достаточно общий , чтобы гарантировать общий базовый класс.

Майк грызун
источник
3
Возможно, потому __iter__, что в Python 3 есть строки?
davidrmcharles
@DavidCharles Да правда? Виноват. Я пользователь Jython, а у Jython в настоящее время нет версии 3.
Майк грызун
На самом деле это не ответ, а скорее комментарий / вопрос, и это неправильно для 3.x. Не могли бы вы его очистить? Можете ли вы добавить обоснование для утверждения «Типы 'str' и 'bytes' не обладают достаточной общей функциональностью, чтобы гарантировать общий базовый класс». Одним из ключевых моментов 3.x было сделать байты Unicode удобными для использования.
smci
Я понятия не имею, почему написал что-то из вышеперечисленного. Предлагаю удалить весь текст под «3.x» ... хотя вы уже отредактировали мой ответ. Если хотите, отредактируйте его подробнее.
Майк грызун
0

Как вы правильно заметили, одна строка - это последовательность символов.

Поэтому вам действительно нужно выяснить, что это за последовательность arg, используя isinstance или type (a) == str.

Если вы хотите реализовать функцию, которая принимает переменное количество параметров, вы должны сделать это следующим образом:

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

функция («ff») и функция («ff», «ff») будут работать.

Я не вижу сценария, в котором нужна функция isiterable (), подобная вашей. Это не isinstance () плохой стиль, а ситуации, когда вам нужно использовать isinstance ().

Отто Аллмендингер
источник
4
Использование type(a) == strследует избегать. Это плохая практика, потому что она не принимает во внимание похожие типы или типы, производные от str. typeпо иерархии типов не лезет, пока не лезет isinstance, поэтому лучше использовать isinstance.
AkiRoss
0

Чтобы подробно collections.pyрассказать об отличном приеме Алекса Мартелли и ответить на некоторые вопросы, связанные с ним: Текущее рабочее решение в python 3.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]
Александр Макфарлейн
источник