2010-08-15 6 views
9

Chcę utworzyć listę, która może akceptować tylko niektóre typy. Jako takie, Próbuję dziedziczą z listy w Pythonie i przesłanianie metody append() tak:Zastąpienie metody dołączania po odziedziczeniu z listy Python

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) 

Ta wola przyczyny nieskończonej pętli, ponieważ ciało append() zwraca się, ale Nie jestem pewien, co zrobić, oprócz używania self.append (element).

Jak powinienem to zrobić?

Odpowiedz

15

Wprowadziłem kilka zmian w twojej klasie. Wydaje się, że działa.

Kilka sugestii: nie używaj type jako słowa kluczowego - type jest wbudowaną funkcją. Zmienne instancji Pythona są dostępne przy użyciu prefiksu self.. Więc używaj 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'> 
+0

Dzięki za pomoc! – chaindriver

3

zamiast self.append(item) użycie super(TypedList, self).append(item) (patrz http://docs.python.org/library/functions.html#super)

+0

Próbowałem, ale nie pozwala mi. # Błąd: TypeError: super (type, obj): obj musi być instancją lub podtypem typu – chaindriver

+0

Aktualizacja: Wydaje się, że TypeError wynika z tego, że ta klasa Typedist i klasa wywołująca ją są w różnych modułach. Pozwól mi zrobić jakiś czek ... – chaindriver

+0

Tak, wydaje się, że jeśli umieścił TypedList w innym module, to super() nie działa więcej. – chaindriver

1

By nie robi to na pierwszym miejscu.

Jeśli nie chcesz, aby coś typu X było na liście, dlaczego to robisz?

To nie jest sarkastyczna odpowiedź. Dodawanie ograniczeń typów, które próbujesz, jest niepotrzebne lub afirmatywnie przynosi efekt przeciwny do zamierzonego. Jest to jednak powszechna prośba ze strony osób wywodzących się z języka, który ma ścisłe kontrole typu kompilacji.

Z tego samego powodu, dla którego nie próbowałbyś 'a string'/2.0, masz taką samą kontrolę nad tym, co zostanie umieszczone na liście. Ponieważ lista będzie zawierała typy heterogeniczne, w najlepszym wypadku TypedList przesunie obiekt TypeError w czasie wykonywania z punktu, w którym używasz elementu w czasie do miejsca, w którym dołączasz go do listy. Biorąc pod uwagę, że Python duck-typing jawnie sprawdza isinstance, wyklucza późniejsze rozszerzenie listy, aby zawierała instancje inne niż type, nie zapewniając żadnej korzyści.

dodana informacja OrderedDict:

na życzenie w komentarzu, zakładając, że Python 2.7 lub większą collections.OrderedDict to zrobi. Na tej stronie dokumentacji, podając 2.4 lub wyżej, jesteś have to add one.

+0

listy zostaną wykorzystane przez innych użytkowników i staram się zapobiec ich umieszczenie w nieprawidłowy typy. Czy powinienem robić to wszystko w inny sposób? – chaindriver

+0

Rozszerzyłem moją odpowiedź. Jeśli biblioteka ma umowę o akceptacji, zadeklaruj ją użytkownikowi biblioteki i pozwól mu zastrzelić własną stopę, jeśli zechce. cf. Pythonowa zasada "Łatwiej prosić o przebaczenie niż prawo". – msw

+0

Rozumiem, dziękuję za wgląd. Zapamiętam to! Właściwie mam inny powód do wdrożenia tego TypedList. Metoda __getitem__ przyjmuje łańcuch lub int. Jeśli pobiera ciąg znaków, przeszukuję każdy element na liście, porównując ciąg znaków z atrybutem o nazwie .name dla elementu. Ten atrybut .name istnieje tylko dla określonej klasy, którą utworzyłem. Mógłbym użyć do tego słownika, ale kolejność dodawania elementów jest ważna, więc muszę użyć listy. Wszelkie sugestie dotyczące lepszego sposobu radzenia sobie z tym? – chaindriver

40

I want to create a list that can only accept certain types. As such, I'm trying to inherit from a list in Python

Nie jest to najlepsze podejście! Listy w Pythonie mają tak wiele metod mutowania, że ​​musielibyśmy nadpisywać kilka (i prawdopodobnie zapomnielibyśmy o niektórych).

Raczej okład lista, dziedziczą collections.MutableSequence i dodać kontroli na nielicznych „dławik punktowych” metod, na których opiera MutableSequence do wdrożenia wszystkich innych.

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 argument jest normalnie krotką typów, które chcesz zezwolić, ale to jest OK, aby przekazać jeden typ tam oczywiście (i, dokonując że jeden typ abstrakcyjną klasą bazową, ABC, można łatwo przeprowadzić dowolne sprawdzanie typu w ten sposób - ale to inny problem).

Oto niektóre przykładowy kod korzystania z tej klasy:

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

wyjście jest:

['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 

Uwaga zwłaszcza że mamy nie nadpisane append - jeszcze dołączyć tam jest i zachowuje się tak samo zgodnie z oczekiwaniami.

nie-tak-tajemnica, że ​​nieco magii objawia się w traceback: _abcoll.py (moduł Implementacja abstrakcyjnych klas bazowych w module collections), na linii 556, narzędzia dołączyć wywołując naszych insert - - które my ma, oczywiście, poprawnie przesłonięte.

Ten "szablonowy wzór metody projektowania" (absolutnie cenny dla wszystkich rodzajów OOP - spójrz na moje przemówienia na temat wzorców projektowych na youtube, a dowiesz się dlaczego ;-), między innymi zaletami, daje nam "dławik" efekt punktowy "Wspominałem wcześniej: dodając kilka sprawdzeń na bardzo niewielu metodach, które musisz zastosować, zyskujesz przewagę, że te sprawdzenia odnoszą się do _all__ innych odpowiednich metod (a zmienne sekwencje w Pythonie mają ich wiele ;-).

Nic dziwnego, że otrzymujemy bardzo mocny i klasyczny wzór "za kulisami", ponieważ cała idea stojąca za tą strategią wdrożenia pochodzi wprost z nieśmiertelnej klasycznej książki "Wzorcowe wzory" (której autorzy często zbiorczo określany jako gang czterech ";-): preferują kompozycję obiektu nad dziedziczeniem. Dziedziczenie (z klas konkretnych) jest bardzo sztywnym mechanizmem sprzęgającym, pełnym" gotchas ", gdy tylko próbujesz go użyć do rób wszystko, nawet odrobinę poza rygorystycznymi ograniczeniami, kompozycja jest niezwykle elastyczna i użyteczna, a dziedziczenie z odpowiednich klas abstrakcyjnych może bardzo ładnie ukończyć obrazek:

Doskonały "Effective C++" Scotta Meyersa, item 33, jeszcze silniej: sprawiają, że klasy inne niż liść są abstrakcyjne. Skoro przez słowo "non-leaf" oznacza "dowolną klasę, która kiedykolwiek została odziedziczona", równoważne sformułowanie byłoby "nigdy nie odziedziczone po konkretnej klasie".

Scott pisze w kontekście C++, oczywiście, ale Paul Haahr daje dokładnie te same informacje dla Javy, sformułowane jako Nie podklasa konkretne zajęcia - i generalnie drugi go dla Pythona, choć nie faworyzują łagodniejsze sformułowanie "gang-of-four", wolą dziedziczenie kompozycji (klasy betonu) (ale rozumiem, że zarówno Scott, jak i Paul często piszą dla publiczności, która potrzebuje bardzo bezpośrednich i silnie sformułowanych rad, niemal sformułowanych jako "przykazania" zamiast porady, nie łagodniej sformułowane, które mogą zbyt łatwo zignorować w imię ich wygody ;-).

+0

Powiedziałeś "Nie najlepsze podejście!" pod warunkiem filozoficznych przyczyn OOP i podklasy, które są rzeczywiście poprawne. Jednak nie adresujesz "czy to w ogóle powinno być zrobione?" lub "jaki mechanizm zapewnia taki mechanizm?". Jak zauważyłem w mojej odpowiedzi, uważam, że wszystko, co na to stać, to przenoszenie błędów typu run-time w czasie, kosztem rozszerzenia. Chciałbym być pokazany, jeśli się mylę. – msw

+0

@msw, należy rozważyć: „Chcę listę modyfikowalnych sekwencji”, tak, że na przykład zapewnione jest ich kolejność, a gdy robię 'dla xw los: X [0] = 23' będę * * pewien, że Pierwszym elementem każdej pozycji jest 23, a później 'dla y in los [0]:' zawsze zaczyna się od 23. 'TypedList (collections.MutableSequence)' robi to dobrze. Jeśli "los" byłby "tylko listą", wydaje się, że działa "los.append (adict)", wydaje się, że działa 'x [0] = 23' (ale ustaw _arbitrary_ element' adict', ** not ** "the first"!) ... wtedy 'for y in los [0]:' byłoby subtelnym _semantic_ failure - brak błędów typu run-time, trudnych do znalezienia błędów! –

+0

Dzięki Alex, ta odpowiedź jest bardzo przydatna. Odpowiedziałeś na tak wiele wątpliwości, które miałem, gdy próbowałem wdrożyć tę klasę, np. jak zastąpić WSZYSTKIE metody z listy? Dziedziczenie z MutableSequence to doskonała metoda! Wymieniłeś także wiele dobrych koncepcji inżynierii oprogramowania! Mam jeszcze jedno pytanie, ale: w metodzie __init__, dlaczego utworzyć pustą listę, a następnie rozszerzenie go z argumentami, a nie tylko przypisanie args do listy tak: self.list = list (args)? – chaindriver

0

Można również użyć klasy wbudowanej array. Działa tylko z typami numerycznymi, ale prawdopodobnie jest najlepszym rozwiązaniem dla takich przypadków. Jest zoptymalizowany, aby zminimalizować zużycie pamięci.

Przykład:

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 

można wykonywać te same operacje jak przy normalnej listy:

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

ale podczas próby wstawienia innego rodzaju następnie określony, wyjątek jest podniesione:

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

Dla wszystkich dostępnych typów wyglądają here

0

Chociaż podoba mi się odpowiedź @Alex Martini, niestety nie jest to praktyczne w większości sytuacji. Istnieje DUŻO zbyt wielu rzeczy zarówno w standardowej bibliotece, jak i w innym kodzie biblioteki, który oczekuje, że typy list będą dziedziczyć po list. Nie możesz nawet wykonać json.dumps() bez dziedziczenia z list, chyba że napiszesz własną metodę serializacji dla swojego typu.

Nie zgadzam się z @msw, ponieważ możesz chcieć ujawnić coś takiego w bibliotece i nie mieć kontroli nad innym kodem.

Klasa list niestety nie pozwala zastąpić jednej funkcji, jak w przykładzie Alexa. Następujące parametry zastępują wszystkie funkcje, które mogą potencjalnie dodać do listy (zobacz tutaj wszystkie funkcje 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 ]) 
Powiązane problemy