print("Hello world!")
Hello world!
Otrzymuj maila, gdy pojawią się nowe wpisy
Link z potwierdzeniem znajdziesz na swoim mailu. Jeśli go tam nie ma, sprawdź spam.
Automatyzacja powtarzalnego tekstu za pomocą Pythona
print()
w Pythonie.
Jakub Jędrusiak
12 maja 2023
W tym tekście opisuję generowanie nowego tekstu o określonej strukturze. Do wyszukiwania i zmieniania określone rzeczy w istniejącym już tekście służą wyrażenia regularne (RegEx), o których piszę w tym tekście. Łącznie to bardzo proste, a jednocześnie bardzo potężne narzędzia, które pozwalają szybko i niskim kosztem odjąć dużo bezsensownej, mechanicznej pracy każdemu. Nie tylko naukowcom czy studentom, ale każdemu, kto pisze tekst na komputerze.
To, co tutaj opiszę, pierwszy raz poważnie wykorzystałem, gdy pomagałem swojej siostrze w pracy. Miała ona wydłużyć plik, w którym zapisywane były teksty, jakie mają pojawić się w live’ie tego dnia (siostra pracuje przy kanale na YouTube). Plik ten miał prostą strukturę.
Data:11.05.2023
I
II
III
Gdy całość się kończyła, osoba odpowiedzialna dopisywała tę strukturę na ileś dni do przodu i tak co jakiś czas. Jest to ten rodzaj pracy, którego nie znoszę i który jest łatwy do zautomatyzowania. Można to zrobić z kilku powodów, z których najważniejszy jest ten – plik miał przewidywalną, z góry określoną strukturę. Miał konkretne stałe elementy i konkretne elementy zmienne. Tutaj elementem zmiennym była data, która zmienia się w sposób przewidywalny1. Mamy więc określony wzór, schemat, który tylko musimy wypełnić datami. To też zrobiłem i w ten sposób wygenerowałem dla siostry plik dla rok do przodu. Powiedziała w pracy, że miała trochę czasu, to dopisała więcej. Podobno się zdziwili.
Ta cecha, tzn. przewidywalność jakiegoś tekstu, pozwala nam zautomatyzować jego pisanie. Nieważne, czy tym tekstem są oznaczenia kolumn (MMPI_1
, MMPI_2
, …, MMPI_567
), czy złożone zagnieżdżone struktury np. pytań i odpowiedzi w ankiecie, jeśli tekst jest przewidywalny, da się go wygenerować.
W tym tekście wykorzystamy sobie język programowania o nazwie Python. Tego typu praca z tekstem jest tak podstawowa, że można ją wykonać w prawie każdym języku programowania (w tym w R za pomocą paste()
czy paste0()
), ale tutaj wykorzystamy Pythona, bo to chyba najpopularniejszy język programowania w ogóle, drugi najpopularniejszy w statystyce, a przy tym jest to język ogólnego przeznaczenia. A także ma nazwę od Monty’ego Pythona, więc wiadomo, że warto. Specjalistą od Pythona nie jestem, moim pierwszym językiem programowania jest R, ale co tam, żyje się raz.
Żeby móc robić cokolwiek w Pythonie, musimy zainstalować sobie interpreter stąd. Interpreter to coś w rodzaju programu, który potrafi czytać i wykonywać kod w danym języku. Można powiedzieć w pewnym uproszczeniu, że instalujemy sobie Pythona. Koniecznie zaznaczcie w trakcie instalacji, żeby dodać Pythona do PATH. Teoretycznie tyle wystarczy, ale żeby uprzyjemnić proces pisania, fajnie jest przygotować sobie jakieś IDE (program do programowania), np. Visual Studio Code albo chociaż porządny edytor tekstu w stylu Notepad++.
Gdy to zrobimy, możemy dotrzeć do Pythona na kilka sposobów. Podstawowy to wejście w terminal (w Windowsie PowerShell albo wiersz polecenia) i wpisanie tam Python
. Gdy potwierdzimy enterem, dostaniemy konsolę Pythona, gdzie możemy wpisywać komendy. Drugi sposób, przez IDE, to stworzenie pliku z rozszerzeniem .py (np. „skrypt.py” albo „znowu_dają_mi_bezsensowna_robote.py”) zapisywanie w nim naszych komend. Drugi sposób przyda się, gdy chcemy sobie komendy zachować na przyszłość albo piszemy coś bardziej skomplikowanego, na wiele linijek. IDE często mają specjalny guzik do uruchomienia takiego kodu2. Możemy też w wierszu polecenia wpisać Python C:\ścieżka\do\pliku.py
, np. Python "C:\Users\Jakub\Desktop\RSES.py"
. Ważne – w „suchym" wierszu polecenia, nie w konsoli Pythona.
print()
i f-stringsTeraz możemy pisać nasz kod w Pythonie. Będę zakładał, że piszemy nasz kod w pliku, ale równie dobrze można to pisać na szybko w konsoli. Często tak robię, jak mam do wygenerowania prostą zbitkę i nie chce mi się tworzyć na to specjalnie pliku. W takim wypadku każdą linijkę kodu wpisujemy w konsolę osobno i osobno zatwierdzamy enterem. Zazwyczja pierwszym, co się pisze, gdy zaczyna się uczyć nowego języka, jest program, który wyświetla tekst „Hello world!“. W Pythonie robimy to funkcją print()
.
Funkcja print()
w większości wystarczy, żebyśmy uzyskali to, co chcemy dziś uzyskać. Wbrew pozorom, jeśli połączymy ją z pętlami, będzie ona potrafiła całkiem sporo. Funkcje w programowaniu zazwyczaj mają po sobie nawiasy, do których wrzucamy różne rzeczy, z którymi funkcja ma coś zrobić. Tak tutaj wrzucamy w te nawiasy tekst, który funkcja ma pokazać w konsoli. Dosłowny tekst zawsze piszemy w cudzysłowie.
Jeśli potrzebujemy, możemy po przecinku dorzucić argument end
, który mówi, co funkcja ma dodać na koniec danego ciągu znaków. Domyślnie jest to nowa linia, którą w informatyce zapisujemy zazwyczaj \n
. Nie robimy rzeczywistej nowej linii! Na przykład możemy zmienić jedną pustą linię na dwie, trzy. Możemy też zamienić nową linię na spację, ale po co, to wyjaśnię później. Możemy też wymyślić coś całkiem szalonego, jeśli mamy taką potrzebę.
Nabierze to więcej sensu, kiedy dojdziemy do pętli i będziemy wyświetlać wiele tekstów, jeden pod drugim. Podobnie więcej sensu nabierze coś, co omówimy teraz, czyli tzw. f-strings. W informatyce słowem string oznacza się dosłowny ciąg znaków. f-strings to sposób, jak do naszego ciągu znaków wrzucić wartość jakiejś zmiennej. Co to znaczy? Jak powiedziałem wyżej, tekst, który chcemy wygenerować, zawiera elementy stałe i elementy zmienne. Elementem zmiennym może być na przykład liczba. Jak połączyć elementy stałe ze zmiennymi? Właśnie za pomocą f-string. Zmienne definiujemy w Pythonie za pomocą znaku =
.
W kodzie powyżej najpierw przypisuję wartość 23 do zmiennej i
(zmienne mogę nazywać jak chcę). Dzięki temu, od tego momentu, gdy napiszę i
, Python zrozumie to jako 23. Można spróbować tego w konsoli. Jeśli po zadeklarowaniu zmiennej wpiszemy w konsolę i
i zatwierdzimy, zobaczymy liczbę 23. Jeśli piszę to w konsoli, a nie w pliku, to najpierw wpisuję i = 23
, klikam enter i dopiero potem odpowiednią funkcję print()
. Następnie do funkcji print()
wrzucam tekst "MMPI_{i}"
. Python wie, że zamiast {i}
musi podstawić prawdziwą wartość zmiennej i
, czyli w tym wypadku 23. Żeby to jednak wiedział, musimy przed samym ciągiem znaków dodać literkę f
, jak format. Dlatego właśnie napisałem f"MMPI_{i}"
i dlatego nazywa się to f-string. Jeśli chcielibyśmy w naszym tekście dodać dosłowne nawiasy klamrowe, np. uzyskać „{MMPI_23}“, takie nawiasy klamrowe piszemy podwójnie – {{
w f-string zmieni się w dosłowny znak {
.
Zawartość zmiennej i
możemy w nawiasach klamrowych też modyfikować.
Pętle to w językach programowania sposób, żeby wiele razy zrobić to samo albo prawie to samo. W (prawie) każdym języku programowania znajdziemy dwa rodzaje pętli – for
i while
.
for
Pętla for
jest najprostszym rodzajem pętli i tym, z czego będziemy stale korzystać. Omówmy sobie ją na przykładzie generowania nazw kolumn.
MMPI_1
MMPI_2
MMPI_3
MMPI_4
MMPI_5
MMPI_6
MMPI_7
MMPI_8
MMPI_9
MMPI_10
i
jest nazwą dla zmiennej, która po kolei przyjmie wartości od 1 do 10. Najpierw wszystko, co znajduje się w pętli, wykona się tak, jakby i
miało wartość 1. Potem wykona się to znowu, ale z i = 2
itd. To jest podstawowy sposób działania zmiennej for
. Potem mamy słowo in
, a za nim zbiór wartości, które i
ma po kolei przyjmować. W tym wypadku tym zbiorem jest funkcja range()
, która sama generuje nam liczby od 1 do 10.
Dlaczego jednak napisałem range(1, 11)
a nie range(1, 10)
? Python działa tutaj specyficznie. Wynika to z faktu, że w informatyce liczy się od 0, nie od 1. Jeśli do funkcji range()
wrzucę tylko jedną liczbę, czyli na przykład range(10)
, to dostanę 10 elementów. Ponieważ jednak pierwszy element to 0, to będą to liczby od 0 do 9. Mogę podać dwie liczby, żeby powiedzieć funkcji range()
, od czego ma zacząć, ale wtedy muszę mieć w głowie, że skoro range(0, 10)
oznacza 10 liczb od 0 do 9, to liczby od 1 do 10 muszę zapisać jako range(1, 11)
. Innymi słowy koniec skali nie wlicza się do zakresu.
Jeśli piszemy to w konsoli, a nie w pliku, możemy zapisać taką pętlę w jednej linijce – for i in range(1, 11): print(f"MMPI_{i}")
. Możemy też zapisać samo for i in range(1, 11):
(nie zapominając o dwukropku) i potwierdzić enterem. W obu wypadkach wyświetli nam się w konsoli wielokropek i będziemy mogli dopisywać kolejne komendy z pętli. Gdy będziemy usatysfakcjonowani, klikamy enter po raz kolejny, a pętla wykonuje się.
Tak jak wspomniałem, domyślnie print()
wyrzuca do konsoli to, co tej funkcji podaliśmy, dodając na koniec nową linię. Możemy jednak chcieć, żeby nasze elementy pojawiły się po przecinku albo oddzielone spacjami (albo jedno i drugie) i wtedy możemy zmienić argument end
.
MMPI_1, MMPI_2, MMPI_3, MMPI_4, MMPI_5, MMPI_6, MMPI_7, MMPI_8, MMPI_9, MMPI_10,
Co prawda po ostatnim elemencie też dostajemy przecinek i spację, ale to już możemy usunąć ręcznie. W Pythonie też da się to zaprogramować, ale nie chcę za bardzo gmatwać.
while
Pętla while
to bardziej podstawowy, prosty rodzaj pętli. Większość pętli while
da się napisać w formie pętli for
, dlatego nie będę się nad tym jakoś szczególnie rozwodził, ale warto wiedzieć, że coś takiego istnieje. Napiszmy przykład z poprzedniej sekcji w postaci pętli while
.
MMPI_1
MMPI_2
MMPI_3
MMPI_4
MMPI_5
MMPI_6
MMPI_7
MMPI_8
MMPI_9
MMPI_10
W pętli while
potrzebujemy jakiejś wcześniej określonej zmiennej, w tym wypadku i
. Pierwszą rzeczą, którą while
robi, jest sprawdzenie, czy warunek jest prawdziwy. Prawdą jest, że 1 jest mniejsze lub równe 10, więc while
puszcza wszystko, co znajduje się w środku pętli. Instrukcja print()
jest identyczna. Kolejna linijka może wydawać się nieco tajemnicza. Służy ona powiększeniu i
o 1. Matematycznie zapis i = i + 1
może wydawać się dziwny, ale trzeba pamiętać, że =
nie oznacza tutaj porównania (to się robi poprzez ==
), tylko przypisanie. Można więc tę komendę przeczytać „Niech i
przyjmie wartość równą aktualnej wartości i
plus jeden”. W skrócie możemy to zapisać jako i += 1
. Po co to robimy? Bo w następnym kroku pętla while
znów sprawdzi, czy warunek jest prawdziwy. Teraz i = 2
, a 2 to ciągle mniej niż 10, więc pętla wykona się znów. Tak będzie robić aż do momentu, w którym warunek nie będzie prawdziwy, a wiec w tym wypadku aż i
nie przyjmie wartości 11. Jeśli nie umieściłbym w kodzie linijki i = i + 1
, warunek i <= 10
byłby zawsze prawdziwy i pętla działałaby wiecznie. Czy raczej do wyczerpania pamięci.
Weźmy sobie na warsztat bardziej złożony przykład. Niedawno musiałem generować bardzo skomplikowany tekst, który stał się częścią ankiety w PsyToolKit. Jest to świetne narzędzie do prowadzenia ankiet i eksperymentów, głównie psychologicznych. Jego największą zaletą – według mnie – jest to, że zarówno ankiety, jak i eksperymenty mogą być pisane w postaci zwykłego tekstu3. Pozwala to na olbrzymią giętkość, jaką zapewniają języki programowania, ale także daje duże możliwości automatyzacji. O PsyToolKit na pewno jeszcze w przyszłości napiszę.
Pokażę teraz, jak sprawnie przerobić kwestionariusz na ankietę w PsyToolKit. Najsprawniej byłoby, co prawda, użyć programiku PsyToolKit Questionnaire Formatter, który opiera się na tym, co tutaj opisuję. Poznajmy ten mechanizm od kuchni, żeby w razie czego móc go dopasować do własnych, specyficznych celów, niekoniecznie związanych w ogóle z PsyToolKit.
Załóżmy, że chcielibyśmy wykorzystać w naszym badaniu kwestionariusz samooceny Rosenberga (1965). Musimy go w takim razie zapisać tak, jak PsyToolKit każe nam formatować pytania do ankiety. Struktura pytania w PsyToolKit wygląda tak:
l: RSES_1
t: radio
q: I feel that I am a person of worth, at least on an equal plane with others.
- Strongly Agree
- Agree
- Disagree
- Strongly Disagree
Po pierwsze mamy l
, czyli label. Posłuży nam to jako wewnętrzna „nazwa” pozycji testowej i nagłówek kolumny w bazie danych. Dalej mamy t
, czyli type, gdzie radio
oznacza pytanie jednokrotnego wyboru. Inne typy znajdziemy w dokumentacji. Następnie mamy q
, czyli question, właściwa treść pozycji testowej i pod nią odpowiedzi wypisane od myślników. Możemy tu dodawać inne rzeczy (np. o: random
, żeby kolejność odpowiedzi była losowa), ale załóżmy, że na ten moment tyle nam wystarczy.
Po pierwsze spróbujmy zidentyfikować, co w naszym schemacie jest stałe i co się zmienia. Tutaj zmieniają się dwie rzeczy – treść pozycji testowej i numerek przy RSES
. Cała reszta jest identyczna dla każdej pozycji testowej.
Skoro musimy mieć w naszych pytaniach treść pozycji testowej, musimy nasz kwestionariusz wkleić do skryptu. Zapiszemy go w postaci listy.
RSES = [
"I feel that I am a person of worth, at least on an equal plane with others.",
"I feel that I have a number of good qualities.",
"All in all, I am inclined to feel that I am a failure.",
"I am able to do things as well as most other people.",
"I feel I do not have much to be proud of.",
"I take a positive attitude toward myself.",
"On the whole, I am satisfied with myself.",
"I wish I could have more respect for myself.",
"I certainly feel useless at times.",
"At times I think I am no good at all."
]
Cała lista jest nawiasach kwadratowych, każdy item jest w cudzysłowie, zaś itemy rozdzielone są przecinkami. Całą listę zapisałem do zmiennej o nazwie RSES
. Teraz możemy powiedzieć Pythonowi, żeby zrobił całą serię pytań w stylu PsyToolKit, gdzie po q
za każdym razem wstawi jedną z pozycji testowych.
for item in RSES:
print("l: RSES_1")
print("t: radio")
print(f"q: {item}")
print("- Strongly Agree\n- Agree\n- Disagree\n- Strongly Disagree")
print() # pusta linijka
l: RSES_1
t: radio
q: I feel that I am a person of worth, at least on an equal plane with others.
- Strongly Agree
- Agree
- Disagree
- Strongly Disagree
l: RSES_1
t: radio
q: I feel that I have a number of good qualities.
- Strongly Agree
- Agree
- Disagree
- Strongly Disagree
l: RSES_1
t: radio
q: All in all, I am inclined to feel that I am a failure.
- Strongly Agree
- Agree
- Disagree
- Strongly Disagree
l: RSES_1
t: radio
q: I am able to do things as well as most other people.
- Strongly Agree
- Agree
- Disagree
- Strongly Disagree
l: RSES_1
t: radio
q: I feel I do not have much to be proud of.
- Strongly Agree
- Agree
- Disagree
- Strongly Disagree
l: RSES_1
t: radio
q: I take a positive attitude toward myself.
- Strongly Agree
- Agree
- Disagree
- Strongly Disagree
l: RSES_1
t: radio
q: On the whole, I am satisfied with myself.
- Strongly Agree
- Agree
- Disagree
- Strongly Disagree
l: RSES_1
t: radio
q: I wish I could have more respect for myself.
- Strongly Agree
- Agree
- Disagree
- Strongly Disagree
l: RSES_1
t: radio
q: I certainly feel useless at times.
- Strongly Agree
- Agree
- Disagree
- Strongly Disagree
l: RSES_1
t: radio
q: At times I think I am no good at all.
- Strongly Agree
- Agree
- Disagree
- Strongly Disagree
Jak widzimy, nasza zmienna w pętli (1) nie musi nazywać się i
oraz (2) nie musi być liczbą. Jak widzimy, możemy wykonać pętlę za każdym razem przypisując do zmiennej kolejny tekst z listy. Każdą linijkę możemy zapisać w osobnej komendzie print()
lub też całość wpisać w jedną komendę, zaznaczając nowe linijki za pomocą \n
. Tak zrobiłem w przedostatniej linijce.
Nasz wynik ma jednak problem – każde pytanie nazywa się RSES_1
. Liczba po RSES_
musi się zmieniać. Tym razem jest to trudniejsze niż wcześniej, bo item
nie jest tutaj liczbą, tylko treścią pytania, więc nie możemy zapisać RSES_{item}
. Z pomocą przychodzi nam jednak funkcja enumerate()
. Pozwala ona przerobić listę na tzw. krotki (ang. tuples, tutaj 2-tuples czyli dwukrotki). Każda taka dwukrotka zawiera numer pozycji na liście (licząc od 0) oraz samą pozycję. Numer jest pierwszy, więc dostaniemy się do niego pisząc item[0]
. Jeśli chcemy dostać treść pozycji testowej, zapiszemy item[1]
. Całość wyglądałaby więc tak:
for item in enumerate(RSES):
print(f"l: RSES_{item[0] + 1}")
print("t: radio")
print(f"q: {item[1]}")
print("- Strongly Agree\n- Agree\n- Disagree\n- Strongly Disagree")
print() # pusta linijka
l: RSES_1
t: radio
q: I feel that I am a person of worth, at least on an equal plane with others.
- Strongly Agree
- Agree
- Disagree
- Strongly Disagree
l: RSES_2
t: radio
q: I feel that I have a number of good qualities.
- Strongly Agree
- Agree
- Disagree
- Strongly Disagree
l: RSES_3
t: radio
q: All in all, I am inclined to feel that I am a failure.
- Strongly Agree
- Agree
- Disagree
- Strongly Disagree
l: RSES_4
t: radio
q: I am able to do things as well as most other people.
- Strongly Agree
- Agree
- Disagree
- Strongly Disagree
l: RSES_5
t: radio
q: I feel I do not have much to be proud of.
- Strongly Agree
- Agree
- Disagree
- Strongly Disagree
l: RSES_6
t: radio
q: I take a positive attitude toward myself.
- Strongly Agree
- Agree
- Disagree
- Strongly Disagree
l: RSES_7
t: radio
q: On the whole, I am satisfied with myself.
- Strongly Agree
- Agree
- Disagree
- Strongly Disagree
l: RSES_8
t: radio
q: I wish I could have more respect for myself.
- Strongly Agree
- Agree
- Disagree
- Strongly Disagree
l: RSES_9
t: radio
q: I certainly feel useless at times.
- Strongly Agree
- Agree
- Disagree
- Strongly Disagree
l: RSES_10
t: radio
q: At times I think I am no good at all.
- Strongly Agree
- Agree
- Disagree
- Strongly Disagree
Zwróćmy uwagę, że w pierwszym print()
napisałem item[0] + 1
. item[0]
to numer pozycji testowej, ale czemu + 1
? Bo liczenie w informatyce zaczyna się od 0 (co ciągle powoduje problemy u całej reszty ludzkości), więc jeśli chcę mieć numerację od 1 do 10 zamiast od 0 do 9, to do każdego numeru muszę dodać 1.
Wynik działania takiej funkcji możemy od razu zapisać do pliku tekstowego za pomocą specjalnego operatora >
w PowerShell4. Da się to zrobić nie wychodząc z Pythona, ale to niepotrzebnie skomplikowane. Druga opcja to po prostu skopiować wygenerowany tekst z konsoli. Jeśli ktoś rzadko z niej korzysta, to ostrzegam, że do kopiowania i wklejania zamiast Ctrl+C i Ctrl+V w konsoli używamy Ctrl+Shift+C i Ctrl+Shift+V. Głównie dlatego, że Ctrl+C ma tam inną funkcję – przerywa aktualnie wykonywane zadanie. Ten sposób może jednak nie być odpowiedni, jeśli tekst jest długi, bo wtedy konsola może zjeść nam kilka (lub bardzo dużo) linijek. Jak więc wykorzystać >
? W PowerShell (nie w konsoli Pythona! w zwykłym, gołym PowerShell) wpisujemy coś takiego:
Python "C:\ścieżka\do\skryptu.py" > "C:\ścieżka\do\pliku.txt"
Jako podpowiedź mogę podrzucić, że Windows 11 pozwala kopiować ścieżki po kliknięciu na plik prawym przyciskiem myszy. W Windowsie 10 też możemy sobie w ten sposób ułatwić życie, tylko klikając prawy przycisk myszy musimy jeszcze przytrzymać shift. Ostateczna komenda mogłaby więc wyglądać tak:
Python "C:\Users\Jakub\Desktop\RSES.py" > "C:\Users\Jakub\Desktop\RSES.txt"
Powoduje to zapisanie tego, co normalnie skrypt wydrukowałby w konsoli, w pliku RSES.txt
na pulpicie. Rozszerzenie .txt
jest konieczne. Oczywiście jeśli Twoja nazwa użytkownika to Jakub. Niestety wpisywanie własnych ścieżek jest konieczne.
Wróćmy do pierwotnego przykładu z plikiem mojej siostry. Jest to przykład o tyle specyficzny, że zmiennym elementem jest tam data. Daty zmieniają się przewidywalne, ale potrzebują specjalnych funkcji, które ogarną takie rzeczy jak to, że różne miesiące mają różną liczbę dni, istnieją lata przestępne itd. Kiedy rzeczywiście miałem ten problem, użyłem funkcji date
w Linuksie, która sama z siebie pozwala na robienie takich rzeczy. Większość osób (niestety) nie korzysta z linuksa, dlatego na potrzeby tego wpisu zaadaptuję to rozwiązanie do Pythona. Albo chociaż spróbuję.
Żeby operować na datach, musimy na szczycie skryptu (lub najpierw w konsoli) zapisać:
Załaduje to pakiet datetime
pozwalający operować na datach. Robimy to tylko raz na daną sesję, czyli jak raz załadujemy ten pakiet, możemy z niego korzystać dopóty, dopóki nie wyjdziemy z Pythona. Jeśli chcemy dostać się do funkcji z pakietu datetime
, musimy zapisać je z „przedrostkiem" datetime.
, jak zobaczymy za chwilę.
Przypomnijmy strukturę pliku, który chcemy stworzyć:
Data:11.05.2023
I
II
III
Po pierwsze musimy ustalić, od jakiej daty chcemy zacząć. Możemy wykorzystać dzisiejszą datę wpisując datetime.date.today()
. Możemy też wybrać datę początkową arbitralnie, używając czegoś w rodzaju datetime.date(2023, 5, 11)
. Data jest w kolejności ISO 8601, czyli rok, miesiąc, dzień.
Po drugie będziemy musieli dodawać do naszej daty dni. Robimy to funkcją datetime.timedelta(days = 1)
. W tej formie do naszej daty dodamy jeden dzień. Takie coś rzeczywiście do daty dodajemy, czyli piszemy na przykład datetime.date(2023, 5, 11) + datetime.timedelta(days = 1)
. Wynikiem będzie tutaj 12 maja 2023 roku.
Po trzecie nasza data musi być w określonym formacie, w tym wypadku DD.MM.RRRR. Domyślnie daty wyświetlają się w formacie ISO 8601, czyli RRRR-MM-DD. Formatować daty można metodą strftime()
. Metody to szczególny rodzaj funkcji, który wykorzystujemy tak, że doklejamy je po kropce do nazwy naszego obiektu np. z datą. Najlepiej będzie to widać w przykładzie. Do samej metody wrzucamy zakodowany format, w jakim datę chcemy uzyskać. Wykorzystamy tutaj specjalne kody, których listę możemy znaleźć tutaj. Potrzebny nam format zakodujemy jako "%d.%m.%Y"
.
Zbierając to wszystko do kupy uzyskujemy coś takiego:
import datetime
start_date = datetime.date(2023, 5, 11)
for i in range(7):
date = start_date + datetime.timedelta(days = i)
date_formatted = date.strftime("%d.%m.%Y")
print(f"Data:{date_formatted}")
print("I\n\nII\n\nIII\n\n")
Data:11.05.2023
I
II
III
Data:12.05.2023
I
II
III
Data:13.05.2023
I
II
III
Data:14.05.2023
I
II
III
Data:15.05.2023
I
II
III
Data:16.05.2023
I
II
III
Data:17.05.2023
I
II
III
Wykorzystałem tutaj kilka zmiennych, które nazwałem start_date
, date
i date_formatted
. Nazwy zmiennych mogą być jakiekolwiek. Wybrałem takie, żeby to było czytelne. Jak to w programowaniu, możemy to napisać na parę sposobów. Dla przykładu tutaj datę już sformatowaną zapisałem w osobnej zmiennej, ale mógłbym też napisać:
…albo w ogóle wszystko zapisać już wewnątrz pętli:
…i to też zadziała. Zależy co uznajemy za bardziej czytelne. Zwróćmy uwagę, że piszemy days = i
, a nie days = {i}
. Nawiasy klamrowe potrzebne są tylko w f-strings. Pamiętamy jeszcze, że domyślnie range(7)
generuje liczby od 0 do 6, więc na początku timedelta()
dodaje 0 dni, potem 1 dzień, 2 dni i aż do 6 dni. Tym razem jest to nam na rękę, bo dzięki temu pierwszą datą jest wybrana przez nas data, a nie dzień później. Uzyskujemy więc tydzień rozpiski. Siła automatyzacji polega na tym, że mając te 4 linijki kodu, taki sam nakład pracy potrzebny jest do zrobienia takiej rozpiski dla tygodnia, miesiąca czy 30 lat5.
Opanowanie automatyzacji wymaga sporo praktyki i początkowo może zajmować więcej czasu, niż wykonanie jakiejś pracy ręcznie. Jednak z doświadczeniem przychodzi efektywność. Już opanowanie podstaw sprawia, że często możemy oszczędzić sobie wielu, wielu godzin pracy, a jeśli poświęcimy na to trochę więcej, możemy wydłużyć sobie życie o naprawdę sporo wolnego. Podsumujmy to, czego się dziś nauczyliśmy.
Możemy automatyzować generowanie tekstu, w którym jakaś część podlega przewidywalnym zmianom.
Za wyświetlanie tekstu odpowiada funkcja print()
.
Możemy powtórzyć jakiś tekst określoną liczbę razy i zaplanować ewentualne zmiany w tym tekście za pomocą pętli for
.
Możemy podstawić do ciągu znaków wartości zmiennych za pomocą f-strings, np. f"MMPI_{i}"
.
Ciągi liczb do pętli generujemy funkcją range()
, przy czym domyślnie liczy ona od 0. Możemy do niej wrzucić jedną liczbę albo koniec i początek ciągu, który chcemy uzyskać. Koniec domyślnie nie wlicza się do ciągu.
Daty to szczególne wartości, którymi zawiadują funkcje z pakietu datetime
.
Zmienne z datami tworzymy za pomocą date()
, różnice w datach liczymy za pośrednictwem timedelta()
, a także możemy formatować daty poprzez metodę strftime()
.
Wyniki działania takich skryptów zapisujemy do plików operatorem >
. Robimy to w konsoli systemowej (np. PowerShell), a nie w konsoli Pythona.
Ciekawostką może być tutaj wczesny kalendarz hebrajski. Kalendarz hebrajski jest księżycowy, ma 12 miesięcy, ale w latach przestępnych dodawany jest 13. miesiąc – adar szeni. Takich lat przestępnych musi przypaść 7 w ciągu 19 lat. Obecnie jest to skodyfikowane (zob. cykl Metona), ale początkowo nie wiadomo było, które konkretnie lata w cyklu mają być przestępne i było to ad hoc ustalane przez społeczność żydowską. Tym samym lata przestępne pojawiały się znienacka i data wcale nie zmieniała się w przewidywalny sposób. Miało to poważne konsekwencje dla daty Wielkanocy, bo zanim został ustalony stabilny wzór (Wielkanocy, nie lat przestępnych w kalendarzu żydowskim, to nadeszło później), nigdy nie wiadomo było, kiedy wypadnie 14 nisan, czyli rocznica śmierci Jezusa według kalendarza żydowskiego.↩︎
Instrukcję, jak uruchamiać takie pliki bezpośrednio w Notepad++, znajdziemy np. tutaj.↩︎
Jest też easy mode, który przypomina generatory na stronach typu Formularze Google.↩︎
Sam korzystam z linuksa (I use arch, BTW), ale nie łudzę się – większość osób, które to czytają, to windowsiarze. W linuksie operator >
działa identycznie.↩︎
Sprawdziłem, taki skrypt generuje u mnie 30 lat rozpiski w 47 milisekund (!), a gotowy plik ma 310 kB.↩︎