Jak kiepsko jest używać plików Python jako plików konfiguracyjnych?

72

Zawsze używałem plików JSON do konfiguracji moich aplikacji. Zacząłem ich używać od czasu, gdy napisałem dużo kodu Java, a teraz pracuję głównie nad rozwojem Pythona po stronie serwera i nauki danych i nie jestem pewien, czy JSON jest już właściwą drogą.

Widziałem, że Celery używa rzeczywistych plików Python do konfiguracji. Początkowo byłem sceptyczny. Ale pomysł użycia prostych struktur danych w Pythonie do konfiguracji zaczyna mi się podobać. Niektóre zalety:

  • Struktury danych będą takie same, jak normalnie koduję. Nie muszę więc zmieniać zdania.
  • Moje IDE (PyCharm) rozumie związek między konfiguracją a kodem. Ctrl+ Bumożliwia łatwe przechodzenie między konfiguracją a kodem.
  • Nie muszę pracować z IMO niepotrzebnym ścisłym JSON . Patrzę na ciebie podwójne cudzysłowy, bez przecinków i komentarzy.
  • Mogę pisać konfiguracje testowe w aplikacji, nad którą pracuję, a następnie łatwo przenosić je do pliku konfiguracyjnego bez konieczności jakiejkolwiek konwersji i analizy JSON.
  • W razie potrzeby można wykonać bardzo proste skrypty w pliku konfiguracyjnym. (Chociaż powinno to być bardzo, bardzo ograniczone.)

Więc moje pytanie brzmi: jeśli się przełączę, jak strzelę sobie w stopę?

Żaden niewykwalifikowany użytkownik końcowy nie będzie korzystał z plików konfiguracyjnych. Wszelkie zmiany w plikach konfiguracyjnych są obecnie zatwierdzane przez Git i są wdrażane na nasze serwery w ramach ciągłego wdrażania. Nie ma żadnych ręcznych zmian konfiguracji, chyba że zdarzy się sytuacja wyjątkowa lub jest w fazie rozwoju.

(Rozważałem YAML , ale coś w tym mnie denerwuje. Więc na razie nie ma go na amerykańskim stole).

André Christoffer Andersen
źródło
39
Niewykwalifikowany nie jest twoim problemem. Szkodliwe jest.
Blrfl,
9
Co rozumiesz przez „poza amerykańskim stołem” ?
Peter Mortensen
24
„Więc na razie nie ma go przy amerykańskim stole”. === „Na razie jest, jak mówią Amerykanie, poza stołem.”
biskup,
7
Jeśli nie lubisz JSON, powinieneś spróbować yaml. Bardzo mi się podoba dla konfiguracji. szczególnie, gdy w grę wchodzą większe łańcuchy, YAML jest bardziej czytelny niż JSON.
Christian Sauer,
5
@ biskup „poza stołem” w języku angielskim w Wielkiej Brytanii oznacza, że ​​nie jest już brany pod uwagę, ponieważ wnioski parlamentarne są przedstawiane na stole w Izbie Gmin w celach informacyjnych podczas dyskusji, stąd też „zgłoszone do dyskusji” (zapis parlamentarny 1799 - books.google.co.uk/… ), AFAIK Znaczenie USA jest takie samo, ale nie wiem, czy masz stół w parlamencie.
Pete Kirkham,

Odpowiedzi:

92

Używając języka skryptowego zamiast pliku konfiguracyjnym świetnie wygląda na pierwszy rzut oka: masz pełną moc tego języka dostępne i można po prostu eval()albo importnie. W praktyce istnieje kilka gotchas:

  • jest to język programowania, którego należy się nauczyć. Aby edytować konfigurację, musisz wystarczająco dobrze znać ten język. Pliki konfiguracyjne mają zwykle prostszy format, który trudniej jest pomylić.

  • jest to język programowania, co oznacza, że ​​konfiguracja może być trudna do debugowania. Za pomocą normalnego pliku konfiguracyjnego można na niego spojrzeć i zobaczyć, jakie wartości podano dla każdej właściwości. Za pomocą skryptu możesz go najpierw wykonać, aby zobaczyć wartości.

  • jest to język programowania, który utrudnia zachowanie wyraźnego rozdziału między konfiguracją a rzeczywistym programem. Czasami chcesz tego rodzaju rozszerzalności, ale w tym momencie prawdopodobnie szukasz prawdziwego systemu wtyczek.

  • jest to język programowania, co oznacza, że ​​config może zrobić wszystko, co potrafi język programowania. Więc albo używasz rozwiązania piaskownicy, które neguje dużą elastyczność języka, albo pokładasz duże zaufanie w autorze konfiguracji.

