Podstawy Pythona + Pierwszy skrypt.

Podstawy Pythona oraz pierwsza wersja aplikacji "TO DO"

22 listopada 2016 12:00
Jakub Januzik
python development

Podstawy Pythona

Uruchamianie

Aby uruchomić Pythona w linii poleceń [np. Linuxowy Bash] wpisujemy komende python

Gdy to zrobimy, naszym oczom ukaże się interpreter, będzie on wyglądał mniej więcej tak:

Python 2.7.6 (default, Jun 22 2015, 17:58:13)
[GCC 4.8.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>>

Od tego momentu, wszystko co wpiszemy, język Python będzie interpretował, czyli będzie starał się rozpoznać instrukcje języka oraz wykonać kod.

Przykładowy skrypt

def main():
    print 'Hello Kuba!'

if __name__ == '__main__':
    main()

Załóżmy, że mamy przygotowany wcześniej skrypt Pythona, który powinien wyświetlić nasze imie. Jak go odpalić?

  1. Zapisujemy skrypt pod dowolną nazwą pliku np. hello.py
  2. Wywołujemy Pythona oraz wskazujemy mu plik, który ma wykonać, w tym przypadku jest komenda to python hello.py
  3. To wszystko, teraz powinniśmy zobaczyć w konsoli output naszego programu, czyli Hello {TwojeImie}.
    $ python hello.py
    Hello Kuba!
    

Podstawy syntaxu - wcięcia

W przeciwieństwie do innych, znanych języków programowania jak np. C++, Java czy C#, Python nie posiada znaków rozpoczynających/kończących bloki kodu. Zamiast tego, Python korzysta z wcięć (ang. indentation). W C++, Javie czy C#, wcięcia są opcjonalne, jednakże bardzo pożądane ze względu na czytelność kodu, z kolei w Pythonie są wymagane.

test_var = True
if test_var:
    print 'Welcome back, Kuba!'
else:
    print 'This will not execute!'

class MyClass(object):
    pass

Należy zachowywać konsekwencję wcięć - nie używać raz tabulatorów, a raz spacji. Popularnym stylem jest używanie 4 spacji. W przypadku niepoprawnych wcięć Python przerwie wykonywanie programu i zostanie rzucony wyjątek IntendationError

Zmienne

Python charakteryzuje się dynamicznym oraz silnym typowaniem. Co to znaczy?

  • Dynamiczne typowanie: Dynaczmicne typowanie oznacza, że podczas definiowania zmiennej, nie musimy podawać jej typu (int, string, char), interpreter Pythona automatycznie wywnioskuje to i przypisze odpowiedni typ do zmiennej. Dodatkowo, do danej zmiennej możemy przypisywać różne typy, np.

    >>> variable = 1
    >>> type(variable)
    <type 'int'>
    >>> variable = 'some string'
    >>> type(variable)
    <type 'str'>
    
  • Silne typowanie: Silne typowanie oznacza, że każda zmienna posiada swój typ i każde porównanie o wartości 'podobnej' ale innego typu zwróci nam fałsz, czyli np. 1 == '1' zawsze jest ewaluowane do fałszu.

Podstawowe struktury danych

Podstawowymi strukturami języka Python są słowniki (dictionary), listy (list) oraz krotki (tuple)

  • Listy: Są to znanie niektorym z C++ wektory, trzymające elementy w kolejności nadanej przez użytkownika. Deklarujemy ją pomocą nawiasów kwadratowych: [, ]. Elementami listy mogą być dowolne obiekty. W jednej liście mogą się również znajdować różne typy obiektów, np. String oraz Int jednocześnie.

    Przykładowa deklaracja listy:

    >>> my_list = [1, 2, 3, 4, 'a', 'b', 'c', [], [1, 2, 3]]
    >>> print my_list
    [1, 2, 3, 4, 'a', 'b', 'c', [], [1, 2, 3]]
    

    Wybrane operacje na listach:

    Do listy można dopisywać element, używajac metody append:

    >>> lista = [11, 22, 33]
    >>> lista.append(44)
    >>> print lista
    [11, 22, 33, 44]
    

    Możemy wybrać konkretny element z listy lub jej wycinek:

    >>> my_list = [1, 2, 3, 4, 'a', 'b', 'c', [], [1, 2, 3]]
    >>> my_list[5]
    'b'
    >>> my_list[0]
    1
    >>> my_list[5:7]
    ['b', 'c']
    >>> my_list[5:]
    ['b', 'c', [], [1, 2, 3]]
    >>> my_list[1:8:2]
    [2, 4, 'b', []]
    

    Ważna uwaga: Elementy listy są indeksowane od 0, także pierwszym elementem listy jest my_list[0] Zadanie dodatkowe: poczytać o range oraz róznicy między sorted(my_list) a my_list.sort()

    Edycja elementu listy:

    my_list[5] = 'TEST'
    >>> print my_list
    [1, 2, 3, 4, 'a', 'TEST', 'c', [], [1, 2, 3]]
    
  • Słowniki: Słowniki są to struktury danych trzymające wartości pod określonym kluczem (HashMapa). Deklarujemy je za pomocą nawiasów klamrowych: {, }:

    >>> my_dict = {1: 'Kuba', 2: 'Patryk'}
    >>> print my_dict
    {1: 'Kuba', 2: 'Patryk'}
    

    Wybieranie elementów ze słownika:

    >>> my_dict[1]
    'Kuba'
    >>> my_dict[2]
    'Patryk'
    

    Błąd podczas gdy klucz nie istnieje:

    >>> my_dict[3]
    Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
    KeyError: 3
    >>>
    

    Dodatkowa informacja: poczytać o funkcji słowników get

  • Krotki: Krotki, albo częściej znane jako tuple, w wielkim skrócie, sa to listy, których nie można edytować.

    >>> my_tuple = (1,2,3,4)
    >>> print my_tuple
    (1, 2, 3, 4)
    >>> print my_tuple[3]
    4
    >>> my_tuple[3] = 5
    Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
    TypeError: 'tuple' object does not support item assignment
    

Instrukcje sterujące

  • Instrukcja warunkowa if:

    Instrukcja warunkowa if daje nam bardzo podstawową, jednocześnie najważniejszą kontrolę nad przebiegiem wykonywania naszego kodu. Dzięki tej instrukcji możemy włączyć/wyłączyć z wykonywania kod, zależnie od np. wartości danej zmiennej. Najłatwiej opisać to przykładem:

    >>> if number == 5:
    ...     print 'number is five!'
    ... elif number == 3:
    ...     print 'number is three!'
    ... else:
    ...     print 'number is neither five NOR three!'
    ...
    number is five!
    
  • Pętla for:

    Dzięki pętli for możemy iterować po danym zbiorze danych oraz na czas iteracji traktować każdy kolejny element tego zbioru osobno. Przykład:

    >>> for number in my_list:
    ...     print number
    ...
    1
    2
    3
    4
    5
    

Połączenie if oraz for:

>>> for number in my_list:
...     if number % 2 == 0:
...             print number
...
2
4

Projekt

Założenia

Naszym projektem będzie prosta aplikacja, na początku konsolowa, później przerobiona na aplikacje webową, której zadaniem będzie organizacja naszego czasu. Dokładniej mówiąc, ta aplikacja będzie zawierała dane nt. zadań, które musimy zrobić, zrobiliśmy lub jesteśmy w trakcie robienia. Poniżej będą opisane poszczególne kroki powstawania takiej aplikacji. Będziemy wykorzystywać wiedzę poznaną podczas wprowadzenia, jednakże tutaj będzie ona rozszerzana, oraz zostaną pokazane dobre praktyki programowania w Pythonie.

Zanim zaczniemy - JSON, co to jest?

Zanim rozpoczniemy pisanie skryptu w Pythonie, jest jeszcze jedna rzecz o której nie wspomniałem wcześniej. W naszym skrypcie, będziemy wykorzystywać dane czytane z pliku. Dane zostaną zapisane w formacie .json. Jest to jeden z najbardziej popularnych formatów danych wykorzystywany w web developmencie. Jest bardzo podobny do Pythonowego dicta, co znacznie ułatwi nam pracę. Jedynymi interesującymi nas róznicami, jest brak możliwości używania ', w JSON musimy korzystać z ".

Przykładowy plik JSON:

    [
        {
            "message": "Nauczyc sie Pythona",
            "id": 1,
            "board": "in progress"
        }, {
            "message": "Zrobic zadanie domowe",
            "id": 2,
            "board": "in progress"
        }, {
            "message": "TEST NOTE",
            "id": 6,
            "board": "in progress"
        }, {
            "message": "Zapisac sie na kurs Pythona",
            "id": 3,
            "board": "done"
        }, {
            "message": "Zrobic tutorial na PyPile",
            "id": 4,
            "board": "to do"
        }, {
            "message": "Przyjsc na wyklad PyPily",
            "id": 5,
            "board": "to do"
        }
    ]

Dokładnie z takich danych wejściowych będziemy korzystac podczas pisania skryptu! Można znaleźć je pod adresem https://goo.gl/oHGJkC

Skrypt

Odczyt z pliku

Pierwszym krokiem będzie utworzenie pliku o nazwie notes.json oraz zapisanie tam danych wyświetlonych wyżej. Link znajduje się w przypisie.

Teraz nauczymy się otwierać pliki w Pythonie. Do tego posłuży nam wbudowana funkcja open, przyjmuje ona dwa argumenty, pierwszym z nich jest nazwa_pliku a drugim tryb_otwarcia. O ile pierwszy jest oczywisty, o tyle z drugim bywają problemy, ponieważ plik możemy otworzyć w kilku celach. 1. Zapis do pliku, jest to flaga w, 2. Odczyt z pliku, flaga r, 3. Dopisanie do pliku, flaga a.

Istnieją również wersje ze znakiem + na końcu, ale w tym przypadku nie będą omawiane.

Przykład otwarcia pliku w Pythonie

>>> file_data = open('test.txt', 'r')
>>> file_data.read()
'Test1\nTest2\n'
>>> file_data.seek(0)
>>> file_data.readlines()
['Test1\n', 'Test2\n']
>>> file_data.close()

Funkcja open, jak wspomniałem wcześniej, otwiera nam plik, w tym przypadku w trybie odczytu.

Funkcja read odczytuje nam całą zawartość.

Funkcja seek pozwala nam przesunąć niewidzialny kursor w dane miejsce w pliku. W tym przypadku na początek.

Funkcja readlines odyczuje nam całą zawartość podzieloną na linie.

Funkcja close zamyka odczyt do pliku.

To wszystko wydaje się bardzo proste, prawda? Niestety (albo właśnie stety) w Pythonie nie odczytujemy plików w ten sposób. Jest to uznawane za złą praktykę. w Pythonie istnieją tzw. context managery, pozwalające na np. łatwiejszy odczyt z pliku, m. in. przez to, że nie musimy się martwić zamknięciem odczytywanego pliku. Oto przykład użycia open jako context managera.

>>> with open('test.txt', 'r') as file_data:
...     file_data.read()
...
'Test1\nTest2\n'

Kluczowymi słowami są tutaj with oraz as, zastępujące nam przypisanie do zmiennej użycie funkcji open. Wartym zauważenia jest brak funkcji close. Jest ona automatycznie wywoływana po wyjściu z bloku open.

Praktyka z otwierania pliku

Czas na trochę praktyki, waszym zadaniem będzie napisanie funkcji o nazwie get_notes_data, która będzie przyjmowała argument o nazwie filename oraz zwracała zawartość tego pliku.

ROZWIĄZANIE:

>>> def get_notes_data(filename):
...     with open(filename, 'r') as file_data:
...             return file_data.read()
...

Odczytywanie danych JSON

Jak mogliście zauwazyć, output naszej funkcji wcale nie wyglądał jak Pythonowy słownik. Dlatego będzie potrzebować przerobić go.

Posłuży nam do tego wbudowana biblioteka json, która umożliwia operacje na plikach właśnnie tego typu.

Aby móc zacząć korzystać z tej biblioteki, na górze pliku py musimy dodać linijkę import json. I tyle. Dzięki temu Python, wie, żeby załadować te bibliotekę do pamięci pdoczas rozruchu programu.

Biblioteka json posiada bardzo przydatną funkcję o nazwie loads, która pozwala na załadowanie danych w formacie JSON oraz przekonwertowanie tego na Pythonowy słownik.

Przeróbcie, proszę waszą funkcję tak, aby korzystała z biblioteki JSON.

ROZWIĄZANIE:

def get_notes_data(filename):
    with open(filename, 'r') as file_data:
        return json.loads(file_data.read(), encoding='utf-8')

Encoding pozwala nam tu uniknąć nieprzyjemności związanych z polskimi znakami, bolączka Pythona w wersji 2.

Spróbujmy odczytać coś z naszego pliku, wywołajmy get_notes_data('notes.json')

Przykładowy output:

>>> get_notes_data('notes_data/notes.json')
[{u'message': u'Nauczyc sie Pythona', u'id': 1, u'board': u'in progress'}, {u'message': u'Zrobic zadanie domowe', u'id': 2, u'board': u'in progress'}, {u'message': u'TEST NOTE', u'id': 6, u'board': u'in progress'}, {u'message': u'Zapisac sie na kurs Pythona', u'id': 3, u'board': u'done'}, {u'message': u'Zrobic tutorial na PyPile', u'id': 4, u'board': u'to do'}, {u'message': u'Przyjsc na wyklad PyPily', u'id': 5, u'board': u'to do'}]

Widać, że dane są już w bardziej przyjemnym formacie niż poprzednio :)

