Jak trzymać konfigurację skryptów? Żeby było bezpiecznie i wygodnie?
Przy prostych zadaniach konfiguracyjne parametry można (ale nie należy!) trzymać inline w kodzie. Ale lepiej w zmiennych (właściwie stałych) zdefiniowanych na początku pliku (i odpowiednio nazwanych, żeby wiedzieć że to stałe związane z konfiguracją). A najlepiej – w pliku zewnętrznym (lub zmiennych systemowych, ale tym się dzisiaj nie zajmiemy).
Photo by Ferenc Almasi on Unsplash
Dlaczego w pliku zewnętrznym? Z co najmniej dwóch powodów:
- prosta przenośność – można przenieść skrypt (albo cały ich zestaw, cały projekt) z jednego komputera na drugi i na przykład zmienić tylko ścieżki do plików z danymi. Można zmienić też adres (i inne dane dostępowe) bazy danych i dzięki temu nie zmieniając ani jednego znaku w oprogramowaniu przejść ze środowiska testowego na produkcyjne.
- bezpieczeństwo – w czasach kiedy kod (nawet firmowy) trzymany jest na GitHubie czy innych tego typu repozytoriach (nawet wewnętrznego BitBucketa to dotyczy) bezpieczeństwo jest ważne i dane dostępowe (loginy, hasła, klucze api itd. itp.) nie powinny być udostępniane. Żeby nie były – nie mogą być w kodzie, muszą być gdzieś poza kodem. W dużym skrócie popularne rozwiązanie wygląda tak, że developerzy nie znają danych dostępowych do produkcji, każdy trzyma lokalnie swoją konfigurację (z np. swoją bazą danych na potrzeby developmentu), a w pliku .gitignore wpisany jest plik konfiguracyjny. To powoduje, że plik z konfiguracją nie trafia do repozytorium.
No dobrze, ale skoro konfiguracja ma być w plikach zewnętrznych to w jakich? Najlepiej takich, które trzymają się jakiegoś standardu.
Standardy są właściwie cztery, my zajmiemy się trzema. I to zajmiemy się w R oraz Pythonie.
Konfiguracja .INI
Pierwszy standard to plik .ini. To plik tekstowy (jak wszystkie inne) o przykładowej strukturze jak poniżej:
1 2 3 4 5 6 7 8 9 10 |
; to jest komentarz [modul1] zmienna_txt = "Jestem \"zmienną\" tekstową z 'ciapkami'" zmienna_num = 123 zmienna_flo = 456.789 ; a to jest komentarz przed drugim modułem [modul2] zmienna_arr_txt = ['str1', 'str2', 'str3'] ; komentarz inline zmienna_arr_num = [123, 456, 789] |
Co my tutaj widzimy? Mamy dwie sekcje nazwane modul1
i modul2
, które mogą odpowiadać za na przykład różne elementy konfiguracyjne – bazę danych, ścieżki do plików, klucze api do różnych systemów. Takie moduły ułatwiają edycję konfiguracji.
W każdym z modułów mamy po dwie zmienne. W pierwszym są to zwykłe, pojedyncze zmienne, a w drugim – listy. Listy mogą być przydatne (na przykład lista adresów mailowych, odbiorców raportów).
Dodatkowo mamy możliwość komentowania w pliku – czasem to się przydaje. A jak działa?
Jak użyć takiego pliku? Zacznijmy od Pythona.
W Pythonie mamy wbudowany pakiet configparser
, który bardzo dobrze radzi sobie z czytaniem (oraz zapisywaniem, ale to dzisiaj odpuszczamy) plików .ini. Przykładowy kod, który wczyta i pokaże nam wybrane pozycje z konfiguracji:
1 2 3 4 5 6 7 8 9 10 11 12 |
import configparser # na potrzeby ładnego pokazywania obiektów import pprint pp = pprint.PrettyPrinter(indent=2) # zbudowanie obiektu parsera config = configparser.ConfigParser() # wczytanie konfiguracji config.read('config.ini') |
Jak wygląda wczytany obiekt?
1 2 3 |
pp.pprint(config) ## <configparser.ConfigParser object at 0x7fd9654dda20> |
Ma to swoją klasę, do elementów wewnątrz można dobrać się poprzez metodę get():
1 2 3 4 5 6 7 |
print(config.get('modul1', 'zmienna_txt')) ## "Jestem \"zmienną\" tekstową z 'ciapkami'" print(config.get('modul1', 'zmienna_num')) ## 123 |
Ale można sięgnąć po wartość też tak jak (nie powinno się) w standardowych słownikach:
1 2 3 |
print(config['modul2']['zmienna_arr_txt']) ## ['str1', 'str2', 'str3'] ; komentarz inline |
Ups – na końcu został nam komentarz. Cała linia (po znaku równości) traktowana jest jako wartość konkretnej zmiennej. Trzeba o tym pamiętać.
A czy da się dostać do jednego elementu na liście?
1 2 3 |
print(config['modul2']['zmienna_arr_num'][1]) ## 1 |
To nie jest jakiś konkretny element z listy (zobacz wyżej jak zdefiniowana jest zmienna_arr_num
)! To jest drugi (indeksowanie od zera) znak w zmiennej zmienna_arr_num
potraktowanej jako string. Bo tutaj wszystko jest stringiem:
1 2 3 4 5 6 7 8 |
print(type(config['modul1']['zmienna_num'])) ## <class 'str'> print(type(config['modul1']['zmienna_flo'])) ## <class 'str'> |
Tak, ta liczba też jest stringiem. Więc trzeba ewentualnie zrobić jawny casting do int lub float jeśli potrzebujecie.
Wszystko poza tymi niedogodnościami wygląda ok, prawda? I tak musimy znać ścieżki (jaki moduł, jaka zmienna) poszczególnych parametrów, więc nic dziwnego że odwołujemy się do nich po konkretnych nazwach.
Zwróć uwagę na to co otrzymujemy w zmienna_txt
z modul1
. W konfiguracji wartość zmiennej jest w cudzysłowach, aby było jasne że to ciąg tekstowy… ale te cudzysłowy trafiają do naszej zmiennej! No słabo trochę (z jednej strony, z drugiej ok). No i trzeba odpowiednio to w konfiguracji wyeskejpować.
A jak to wygląda w R? Podobnie jak w Pythonie – mamy gotową bibliotekę (już nie wbudowaną, trzeba ją sobie zainstalować przez install.packages("configr")
) – configr. Ta z kolei jest na tyle uniwersalna, że posłuży nam przy wszystkich omawianych dzisiaj formatach plików konfiguracyjnych. Przykładowy skrypt wyciągający dane z pliku .ini:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
library(configr) # wczytanie konfiguracji config <- read.config("config.ini") print(config) ## $modul1 ## $modul1$zmienna_txt ## [1] "\"Jestem \\\"zmienną\\\" tekstową z 'ciapkami'\"" ## ## $modul1$zmienna_num ## [1] "123" ## ## $modul1$zmienna_flo ## [1] "456.789" ## ## $modul2 ## $modul2$zmienna_arr_txt ## [1] "['str1', 'str2', 'str3'] ; komentarz inline" ## ## $modul2$zmienna_arr_num ## [1] "[123, 456, 789]" cat(config$modul1$zmienna_txt) ## "Jestem \"zmienną\" tekstową z 'ciapkami'" cat(config$modul1$zmienna_num) ## 123 cat(config$modul2$zmienna_arr_txt) ## ['str1', 'str2', 'str3'] ; komentarz inline |
Tutaj podobnie jak w Pythonie – komentarz inline nie jest obsługiwany (w sumie .ini nie mają jako takiej sztywnej definicji, a gdyby miały – taki komentarz byłby niedozwolony).
1 2 3 |
cat(config$modul2$zmienna_arr_num) ## [123, 456, 789] |
Uwaga – wynik ponownie jest stringiem a nie listą, co widać tutaj:
1 2 3 |
cat(class(config$modul2$zmienna_arr_txt)) ## character |
tutaj:
1 2 3 |
cat(class(config$modul2$zmienna_arr_num)) ## character |
oraz tutaj:
1 2 3 |
cat(config$modul2$zmienna_arr_num[2]) ## NA |
Z grubsza więc jest tak samo jak w Pythonie.
Podsumowując – pliki INI:
- pozwalają na komentarze – ale tylko jako jedna linia, bez inline
- każda wartość przypisana do zmiennej jest typu string
- nie można użyć zagnieżdżeń
Konfiguracja .JSON
Pliki .ini nie pozwalają na zagnieżdżanie elementów – mamy płaską listę zmiennych (dla wygody upakowaną w moduły, o ile istnieje taka potrzeba). A pliki .json już takie zagnieżdżanie umożliwiają. Po co to? Ano na przykład w jednym pliku możemy trzymać konfigurację wszystkich środowisk (zagnieżdżony obiekt to konfiguracja jednego ze środowisk) i tylko jedną zmienną (też w pliku konfiguracyjnym) decydować którego środowiska używamy. Średnio to bezpieczne rozwiązanie, ale się da.
Zbudujmy więc kolejną konfigurację, tym razem już szerszą – taką, która pozwala na wykorzystanie zarówno zagnieżdżenia pojedynczych obiektów jak i na zbudowanie listy obiektów:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
{ "modul1": { "zmienna_txt": "Jestem \"zmienną\" tekstową z 'ciapkami'", "zmienna_num": 123, "zmienna_flo": 456.789 }, "modul2": { "zmienna_arr_txt": ["str1", "str2", "str3"], "zmienna_arr_num": [123, 456, 789] }, "modul3": { "obiekt1": { "nazwa": "Obiekt Jeden", "numer": 1, "tablica_num": [1, 1, 1], "tablica_str": [ "obj 1 - string 1", "obj 1 - string 2", "obj 1 - string 3" ] }, "obiekt2": { "nazwa": "Obiekt dwa", "numer": 2, "tablica_num": [2, 2, 2], "tablica_str": [ "obj 2 - string 1", "obj 2 - string 2" ] } }, "modul4": [ { "nazwa": "array of objects 1", "wartosc": 1, "inna_wartosc": "abc" }, { "nazwa": "array of objects 2", "wartosc": 2, "inna_wartosc": "def" }, { "nazwa": "array of objects 3", "wartosc": 3, "inna_wartosc": "ghi" }, { "nazwa": "array of objects 4", "wartosc": 4, "inna_wartosc": "jkl" } ] } |
W powyższej konfiguracji modul1
oraz modul2
są żywcem przeniesione z pliku .ini, a kolejne dwa moduły pokazują rzeczy (cuda, o jakich nie śniło się filozofom! ;-) na które format INI nie pozwala.
W modul3
mamy dwa obiekty zagnieżdżone pod modułem. Oba obiekty z grubsza są takie same, ale są to dwa odrębne byty. Aby dostać się do nazwy obiektu trzeba przejść po drzewie: moduł -> obiekt -> pole -> wartość.
Z kolei w modul4
mamy cztery obiekty zagnieżdżone pod modułem ale w formie listy. Zatem aby dostać się do nazwy konkretnego elementu trzeba przejść ścieżką: moduł -> jeden z elementów listy -> pole -> wartość.
Odczytajmy taką konfigurację w Pythonie. Będzie bardzo prosto, o ile potrafisz odczytać pliki JSON.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
import json import pprint pp = pprint.PrettyPrinter(indent=2) # wczytujemy plik JSON with open('config.json', 'r') as fp: config = json.load(fp) # jak wygląda w całości nasz obiekt? pp.pprint(config) ## { 'modul1': { 'zmienna_flo': 456.789, ## 'zmienna_num': 123, ## 'zmienna_txt': 'Jestem "zmienną" tekstową z \'ciapkami\''}, ## 'modul2': { 'zmienna_arr_num': [123, 456, 789], ## 'zmienna_arr_txt': ['str1', 'str2', 'str3']}, ## 'modul3': { 'obiekt1': { 'nazwa': 'Obiekt Jeden', ## 'numer': 1, ## 'tablica_num': [1, 1, 1], ## 'tablica_str': [ 'obj 1 - string 1', ## 'obj 1 - string 2', ## 'obj 1 - string 3']}, ## 'obiekt2': { 'nazwa': 'Obiekt dwa', ## 'numer': 2, ## 'tablica_num': [2, 2, 2], ## 'tablica_str': [ 'obj 2 - string 1', ## 'obj 2 - string 2']}}, ## 'modul4': [ { 'inna_wartosc': 'abc', ## 'nazwa': 'array of objects 1', ## 'wartosc': 1}, ## { 'inna_wartosc': 'def', ## 'nazwa': 'array of objects 2', ## 'wartosc': 2}, ## { 'inna_wartosc': 'ghi', ## 'nazwa': 'array of objects 3', ## 'wartosc': 3}, ## { 'inna_wartosc': 'jkl', ## 'nazwa': 'array of objects 4', ## 'wartosc': 4}]} |
Wygląda (z grubsza, jeśli pominąć formatowanie) jak nasz źródłowy JSON, prawda?
Jak dostać się do wartości kolejnych elementów? Tak jak do elementów słownika (dict) w Pythonie. Tutaj akurat słownik słowników:
1 2 3 |
print(config['modul1']['zmienna_txt']) ## Jestem "zmienną" tekstową z 'ciapkami' |
A jak mamy dodatkowo listę to po prostu używamy indeksu:
1 2 3 |
print(config['modul3']['obiekt2']['tablica_str'][1]) ## obj 2 - string 2 |
Dla listy obiektów – najpierw indeks na liście, a potem nazwa elementu:
1 2 3 |
print(config['modul4'][1]['nazwa']) ## array of objects 2 |
A co z typem? Jeśli liczba nie jest w ciapkach to…
1 2 3 4 5 6 7 8 |
print(type(config['modul1']['zmienna_num'])) ## <class 'int'> print(type(config['modul1']['zmienna_flo'])) ## <class 'float'> |
…jest tego typu którego być powinna (int lub float).
Wszystko zgodnie z intuicją – dokładnie tak, jak można się spodziewać. Czy w R jest tak samo?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 |
library(configr) config <- read.config("config.json") print(config) ## $modul1 ## $modul1$zmienna_txt ## [1] "Jestem \"zmienną\" tekstową z 'ciapkami'" ## ## $modul1$zmienna_num ## [1] 123 ## ## $modul1$zmienna_flo ## [1] 456.789 ## ## $modul2 ## $modul2$zmienna_arr_txt ## [1] "str1" "str2" "str3" ## ## $modul2$zmienna_arr_num ## [1] 123 456 789 ## ## $modul3 ## $modul3$obiekt1 ## $modul3$obiekt1$nazwa ## [1] "Obiekt Jeden" ## ## $modul3$obiekt1$numer ## [1] 1 ## ## $modul3$obiekt1$tablica_num ## [1] 1 1 1 ## ## $modul3$obiekt1$tablica_str ## [1] "obj 1 - string 1" "obj 1 - string 2" "obj 1 - string 3" ## ## $modul3$obiekt2 ## $modul3$obiekt2$nazwa ## [1] "Obiekt dwa" ## ## $modul3$obiekt2$numer ## [1] 2 ## ## $modul3$obiekt2$tablica_num ## [1] 2 2 2 ## ## $modul3$obiekt2$tablica_str ## [1] "obj 2 - string 1" "obj 2 - string 2" ## ## ## $modul4 ## nazwa wartosc inna_wartosc ## 1 array of objects 1 1 abc ## 2 array of objects 2 2 def ## 3 array of objects 3 3 ghi ## 4 array of objects 4 4 jkl ## cat(config$modul1$zmienna_txt) ## Jestem "zmienną" tekstową z 'ciapkami' cat(config$modul3$obiekt2$tablica_str[2]) ## obj 2 - string 2 |
Sięgamy do konkretnego elementu tablicy!
1 2 3 |
cat(config$modul4$nazwa[2]) ## array of objects 2 |
Uwaga – inna kolejność parametrów niż w pliku konfiguracyjnym: najpierw mamy nazwę pola, a potem numer na liście (indeks) obiektów. Może to wprowadzić zamieszanie.
Sprawdźmy jeszcze czy zachowane są typy danych:
1 2 3 4 5 6 7 8 |
cat(class(config$modul1$zmienna_num)) ## integer cat(class(config$modul1$zmienna_flo)) ## numeric |
Jest tak jak być powinno – liczby całkowite są typu integer, a zmiennoprzecinkowe – numeric.
Zwróć też uwagę, że config$modul4
to właściwie gotowy data frame… co ma swoje zalety. W Pythonie z użyciem Pandas też łatwo data frame’a z JSONa zbudować.
Podsumowując – pliki JSON:
- nie pozwalają na komentarze
- zachowane są typy danych
- stringi trzeba ubierać w ciapki
- można użyć zagnieżdżeń i list elementów/obiektów
Konfiguracja .YAML
Pliki YAML pozwalają na wszystko to co JSON, ale mają dwa miłe dodatki. Po pierwsze – są chyba nieco bardziej czytelne – nie ma tylu nawiasów. Ale trzeba dbać o wcięcia. Na szczęście większość edytorów potrafi tego pilnować (albo ma wtyczki do tego).
Druga zaleta – komentarze.
Zobaczmy przykładową konfigurację – identyczną z tą z JSONa:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
modul1: # tutaj można dodać komentarz zmienna_txt: Jestem "zmienną" tekstową z 'ciapkami' zmienna_num: 123 zmienna_flo: 456.789 modul2: zmienna_arr_txt: # na tym poziomie też można komentować! - "str1" - "str2" - "str3" zmienna_arr_num: [123, 456, 789] modul3: obiekt1: nazwa: "Obiekt Jeden" numer: 1 tablica_num: [1, 1, 1] tablica_str: - "obj 1 - string 1" - "obj 1 - string 2" # a nawet na tym! - "obj 1 - string 3" obiekt2: nazwa: "Obiekt dwa" numer: 2 tablica_num: [2, 2, 2] tablica_str: ["obj 2 - string 1", "obj 2 - string 2"] # zapisane jako tablica, a nie od kresek # oczywiście tutaj komentarze też są dozwolone modul4: - nazwa: array of objects 1 wartosc: 1 inna_wartosc: abc - nazwa: array of objects 2 wartosc: 2 inna_wartosc: def - nazwa: array of objects 3 wartosc: 3 inna_wartosc: ghi - nazwa: array of objects 4 wartosc: 4 inna_wartosc: jkl |
Przeczytajmy to w Pythonie. Potrzebujemy pakietu pyyaml
– jeśli go nie masz to zainstaluj via pip lub conda (jeśli używasz Anacondy).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
import yaml import pprint pp = pprint.PrettyPrinter(indent=2) with open('config.yaml', 'r') as fp: config = yaml.safe_load(fp) pp.pprint(config) ## { 'modul1': { 'zmienna_flo': 456.789, ## 'zmienna_num': 123, ## 'zmienna_txt': 'Jestem "zmienną" tekstową z \'ciapkami\''}, ## 'modul2': { 'zmienna_arr_num': [123, 456, 789], ## 'zmienna_arr_txt': ['str1', 'str2', 'str3']}, ## 'modul3': { 'obiekt1': { 'nazwa': 'Obiekt Jeden', ## 'numer': 1, ## 'tablica_num': [1, 1, 1], ## 'tablica_str': [ 'obj 1 - string 1', ## 'obj 1 - string 2', ## 'obj 1 - string 3']}, ## 'obiekt2': { 'nazwa': 'Obiekt dwa', ## 'numer': 2, ## 'tablica_num': [2, 2, 2], ## 'tablica_str': [ 'obj 2 - string 1', ## 'obj 2 - string 2']}}, ## 'modul4': [ { 'inna_wartosc': 'abc', ## 'nazwa': 'array of objects 1', ## 'wartosc': 1}, ## { 'inna_wartosc': 'def', ## 'nazwa': 'array of objects 2', ## 'wartosc': 2}, ## { 'inna_wartosc': 'ghi', ## 'nazwa': 'array of objects 3', ## 'wartosc': 3}, ## { 'inna_wartosc': 'jkl', ## 'nazwa': 'array of objects 4', ## 'wartosc': 4}]} print(config['modul1']['zmienna_txt']) ## Jestem "zmienną" tekstową z 'ciapkami' |
Porównaj wynik z tym jak ciąg został wpisany w konfigurację – jest znak w znak tak samo. To bardzo wygodne.
1 2 3 4 5 6 7 8 |
print(type(config['modul1']['zmienna_num'])) ## <class 'int'> print(type(config['modul1']['zmienna_flo'])) ## <class 'float'> |
Jak widać klasy danych liczbowych są zachowane. To również wygodne, ale nic szczególnego w porównaniu z JSONem.
1 2 3 4 5 6 7 8 |
print(config['modul3']['obiekt2']['tablica_str'][1]) ## obj 2 - string 2 print(config['modul4'][1]['nazwa']) ## array of objects 2 |
Właściwie nie ma czego komentować – wszystko zachowuje się tak jak powinno. Typy zmiennych są rozpoznawane, stringi nie potrzebują ciapek, listy da się indeksować – nie ważne czy to listy elementów jednego typu czy całych obiektów.
A jak czytać to w R? Przy użyciu pakietu configr
właściwie ciągle tak samo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 |
library(configr) config <- read.config("config.yaml") print(config) ## $modul1 ## $modul1$zmienna_txt ## [1] "Jestem \"zmienną\" tekstową z 'ciapkami'" ## ## $modul1$zmienna_num ## [1] 123 ## ## $modul1$zmienna_flo ## [1] 456.789 ## ## $modul2 ## $modul2$zmienna_arr_txt ## [1] "str1" "str2" "str3" ## ## $modul2$zmienna_arr_num ## [1] 123 456 789 ## ## $modul3 ## $modul3$obiekt1 ## $modul3$obiekt1$nazwa ## [1] "Obiekt Jeden" ## ## $modul3$obiekt1$numer ## [1] 1 ## ## $modul3$obiekt1$tablica_num ## [1] 1 1 1 ## ## $modul3$obiekt1$tablica_str ## [1] "obj 1 - string 1" "obj 1 - string 2" "obj 1 - string 3" ## ## $modul3$obiekt2 ## $modul3$obiekt2$nazwa ## [1] "Obiekt dwa" ## ## $modul3$obiekt2$numer ## [1] 2 ## ## $modul3$obiekt2$tablica_num ## [1] 2 2 2 ## ## $modul3$obiekt2$tablica_str ## [1] "obj 2 - string 1" "obj 2 - string 2" ## ## ## $modul4 ## $modul4[[1]] ## $modul4[[1]]$nazwa ## [1] "array of objects 1" ## ## $modul4[[1]]$wartosc ## [1] 1 ## ## $modul4[[1]]$inna_wartosc ## [1] "abc" ## ## $modul4[[2]] ## $modul4[[2]]$nazwa ## [1] "array of objects 2" ## ## $modul4[[2]]$wartosc ## [1] 2 ## ## $modul4[[2]]$inna_wartosc ## [1] "def" ## ## $modul4[[3]] ## $modul4[[3]]$nazwa ## [1] "array of objects 3" ## ## $modul4[[3]]$wartosc ## [1] 3 ## ## $modul4[[3]]$inna_wartosc ## [1] "ghi" ## ## $modul4[[4]] ## $modul4[[4]]$nazwa ## [1] "array of objects 4" ## ## $modul4[[4]]$wartosc ## [1] 4 ## ## $modul4[[4]]$inna_wartosc ## [1] "jkl" |
Jak widzimy zmienia się tylko delikatnie (w porównaniu do JSONa) struktura obiektu stworzonego po wczytaniu YAMLa. Jak sięgać do środka?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
cat(config$modul1$zmienna_txt) ## Jestem "zmienną" tekstową z 'ciapkami' cat(config$modul1$zmienna_num) ## 123 cat(class(config$modul1$zmienna_num)) ## integer cat(class(config$modul1$zmienna_flo)) ## numeric cat(config$modul2$zmienna_arr_txt) ## str1 str2 str3 cat(config$modul2$zmienna_arr_num) ## 123 456 789 |
Sięganie do elementu tablicy jest intuicyjne:
1 2 3 |
cat(config$modul3$obiekt2$tablica_str[2]) ## obj 2 - string 2 |
Nawet jeśli to tablica bardziej złożonych obiektów:
1 2 3 |
cat(config$modul4[[2]]$nazwa) ## array of objects 2 |
Uwaga – inna kolejność sięgania do parametrów niż w przypadku sięgania do nich wczytanych z JSONa, ale tutaj zgodna z logiką.
Trudniej z YAMLa (w porównaniu do JSONa) uzyskać data frame. Ale YAML to format do konfiguracji a nie przekazywana danych. Do danych JSON sprawdza się zdecydowanie lepiej (co widać we wszelakich API).
Podsumowując – pliki YAML:
- pozwalają na komentarze na każdym poziomie
- zachowane są typy danych
- stringów mogą być bez ciapek
- można użyć zagnieżdżeń właściwie na każdym poziomie
- format daje nam trochę swobody w pisaniu (np. definiowanie tablic) i czyta się podobnie do naturalnego języka
No ideał! ;)
GitHub
Żeby było łatwiej i nie trzeba było kopiować kodu z tego wpisu wszystko wrzuciłem na swojego GitHuba – śmiało forkuj, jeśli potrzebujesz.
A jeśli jesteś już tutaj to możesz też wykonać jeden klik i zapisać się na newsletter.
mało bezpieczne, ale działa
lista = eval( config[’modul2′][’zmienna_arr_num’] )