Zatem użycie skryptu do konfiguracji jest prawdopodobnie OK, jeśli odbiorcami twojego narzędzia są programiści, np. Sphinx config lub setup.py w projektach Python. Inne programy z konfiguracją wykonywalną to powłoki takie jak Bash i edytory takie jak Vim.

Użycie języka programowania do konfiguracji jest konieczne, jeśli config zawiera wiele sekcji warunkowych lub jeśli zapewnia wywołania zwrotne / wtyczki. Używanie skryptu bezpośrednio zamiast eval () - niektóre pola konfiguracji wydają się być bardziej debugowalne (pomyśl o śladach stosu i numerach linii!).

Dobrym pomysłem może być również bezpośrednie użycie języka programowania, jeśli konfiguracja jest tak powtarzalna, że ​​piszesz skrypty do automatycznego generowania konfiguracji. Ale może lepszy model danych dla konfiguracji mógłby wyeliminować potrzebę takiej jawnej konfiguracji? Na przykład może być pomocne, jeśli plik konfiguracyjny może zawierać symbole zastępcze, które później rozwiniesz. Inną często widzianą funkcją jest wiele plików konfiguracyjnych o różnym priorytecie, które mogą się nadpisywać, choć wprowadza to pewne problemy.

W większości przypadków pliki INI, pliki właściwości Java lub dokumenty YAML są znacznie lepiej dostosowane do konfiguracji. W przypadku złożonych modeli danych może również obowiązywać XML. Jak zauważyłeś, JSON ma pewne aspekty, które sprawiają, że nie nadaje się jako plik konfiguracyjny do edycji przez człowieka, chociaż jest to dobry format wymiany danych.

amon
źródło
25
Istnieje kilka formatów plików konfiguracyjnych, które są najsłynniej „przypadkowo zakończone przez Turinga” sendmail.cf. Oznaczałoby to, że użycie rzeczywistego języka skryptowego może być korzystne, ponieważ w rzeczywistości został on zaprojektowany tak, aby był kompletny w Turingu. Jednakże Turing-kompletność i „Tetris kompletność” to dwie różne rzeczy, a jednocześnie sendmail.cfmoże obliczyć dowolne funkcje, może nie wysyłać swojej /etc/passwdnad siatką lub formatowania dysku twardym, który Python lub Perl byłby w stanie.
Jörg W Mittag
3
@ JörgWMittag Turyn-zupełność nie oznacza, że ​​można wysyłać rzeczy przez sieć lub uzyskiwać dostęp do dysku twardego. Oznacza to, że kompletność w Turynie dotyczy przetwarzania, a nie operacji we / wy. Na przykład CSS jest uważany za ukończony w Turynie, ale nie będzie bałaganu w twoim stałym magazynie. Powiedzieliście gdzie indziej, że „Idris jest całkowicie czystym językiem funkcjonalnym, więc z definicji nie jest kompletnym językiem Turinga”, który nie następuje, i najwyraźniej jest to Turyn kompletny. Byłem przekonany, że użycie Testris-complete oznaczało, że język był kompletny w Turynie, ale nie był w stanie wykonać pełnego wejścia / wyjścia ... wygląda na to, że nie to masz na myśli.
Theraot
6
@Theraot: „Total” oznacza, że ​​zawsze zwraca. Maszyna Turinga może wykonać nieskończoną pętlę, tzn. Ma zdolność do powrotu. Ergo, Idris nie może zrobić wszystkiego, co robi Maszyna Turinga, co oznacza, że nie jest ona kompletna. Dotyczy to wszystkich języków o typie zależnym. Istotą języka zależnego od typu jest to, że możesz decydować o dowolnych właściwościach programów, podczas gdy w języku kompletnym Turinga nie możesz nawet decydować o trywialnych właściwościach, takich jak „czy ten program się zatrzymuje?” Wszystkie języki z definicji nie są kompletne, ponieważ maszyny Turinga są częściowe.
Jörg W Mittag
10
Definicja z „Turing-complete” jest „można zaimplementować maszyny Turinga”. Definicja „Tetris-complete” to „można zaimplementować Tetris”. Istotą tej definicji jest to, że kompletność Turinga po prostu nie jest zbyt interesująca w prawdziwym świecie. Istnieje wiele przydatnych języków, które nie są pełne Turinga, np. HTML, SQL (sprzed 1999 r.), Różne DSL itp. OTOH, kompletność Turinga oznacza tylko, że można obliczyć funkcje na liczbach naturalnych, to nie oznacza drukowanie na ekranie, dostęp do sieci, interakcja z użytkownikiem, systemem operacyjnym, środowiskiem - wszystko to jest ważne.
Jörg W Mittag
4
Powodem, dla którego Edwin Brady wykorzystał ten przykład, jest to, że wiele osób uważa, że ​​języków, które nie są kompletne w Turingu, nie można używać do programowania ogólnego. Ja sam tak myślałem, ponieważ w końcu wiele interesujących programów to w zasadzie niekończące się pętle, których nie chcemy zatrzymywać , np. Serwery, systemy operacyjne, pętle zdarzeń w GUI, pętle do gier. Wiele programów przetwarza nieskończone dane, np. Strumienie zdarzeń. Kiedyś myślałem, że nie można napisać, że w łącznej języka, ale ponieważ dowiedział się, że może i tak uważam, że to dobry pomysł, aby mieć pojęcie o tej możliwości.
Jörg W Mittag
50