Wyświetlanie rezultatów

Teraz, gdy potrafimy odczytać nasze dane z pliku, chcielibyśmy je jakoś wyświetlić na konsoli, posłuży nam do tego funkcja print_column, którą zaraz napiszemy.

Jej zadaniem, będzie znalezienie wszystkich notatek, które dane pod kluczem board mają takie same jak podany argument column_name.

Problemem będzie filtrowanie notatek, dlatego od tego zaczniemy.

Wczytajcie prosze wszystkie notatki do zmiennej notes, używając poprzedniej funkcji.

ROZWIĄZANIE:

>>> notes = get_notes_data('notes_data/notes.json')
>>> print notes
[{u'message': u'Nauczyc sie Pythona', u'id': 1, u'board': u'in progress'}, {u'message': u'Zrobic zadanie domowe', u'id': 2, u'board': u'in progress'}, {u'message': u'TEST NOTE', u'id': 6, u'board': u'in progress'}, {u'message': u'Zapisac sie na kurs Pythona', u'id': 3, u'board': u'done'}, {u'message': u'Zrobic tutorial na PyPile', u'id': 4, u'board': u'to do'}, {u'message': u'Przyjsc na wyklad PyPily', u'id': 5, u'board': u'to d'}]

Aby wyciągnąć wszystkie notatki, które posiadają board równy dla przykładu in progress wykorzystamy pętlę for oraz instrukcję if, potrzebna nam będzie także lista do której dopiszemy te dane.

