Правильный способ определения параметра последовательности?

Я хочу написать функцию, которая принимает параметр, который может быть либо последовательностью, либо одним значением. Тип значения - str, int и т.д., Но я не хочу, чтобы он был ограничен жестко запрограммированным списком. Другими словами, я хочу знать, является ли параметр X последовательностью или чем-то, что мне нужно преобразовать в последовательность, чтобы избежать особой оболочки позже. Я мог бы сделать

type(X) in (list, tuple)

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

-N.

Изменить: см. мой "ответ" ниже, почему большинство из этих ответов мне не помогают. Возможно, вам есть что предложить.

Ответ 1

Проблема со всем вышеперечисленным упомянутыми способами является то, что str считается последовательностью (iterable, имеет getitem и т.д.), но это обычно рассматривается как отдельный элемент.

Например, функция может принимать аргумент, который может быть либо именем файла или список имен файлов. Что за самый Pythonic способ для функции определить первое из последнего?

Основываясь на пересмотренном вопросе, это звучит так, как будто вы хотите что-то большее:

def to_sequence(arg):
    ''' 
    determine whether an arg should be treated as a "unit" or a "sequence"
    if it a unit, return a 1-tuple with the arg
    '''
    def _multiple(x):  
        return hasattr(x,"__iter__")
    if _multiple(arg):  
        return arg
    else:
        return (arg,)

>>> to_sequence("a string")
('a string',)
>>> to_sequence( (1,2,3) )
(1, 2, 3)
>>> to_sequence( xrange(5) )
xrange(5)

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

При использовании этого параметра убедитесь, что все полученные данные могут обрабатывать итерации.

Ответ 2

Начиная с версии 2.6, используйте абстрактные базовые классы.

>>> import collections
>>> isinstance([], collections.Sequence)
True
>>> isinstance(0, collections.Sequence)
False

Кроме того, ABC может быть настроена для учета исключений, например, не считая последовательности, которые должны быть последовательностями. Вот пример:

import abc
import collections

class Atomic(object):
    __metaclass__ = abc.ABCMeta
    @classmethod
    def __subclasshook__(cls, other):
        return not issubclass(other, collections.Sequence) or NotImplemented

Atomic.register(basestring)

После регистрации класс Atomic может использоваться с isinstance и issubclass:

assert isinstance("hello", Atomic) == True

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

Обратите внимание, что в Python 3 синтаксис для определения метаклассов изменился, а абстракционный суперкласс basestring был удален, для чего вместо него требуется следующее:

class Atomic(metaclass=abc.ABCMeta):
    @classmethod
    def __subclasshook__(cls, other):
        return not issubclass(other, collections.Sequence) or NotImplemented

Atomic.register(str)

При желании можно написать код, совместимый как с Python 2.6+, так и с 3.x, но для этого требуется использование немного более сложного метода, который динамически создает необходимый абстрактный базовый класс, тем самым избегая синтаксических ошибок из-за разность синтаксиса метакласса. Это по существу то же самое, что и у Benjamin Peterson six модуль with_metaclass() функция делает.

class _AtomicBase(object):
    @classmethod
    def __subclasshook__(cls, other):
        return not issubclass(other, collections.Sequence) or NotImplemented

class Atomic(abc.ABCMeta("NewMeta", (_AtomicBase,), {})):
    pass

try:
    unicode = unicode
except NameError:  # 'unicode' is undefined, assume Python >= 3
    Atomic.register(str)  # str includes unicode in Py3, make both Atomic
    Atomic.register(bytes)  # bytes will also be considered Atomic (optional)
else:
    # basestring is the abstract superclass of both str and unicode types
    Atomic.register(basestring)  # make both types of strings Atomic

В версиях до версии 2.6 существуют тестеры типа в модуле operator.

>>> import operator
>>> operator.isSequenceType([])
True
>>> operator.isSequenceType(0)
False

Ответ 3

Последовательности описаны здесь: https://docs.python.org/2/library/stdtypes.html#sequence-types-str-unicode-list-tuple-bytearray-buffer-xrange

Таким образом, последовательности не совпадают с итерируемыми объектами. Я думаю, что последовательность должна __getitem__, тогда как итеративные объекты должны реализовывать __iter__. Так, например, строка представляет собой последовательности и не реализует __iter__, объекты xrange являются последовательностями и не реализуют __getslice__.

Но из того, что вы видели, чтобы делать, я не уверен, что вам нужны последовательности, а скорее повторяющиеся объекты. Так что для hasattr("__getitem__", X) вам нужны последовательности, но скорее верните hasattr("__iter__", X), если вам не нужны строки, например.