+1 do wszystkiego w odpowiedzi amona . Chciałbym dodać to:

Pożałujesz użycia kodu Python jako języka konfiguracji przy pierwszym zaimportowaniu tej samej konfiguracji z kodu napisanego w innym języku. Na przykład, jeśli kod, który jest częścią twojego projektu i jest napisany w C ++ lub Ruby lub coś innego wymaga pobrania konfiguracji, musisz połączyć interpreter Pythona jako bibliotekę lub przeanalizować konfigurację w koprocesie Pythona, oba które są niezręczne, trudne lub nadmiernie kosztowne.

Cały kod, który dzisiaj importuje tę konfigurację, może być napisany w Pythonie, i możesz pomyśleć, że będzie to również jutro, ale czy wiesz na pewno?

Powiedziałeś, że użyjesz logiki (cokolwiek innego niż statyczne struktury danych) w swojej konfiguracji oszczędnie, jeśli w ogóle, co jest dobre, ale jeśli w ogóle jest jej trochę, w przyszłości będzie trudno ją cofnąć, więc może wrócić do deklaratywnego pliku konfiguracyjnego.

EDYCJA dla rekordu: kilka osób skomentowało tę odpowiedź na temat tego, jak prawdopodobne lub mało prawdopodobne jest, że projekt zostanie kiedykolwiek całkowicie przepisany w innym języku. Można śmiało powiedzieć, że kompletne przepisywanie wsteczne jest prawdopodobnie rzadko spotykane. W rzeczywistości miałem na myśli fragmenty tego samego projektu (wymagającego dostępu do tej samej konfiguracji) napisane w różnych językach. Na przykład serwowanie stosu w C ++ dla szybkości, wsadowe czyszczenie bazy danych w Pythonie, niektóre skrypty powłoki jako klej. Zastanów się też nad tą sprawą :)

