Правильный способ определения параметра последовательности? - PullRequest
18 голосов
/ 20 ноября 2008

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

type(X) in (list, tuple)

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

-N.

Редактировать : см. Мой «ответ» ниже, почему большинство этих ответов мне не помогают. Может быть, вам есть что посоветовать.

Ответы [ 12 ]

19 голосов
/ 20 ноября 2008

Начиная с версии 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, но для этого требуется использование немного более сложной техники, которая динамически создает необходимый абстрактный базовый класс, тем самым избежать синтаксических ошибок из-за разницы в синтаксисе метакласса. По сути, это то же самое, что и функция Бенджамина Петерсона шесть модуля 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
5 голосов
/ 08 января 2009

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

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

Исходя из пересмотренного вопроса, звучит так, будто вы хотите что-то похожее на:

def to_sequence(arg):
    ''' 
    determine whether an arg should be treated as a "unit" or a "sequence"
    if it's 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)

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

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

4 голосов
/ 01 декабря 2008

ИМХО, способ python - передать список как * список. Как в:

myfunc(item)
myfunc(*items)
4 голосов
/ 20 ноября 2008

Последовательности описаны здесь: 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), если вы не хотите, например, строки.

3 голосов
/ 20 ноября 2008

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

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

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

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

 if not isinstance(X, basestring)
3 голосов
/ 20 ноября 2008

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

2 голосов
/ 23 ноября 2008

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

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

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

Должен ли я опубликовать это как новый вопрос? Редактировать оригинал?

1 голос
/ 04 декабря 2008

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

def is_iterable(x):
  if type(x) == str:
    return False
  try:
    iter(x)
    return True
  except TypeError:
    return False
1 голос
/ 20 ноября 2008

Исправленный ответ:

Я не знаю, соответствует ли ваша идея «последовательности» тому, что в руководствах по 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
1 голос
/ 20 ноября 2008

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

is_sequence = '__getslice__' in dir(X)

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

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

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