Ответ 4

IMHO, путь python должен передать список как * список. Как в:

myfunc(item)
myfunc(*items)

Ответ 5

В таких случаях я предпочитаю просто всегда брать тип последовательности или всегда брать скаляр. Строки не будут единственными типами, которые будут плохо себя вести в этой настройке; скорее, любой тип, который имеет совокупное использование и позволяет итерации по его частям, может плохо себя вести.

Ответ 6

Самый простой способ - проверить, можете ли вы превратить его в итератор. то есть

try:
    it = iter(X)
    # Iterable
except TypeError:
    # Not iterable

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

Как отмечали другие, строки также являются итерабельными, поэтому, если вам нужно их исключить (особенно важно, если рекурсия через элементы, поскольку список (iter ('a')) снова дает ['a'], тогда вам может понадобиться специально исключить их с помощью:

 if not isinstance(X, basestring)

Ответ 7

Я новичок здесь, поэтому не знаю, как правильно это сделать. Я хочу ответить на мои ответы:

Проблема со всеми вышеперечисленными способами заключается в том, что str считается последовательностью (она итерабельна, имеет __getitem__ и т.д.), но она обычно рассматривается как отдельный элемент.

Например, функция может принимать аргумент, который может быть либо именем файла, либо списком имен файлов. Какой самый питонический способ для функции обнаружить первый из последнего?

Должен ли я опубликовать это как новый вопрос? Изменить исходный?

Ответ 8

Я думаю, что я бы сделал, это проверить, имеет ли объект определенные методы, указывающие, что это последовательность. Я не уверен, есть ли официальное определение того, что делает последовательность. Лучшее, о чем я могу думать, это поддержка среза. Поэтому вы можете сказать:

is_sequence = '__getslice__' in dir(X)

Вы также можете проверить, какие функции вы собираетесь использовать.

Как отмечалось в комментарии к pi, одна проблема заключается в том, что строка представляет собой последовательность, но вы, вероятно, не хотите рассматривать ее как одну. Вы можете добавить явный тест, что тип не str.

Ответ 9

Пересмотренный ответ:

Я не знаю, соответствует ли ваша идея "последовательности" тому, что руководства Python называют "" Тип последовательности ", но в случае это так, вы должны искать метод __Contains__. Это метод, которым Python использует для реализации проверки" если что-то в объекте":

if hasattr(X, '__contains__'):
    print "X is a sequence"

Мой оригинальный ответ:

Я бы проверял, что полученный вами объект реализует интерфейс итератора:

if hasattr(X, '__iter__'):
    print "X is a sequence"

Для меня, что самое близкое соответствие с вашим определением последовательности, так как это позволит вам сделать что-то вроде:

for each in X:
    print each

Ответ 10

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

def is_iterable(x):
  if type(x) == str:
    return False
  try:
    iter(x)
    return True
  except TypeError:
    return False

Ответ 11

Вы задаете неправильный вопрос. Вы не пытаетесь обнаружить типы в Python; вы обнаруживаете поведение.

  • Напишите еще одну функцию, которая обрабатывает одно значение. (назовите его _use_single_val).
  • Напишите одну функцию, которая обрабатывает параметр последовательности. (назовите его _use_sequence).
  • Напишите третью родительскую функцию, которая вызывает два выше. (назовите его use_seq_or_val). Обведите каждый вызов обработчиком исключений, чтобы поймать недопустимый параметр (т.е. Не одно значение или последовательность).
  • Напишите модульные тесты, чтобы передать правильные и неправильные параметры родительской функции, чтобы убедиться, что они правильно выбрали исключения.

    def _use_single_val(v):
        print v + 1  # this will fail if v is not a value type

    def _use_sequence(s):
        print s[0]   # this will fail if s is not indexable

    def use_seq_or_val(item):    
        try:
            _use_single_val(item)
        except TypeError:
            pass

        try:
            _use_sequence(item)
        except TypeError:
            pass

        raise TypeError, "item not a single value or sequence"

EDIT: пересмотрен для обработки "последовательности или единственного значения", заданной в вопросе.

Ответ 12

Вы можете передать свой параметр во встроенной функции len() и проверить, вызывает ли это ошибку. Как говорили другие, тип строки требует специальной обработки.

В соответствии с документацией функция len может принимать последовательность (строка, список, кортеж) или словарь.

Вы можете проверить, что объект представляет собой строку со следующим кодом:

x.__class__ == "".__class__