Celada
źródło
1
@Mast, przepraszam, ale nie podążam. Nazwa pliku (bez względu na to, czy kończy się na .py) nie jest tu ani tam. Chodzi mi o to, że jeśli jest napisany w języku Python, potrzebujesz interpretera języka Python, aby go odczytać.
Celada,
12
@Mast Myślę, że źle to analizujesz. Z tej odpowiedzi (zarówno oryginalnej, jak i edytowanej) wziąłem pod uwagę, że wybór zapisu plików konfiguracyjnych w języku programowania jest trudniejszy w pisaniu kodu w innym języku. Np. Decydujesz się przenieść swoją aplikację na Anrdoid / iPhone i będziesz używać innego języka. Musisz albo (a) polegać na interprecie Pythona w aplikacji na telefon komórkowy (nie jest to idealne rozwiązanie), (b) ponownie napisać konfigurację w formacie niezależnym od języka i przepisać kod Pythona, który go używał, lub (c) utrzymywać dwa formaty konfiguracji w przyszłości.
Jon Bentley,
4
@JonBentley Podejrzewam, że problem będzie istotny, jeśli planowane są projekty wielojęzyczne. Nie zrobiłem tego wrażenia z OP. Ponadto użycie plików tekstowych do konfiguracji nadal wymaga dodatkowego kodu (we wszystkich językach) do faktycznego parsowania / konwersji wartości. Technicznie, jeśli ograniczają stronę Pythona do key=valueprzypisań do konfiguracji, nie rozumiem, dlaczego program Java / C ++ nie mógł odczytać pliku Python jako zwykłego pliku tekstowego i przeanalizować go tak samo, jeśli trzeba przejść do czegoś innego w przyszłość. Nie widzę potrzeby pełnoprawnego interpretera języka Python.
code_dredd
3
@ray Prawda, ale odpowiedź jest nadal przydatna, ponieważ pytania nie powinny dotyczyć tylko osoby, która je opublikowała. Jeśli użyjesz standardowego formatu (np. INI, JSON, YAML, XML itp.), Prawdopodobnie będziesz używać istniejącej biblioteki parsowania zamiast pisać własną. Ogranicza to dodatkową pracę do klasy adaptera do współpracy z biblioteką parsującą. Jeśli ograniczasz się do klucza = wartość, to eliminuje większość powodów, dla których OP powinien używać Pythona i równie dobrze możesz użyć rozpoznanego formatu.
Jon Bentley,
3
Musiałem to zrobić kilka lat temu, gdy narzędzie napisane w Lua używało skryptu Lua jako konfiguracji, a następnie chcieli, abyśmy napisali nowe narzędzie w języku C #, a konkretnie poprosili nas o użycie skryptu konfiguracji Lua. Mieli w sumie 2 wiersze, które były w rzeczywistości programowalne i nie były proste x = y, ale nadal musiałem się uczyć o otwartych źródłowych interpretatorach Lua dla .net z ich powodu. To nie jest czysto teoretyczny argument.
Kevin Fee
21

Inne odpowiedzi są już bardzo dobre, przedstawię swoje doświadczenia związane ze stosowaniem w świecie rzeczywistym w kilku projektach.

Plusy

W większości są już określone:

  • jeśli jesteś w programie Pythona, parsowanie to pestka ( eval); działa automatycznie nawet w przypadku bardziej złożonych typów danych (w naszym programie mamy punkty geometryczne i transformacje, które są zrzucane / ładowane dokładnie przez repr/ eval);
  • tworzenie „fałszywej konfiguracji” za pomocą zaledwie kilku linii kodu jest trywialne;
  • masz lepsze struktury i, IMO, znacznie lepszą składnię niż JSON (jeez nawet po prostu komentowanie i brak konieczności umieszczania podwójnych cudzysłowów wokół klawiszy słownika jest dużym zyskiem z czytelności).

Cons

  • złośliwi użytkownicy mogą zrobić wszystko, co może zrobić główny program; Nie uważam tego za zbyt duży problem, ponieważ ogólnie, jeśli użytkownik może zmodyfikować plik konfiguracyjny, może już zrobić wszystko, co może zrobić aplikacja;
  • jeśli nie jesteś już w programie Python, teraz masz problem. Podczas gdy niektóre z naszych plików konfiguracyjnych pozostały prywatne dla ich oryginalnej aplikacji, w szczególności jeden przyszedł do przechowywania informacji używanych przez kilka różnych programów, z których większość jest obecnie w C ++, które mają teraz zhakowany parser dla źle zdefiniowanego małego podzbiór Pythona repr. To oczywiście zła rzecz.
  • Nawet jeśli Twój program pozostaje w języku Python, możesz zmienić jego wersję. Powiedzmy, że twoja aplikacja rozpoczęła się w Pythonie 2; po wielu testach udało Ci się przeprowadzić migrację do Pythona 3 - niestety tak naprawdę nie przetestowałeś całego swojego kodu - masz wszystkie pliki konfiguracyjne leżące na komputerach klientów, napisane dla Pythona 2 i na których nie naprawdę mają kontrolę. Nie można nawet zapewnić „trybu zgodności” do odczytywania starych plików konfiguracyjnych (co często odbywa się w przypadku formatów plików), chyba że chcesz spakować / wywołać interpreter języka Python 2!
  • Nawet jeśli jesteś w Pythonie, modyfikacja pliku konfiguracyjnego z kodu jest prawdziwym problemem, ponieważ ... no cóż, modyfikacja kodu wcale nie jest trywialna, szczególnie kod, który ma bogatą składnię i nie jest w LISP lub podobny. Jeden z naszych programów ma plik konfiguracyjny, którym jest Python, napisany ręcznie ręcznie, ale który później okazał się przydatny do manipulowania za pomocą oprogramowania (szczególnym ustawieniem jest lista rzeczy, których porządkowanie przy użyciu GUI jest znacznie łatwiejsze). To duży problem, ponieważ:

    • nawet samo wykonanie analizy składni → AST → przepisywanie w obie strony nie jest trywialne (zauważysz, że połowa proponowanych rozwiązań jest później oznaczona jako „przestarzałe, nie używaj, nie działa we wszystkich przypadkach”);
    • nawet jeśli zadziałały, poziom AST jest zdecydowanie za niski; jesteś ogólnie zainteresowany manipulowaniem wynikami obliczeń wykonanych w pliku, a nie krokami, które do niego doprowadziły;
    • co prowadzi nas do prostego faktu, że nie można po prostu edytować wartości, którymi jesteś zainteresowany, ponieważ mogą one zostać wygenerowane przez skomplikowane obliczenia, których nie możesz zrozumieć / manipulować za pomocą kodu.

    Porównaj to z JSON, INI lub (Boże nie!) XML, gdzie reprezentację w pamięci można zawsze edytować i zapisywać bez utraty danych (XML, gdzie większość parserów DOM może zachować białe znaki w węzłach tekstowych i węzłach komentarzy) lub przynajmniej utrata tylko formatowania (JSON, gdzie sam format nie pozwala na więcej niż czytane nieprzetworzone dane).


