Переопределение метода добавления после наследования из списка Python - PullRequest
11 голосов
/ 15 августа 2010

Я хочу создать список, который может принимать только определенные типы. Поэтому я пытаюсь наследовать от списка в Python и переопределяю метод append () следующим образом:

class TypedList(list):
    def __init__(self, type):
        self.type = type

    def append(item)
        if not isinstance(item, type):
            raise TypeError, 'item is not of type %s' % type
        self.append(item)  #append the item to itself (the list)

Это может вызвать бесконечный цикл, потому что тело append () вызывает себя, но я не уверен, что делать, кроме как использовать self.append (item).

Как мне это сделать?

Ответы [ 6 ]

45 голосов
/ 15 августа 2010

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

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

Скорее, обернуть список, наследовать от collections.MutableSequence, идобавьте свои проверки к очень немногим методам «дросселирования», на которые опирается MutableSequence для реализации всех остальных.

import collections

class TypedList(collections.MutableSequence):

    def __init__(self, oktypes, *args):
        self.oktypes = oktypes
        self.list = list()
        self.extend(list(args))

    def check(self, v):
        if not isinstance(v, self.oktypes):
            raise TypeError, v

    def __len__(self): return len(self.list)

    def __getitem__(self, i): return self.list[i]

    def __delitem__(self, i): del self.list[i]

    def __setitem__(self, i, v):
        self.check(v)
        self.list[i] = v

    def insert(self, i, v):
        self.check(v)
        self.list.insert(i, v)

    def __str__(self):
        return str(self.list)

Аргумент oktypes обычно представляет собой набор типов, которые вы хотите разрешить, ноКонечно, все в порядке, чтобы передать один тип (и, сделав этот тип абстрактным базовым классом, ABC, вы можете легко выполнить любой тип проверки типа по вашему выбору таким способом - но это другая проблема).

Вот пример кода, использующего этот класс:

x = TypedList((str, unicode), 'foo', 'bar')
x.append('zap')
print x
x.append(23)

вывод:

['foo', 'bar', 'zap']
Traceback (most recent call last):
  File "tl.py", line 35, in <module>
    x.append(23)
  File "/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/_abcoll.py", line 556, in append
    self.insert(len(self), value)
  File "tl.py", line 25, in insert
    self.check(v)
  File "tl.py", line 12, in check
    raise TypeError, v
TypeError: 23

Обратите внимание, в частности, что not переопределеноappend - но приложение append существует и ведет себя так, как и ожидалось.

Не секрет, что за этим чудом раскрывается в трассировке: _abcoll.py (модуль реализации абстрактных базовых классов).в модуле collections), встрока 556, реализует добавление путем вызова нашего insert - который у нас есть , конечно, должным образом переопределен.

Этот "шаблон проектирования метода шаблона" (абсолютноДрагоценный для всех видов ООП - посмотрите мои выступления по шаблонам проектирования на YouTube, и вы поймете, почему ;-), помимо других преимуществ, дает нам «эффект удушья», о котором я упоминал ранее: добавив несколько проверок вочень мало методов, которые вы должны реализовать, вы получаете преимущество в том, что эти проверки применяются к _all__ другим соответствующим методам (и у изменяемых последовательностей в Python их много; -).

Не удивительно, что мы в конечном итогес очень мощным и классическим шаблоном дизайна «за кулисами», потому что вся идея этой стратегии реализации взята из бессмертной классической книги «Шаблоны проектирования» (авторов которой все вместе называют бандой четырех);): предпочитает композицию объекта наследованию .Наследование (от конкретных классов) - это очень жесткий связующий механизм, полный «ловушек», как только вы пытаетесь использовать его, чтобы сделать что-либо, даже слегка выходящее за его строгие пределы;композиция очень гибкая и полезная, и наследование от соответствующих абстрактных классов может очень хорошо дополнить картину.

Скотт Мейерс отлично «Эффективный C ++», item 33 ,выражает это еще сильнее: делает не листовые классы абстрактными .Поскольку под «неконечным» он подразумевает «любой класс, который когда-либо наследуется», эквивалентное выражение будет «никогда не наследуется от конкретного класса».

Скотт, конечно, пишет в контексте C ++, но Пол Хаар дает точно такой же совет для Java, сформулированный как Не делите подклассы на конкретные классы - и я обычно поддерживаю его для Python, хотя я действительно одобряю банду четырехболее мягкая формулировка, предпочитают композицию, а не наследование (конкретный класс) (но я понимаю, что и Скотт, и Пол часто пишут для аудитории, которая нуждается в очень прямом и строго сформулированном совете, почти сформулированном как "заповеди", а не совет, не более мягко сформулированный, который они могли бы слишком легко игнорировать во имя своего удобства; -).

16 голосов
/ 15 августа 2010

