16 Rozszerzone wykrojenia

Od czasów Pythona 1.4 składnia wykrojeń pozwalała na użycie trzeciego argumentu, oznaczającego "krok". Wszystkie następujące napisy są więc zgodne ze składnią Pythona: L[1:10:2], L[:-1:1], L[::-1]. Własność ta została dodana na prośbę autorów pracujących nad projektem Numerical Python, w którym trzeci argument jest wykorzystywany bardzo intensywnie. Dotychczas jednak, choć składnia dopuszczała użycie rozszerzonego zapisu, wbudowane typy sekwencyjne, takie jak listy, krotki, czy napisy, nigdy z niego nie korzystały (przy użyciu rozszerzonego wykrojenia generowany był wyjątek TypeError. Dzięki łatce Michaela Hudsona w Pythonie 2.3 niedociągnięcie to zostało poprawione.

Możemy teraz na przykład wybrać z listy wszystkie elementy o parzystych indeksach:

>>> L = range(10)
>>> L[::2]
[0, 2, 4, 6, 8]

Wartości ujemne są również dopuszczalne, pozwalając na utworzenie kopii listy w odwrotnym porządku:

>>> L[::-1]
[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]

Podobnie rzecz się ma z krotkami, tablicami i napisami:

>>> s='abcd'
>>> s[::2]
'ac'
>>> s[::-1]
'dcba'

Operując na sekwencjach zmiennych (tj. na listach lub tablicach), możemy używać rozszerzonych wykrojeń w części docelowej instrukcji przypisania lub usunięcia, jednak pomiędzy przypisaniami do zwykłych i rozszerzonych wykrojeń występują pewne różnice. Wykonując przypisanie do zwykłego wykrojenia możemy zmienić długość sekwencji:

>>> a = range(3)
>>> a
[0, 1, 2]
>>> a[1:3] = [4, 5, 6]
>>> a
[0, 4, 5, 6]

Elastyczność rozszerzonych wykrojeń jest jednak ograniczona -- jeśli przy przypisaniu do rozszerzonego wykrojenia po prawej stronie znaku równości występuje lista, to musi ona zawierać tę samą liczbę elementów, co samo wykrojenie:

>>> a = range(4)
>>> a
[0, 1, 2, 3]
>>> a[::2]
[0, 2]
>>> a[::2] = [0, -1]
>>> a
[0, 1, -1, 3]
>>> a[::2] = [0,1,2]
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
ValueError: attempt to assign sequence of size 3 to extended slice of size 2

Usuwanie działa w bardziej oczywisty sposób:

>>> a = range(4)
>>> a
[0, 1, 2, 3]
>>> a[::2]
[0, 2]
>>> del a[::2]
>>> a
[1, 3]

Można również przekazywać obiekty wykrojenia metodzie __getitem__ wbudowanych typów sekwencyjnych:

>>> range(10).__getitem__(slice(0, 5, 2))
[0, 2, 4]

lub też używać obiektów wykrojenia bezpośrednio w odwołaniach do indeksów:

>>> range(10)[slice(0, 5, 2)]
[0, 2, 4]

Aby ułatwić implementowanie sekwencji, które obsługują rozszerzone wykrojenia, do obiektów wykrojenia dodano metodę indices(długość), która po podaniu długości sekwencji zwraca krotkę (początek, koniec, krok), którą można przekazać bezpośrednio do funkcji range(). Metoda indices() obsługuje indeksy pominięte lub wykraczające poza dopuszczalny zakres w sposób zgodny ze zwykłymi wykrojeniami (a stwierdzenie to kryje w sobie szereg nieciekawych szczegółów). Metody należy używać w następujący sposób:

class UdawanaSekw:
    ...
    def oblicz_element(self, i):
        ...
    def __getitem__(self, element):
        if isinstance(element, slice):
            indeksy = element.indices(len(self))
            return UdawanaSekw([self.oblicz_element(i) for i in range(*indeksy)])
        else:
            return self.oblicz_element(i)

W przykładzie widać też, że wbudowany obiekt slice jest teraz obiektem typu wykrojeniowego, a nie funkcją. Jest to zgodne z duchem zmian wprowadzonych w Pythonie 2.2, w którym typami stały się int, str.

Zajrzyj do Informacji na temat tej publikacji... aby pomóc w jej rozwoju.