Jak zwykle nie ma jednoznacznego rozwiązania; moja obecna polityka w tej kwestii to:

  • jeśli plik konfiguracyjny to:

    • z pewnością dla aplikacji Pythona i dla niej prywatnej - tak jak teraz nikt inny nie będzie próbował z niej czytać;
    • odręcznie;
    • pochodzący z zaufanego źródła;
    • używanie docelowych typów danych aplikacji jest naprawdę zaletą;

    plik Python może być prawidłowym pomysłem;

  • jeśli zamiast tego:

    • może istnieć możliwość odczytania z niego innej aplikacji;
    • istnieje możliwość edycji tego pliku przez aplikację, być może nawet samą moją aplikację;
    • pochodzi z niezaufanego źródła.

    format „tylko dane” może być lepszym pomysłem.

Zauważ, że nie trzeba dokonywać jednego wyboru - niedawno napisałem aplikację, która wykorzystuje oba podejścia. Mam prawie nigdy nie zmodyfikowany plik z pierwszą konfiguracją, odręcznymi ustawieniami, w których są zalety posiadania fajnych bonusów w Pythonie, oraz plik JSON do konfiguracji edytowany z interfejsu użytkownika.

Matteo Italia
źródło
1
bardzo dobra uwaga na temat generowania lub przepisywania konfiguracji! Ale niewiele formatów innych niż XML może zachować komentarze w danych wyjściowych, co uważam za niezwykle ważne dla konfiguracji. Inne formaty czasami wprowadzają note:pole, które jest ignorowane w konfiguracji.
amon
2
„jeśli użytkownik może zmodyfikować plik konfiguracyjny, może już zrobić wszystko, co może zrobić aplikacja” - nie jest to do końca prawdą. Co powiesz na testowanie niż błyszczący plik konfiguracji, którego ktoś nie znasz, przesłał na pastebin?
Dmitry Grigoryev
2
@DmitryGrigoryev: jeśli dążysz do tego celu, możesz również powiedzieć ofierze, aby niektóre z nich skopiowały i wkleiły curl ... | bash, jest to mniej kłopotliwe. :-P
Matteo Italia
@DmitryGrigoryev: i jest to rodzaj rzeczy, która może pozwolić komuś całkowicie zepsuć system produkcyjny pierwszego dnia pracy. Jeśli paral to „paral”, oznacza to, że nie ma możliwości sprawdzenia go pod kątem problemów przed odczytaniem. (ten sam powód, dla którego skrypty powłoki są tak złe w produkcji). INI, YAML lub JSON są pod tym względem bezpieczne.
Joe
1
@DmitryGrigoryev: Chodzi mi o to, że jeśli twój typ ofiary jest na tyle głupi, aby ślepo skopiować i wkleić plik konfiguracyjny, prawdopodobnie możesz go nakłonić do zrobienia czegokolwiek na ich komputerze za pomocą mniej skośnych metod („wklej to do konsoli, aby napraw swój problem! ”). Ponadto, nawet w przypadku plików wykonywalnych, które nie są wykonywane, istnieje wiele możliwości wyrządzenia szkody - nawet złośliwe wskazywanie logowania do plików krytycznych (jeśli aplikacja działa z wystarczającymi uprawnieniami), możesz siać spustoszenie w systemie. Właśnie dlatego uważam, że w praktyce nie ma dużej różnicy w zakresie bezpieczeństwa.
Matteo Italia,
8

