Как указать переменную, является итерируемой, но не строкой
У меня есть функция, которая принимает аргумент, который может быть либо отдельным элементом, либо двойным элементом:
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.