Diversity w polskim IT
James Briggs
James BriggsAI Consultant @ UBS

Nietypowe, ale przydatne funkcje Pythona

Poznaj kilka mało znanych, ale bardzo przydatnych funkcji Pythona i zobacz, jak możesz ich poprawnie używać.
22.09.20205 min
Nietypowe, ale przydatne funkcje Pythona

Python zawsze czymś nas zaskoczy — w tym języku jest po prostu zbyt wiele wspaniałych funkcji. Oznacza to na szczęście, że zawsze będzie tam coś do nauki. Z biegiem czasu wyrobiłem sobie nawyk zapisywania każdej nowej funkcji, na jaką się w tym języku natknąłem. Są one dość interesujące, ale nie można ich używać w wielu przypadkach. Czasami jednak spotykam się z jakąś nietypową funkcją, która ma naprawdę szerokie zastosowanie — i często zmienia ona sposób, w jaki koduję. Stworzyłem sobie listę takich funkcji, a w tym artykule omówię pięć moich ulubionych, które się tam znajdują.

Get Method for Dictionaries - no more KeyErrors
Tree Datatypes - or autovivification
Advanced List Indexing - [::3]?
Decorator Functions - those @ things
Denote Scopes with Braces - not whitespace (my favorite feature)

Metoda Get dla słowników

Metoda słownikowa get wykonuje tę samą operację, co bardziej powszechna składnia dict[key] z jedną jednak różnicą — nie zgłaszamy tu błędu, jeśli key nie istnieje w naszym słowniku:

dictionary = {
    'one': 1,
    'two': 2
}
dictionary['three']
[Out]: KeyError: 'three'


Razem z get —

dictionary.get('three')
[Out]: None


Zamiast zwrócić KeyError, metoda get zwraca None. Możemy pójść o krok dalej, określając wartość do zwrócenia, jeśli key nie istnieje z drugim argumentem metody get:

dictionary.get('three', False)
[Out]: False
dictionary.get('three', "doesn't exist")
[Out]: 'doesn't exist'


Na koniec: jeśli znasz zawartość swojego słownika, to nie używaj get, ponieważ jest to wolniejsze (dzięki Petru).

Typy danych drzewa

Typ danych drzewa wygląda następująco:


Reprezentacja słów w zdaniu i odpowiadających im części mowy w drzewie. 


Powyższy rysunek reprezentuje hierarchiczną strukturę drzewa z wartością główną na najwyższej warstwie, która rozgałęzia się w dół do węzłów potomnych. Każdy węzeł potomny ma jeden węzeł nadrzędny, a każdy węzeł nadrzędny może mieć jeden lub więcej węzłów potomnych. Nasza reprezentacja w Pythonie będzie bardzo podobna do zagnieżdżonego słownika, który zbudujemy w następujący sposób:

tree = {
    'carnivora': {
        'canis': {
            'c.lupus': 'c.l.familiaris'
        },
        'felis': 'f.catus'
    }
}


Musimy tutaj zdefiniować nowy słownik dla każdego węzła potomnego, krok po kroku. Jest to powolny, nieuporządkowany i podatny na błędy proces — wyobraź sobie to dla prostego pięciowarstwowego drzewa, w którym każdy węzeł nadrzędny ma tylko dwa węzły potomne. Możemy zbudować nasz typ danych drzewa w następujący sposób:

class Tree(dict):
    def __missing__(self, key):
        value = self[key] = type(self)()
        return value


Zamiast definiować teraz każdy słownik podrzędny, możemy natychmiast budować całe gałęzie:

tree = Tree()
tree['carnivora']['canis']['c.lupus'] = 'c.l.familiaris'
tree['carnivora']['felis'] = 'f.catus'
print(tree)
[Out]: {
           'carnivora': {
               'canis': {
                   'c.lupus': 'c.l.familiaris'
               },
               'felis': 'f.catus'
           }
       }



Naukowe drzewo klasyfikacyjne przedstawiające najlepszego przyjaciela człowieka i to drugie zwierzę. Zdjęcie: Jamie Street, Unsplash (po lewej) oraz Kari Shea, Unsplash (po prawej).


Metoda ta ma swoją nazwę w języku angielskim — autovivification. Można to określić jako tworzenie nowych tablic mieszających, za każdym razem, gdy spotykamy dereferencję niezdefiniowanej wartości. Kolejną implementację w jednej linijce (z wyłączeniem importu) można znaleźć tutaj.

Zaawansowane indeksowanie list


