2015-06-23 7 views
6

Piszę plik YAML przy użyciu https://pypi.python.org/pypi/ruamel.yamlJak mogę dodać komentarza do pliku YAML w Pythonie

Kod jest tak:

import ruamel.yaml 
from ruamel.yaml.comments import CommentedSeq 

d = {} 
for m in ['B1', 'B2', 'B3']: 
    d2 = {} 
    for f in ['A1', 'A2', 'A3']: 
     d2[f] = CommentedSeq(['test', 'test2']) 
     if f != 'A2': 
      d2[f].fa.set_flow_style() 
    d[m] = d2 

    with open('test.yml', "w") as f: 
     ruamel.yaml.dump(
      d, f, Dumper=ruamel.yaml.RoundTripDumper, 
      default_flow_style=False, width=50, indent=8) 

Chcę po prostu dodać komentarz na górze jak :

# Data for Class A 

Przed danymi YAML.

Odpowiedz

3

W swoim bloku with możesz pisać cokolwiek chcesz do pliku. Ponieważ wystarczy komentarz na górze, dodać wywołanie do f.write() przed wywołaniem ruamel:

with open('test.yml', "w") as f: 
    f.write('# Data for Class A\n') 
    ruamel.yaml.dump(
     d, f, Dumper=ruamel.yaml.RoundTripDumper, 
     default_flow_style=False, width=50, indent=8) 
+0

to było łatwe :) – user3214546

1

To jest w zasadzie możliwe, ponieważ można w obie strony, takie „start-of-file” komentuje, ale nie jest dobrze obsługiwany w bieżącym pliku ruamel.yaml 0.10, a już na pewno nie w "zaczynaniu od zera" (tzn. bez zmiany istniejącego pliku). U dołu jest łatwe i stosunkowo przyjemne rozwiązanie, ale najpierw chciałbym przedstawić brzydkie obejście i krok po kroku, jak to zrobić.

brzydki:
brzydki sposób to zrobić, to po prostu dodać komentarz do pliku przed zapisem danych YAML do niego. To jest wkładka:

f.write('# Data for Class A\n') 

tuż przed ruamel.yaml.dump(...)

Krok po kroku:
wstawić komentarz do struktury danych, więc powyższe Hack nie jest to konieczne, najpierw należy upewnić się, Twoje dane d to typ CommentedMap.Jeśli porównać różnicę tej zmiennej d z jednym, który ma komentarz ładując skomentował YAML powrotem do c

import ruamel.yaml 
from ruamel.yaml.comments import Comment, CommentedSeq, CommentedMap 

d = CommentedMap()    # <<<<< most important 
for m in ['B1', 'B2', 'B3']: 
    d2 = {} 
    for f in ['A1', 'A2', 'A3']: 
     d2[f] = CommentedSeq(['test', 'test2']) 
     if f != 'A2': 
      d2[f].fa.set_flow_style() 
    d[m] = d2 

yaml_str = ruamel.yaml.dump(d, Dumper=ruamel.yaml.RoundTripDumper, 
          default_flow_style=False, width=50, indent=8) 

assert not hasattr(d, Comment.attrib) # no attribute on the CommentedMap 

comment = 'Data for Class A' 
commented_yaml_str = '# ' + comment + '\n' + yaml_str 
c = ruamel.yaml.load(commented_yaml_str, Loader=ruamel.yaml.RoundTripLoader) 
assert hasattr(c, Comment.attrib) # c has the attribute 
print c.ca       # and this is what it looks like 
print d.ca       # accessing comment attribute creates it empty 
assert hasattr(d, Comment.attrib) # now the CommentedMap has the attribute 

Drukuje:

Comment(comment=[None, [CommentToken(value=u'# Data for Class A\n')]], 
    items={}) 
Comment(comment=None, 
    items={}) 

Comment ma atrybut comment który potrzebuje do ustawienia na listę 2 elementów składającą się z komentarza EOL (zawsze tylko jednego) i listy poprzednich komentarzy liniowych (w postaci CommentTokens)

Aby utworzyć CommentToken trzeba (fake) StartMark który mówi, która kolumna zaczyna:

from ruamel.yaml.error import Mark 
start_mark = Mark(None, None, None, 0, None, None) # column 0 

Teraz można tworzyć token:

from ruamel.yaml.tokens import CommentToken 

ct = CommentToken('# ' + comment + '\n', start_mark, None) 

przypisać znak jako pierwszy element poprzedzający lista na CommentedMap:

d.ca.comment = [None, [ct]] 
print d.ca # in case you want to check 

daje:

Comment(comment=[None, [CommentToken(value='# Data for Class A\n')]], 
    items={}) 

I wreszcie:

print ruamel.yaml.dump(d, Dumper=ruamel.yaml.RoundTripDumper) 

daje:

# Data for Class A 
B1: 
     A1: [test, test2] 
     A3: [test, test2] 
     A2: 
     - test 
     - test2 
B2: 
     A1: [test, test2] 
     A3: [test, test2] 
     A2: 
     - test 
     - test2 
B3: 
     A1: [test, test2] 
     A3: [test, test2] 
     A2: 
     - test 
     - test2 

Oczywiście, że nie ma potrzeby tworzenia obiektu c, że jest tylko dla ilustracji.

Co należy użyć: aby cały ćwiczenie nieco łatwiej można po prostu zapomnieć o szczegóły i poprawki w następujący sposób do CommentedBase kiedyś:

from ruamel.yaml.comments import CommentedBase 

def set_start_comment(self, comment, indent=0): 
    """overwrites any preceding comment lines on an object 
    expects comment to be without `#` and possible have mutlple lines 
    """ 
    from ruamel.yaml.error import Mark 
    from ruamel.yaml.tokens import CommentToken 
    if self.ca.comment is None: 
     pre_comments = [] 
     self.ca.comment = [None, pre_comments] 
    else: 
     pre_comments = self.ca.comments[1] 
    if comment[-1] == '\n': 
     comment = comment[:-1] # strip final newline if there 
    start_mark = Mark(None, None, None, indent, None, None) 
    for com in comment.split('\n'): 
     pre_comments.append(CommentToken('# ' + com + '\n', start_mark, None)) 

if not hasattr(CommentedBase, 'set_start_comment'): # in case it is there 
    CommentedBase.set_start_comment = set_start_comment 

a potem po prostu zrobić:

d.set_start_comment('Data for Class A') 
+0

Wydaje się, że jest to dużo pracy, tylko po to, aby obejść funkcję 'f.write()'. Jakie są korzyści? To powiedziawszy, wygląda na to, że miałoby to sens jako wbudowana część ruamel, być może wysłanie im prośby o pociągnięcie? – dimo414

+2

@ dimo414 Tak, jeśli dotyczy to tylko górnej części pliku, to jest narzut. Ale jeśli dodasz go do danych, a następnie użyjesz danych na liście i zapiszesz, że to działa. Chciałem to rozpracować, aby włączyć do ruamel.yaml w ogólny sposób, ponieważ nie jest to konieczne, ponieważ jestem autorem. Tak będzie w kolejnej aktualizacji, którą przesyłam do PyPI (10.1). Wtedy mogę zmniejszyć odpowiedź tylko do ostatniej linii ;-) – Anthon