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


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

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(), потому что это не хорошая практика (так мне сказали).

7 59

7 ответов:

использовать isinstance (я не понимаю, почему это плохая практика)

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

обратите внимание на использование строковых типов. Это гарантирует, что мы не забудем о каком-то неясном типе строки.

С другой стороны, это также работает для производных строковых классов.

class MyString(str):
    pass

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

кроме того, вы можете взглянуть на это предыдущий вопрос.

Ура.


Примечание: поведение изменилось в Python 3, как StringTypes и basestring больше не определены. В зависимости от ваших потребностей, вы можете заменить их в isinstance by str, или кортеж подмножества (str, bytes, unicode), например, для пользователей Cython. Как @Theron Luhn упоминается, вы также можете использовать six.

начиная с Python 2.6, с введением абстрактных базовых классов,isinstance (используется для Азбуки, а не для конкретных классов) теперь считается вполне приемлемым. В частности:

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 на нем.

по состоянию на 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

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

это проверяет наличие нестроковой итерации (mis) с помощью встроенного hasattr который поднимет a TypeError когда его второй аргумент не является строкой или строкой Юникода.

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

import types
import collections

#[...]

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

#[Do stuff...]

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


Edit: Python3

types.StringTypes == (str, unicode). В Phython3 эквиваленте составляет:

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

хах, не понимаю... что случилось с

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.

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

Итак, то, что вы действительно хотите сделать, это выяснить, какая последовательность arg is с помощью isinstance или type (a)==str.

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

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

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

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