В python есть ли способ узнать, реализует ли объект "реализует интерфейс", прежде чем передать его функции?

Я знаю, что это может показаться глупым вопросом, особенно тем, кто знает природу питона, но мне просто интересно, есть ли способ узнать, реализует ли объект "интерфейс", чтобы сказать?

Чтобы привести пример того, что я хочу сказать:

скажем, у меня есть эта функция:

def get_counts(sequence):
     counts = {}
     for x in sequence:
         if x in counts:
             counts[x] += 1
         else:
             counts[x] = 1
     return counts

Мой вопрос: есть ли способ убедиться, что объект передан функции iterable? Я знаю, что в Java или С# я мог бы сделать это, приняв метод любым объект, реализующий конкретный интерфейс, скажем (например) iIterable следующим образом: void get_counts(iIterable sequence)

Мое предположение заключается в том, что в Python мне пришлось бы использовать предварительные проверки самоанализаdecorator возможно?) и бросать пользовательский exception, если у объекта нет __iter__). Но существует ли более pythonic способ сделать это?

Ответ 1

Использовать полиморфизм и утиную печать до isinstance() или интерфейсов

Обычно вы определяете, что хотите делать с вашими объектами, либо используйте полиморфизм, чтобы настроить, как каждый объект реагирует на то, что вы хотите сделать, или вы используете печатание утки; если объект под рукой может сделать то, что вы хотите сделать в первую очередь. Это взаимозависимость по сравнению с компромиссом самоанализа, общепринятая мудрость утверждает, что вызов предпочтительнее интроспекции, но в Python предпочтение отдается методу тестирования isinstance.

Итак, вам нужно разобраться, почему вам нужно фильтровать на нем, или что-то в этом нетребоваемо; Зачем тебе это нужно знать? Просто используйте теги try: iter(object), except TypeError: # not iterable для тестирования.

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

ABCs

С утиным типом, вы можете обнаружить, что вам нужно проверить несколько методов, и, следовательно, тест isinstance() может выглядеть лучше. В таких случаях использование опции Абстрактный базовый класс (ABC) также может быть вариантом; Например, использование ABC позволяет вам "рисовать" несколько разных классов как подходящий тип для данной операции. Использование ABC позволяет сосредоточиться на задачах, которые необходимо выполнить, а не на конкретных используемых реализациях; вы можете иметь Paintable ABC, a Printable ABC и т.д.

Интерфейсы Zope и архитектура компонентов

Если вы обнаружите, что ваше приложение использует огромное количество ABC или вам нужно добавлять полиморфные методы в свои классы для решения различных ситуаций, следующий шаг - рассмотреть возможность использования полномасштабной архитектуры компонентов, например, Zope Component Architecture (ZCA).

zope.interface интерфейсы - это ABC на стероидах, особенно в сочетании с адаптерами ZCA. Интерфейсы документируют ожидаемое поведение класса:

if IFrobnarIterable.providedBy(yourobject):
    # it'll support iteration and yield Frobnars.

но также позволяет вам искать адаптеры; вместо того, чтобы вводить все виды поведения для каждого использования форм в ваших классах, вы реализуете адаптеры для обеспечения полиморфного поведения для конкретных случаев использования. Вы можете адаптировать свои объекты для печати, итерации или экспорта в XML:

class FrobnarsXMLExport(object):
    adapts(IFrobnarIterable)
    provides(IXMLExport)

    def __init__(self, frobnariterator):
        self.frobnars = frobnariterator

    def export(self):
        entries = []
        for frobnar in self.frobnars:
            entries.append(
                u'<frobnar><width>{0}</width><height>{0}</height></frobnar>'.format(
                    frobnar.width, frobnar.height)
        return u''.join(entries)

и ваш код просто должен искать адаптеры для каждой формы:

for obj in setofobjects:
    self.result.append(IXMLExport(obj).export())

Ответ 2

Python (начиная с версии 2.6) имеет абстрактные базовые классы (как виртуальные интерфейсы), которые более гибкий, чем интерфейсы Java или С#. Чтобы проверить, является ли объект итерируемым, используйте collections.Iterable:

if isinstance(obj, collections.Iterable):
    ...

Однако, если ваш блок else просто поднимет исключение, тогда самый ответ на Python: не проверяйте! Это до вашего вызывающего абонента, чтобы пройти соответствующий тип; вам просто нужно документировать, что вы ожидаете итерируемый объект.

Ответ 3

Питоновский способ - использовать утиную печать и "просить прощения, а не разрешения". Обычно это означает выполнение операции в блоке try, предполагая, что он ведет себя так, как вы ожидаете, а затем обрабатывать другие случаи в блоке except.

Ответ 4

Я думаю, что это так, что сообщество порекомендует вам сделать это:

import sys

def do_something(foo):
    try:
        for i in foo:
            process(i)
    except:
        t, ex, tb = sys.exc_info()
        if "is not iterable" in ex.message:
            print "Is not iterable"

do_something(True)

Или вы можете использовать что-то вроде zope.interface.