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

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

type(X) in (list, tuple)

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

-N.

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

Обратите внимание, что объект типа str также является типом последовательности!

pi. 20.11.2008 16:56

@pi: правильно, и это настоящая проблема. Все "хорошие" ответы ниже не учитывают это.

noamtm 23.11.2008 13:26

Хм. На самом деле я бы сказал, что вы на самом деле не определили проблему. Если бы вы изначально сказали, в чем заключалась задача, вы могли бы сразу получить более полезные ответы.

Matthew Schinckel 23.11.2008 14:14

Суетливость заключается в том, что OP означает не «последовательность» в смысле Python (где последовательность имеет четко определенное значение), а в более свободном смысле «несколько вещей» против «одной вещи».

Gregg Lind 21.03.2010 22:51
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
18
4
4 606
12
Перейти к ответу Данный вопрос помечен как решенный

Ответы 12

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

is_sequence = '__getslice__' in dir(X)

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

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

hasattr (X, 'Getlice') - более эффективный способ сделать то же самое

Moe 20.11.2008 17:12
Getlice на самом деле устарел - срезы в пользовательских типах лучше обрабатывать, принимая объекты срезов в методе getitem, поэтому вещи могут быть разрезаны без Getlice. Я думаю, что вам также не нужно быть нарезанным, чтобы считаться последовательностью.
Brian 20.11.2008 18:42

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

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

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

x.__class__ == "".__class__

Лучший способ проверить строку - isinstance(x, basestr).

Ned Batchelder 23.11.2008 15:55

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

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

Файловые объекты имеют интерфейс итератора. Могут быть и другие объекты, которые не являются истинными последовательностями.

Dave Costa 20.11.2008 17:22

@Dave: вы правы насчет файловых объектов, имеющих интерфейс итератора. Я добавлю новый исправленный ответ

Ricardo Reyes 20.11.2008 20:48

Я повторю комментарий, сделанный к другому сообщению - вы должны использовать hasattr (X, 'содержит') вместо 'содержит' в dir (X)

Moe 20.11.2008 22:40

@Moe: разумно ожидать, что dir () никогда не вернет очень длинный список, поэтому разница в производительности между in и hasattr будет незначительной. И я думаю, что «in» яснее и лучше показывает намерение проверки, хотя, конечно, это субъективно.

Ricardo Reyes 20.11.2008 23:47

@ Рикардо: Я согласен с Мо. hasattr и понятнее, и быстрее.

Ned Batchelder 23.11.2008 15:56

Оба на самом деле здесь ошибочны (поскольку hasattr (list, 'содержит') тоже верно), но IMHO hasattr тоже лучше. dir () удобен для интерактивного использования, а не предназначен для получения исчерпывающих списков методов. См. docs.python.org/library/functions.html#dir

Brian 24.11.2008 14:54

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

Brian 24.11.2008 14:55

@ Брайан: ты прав. Я никогда не знал об ограничениях dir и всегда считал выбор между dir и hasattr делом своего выбора. Я соответствующим образом отредактирую свой ответ. Спасибо за информацию.

Ricardo Reyes 27.11.2008 17:24

Одно небольшое исправление: это hasattr (X, имя метода), а не hasattr, являющийся методом объекта.

Brian 28.11.2008 12:39

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

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

hasattr недостаточно для проверки его итерации. Список тип также будет иметь член iter, но сам не повторяемый. т.е. hasattr (list, 'iter') == True, но iter (list) -> «TypeError: объект 'type' не повторяется». Он также будет пропускать объекты, реализующие getitem, но не iter

Brian 24.11.2008 14:36

Хорошо для конкретного случая списка: это встроенный метод, а iter - статический метод (1 аргумент). В общем, да, утиный набор не работает! Но iter () создает итерируемый объект из не повторяемого (а не решение). Единственное «верное» решение - исключить / включить желаемые типы, как это сделали вы и noamtm.

Piotr Lesnicki 03.12.2008 05:43

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

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

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

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

 if not isinstance(X, basestring)

Мне это не помогает, потому что объект str повторяется, но я хочу рассматривать его как единый элемент.

noamtm 23.11.2008 13:22

Вот почему я упомянул проверку экземпляра. Поскольку строки являются полностью повторяемыми и индексируемыми, невозможно исключить их на основе их «последовательности», которая также не исключает какой-либо другой тип последовательности. Единственный реальный вариант - добавить явную проверку isinstance, чтобы рассматривать их как исключение.

Brian 24.11.2008 12:54

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

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

    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"

Обновлено: исправлено, чтобы обрабатывать «последовательность или одно значение», о котором говорится в вопросе.

Я не знаю, почему это было отклонено - я думаю, что это лучший намек. Рабочий процесс с динамически типизированным языком должен быть не «какого типа этот объект», а «что этот объект может делать?» Это то же самое, что и тестирование браузеров, для какого они браузера, а не того, как они ведут себя в контексте.

Matthew Schinckel 23.11.2008 14:13

Разве не об этом спрашивают? У него есть метод, основанный на обнаружении типа (тип (X) в (список, кортеж)), но ему нужен метод, который будет обнаруживать на основе поведения (действует как последовательность). Для его целей тоже нет «неправильных» параметров, он просто хочет, чтобы они делали разные вещи.

Brian 24.11.2008 14:44

-1, я согласен с определением поведения, но вопрос в перегрузке на основе последовательности / непоследовательности

orip 04.12.2008 19:39

Начиная с версии 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)

После регистрации класс Атомный можно использовать с это экземпляр и isubclass:

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

>>> assert operator.isSequenceType ("hello") == True (как указано в другом месте, строки являются последовательностями, и Coady, я уверен, хорошо знает .... исходный вопрос был недостаточно задан.

Gregg Lind 21.03.2010 22:44

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

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

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

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

I Ответ Коуди является лучшим, потому что он позволяет выбирать, какие классы считаются последовательностями или нет, независимо от того, какие специальные методы они могут иметь или не иметь.

martineau 02.12.2014 22:23

IMHO, способ python - передать список как * list. Как в:

myfunc(item)
myfunc(*items)

Фактически myfunc(*items) передает кортеж списка элементов.

martineau 11.09.2012 19:17

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

def is_iterable(x):
  if type(x) == str:
    return False
  try:
    iter(x)
    return True
  except TypeError:
    return False
Ответ принят как подходящий

The problem with all of the above mentioned ways is that str is considered a sequence (it's iterable, has getitem, etc.) yet it's usually treated as a single item.

For example, a function may accept an argument that can either be a filename or a list of filenames. What's the most Pythonic way for the function to detect the first from the latter?

Судя по пересмотренному вопросу, похоже, что вы хотите что-то вроде:

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)

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

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

Как отметил @Jim Brissom в комментарии о этот ответ к связанному с ним вопросу, строки, не имеющие атрибута __iter__, были несоответствием, которое было исправлено в Python 3, что делает этот подход несовместимым с будущим.

martineau 02.06.2011 05:26

Другие вопросы по теме