Utwórzmy więc pustą listę o nazwie column_notes.

Teraz poiterujmy za pomocą for po naszej zmiennej `notes.

for note in notes:
    print note

Prościzna, jednakże, teraz chcielibyśmy wyświetlić tylko te notatki, które mają pod kluczem board wartość in progress.

>>> for note in notes:
...     if note['board'] == 'in progress':
...             print note
...
{u'message': u'Nauczyc sie Pythona', u'id': 1, u'board': u'in progress'}
{u'message': u'Zrobic zadanie domowe', u'id': 2, u'board': u'in progress'}
{u'message': u'TEST NOTE', u'id': 6, u'board': u'in progress'}

Zamiast wyświetlania, dopiszmy je do zmiennej column_notes używając append.

>>> for note in notes:
...     if note['board'] == 'in progress':
...             column_notes.append(note)
...
>>> print column_notes
[{u'message': u'Nauczyc sie Pythona', u'id': 1, u'board': u'in progress'}, {u'message': u'Zrobic zadanie domowe', u'id': 2, u'board': u'in progress'}, {u'message': u'TEST NOTE', u'id': 6, u'board': u'in progress'}]

Wszystko działa! Świetnie! Jednakże, można to zrobić lepiej, w jednej linijce, wykorzystując list comprehension, czyli wyrażenia listowe. Pozwalają nam one zastąpić np. mniej skomplikowane pętle for

Najpierw jednak przeanalizujmy naszą pętlę for oraz sprowadźmy ją do ogólnego wzoru.

`for` **zmienna** in **kolekcja**:
    `if` *warunek*:
        zrób_cos

List comprehension pozwalają nam na dynamiczne tworzenie listy, bez potrzeby inicjalizowania zmiennej w postaci pustej listy.

Ogólny podstawowy wzór list comprehension

[**zmienna** `for` **zmienna* in **kolekcja**]

Spróbujmy wykorzystać to zamiast naszego pierwszego fora, zamiast kolekcja wstawmy notes, a zmienna może być dowolna, byleby niezmienna, dobrą praktyką jest nazywanie zmiennych, tak, aby odzwierciedlały zawartośc. W tym przykładzie na zmienna nada się note.

>>> [note for note in notes]
[{u'message': u'Nauczyc sie Pythona', u'id': 1, u'board': u'in progress'}, {u'message': u'Zrobic zadanie domowe', u'id': 2, u'board': u'in progr
ess'}, {u'message': u'TEST NOTE', u'id': 6, u'board': u'in progress'}, {u'message': u'Zapisac sie na kurs Pythona', u'id': 3, u'board': u'done'}
, {u'message': u'Zrobic tutorial na PyPile', u'id': 4, u'board': u'to do'}, {u'message': u'Przyjsc na wyklad PyPily', u'id': 5, u'board': u'to do'}]

Wszystko ładnie. Ale co, gdy chcemy wprowadzić naszego if?

Zmodyfikujmy wzór comprehension do postaci:

[**zmienna** `for` **zmienna* in **kolekcja** if **proste_wyrażenie**]

Wykorzystajmy to do naszych celów.

>>> [note for note in notes if note['board'] == 'in progress']
[{u'message': u'Nauczyc sie Pythona', u'id': 1, u'board': u'in progress'}, {u'message': u'Zrobic zadanie domowe', u'id': 2, u'board': u'in progr
ess'}, {u'message': u'TEST NOTE', u'id': 6, u'board': u'in progress'}]

Gratulacje! Właśnie pofiltrowaliśmy notatki. Przenieśmy to do funkcji print_column.

Tymczasowy przykładowy podgląd funkcji print_column

    def print_column(column_name, notes):
        column_notes = [note for note in notes if note['board'] == column_name]
        print column_notes

Teraz od nas zależy jak chcemy wyświetlić ich zawartość. Proponowany układ to:

-----NAZWA TABELKI -----
1. Notatka pierwsza
2. Notatka druga

------------------------


-----NAZWA DRUGIEJ TABELKI -----
1. Notatka pierwsza
2. Notatka druga


-----------------------

Taki przykład będziemy omawiać niżej.

Zacznijmy od wyświetlenia nazwy tabelki. Zakładając, że nazwa tabelki znajduje się w zmiennej column_name (jak w naszej funkcji), chcemy sformatować string tak, aby wyglądał jak w przykładzie. Użyjemy tu funkcji format, która pozwala uzupełniać stringi wartościami z innych zmiennych.

Przykład, będący jednocześnie rozwiązaniem problemu:

>>> column_name = 'in progress'
>>>'-----{}-----'.format(column_name)
'-----in progress-----'

funkcja format zamienia znak {} na zmienną. Ma też kilka bardziej rozszerzonych rozwiązań, ale tym razem je pominiemy.

Zmodyfikujmy funkcję print_column aby przypisywała do zmiennej column_header to, czego przed momentem się nauczyliśmy.

Czas na śmietankę wyświetlania danych, czyli iterację po notatkach oraz wyświetlenie ich wartości spod klucza 'message'. Spróbujemy umieścić całą sformatowaną zawartość w jednej zmiennej, aby nauczyć się dodatkowych operacji na stringach.

Zacznijmy od wyświetlenia zawartości notatek, każdej w osobnej linii, posłuży nam tu poznany wcześniej list comprehension na zmiennej column_notes.

Aby to osiągnąć, musimy przypisać do listy wartości notatek spod klucza message.

ROZWIĄZANIE

[note['message'] for note in column_notes]

Przypiszmy nasze rozwiązanie do zmiennej column_content.

Teraz, skorzystamy z funkcji join pozwalającej nam na łączenie każdego z elementów elementów listy z danym stringiem. Tutaj, będzie to znak nowej linii \n.

>>> '\n'.join(column_content)
u'Nauczyc sie Pythona\nZrobic zadanie domowe\nTEST NOTE'

Nie wygląda to za dobrze, prawda? Warto jednakże zaważyć, że pojawiły się znaki nowej linii.

Spróbujmy wywołać to samo z poleceniem print

>>> print '\n'.join(column_content)
Nauczyc sie Pythona
Zrobic zadanie domowe
TEST NOTE

Teraz wygląda to zdecydowanie lepiej. Przypiszmy to do zmiennej column_content_pretty

ZADANIE DOMOWE: Python posiada wbudowaną funkcję enumerate pozwalającą na indeksowanie kolejnych elementów. Zadaniem domowym jest użycie enumerate aby zmienić output i dodać do niego Numer porządkowy.

Oczekiwany output w tym miejscu:

1. Nauczyc sie Pythona
2. Zrobic zadanie domowe
3. TEST NOTE

Wyświetlenie wartości kolumny.

Teraz, gdy mamy już header oraz zawartość, wystarczy to 'skleić' i wyświetlić razem. Użyjmy funkcji format do tego.

>>> print '{}\n{}\n\n'.format(column_header, column_content_pretty)
-----in progress-----
Nauczyc sie Pythona
Zrobic zadanie domowe
TEST NOTE

Jedyne czego brakuje, to naszej pseudo linii oddzielającej kolumne od pozostałych. Dodajmy ją.

>>> print '{}\n{}\n{}\n'.format(column_header, column_content_pretty, '-'*20)
-----in progress-----
Nauczyc sie Pythona
Zrobic zadanie domowe
TEST NOTE
--------------------

Et voila! Wyświetlanie zawartości kolumny zakończone.

Pamiętajmy aby dodać to wszystko do funkcji print_column.

Przykładowa definicja funkcji print_column:

def print_column(column_name, notes):
    column_notes = [note for note in notes if note['board'] == column_name]
    column_header = '-----{}-----'.format(column_name)
    column_content = [note['message'] for note in column_notes]
    column_content_pretty = '\n'.join(column_content)
    print '{}\n{}\n\n'.format(column_header, column_content_pretty)

Czas na wyświetlanie CAŁOŚĆI danych.

Napiszemy do tego funkcję o nazwie print_data, która przyjmie argument notes czyli listę naszych notatek. Wewnątrz funkcji, do wyświetlenia danych skorzystamy z napisanej wcześniej funkcji print_column.

Jednakże, aby wyświetlić dane kolumn, potrzebujemy najpierw wiedziec, jakie kolumny istnieją. Skorzystamy tutaj z wcześniej nie omawianej struktury danych - zbioru (ang. set). Zbiór jest to po prostu zbiór teoriomnogościowy, czyli struktura danych, nieco podobna do listy i tupli. Podstawowa różnica, która da nam tu przewagem jest taka, że set nie pozwala na posiadanie dwóch takich samych wartości, jesli spróbujemy dodać taką wartość, nic się po prostu nie stanie. Jego wadą z kolei jest fakt, iż nie wspiera indeksowania tak jak lista czy tupla.

Zbieranie informacji nt. kolumn: Mając z tyłu głowy, to co przed chwilą zostało powiedziane, łatwym sposobem możemy wydostać informacje o tym, jakie kolumny istnieją w naszych notatkach.

  • Będziemy musieli poiterowac po każdej notatce i przypisać jej wartość spod klucza board do listy. [Użyjemy comprehension].

  • Przerobimy naszą listę na set, przez co pozbędziemy się duplikatów.

1.

all_boards = [note['board'] for note in notes]

2.

columns = set(all_boards)
>>> columns
set([u'in progress', u'done', u'to do'])

I to wszystko! Tym prostym trikiem wyciągnęliśmy informację nt. wszystkich istniejących tabel.

Ostatnią rzecza, którą musimy zrobić w funkcji print_data to iteracja po utworzonym zbiorze oraz wywołanie funkcji print_column dla każdej tabeli osobno:

Zapisac sie na kurs Pythona
--------------------

-----to do-----
Zrobic tutorial na PyPile
Przyjsc na wyklad PyPily
--------------------

Gratulacje! Tym sposobem nauczyliśmy się wyświetlać notatki!

Oczywiście, nie jest to jeszcze najbardziej optymalny sposób na to. Ale dla nas jest to wynik satysfakcjonujący.

Dodawanie nowych notatek

W poprzednim 'rozdziale' nauczyliśmy się wyświetlać notatki, czas na dodawanie nowych.

Aby dodać nową notatkę potrzebne nam będą trzy informacje. Treść notki, tabelę do której trzeba przypisać oraz id. Id będzie nam potrzebne do usuwania w przyszłości.

Do generowania ID wykorzystamy funkcję, którą przygotowałem wcześniej. Oto jej definicja:

def generate_id():
    max_id = 0
    for note in get_all_notes('notes_data/notes.json'):
        if note['id'] > max_id:
            max_id = note['id']

    return max_id + 1

Następnym krokiem będzie napisanie funkcji o nazwie add_note, która powinna przyjmować trzy argumenty: column_name - nazwe kolumny do której dodamy notatkę note_message - treść notatki * notes - zmienna zawierająca wszystkie notatki, była wykorzystywana wcześniej

Gdy posiadamy już te informacje, możemy stworzyć notatkę. Jak pamiętacie nasza notatka jest słownikiem oraz zawiera trzy wartości, id, message oraz board. Pierwsza wartość będzie generowana przez funkcję podaną wyżej, dwie pozostałe są argumentami funkcji add_note. Także stwórzmy sobie teraz słownik reprezentujący naszą notatkę:

def add_note(column_name, note_message, notes):
    note_data = {
        'message': note_message,
        'id': generate_id(),
        'board': column_name,
    }
    notes.append(note_data)
    return note_data  # optional

Zwracamy również treść notatki, jednakże jest to opcjonalne.

Następnie sprawdźmy czy notatka się dodała do zmiennej notes.

Niestety, te rozwiązanie posiada ogromną wadę. Sprawdźcie plik notes.json. Naszej notatki tam nie ma, to znaczy, że po uruchomieniu naszego programu po raz drugi, nasza świeżo dodana notatka się pojawi.

Aby to naprawić, będziemy musieli obsłużyć zapis naszych notatek do pliku. Napiszmy funkcję save_notes, które będzie przeciwieństwem get_notes_data. Tutaj również użyjemy poznane wcześniej with open oraz bibliotekę JSON. Musimy przekonwertować naszą listę słowników na obiekt JSON, posłuży nam do tego metoda dump, która jako pierwszy parametr przyjmuje dane, drugim parametrem jest zaś zmienna przechowująca otwarty plik.

ROZWIĄZANIE:

def save_notes(filename, notes):
    with open(filename, 'w') as notes_file:
        json.dump(notes, notes_file, indent=4)

opcjonalny parametr indent pozwala na sterowanie wcięciami podczas konwersji na obiekt JSON.

Teraz, gdy mamy zapisywanie notatek, możemy zmodyfikować naszą funkcję add_note aby zapisywała do pliku.

Finalna wersja tej funkcji powinna wyglądać mniej więcej tak:

def add_note(column_name, note_message, notes):
    note_data = {
        'message': note_message,
        'id': generate_id(),
        'board': column_name,
    }
    notes.append(note_data)
    save_notes('notes_data/notes.json', notes)
    return note_data  # optional

Usuwanie notatek

Powinniśmy również umożliwić usuwanie notatek. Napiszemy do tego funkcję remove_note, aby usunąć notatkę, będziemy potrzebować jej id. Do usunięcia notatki z naszej listy posłużą nam dwie podstawowe metody list, pop oraz index. Funkcja pop usuwa element, przy okazji zwracając wartość. Funkcja index zwraca nam indeks danego elementu w liście.

Znając te informacje możemy zając się wyszukiwaniem notatek o podanym ID. Użyjemy do tego pętli for oraz instrukcji warunkowej if.

ROZWIĄZANIE:

def remove_note(note_id, notes):
    for note in notes:
        if note['id'] == note_id:
            print note

Aby wyciągnąć indeks naszej notatki w liście możemy użyć, jak wspomniałem wcześniej, pop oraz index lub funkcji remove.

Może to wyglądać np. tak: notes.pop(notes.index(note)).

Teraz, gdy już pozbyliśmy się notatki z listy, musimy zapisać plik, aby zniknęła także z naszego pliku notes.json.

Edycja notatek

Gratulacje! Potrafimy już odczytać notatki z pliku, dodać nowe, zapisać notatki do pliku oraz usunąć je. Pozostał czas na edycję istniejących.

Do tego będzie nam potrzebny mały kawałek kodu który przygotowałem wcześniej:

def edit_note(note_id, message):
    message = [90, 97, 100, 97, 110, 105, 101, 32, 68, 111, 109, 111, 119, 101]
    print ''.join(map(lambda x: chr(x), message))

Dostępne jest również online pod adresem https://goo.gl/1FR9ho

Comments