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

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

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

чтобы:

>>> iterable( ("f","f") )
yes

>>> iterable( ["f","f"] )
yes

>>> iterable("ff")
no

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

Ответ 1

Использовать 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.

Ответ 2

Так как 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 doesn 't, заключается в том, что последний имеет дополнительную милю, чтобы гарантировать, что строки считаются итерируемыми, вызывая Iterable.register(str) явно сразу после этого оператора class.

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

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

Ответ 3

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

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

Ответ 4

Начиная с 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)


x = iterable(("f", "f")) and iterable(["f", "f"]) and not iterable("ff")

print(x)

Ответ 5

Объединив предыдущие ответы, я использую:

import types
import collections

#[...]

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

#[Do stuff...]

Не 100% доказательство дураков, но если объект не является итерируемым, вы все равно можете позволить ему пройти и вернуться к печати утки.

Ответ 6

Как вы правильно указываете, одна строка представляет собой последовательность символов.

Итак, вы действительно хотите узнать, что такое последовательность 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().

Ответ 7

Да, не понимаю... что не так с ходом

hasattr( x, '__iter__' )

?

... NB elgehelge помещает это в комментарий здесь, говоря "посмотри на мой более подробный ответ", но я не смог найти его/ее подробный ответ

позже

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

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

? По-видимому, "basestring" больше не является типом в Python3:

https://docs.python.org/3.0/whatsnew/3.0.html

The builtin basestring abstract type was removed. Use str instead. The str and bytes types don’t have functionality enough in common to warrant a shared base class.