Kroki

Istnieje kilka nieznanych metod tworzenia wycinków list, pomimo że są one przydatne. Pierwszą z nich są kroki:

x = [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
x[::2]
[Out]: [0, 4, 8, 12, 16]


Składnia, której tutaj używamy, to list[start: end: step] — ponieważ pozostawiamy początek i koniec puste i iterujemy od samego początku do końca listy, co drugi element.

x[3:8:2]
[Out]: [6, 10, 14]



Named Slices

Poniższa metoda dzielenia listy nosi nazwę named slices. Przypisujemy tutaj wycinek do zmiennej. Na przykład:

named_slice = slice(5, None)  # this is equivalent to [5:]


Możemy następnie zastosować nasz nazwany wycinek w dowolnej liście.

x[named_slice]
[Out]: [10, 12, 14, 16, 18]


Składnia, której tutaj używamy, używa tego samego wzorca z określeniem początku, końca, kroku — slice(start, end, step). Możemy przepisać x[3:8:2] w następujący sposób:

new_slice = slice(3, 8, 2)
x[new_slice]
[Out]: [6, 10, 14, 18]

Dekoratory

Dekoratory to jedna z tych dziwnych rzeczy typu @func_name, które wielu z nas z pewnością gdzieś widziało. Flash bardzo często je wykorzystuje. Są one zaskakująco łatwe do zrozumienia i niezwykle przydatne. Dekoratory pozwalają nam modyfikować zachowanie naszej funkcji bez potrzeby modyfikowania jej.

Możemy, na przykład, zdefiniować funkcję pointless, która będzie iterować po zakresie liczb, zwracając ostatecznie drukowanie końcowej wartości pomnożonej przez 2:

def pointless():
    for i in range(20000000):
        x = i*2
    print(x)
[Out]: 39999998


Funkcja ta będzie się uruchamiać przez dłuższy czas, ale tego właśnie chcemy. Będziemy mierzyć czas jej działania za pomocą dekoratora. Dekorator jest definiowany, jak każda normalna funkcja:

def timer(func):
    def wrapper():
        start = datetime.datetime.now()
        func()
        runtime = datetime.datetime.now() - start
        print(runtime)
    return wrapper


Możemy następnie użyć @syntax podczas definiowania funkcji do dziedziczenia zachowania timera.

@timer
def pointless():
    for i in range(20000000):
        x = i*2
    print(x)
pointless()
[Out]: 39999998
       0:00:01.220755  <-- this is returned from our decorator


Możemy również użyć wielu dekoratorów. Zdefiniujmy jeden i nazwizjmy go repeat. Będzie on dwukrotnie iterował po dowolnej funkcji.

def repeat(func):
    def wrapper():
        for i in range(2):
            func()
            print(i)
    return wrapper


Jeśli zastosujemy teraz dekoratory @timer oraz @repeat do funkcji pointless, oto, co otrzymamy:

@timer
@repeat
def pointless():
    for i in range(20000000):
        x = i*2
    print(x)
pointless()
[Out]: 39999998
       0               <-- printed in @repeat
       39999998
       1               <-- @repeat again
       0:00:01.220755  <-- printed from @timer


Opakowaliśmy tutaj pointless w @repeat, a wynikową funkcję w @timer. Wygląda to całkiem potwornie. Dekoratory, których tutaj użyliśmy, to tylko „zabawki”. Możemy z nimi zrobić dużo więcej — polecam zapoznać się z następującymi artykułami:

Oznaczanie zasięgów nawiasami klamrowymi

Jest to moja ulubiona zaawansowana funkcja w Pythonie. Zamiast polegać na wcięciach przy oznaczaniu zasięgów, możemy użyć nawiasów klamrowych. Importujemy funkcję z biblioteki __future__:

from __future__ import braces


I ruszamy!

No i tyle! Oto top pięć nietypowych, ale bardzo przydatnych funkcji w Pythonie. Poniżej wyróżnienia:

>>> import this
>>> import antigravity
>>> hash(float('inf'))


Przygotowałem małe repozytorium na GitHubie z przykładami wszystkich powyższych funkcji i nie tylko. Sprawdź, czy znajdziesz tam powyższe fragmenty kodu. Oczywiście możesz też dodać własne.

Jeśli masz jakieś sugestie albo pytania, skontaktuj się ze mną za pośrednictwem Twittera.

Dziękuję za uwagę!


Oryginał tekstu w języku angielskim przeczytasz tutaj.

<p>Loading...</p>