2009-06-28 1 views
52

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

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

Какая версия Python? Я считаю, что ответ отличается от 2. * и 3 –

+0

Я использую версию 2.5 – priestc

+4

Вам сказали неправильно, isinstance - неплохая практика. –

ответ

34

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

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

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

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

class MyString(str): 
    pass 

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

Кроме того, вы можете захотеть взглянуть на эту previous question.

Cheers.


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

+0

Nice, scvalex. Я удаляю свой -1 сейчас и делаю его +1 :-). – Tom

+1

Я думаю, что идея плохой практики связана с принципом [утка ввода текста] (http://en.wikipedia.org/wiki/Duck_typing). Являясь членом определенного класса, это не означает, что это объект _only_, который можно использовать, и что ожидаемые методы доступны. Но я думаю, что иногда вы просто не можете сделать вывод о том, что делает этот метод, даже если он присутствует, поэтому «isinstance» может быть единственным способом. – estani

+3

-1, условный будет 'True', даже если input - число, функция, класс ... –

1

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

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

+1

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

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.

+0

В Python 3 'isinstance ('spam', NonStringIterable)' возвращает 'True'. –

+1

* (...) и в Python 2.6, и позже это считается надлежащим способом воплощения таких проверок (...) * Как можно было бы считать, что злоупотребление хорошо известной концепцией абстрактного класса можно было бы считать * правильным способом * не поддается пониманию. Правильный способ - ввести вместо этого некоторый * looklike *. –

+0

Алекс, можете ли вы ответить на утверждение Ника, что это не работает в Python 3? Мне нравится ответ, но я хотел бы убедиться, что я пишу будущий код. –

4

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

3

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

import types 
import collections 

#[...] 

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

#[Do stuff...] 

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

+0

Ваш оператор import должен быть 'types' not 'type' – PaulR

1

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

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

Возможно, потому что '__iter__' есть в строках в Python 3? –

+0

@DavidCharles О, правда? Виноват. Я пользователь Jython, и Jython в настоящее время не имеет версии 3. –

5

По состоянию на 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) 
Смежные вопросы