2015-01-12 4 views
18

В Python, при реализации типа последовательности, я часто (условно говоря) найти себе написание кода, как это:Как написать __getitem__ чисто?

class FooSequence(collections.abc.Sequence): 
    # Snip other methods 

    def __getitem__(self, key): 
     if isinstance(key, int): 
      # Get a single item 
     elif isinstance(key, slice): 
      # Get a whole slice 
     else: 
      raise TypeError('Index must be int, not {}'.format(type(key).__name__)) 

код проверяет тип аргумента явно с isinstance(). Это regarded as an antipattern в сообществе Python. Как этого избежать?

  • Я не могу использовать functools.singledispatch, потому что quite deliberately несовместимы с методами (он будет пытаться направить на self, что совершенно бесполезно, так как мы уже диспетчеризация на self через ООП полиморфизм). Он работает с @staticmethod, но что, если мне нужно получить материал из self?
  • Кастинг до int(), а затем ловли TypeError, проверка на ломтик и, возможно, повторное поднятие все еще некрасиво, хотя, возможно, немного меньше.
  • Возможно, было бы проще преобразовать целые числа в одноэлементные срезы и обрабатывать обе ситуации с одним и тем же кодом, но у этого есть свои проблемы (возврат 0 или [0]?).

ответ

13

Как ни странно, я подозреваю, что так, как у вас есть, это лучший способ обойти все. Шаблоны обычно существуют, чтобы охватить случаи общего использования, но это не означает, что их следует воспринимать как Евангелие, когда их следование усложняет жизнь. Основная причина, которую PEP 443 дает для исключения при явной проверке типов, заключается в том, что она «хрупка и закрыта для расширения». Однако это в основном относится к пользовательским функциям, которые в любое время используют разные типы. От Python docs on __getitem__:

Для типов последовательностей принятыми ключами должны быть целые и срезные объекты. Обратите внимание, что специальная интерпретация отрицательных индексов (если класс хочет эмулировать тип последовательности) зависит от метода __getitem __(). Если ключ имеет неподходящий тип, TypeError может быть поднят; если из значения вне набора индексов для последовательности (после любой специальной интерпретации отрицательных значений), IndexError должен быть поднят. Для типов сопоставления, если отсутствует ключ (не в контейнере), KeyError должен быть поднят.

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

Если вы хотите избежать явной проверки типов, я бы указал вам на this SO answer. Он содержит краткую реализацию декоратора @methdispatch (не мое имя, но я буду рулон с ним), что позволяет @singledispatch работать с методами, заставляя его проверять args[1] (arg), а не args[0] (self). Использование этого должно позволить вам использовать индивидуальную отправку с помощью метода __getitem__.

Независимо от того, считаете ли вы, что любой из этих «pythonic» зависит от вас, но помните, что, хотя Zen of Python отмечает, что «специальные случаи не являются достаточно специальными, чтобы нарушать правила», он сразу же отмечает, что " практичность превосходит чистоту ». В этом случае просто проверка на наличие двух типов, которые явно указывается в документации, - это то, что поддерживает __getitem__, и это похоже на практический способ для меня.

0

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

class UnannoyingSequence(collections.abc.Sequence): 

    def __getitem__(self, key): 
     if isinstance(key, int): 
      return self.getitem(key) 
     elif isinstance(key, slice): 
      return self.getslice(key) 
     else: 
      raise TypeError('Index must be int, not {}'.format(type(key).__name__)) 

    # default implementation in terms of getitem 
    def getslice(self, key): 
     # Get a whole slice 

class FooSequence(UnannoyingSequence): 
    def getitem(self, key): 
     # Get a single item 

    # optional efficient, type-specific implementation not in terms of getitem 
    def getslice(self, key): 
     # Get a whole slice 

Это очищает FooSequence достаточно того, что я мог бы даже сделать это таким образом, если У меня был только один производный класс. Я удивлен, что стандартная библиотека уже не работает.

+1

Конечно, язык * используется *, чтобы сделать это для нас с '__getslice__', но это устарело, что заставляет меня задуматься о надежности всей техники. – Kevin

+0

@ Кевин, он устарел по разным причинам. Они хотели создать объект 'slice', но' __getslice__' взял аргументы 'i' и' j'. Это сломало бы обратную совместимость. –

-1

Чтобы оставаться pythonic, у вас есть работа с семантикой, а не с типом объектов. Поэтому, если у вас есть какой-то параметр в качестве доступа к последовательности, просто используйте его так. Используйте абстракцию для параметра как можно дольше. Если вы ожидаете набор идентификаторов пользователей, не ожидайте набора, а скорее некоторую структуру данных со способом add. Если вы ожидаете какой-то текст, не ожидайте объект unicode, а скорее некоторый контейнер для символов с encode и decode методами.

Я предполагаю, что в общем случае вы хотите сделать что-то вроде «Использовать поведение базовой реализации, если не предусмотрено специальное значение. Если вы хотите реализовать __getitem__, вы можете использовать различие случаев, когда что-то другое происходит, если одно специальное значение . в условии, что я хотел бы использовать следующую схему:

class FooSequence(collections.abc.Sequence): 
    # Snip other methods 

    def __getitem__(self, key): 
     try: 
      if key == SPECIAL_VALUE: 
       return SOMETHING_SPECIAL 
      else: 
       return self.our_baseclass_instance[key] 
     except AttributeError: 
      raise TypeError('Wrong type: {}'.format(type(key).__name__)) 

Если вы хотите, чтобы отличить одно значение (в PERL терминологии «скаляр») и последовательность (в терминологии «коллекции» Java), то pythonically fine, чтобы определить, реализован ли итератор. Вы можете использовать шаблон try-catch или hasattr, как и сейчас:

>>> a = 42 
>>> b = [1, 3, 5, 7] 
>>> c = slice(1, 42) 
>>> hasattr(a, "__iter__") 
False 
>>> hasattr(b, "__iter__") 
True 
>>> hasattr(c, "__iter__") 
False 
>>> 

Применительно к нашему примеру:

class FooSequence(collections.abc.Sequence): 
    # Snip other methods 

    def __getitem__(self, key): 
     try: 
      if hasattr(key, "__iter__"): 
       return map(lambda x: WHATEVER(x), key) 
      else: 
       return self.our_baseclass_instance[key] 
     except AttributeError: 
      raise TypeError('Wrong type: {}'.format(type(key).__name__)) 

динамических языков программирования, таких как питон и использовать рубин утка печатать. И утка - это животное, которое ходит как утка, плавает, как утка и шарлатанцы, как утка. Не потому, что кто-то называет это «утка».

+2

Я бы согласился, но в документации для getitem явно указано «разрешить только объекты типа int и slice». Нет причин разрешать или принимать итерации по той простой причине, что getitem не должен принимать итерации, и для целей оператора '[]' список никогда не будет предоставлен. Утиная печать - все хорошо и хорошо, но в документации очень четко указано, какие предметы принимать. Дополнительную информацию см. В [документах python для getitem] (https://docs.python.org/3.4/reference/datamodel.html#object.__getitem__). –

+0

У меня нет базовой реализации. Я делаю все вручную. Код был упрощен для пояснительных целей; мой фактический код 3D-sliceable, не использует NumPy и должен делать справедливую сумму предварительной обработки индекса, которая в противном случае была бы довольно отвлекающей. – Kevin

+0

В этом случае я считаю, что ответ @ SevenDeadlySins наиболее полезен. – meisterluk

Смежные вопросы