как сказать переменная итеративна, но не строка - PullRequest
71 голосов
/ 28 июня 2009

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

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 ]

42 голосов
/ 28 июня 2009

Используйте 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.

18 голосов
/ 02 июня 2017

Начиная с 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
16 голосов
/ 28 июня 2009

Начиная с 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 на нем.

4 голосов
/ 20 июня 2013

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

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

3 голосов
/ 18 декабря 2014

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

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):
2 голосов
/ 10 сентября 2015

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

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.
1 голос
/ 28 июня 2009

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

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

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

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...