Główne pytanie brzmi: czy chcesz, aby plik konfiguracyjny był w jakimś kompletnym języku Turinga (takim jak Python)? Jeśli tego chcesz, możesz również rozważyć osadzenie innego języka skryptowego (kompletnego Turinga), takiego jak Guile lub Lua (ponieważ może być postrzegany jako „prostszy” w użyciu lub osadzaniu niż Python; przeczytaj rozdział Rozszerzanie i Osadzanie Pythona ). Nie będę o tym dalej dyskutować (ponieważ inne odpowiedzi - na przykład Amon - omawiał to dogłębnie), ale zauważam, że osadzenie języka skryptowego w twojej aplikacji jest ważnym wyborem architektonicznym , który powinieneś rozważyć bardzo wcześnie; Naprawdę nie polecam dokonywania tego wyboru później!

Dobrze znanym przykładem programu konfigurowalnego przez „skrypty” jest edytor GNU emacs (lub prawdopodobnie AutoCAD w sferze zastrzeżonej); więc miej świadomość, że jeśli zaakceptujesz skrypty, jakiś użytkownik w końcu skorzysta - i być może nadużywanie, z twojego punktu widzenia - z tego narzędzia i stworzy skrypt wielotysięczny; dlatego wybór wystarczająco dobrego języka skryptowego jest ważny.

Jednak (przynajmniej w systemach POSIX), możesz uznać za wygodne włączenie dynamicznego obliczania „pliku” konfiguracji w czasie inicjalizacji (oczywiście pozostawiając ciężar rozsądnej konfiguracji administratorowi systemu lub użytkownikowi; w rzeczywistości jest to konfiguracja tekst pochodzący z jakiegoś pliku lub polecenia). W tym celu możesz po prostu przyjąć konwencję (i udokumentować ją), że ścieżka pliku konfiguracyjnego zaczynająca się od np. A !lub a |jest w rzeczywistości poleceniem powłoki , które czytałbyś jako potok . To pozostawia użytkownikowi możliwość wyboru dowolnego „preprocesora” lub „języka skryptowego”, który jest mu najbardziej znany.

(jeśli użytkownik akceptuje konfigurację obliczaną dynamicznie, musi zaufać użytkownikowi w kwestiach bezpieczeństwa)

Zatem w kodzie inicjalizacyjnym main(na przykład) zaakceptujesz jakiś --config argument confarg i wyciągniesz FILE*configf;z niego trochę . Jeśli ten argument zaczyna się od !(tzn. Jeśli (confarg[0]=='!')....), użyjesz configf = popen(confarg+1, "r");i zamkniesz ten potok za pomocą pclose(configf);. W przeciwnym razie użyjesz configf=fopen(confarg, "r");i zamkniesz ten plik za pomocą fclose(configf);(nie zapomnij o sprawdzaniu błędów). Patrz rura (7) , popen (3) , fopen (3) . Aby zapoznać się z aplikacją zakodowaną w języku Python, przeczytaj o os.popen itp.

(dokument również dla dziwnego użytkownika, który chce przekazać plik konfiguracyjny o nazwie !foo.configpass, ./!foo.configaby ominąć popenpowyższą sztuczkę)

Przy okazji, taka sztuczka jest jedynie wygodą (aby uniknąć wymagania od zaawansowanego użytkownika np. Kodowania jakiegoś skryptu powłoki w celu wygenerowania pliku konfiguracyjnego ). Jeśli użytkownik chce zgłosić błąd, powinien wysłać wygenerowany plik konfiguracyjny ...

Zauważ, że możesz również zaprojektować swoją aplikację z możliwością używania i ładowania wtyczek w czasie inicjalizacji, np. Za pomocą dlopen (3) (i musisz zaufać swojemu użytkownikowi w kwestii tej wtyczki). Ponownie, jest to bardzo ważna decyzja architektoniczna (i musisz zdefiniować i dostarczyć trochę raczej stabilnego API i konwencji dotyczących tych wtyczek i twojej aplikacji).

W przypadku aplikacji napisanej w języku skryptowym, takim jak Python, można również zaakceptować argument programu dla eval lub exec lub podobnych prymitywów. Ponownie, problemy związane z bezpieczeństwem są wówczas przedmiotem zainteresowania (zaawansowanego) użytkownika.

