2009-11-10 17 views
36

W Pythonie, właśnie przeczytałem formularz w formie pliku tekstowego i chciałbym wiedzieć, jak kodować, aby zignorować komentarze hash # na początku linia.Python: Jak zignorować linie #comment podczas czytania w pliku

myślę, że powinno być coś takiego:

for 
    if line !contain # 
     then ...process line 
    else end for loop 

Ale jestem nowy w Pythonie i nie wiem składni

Odpowiedz

46

można użyć startswith()

np

for line in open("file"): 
    li=line.strip() 
    if not li.startswith("#"): 
     print line.rstrip() 
+0

yes.thanks za zauważenie. – ghostdog74

+4

... podczas ignorowania wiodących białych znaków: 'if not line.strip(). Startswith (" # ")' – exhuma

+10

Twój kod ma 'dla linii w open (" file "):', która pozostawia uchwyt otwartego pliku. Powinieneś zachować wartość zwracaną przez 'open (" plik ")' i wywołać 'close()' bezpośrednio po zakończeniu lub użyj instrukcji 'with' (zobacz http://docs.python.org/ library/stdtypes.html # file.close). –

38

Zalecam, aby nie ignorować całej linii, gdy pojawi się # znaków; po prostu zignoruj ​​resztę linii. Można to zrobić łatwo z funkcją metody ciąg nazwie partition:

with open("filename") as f: 
    for line in f: 
     line = line.partition('#')[0] 
     line = line.rstrip() 
     # ... do something with line ... 

partition zwraca krotki: wszystko przed ciągiem partycji, ciąg partycji, a wszystko po ciąg partycji. Więc, indeksując z [0], bierzemy tylko część przed ciągiem partycji.

EDIT: Jeśli używasz wersji Pythona, który nie ma partition(), tutaj jest kod można użyć:

with open("filename") as f: 
    for line in f: 
     line = line.split('#', 1)[0] 
     line = line.rstrip() 
     # ... do something with line ... 

ten dzieli ciąg na znak „#”, a następnie trzyma wszystko przed podziałem. Argument 1 powoduje zatrzymanie metody .split() po jednym podziale; ponieważ pobieramy 0. podłańcuch (poprzez indeksowanie z [0]) otrzymalibyśmy tę samą odpowiedź bez argumentu 1, ale może to być trochę szybciej. (Uproszczone z mojego oryginalnego kodu dzięki komentarzowi z @gnr. Mój oryginalny kod był niepoprawny bez żadnego powodu, dzięki, @gnr.)

Możesz również napisać własną wersję partition(). Oto jeden nazywa part():

def part(s, s_part): 
    i0 = s.find(s_part) 
    i1 = i0 + len(s_part) 
    return (s[:i0], s[i0:i1], s[i1:]) 

@dalle zauważyć, że '#' mogą pojawić się wewnątrz łańcucha. Nie jest łatwo obsłużyć ten przypadek poprawnie, więc zignorowałem go, ale powinienem był coś powiedzieć.

Jeśli twój plik wejściowy ma dość proste reguły dla cudzysłowów, nie jest to trudne. Byłoby ciężko, gdybyś zaakceptował dowolny legalny łańcuch cytowany w Pythonie, ponieważ istnieją cytaty wielowierszowe o pojedynczych cudzysłowach, podwójnych cudzysłowach, z odwrotnym ukośnikiem wymykającym się z potrójnych cytowanych ciągów (za pomocą pojedynczych lub podwójnych cudzysłowów), oraz nawet surowe struny! Jedyny możliwy sposób, aby poprawnie obsłużyć wszystko, co byłoby skomplikowanym automatem stanów.

Ale jeśli ograniczymy się do zwykłego cytowanego ciągu, możemy obsłużyć go prostym automatem stanów. Możemy nawet zezwolić na cudzysłowy w cudzysłowie wewnątrz łańcucha.

c_backslash = '\\' 
c_dquote = '"' 
c_comment = '#' 


def chop_comment(line): 
    # a little state machine with two state varaibles: 
    in_quote = False # whether we are in a quoted string right now 
    backslash_escape = False # true if we just saw a backslash 

    for i, ch in enumerate(line): 
     if not in_quote and ch == c_comment: 
      # not in a quote, saw a '#', it's a comment. Chop it and return! 
      return line[:i] 
     elif backslash_escape: 
      # we must have just seen a backslash; reset that flag and continue 
      backslash_escape = False 
     elif in_quote and ch == c_backslash: 
      # we are in a quote and we see a backslash; escape next char 
      backslash_escape = True 
     elif ch == c_dquote: 
      in_quote = not in_quote 

    return line 

tak naprawdę nie chce, aby to skomplikowane pytanie oznakowanego „początkujący”, ale ta maszyna stan jest dość prosta, a mam nadzieję, że to będzie ciekawe.

+1

To prawda, ale pewnie też musisz się martwić o cytowane #, jeśli nie masz racji. – dalle

+1

Mała uwaga do wykonania dla OP jest taka, że ​​partycja nie jest dostępna w starszej wersji. – ghostdog74

+0

Oh heck, to prawda: 'partition()' jest tylko w Pythonie 2.5 i nowszych. Zmienię moją odpowiedź i dodaję kolejne rozwiązanie. – steveha

3

Bardziej zwarta wersja wyrażenia filtrującego może również wyglądać tak:

for line in (l for l in open(filename) if not l.startswith('#')): 
    # do something with line 

(l for ...) jest nazywany „generator wyrażenie”, który działa tu jako iterator owijania że będzie odfiltrować wszystkie niepotrzebne linie z pliku podczas iteracji ponad tym. Nie należy mylić tego samego z kwadratowymi blokadami [l for ... ], co jest "rozumieniem listy", które najpierw odczyta wszystkie linie z pliku do pamięci i dopiero wtedy rozpocznie się iterowanie.

Czasami chcesz mieć mniej jedną Liney i bardziej czytelne:

lines = open(filename) 
lines = (l for l in lines if ...) 
# more filters and mappings you might want 
for line in lines: 
    # do something with line 

Wszystkie filtry będą wykonywane na bieżąco w jednej iteracji.

5

Jest to najkrótsza forma:

for line in open(filename): 
    if line.startswith('#'): 
    continue 
    # PROCESS LINE HERE 

Sposób startswith() na sznurku zwraca True jeśli ciąg to nazwać na rozruchów napisu została przekazana w

Chociaż jest to w porządku w. niektóre okoliczności, takie jak skrypty powłoki, ma dwa problemy. Po pierwsze, nie określa sposobu otwierania pliku. Domyślnym trybem otwierania pliku jest 'r', co oznacza "przeczytaj plik w trybie binarnym". Ponieważ oczekujesz pliku tekstowego, lepiej go otworzyć pod numerem 'rt'. Chociaż rozróżnienie to nie ma znaczenia w systemach operacyjnych podobnych do UNIX, jest ważne w systemie Windows (i na komputerach Mac z pre-OS X).

Drugi problem to uchwyt pliku otwartego. Funkcja open() zwraca obiekt pliku i dobrą praktyką jest zamykanie plików, gdy skończysz. Aby to zrobić, wywołaj metodę close() na obiekcie. Teraz Python będzie prawdopodobnie prawdopodobnie zrobić to dla ciebie, ostatecznie; w obiektach Pythona są liczone referencje, a gdy licznik odwołań do obiektu osiągnie zero, zostaje zwolniony, a w pewnym momencie po zwolnieniu obiektu Python wywoła jego destruktor (specjalna metoda o nazwie __del__). Zauważ, że powiedziałem: prawdopodobnie: Python ma zły zwyczaj nie wywoływania destruktora na obiektach, których liczebność spada do zera krótko przed zakończeniem programu. Chyba się spieszy!

W przypadku krótkotrwałych programów, takich jak skrypty powłoki, a szczególnie w przypadku obiektów plików, nie ma to znaczenia. Twój system operacyjny automatycznie oczyści wszystkie uchwyty plików pozostawione otwarte po zakończeniu programu. Ale jeśli otworzyłeś plik, przeczytaj zawartość, a następnie rozpocznij długie obliczenia bez jawnego zamykania uchwytu pliku, Python prawdopodobnie pozostawi uchwyt pliku otwarty podczas obliczania. I to jest zła praktyka.

Ta wersja będzie działać w dowolnej wersji 2.x Pythona, a poprawki oba problemy omówiłem powyżej:

f = open(file, 'rt') 
for line in f: 
    if line.startswith('#'): 
    continue 
    # PROCESS LINE HERE 
f.close() 

Jest to najlepszy ogólny formularz dla starszych wersji Pythona.

Zgodnie z sugestią Steveha, użycie wyrażenia "with" jest obecnie uważane za najlepszą praktykę. Jeśli używasz 2.6 lub nowszy, należy go zapisać w następujący sposób:

with open(filename, 'rt') as f: 
    for line in f: 
    if line.startswith('#'): 
     continue 
    # PROCESS LINE HERE 

Instrukcja "with" wyczyści uchwyt pliku.

W swoim pytaniu powiedziałeś "linie zaczynające się od #", więc to właśnie ci pokazałem. Jeśli chcesz odfiltrować linie zaczynające się od opcjonalnych białych znaków i , a następnie, a '#', powinieneś usunąć białe spacje, zanim zaczniesz szukać "#". W takim przypadku należy zmienić:

if line.startswith('#'): 

do tego:

if line.lstrip().startswith('#'): 

w Pythonie ciągi są niezmienne, więc to nie zmienia wartości line. Metoda lstrip() zwraca kopię ciągu z usuniętymi wszystkimi jego wiodącymi białymi znakami.

+0

"Python ma zły zwyczaj nie wywoływania destruktora na obiektach, których liczebność spada do zera krótko przed zakończeniem programu." Czy masz dowód na to roszczenie? – gotgenes

+0

"Nie można zagwarantować, że metody __del __() wywoływane są dla obiektów, które nadal istnieją po wyjściu interpretera." Ostatnie zdanie akapitu o __del __(): http://docs.python.org/reference/datamodel.html#object.__del__ To jest dokumentacja dla 2.6; to samo dotyczy 3.1. Zgaduję, że to, co napisałem, nie było całkowicie dokładne. Dokładne fakty są jednak istotne z punktu widzenia, który robiłem. Nie jestem pewien, czy warto edytować poprawną odpowiedź. –

-1

I mają tendencję do używania

for line in lines: 
    if '#' not in line: 
     #do something 

to zignoruje całą linię, choć odpowiedź, która zawiera rpartition ma moje upvote ponieważ może zawierać żadnych informacji z przed #

5

ja niedawno znaleziono że funkcja generatora świetnie sobie z tym radzi. Użyłem podobne funkcje, aby pominąć linie komentarza, puste wiersze itp

zdefiniować moja funkcja jako

def skip_comments(file): 
    for line in file: 
     if not line.strip().startswith('#'): 
      yield line 

ten sposób, mogę tylko zrobić

f = open('testfile') 
for line in skip_comments(f): 
    print line 

Jest wielokrotnego użytku w poprzek cały mój kod i mogę dodać dowolną dodatkową obsługę/logowanie/etc. tego potrzebuję.

6

Idę o tak późno, ale problem z obsługą stylu powłoki (lub stylu Pythona) # komentarze są bardzo powszechne.

Używam kodu prawie za każdym razem, gdy czytam plik tekstowy.
Problem polega na tym, że nie obsługuje prawidłowo cytowanych lub unikanych komentarzy. Działa to jednak w prostych przypadkach i jest łatwe.

for line in whatever: 
    line = line.split('#',1)[0].strip() 
    if not line: 
     continue 
    # process line 

Bardziej wytrzymałe rozwiązaniem jest użycie shlex:

import shlex 
for line in instream: 
    lex = shlex.shlex(line) 
    lex.whitespace = '' # if you want to strip newlines, use '\n' 
    line = ''.join(list(lex)) 
    if not line: 
     continue 
    # process decommented line 

Ten shlex podejście obsługuje nie tylko cytaty i ucieka prawidłowo, to dodaje wiele chłodnym funkcjonalności (jak zdolność mieć źródło pliki inne pliki, jeśli chcesz). Nie testowałem go pod kątem szybkości na dużych plikach, ale jest wystarczająco mały.

Wspólna sprawa kiedy również podział każdą linię wejścia do pól (na spacji) jest jeszcze prostsza:

import shlex 
for line in instream: 
    fields = shlex.split(line, comments=True) 
    if not fields: 
     continue 
    # process list of fields 
+0

To zasługuje na więcej upvotes! Nawet rozwiązanie bez 'shlex' jest bardziej kompleksowe niż zaakceptowana odpowiedź (co jest dobre dla prostych przypadków użycia, ale kłopot polega na tym, że jeśli powiesz użytkownikom" możesz wstawiać tam komentarze, ale tylko wtedy, gdy rozpoczynają linię ", ty" Będę pierwszym, który zapomni to ograniczenie). – dlukes

2

wiem, że jest to stary wątek, ale jest to funkcja, że ​​generator używać do własnych celów.Usuwa komentarze bez względu na to, gdzie w linii pojawiają się linie , a także puste linie wiodące/końcowe i puste linie. Poniższy tekst źródło:

# Comment line 1 
# Comment line 2 

# host01 # This host commented out. 
host02 # This host not commented out. 
host03 
    host04 # Oops! Included leading whitespace in error! 

będzie wydajność:

host02 
host03 
host04 

Tutaj udokumentowane kod, który obejmuje prezentację:

def strip_comments(item, *, token='#'): 
    """Generator. Strips comments and whitespace from input lines. 

    This generator strips comments, leading/trailing whitespace, and 
    blank lines from its input. 

    Arguments: 
     item (obj): Object to strip comments from. 
     token (str, optional): Comment delimiter. Defaults to ``#``. 

    Yields: 
     str: Next non-blank line from ``item`` with comments and 
      leading/trailing whitespace removed. 

    """ 

    for line in item: 
     s = line.split(token, 1)[0].strip() 
     if s != '': 
      yield s 


if __name__ == '__main__': 
    HOSTS = ['# Comment line 1', 
      '# Comment line 2', 
      '', 
      '# host01 # This host commented out.', 
      'host02 # This host not commented out.', 
      'host03', 
      ' host04 # Oops! Included leading whitespace in error!',] 

    hosts = strip_comments(HOSTS) 
    for host in hosts: 
     print('\'%s\'' % host) 

normalnym przypadku używania będzie się rozebrać na komentarze plik (np. plik hosts, jak w powyższym przykładzie). Jeśli jest to przypadek, to koniec ogona powyższego kodu będzie zmodyfikowany:

if __name__ == '__main__': 
    with open('hosts.txt', 'r') as f: 
     hosts = strip_comments(f) 

    for host in hosts: 
     print('\'%s\'' % host) 
1

Korzystanie regex re.compile("^(?:\s+)*#|(?:\s+)") pominąć nowe linie i komentarze.

Powiązane problemy