Я внес некоторые изменения в ваш класс. Кажется, это работает.

Пара предложений: не используйте type в качестве ключевого слова - type - встроенная функция. Доступ к переменным экземпляра Python осуществляется с использованием префикса self.. Так что используйте self.<variable name>.

class TypedList(list):
    def __init__(self, type):
        self.type = type

    def append(self, item):
        if not isinstance(item, self.type):
            raise TypeError, 'item is not of type %s' % self.type
        super(TypedList, self).append(item)  #append the item to itself (the list)

from types import *
tl = TypedList(StringType)
tl.append('abc')
tl.append(None)
Traceback (most recent call last):
  File "<pyshell#25>", line 1, in <module>
    tl.append(None)
  File "<pyshell#22>", line 7, in append
    raise TypeError, 'item is not of type %s' % self.type
TypeError: item is not of type <type 'str'>
3 голосов
/ 15 августа 2010

вместо self.append(item) используйте super(TypedList, self).append(item) (см. http://docs.python.org/library/functions.html#super)

1 голос
/ 19 июля 2016

Хотя мне нравится ответ @Alex Martini, он, к сожалению, не подходит для большинства ситуаций. И в стандартной библиотеке, и в другом библиотечном коде Слишком много вещей, которые ожидают, что типы списков наследуются от list. Вы даже не можете сделать json.dumps() без наследования от list, если вы не напишите свой собственный метод сериализации для вашего типа.

Я бы не согласился с @msw, потому что вы можете захотеть выставить что-то подобное в библиотеке и не иметь контроля над другим кодом.

К сожалению, класс list не позволяет вам переопределить одну функцию, как в примере Алекса. Следующее переопределяет все функции, которые потенциально могут быть добавлены в список (все функции списка см. Здесь: https://docs.python.org/2/library/stdtypes.html#mutable-sequence-types).

import collections

class CheckedList(list, collections.MutableSequence):
    def __init__(self, check_method, iterator_arg=None):
        self.__check_method = check_method
        if not iterator_arg is None:
            self.extend(iterator_arg) # This validates the arguments...

    def insert(self, i, v):
        return super(CheckedList, self).insert(i, self.__check_method(v))

    def append(self, v):
        return super(CheckedList, self).append(self.__check_method(v))

    def extend(self, t):
        return super(CheckedList, self).extend([ self.__check_method(v) for v in t ])

    def __add__(self, t): # This is for something like `CheckedList(validator, [1, 2, 3]) + list([4, 5, 6])`...
        return super(CheckedList, self).__add__([ self.__check_method(v) for v in t ])

    def __iadd__(self, t): # This is for something like `l = CheckedList(validator); l += [1, 2, 3]`
        return super(CheckedList, self).__iadd__([ self.__check_method(v) for v in t ])

    def __setitem__(self, i, v):
        if isinstance(i, slice):
            return super(CheckedList, self).__setitem__(i, [ self.__check_method(v1) for v1 in v ]) # Extended slice...
        else:
            return super(CheckedList, self).__setitem__(i, self.__check_method(v))

    def __setslice__(self, i, j, t): # NOTE: extended slices use __setitem__, passing in a tuple for i
        return super(CheckedList, self).__setslice__(i, j, [ self.__check_method(v) for v in t ])
1 голос
/ 10 сентября 2015

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

Пример:

from array import array
array('c', 'hello world')     # char array
array('u', u'hello \u2641')   # unicode array
array('l', [1, 2, 3, 4, 5])   # long array
array('d', [1.0, 2.0, 3.14])  # double array

Вы можете выполнять те же операции, что и с обычным списком:

chars = array('c')            
chars.extend('foo')

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

>>> chars.extend([5,10])
Traceback (most recent call last):
   File "<stdin>", line 1, in <module>
TypeError: array item must be char  

Для всех доступных типов смотрите здесь

1 голос
/ 15 августа 2010

Во-первых, не делая этого.

Если вы не хотите, чтобы что-то типа X в вашем списке, почему вы помещаете это туда?

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

По той же причине, по которой вы не попытались бы 'a string' / 2.0, вы имеете тот же контроль над тем, что заносится в список. Поскольку список, к счастью, будет содержать гетерогенные типы, в лучшем случае TypedList переместит ошибку TypeError во время выполнения из точки, в которой вы используете элемент, во время, куда вы добавите его в список. Учитывая, что Python duck-typing явно проверяет isinstance, препятствует более позднему расширению списка, чтобы содержать экземпляры, отличные от type, но не дает никаких преимуществ.

добавлена ​​информация OrderedDict :

За запрос в комментарии, предполагая, что Python 2.7 или выше collection.OrderedDict сделает это. На этой странице документации, если вы получили 2,4 или более, вам нужно добавить один .

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