Jeśli chodzi o format tekstowy pliku konfiguracyjnego ( niezależnie od tego, czy jest generowany, czy nie), uważam, że najczęściej musisz go dobrze udokumentować (a wybór określonego formatu nie jest tak ważny; zalecam jednak, aby użytkownik mógł niektóre -przesłane-komentarze w nim). Możesz użyć JSON (najlepiej z niektórym parserem JSON, który akceptuje i pomija komentarze ze zwykłymi //do eol lub /*... */...), YAML, XML, INI lub własną rzeczą. Analiza pliku konfiguracyjnego jest dość łatwa (a znajdziesz wiele bibliotek związanych z tym zadaniem).

Basile Starynkevitch
źródło
+1 za wzmiankę o kompletności języków programowania Turinga. Niektóre interesujące prace ujawniają, że ograniczenie mocy obliczeniowej formatu wejściowego jest kluczem do zabezpieczenia warstwy obsługi danych wejściowych. Korzystanie z języka programowania Turing-complete idzie w przeciwnym kierunku.
Matheus Moreira,
2

Czy dodając do odpowiedzi amona , zastanawiałeś się nad alternatywami? JSON to może więcej niż potrzebujesz, ale pliki Pythona prawdopodobnie spowodują problemy w przyszłości z powodów wymienionych powyżej.

Jednak Python ma już parser konfiguracji dla bardzo prostego języka konfiguracji, który może spełnić wszystkie Twoje potrzeby. ConfigParserModuł implementuje prosty język config.

CodeMonkey
źródło
1
Używanie czegoś „podobnego do… plików Microsoft Windows INI” wydaje się złym pomysłem, zarówno z tego powodu, że nie jest to szczególnie elastyczny format, jak i dlatego, że „podobny” oznacza nieudokumentowane niezgodności.
Pete Kirkham,
1
@PeteKirkham Cóż, to proste, jest udokumentowane i jest częścią standardowej biblioteki Pythona. Może to być idealne rozwiązanie dla potrzeb OP, ponieważ szuka czegoś, co jest obsługiwane bezpośrednio przez Python i jest prostsze niż JSON. Dopóki nie sprecyzuje, jakie są jego potrzeby, myślę, że ta odpowiedź może mu pomóc.
CodeMonkey
1
Miałem zamiar w zasadzie to zasugerować - zobaczyć, jakie typy plików konfiguracyjnych obsługują biblioteki Python i wybrać jeden z nich. Ponadto Powershell ma pojęcie sekcji danych - które pozwalają na ograniczone konstrukcje językowe Powershell - chroniące przed złośliwym kodem. Jeśli Python ma bibliotekę, która obsługuje ograniczony podzbiór Pythona do konfiguracji, to przynajmniej łagodzi jedną z wad w stosunku do pomysłu w OP.
ẘpẘ
1
@PeteKirkham Bardziej prawdopodobne jest, że problem będzie odwrotny. Windows zwykle ma mnóstwo nieudokumentowanych bzdur, które wybuchają na tobie. Python jest zwykle dobrze udokumentowany i prosty. To powiedziawszy, jeśli wszystko, czego potrzebujesz, to proste pary klucz / wartość ( być może z sekcjami), to całkiem dobry wybór. Podejrzewam, że obejmuje to 90% przypadków użycia. Gdyby pliki konfiguracyjne .NET były ini zamiast monstrualnego XML-a ze schematem, który w rzeczywistości koduje maskowanie jako config, wszyscy bylibyśmy o wiele lepsi.
jpmc26,
1
@PeteKirkham Nie bardzo. INI jest najlepszy przede wszystkim do prostych przypadków użycia, istnieje szansa, że ​​można uniknąć wszelkich niezgodności. Nie mają również znaczenia, jeśli nie używasz pliku w dwóch różnych językach, a nawet jeśli tak, prawdopodobnie możesz znaleźć otwarte implementacje w dowolnym języku (co pozwala albo nie mieć niezgodności, albo przynajmniej wiedzieć dokładnie, co oni są). Zgadzam się, że powinieneś użyć innego formatu, jeśli twój przypadek użycia jest na tyle skomplikowany, że zaczynasz się z nim spotykać lub jeśli nie możesz znaleźć istniejącej implementacji, której możesz zaufać, ale to nie jest powszechne.
jpmc26
1

Od dłuższego czasu pracuję z dobrze znanym oprogramowaniem, którego pliki konfiguracyjne są zapisane w języku TCL, więc pomysł nie jest nowy. Działa to całkiem dobrze, ponieważ użytkownicy, którzy nie znają języka, mogą nadal pisać / edytować proste pliki konfiguracyjne za pomocą pojedynczej set name valueinstrukcji, podczas gdy bardziej zaawansowani użytkownicy i programiści mogą wyciągać z tego wyrafinowane sztuczki.

Nie sądzę, że „pliki konfiguracyjne mogą być trudne do debugowania” jest ważnym problemem. Tak długo, jak aplikacja nie zmusza użytkowników do pisania skryptów, użytkownicy mogą zawsze używać prostych przypisań w swoich plikach konfiguracyjnych, co nie jest trudniejsze do uzyskania w porównaniu do JSON lub XML.

Przepisywanie konfiguracji jest problemem, choć nie jest tak złe, jak się wydaje. Aktualizacja dowolnego kodu jest niemożliwa, ale ładowanie konfiguracji z pliku, modyfikowanie go i zapisywanie z powrotem jest. Zasadniczo, jeśli wykonasz skrypty w pliku konfiguracyjnym, który nie jest tylko do odczytu, po zapisaniu skończy się odpowiednia lista set name valueinstrukcji. Dobrą wskazówką, że tak się stanie, jest komentarz „nie edytuj” na początku pliku.

Jedną rzeczą do rozważenia jest to, że twoje pliki konfiguracyjne nie będą niezawodnie odczytywane przez proste narzędzia oparte na wyrażeniach regularnych, takie jak sed, ale o ile rozumiem, nie jest tak już w przypadku twoich obecnych plików JSON, więc nie ma wiele do stracenia.

Tylko upewnij się, że używasz odpowiednich technik piaskownicy podczas wykonywania plików konfiguracyjnych.

Dmitrij Grigoriew
źródło
1
„Oprogramowanie” jest niezliczona rzeczownik, więc powinno być „ niektóre dobrze znane oprogramowanie.”
jpmc26,
1

Oprócz wszystkich ważnych punktów innych dobrych odpowiedzi tutaj (wow, nawet wspomnieli o koncepcji Turinga-kompletnego), istnieje kilka solidnych praktycznych powodów, aby NIE używać pliku Python jako konfiguracji, nawet jeśli pracujesz na Pythonie- tylko projekt.

  1. Ustawienia w pliku źródłowym Pythona są technicznie częścią wykonywalnego kodu źródłowego, a nie pliku danych tylko do odczytu. Jeśli pójdziesz tą drogą, zazwyczaj tak zrobisz import config, ponieważ tego rodzaju „wygoda” była prawdopodobnie jednym z głównych powodów, dla których ludzie zaczęli od używania pliku Python jako konfiguracji. Teraz masz tendencję do zatwierdzania pliku config.py w repozytorium, w przeciwnym razie użytkownik końcowy napotka mylący błąd ImportError, gdy spróbuje uruchomić program po raz pierwszy.

  2. Zakładając, że faktycznie zatwierdzasz plik config.py w repozytorium, teraz członkowie Twojego zespołu prawdopodobnie mieliby inne ustawienia w innym środowisku. Wyobraź sobie, że pewnego dnia jakiś członek przypadkowo zatwierdza swój lokalny plik konfiguracyjny do repozytorium.

  3. Wreszcie, twój projekt może mieć hasła w pliku konfiguracyjnym. (To samo w sobie jest dyskusyjną praktyką, ale i tak się dzieje.) A jeśli plik konfiguracyjny istnieje w repozytorium, ryzykujesz, że poświadczysz publiczne repozytorium.

Teraz, używając pliku konfiguracyjnego zawierającego tylko dane, takiego jak uniwersalny format JSON, można uniknąć wszystkich 3 powyższych problemów, ponieważ możesz rozsądnie poprosić użytkownika o wymyślenie własnego pliku config.json i przesłanie go do programu.

PS: To prawda, że ​​JSON ma wiele ograniczeń. 2 z ograniczeń wymienionych przez PO można rozwiązać za pomocą kreatywności.

  • Jak wstawić komentarze do pliku JSON (poprawnie)
  • I zwykle mam symbol zastępczy, aby ominąć regułę przecinka końcowego. Lubię to:

    {
        "foo": 123,
        "bar": 456,
        "_placeholder_": "all other lines in this file can now contain trailing comma"
    }
RayLuo
źródło