Discussion:
[Python] Kan man springe over midt i en løkke?
(for gammel til at besvare)
Bertel Lund Hansen
2008-10-21 21:40:19 UTC
Permalink
Hej alle

Jeg bakser med et problem med at læse headers i usenetindlæg.
Jeg bruger en metode hvor jeg splitter linjen ved kolon:

headtag,headvalue=headline.split(':',1)

Det giver problemer hvis der ikke er noget kolon. Eksempel:

Content-Type: text/plain;
format=flowed;
charset="iso-8859-1";
reply-type=original
Content-Transfer-Encoding: 8bit

Senere skal jeg aflæse disse speciallinjer, men lige nu vil jeg bare skippe dem.
Løkken starter her:

for h in range(len(headlines)):
headline=headlines[h]

Og her er testen:

if headtag=='content-type' or headtag=='content-transfer-encoding':
while headlines[h+1][0] in string.whitespace: h+=1
continue

Men det ser ikke ud til at man kan få lov at modificere løkke-variablen.
Programmet hænger (fordi jeg har indlagt en nødbremse) ved linjen

format=flowed;

som bearbejdes som en normal linje hvilket jeg netop ville undgå.
Det giver nemlig fejl ved .split(,1) der skal bruge 2 elementer.

Hvordan løser jeg det problem? Skal jeg virkelig opbygge min løkke selv?

h=0
while enmasse:
h+=1
osv.
--
Bertel
http://bertel.lundhansen.dk/ FIDUSO: http://fiduso.dk/
Torben Simonsen
2008-10-21 22:16:35 UTC
Permalink
Post by Bertel Lund Hansen
Jeg bakser med et problem med at læse headers i usenetindlæg.
Hmm, du har muligvis en god grund til selv at ville parse
teksten, men det lyder lidt som om, at du er ved at opfinde
et hjul, som allerede er opfundet.

Python har et modul ved navn "email", som burde kunne klare
alt det, du har brug for, med hensyn til at parse headers og
dertil også et hav af andre ting som f.eks. at dekode MIME-
kodede parts og den slags. Så med en simpel "import email"
burde du kunne spare dig selv for en masse arbejde.
--
-- Torben.
Klaus Alexander Seistrup
2008-10-22 06:59:43 UTC
Permalink
Post by Bertel Lund Hansen
headtag,headvalue=headline.split(':',1)
Det giver problemer hvis der ikke er noget kolon.
Som Torben skriver, er du nok bedre tjent ved at bruge email-modulet
til at læse den slags filformater. Men mere generelt kan man klare
problemet på fx denne måde:

(tag, val) = (line.split(':', 1) + ['', ''])[:2]

Mvh,
--
Klaus Alexander Seistrup
http://klaus.seistrup.dk/
Bertel Lund Hansen
2008-10-22 07:37:15 UTC
Permalink
Post by Klaus Alexander Seistrup
Som Torben skriver, er du nok bedre tjent ved at bruge email-modulet
til at læse den slags filformater.
Det vil jeg i hvert fald kikke på, men det er nyt for mig.

Jeg er ikke ved at simulere en mailklient. Jeg er ved at lave et
statistikprogram, så det er nok ikke alle de færdige løsninger
der passer til mit behov, men det ser ud til at der er en del
utilities jeg kan bruge.

Umiddelbart er jeg dog lidt i tvivl om hvordan jeg får adgang til
funktionerne. Der står f.eks. om Message-konstruktoren at den
ikke tager en parameter, og så er jeg lidt på bar bund.
Jo. Jeg kunne også teste i hver løkke om der forekommer et kolon
i strengen. Fidusen med at springe over er at det kun udføres når
der er brug for det. Så af generel interesse er jeg stadig
nysgerrig efter hvordan man kan skippe nogle linjer midt i en
løkke (der læser en linje ad gangen).

Løsninger ved både

for lin in linelist:

og

for n in range(len(linelist)):
lin=linelist[n]

har interesse.
--
Bertel
http://bertel.lundhansen.dk/ FIDUSO: http://fiduso.dk/
Klaus Alexander Seistrup
2008-10-22 08:59:56 UTC
Permalink
Post by Bertel Lund Hansen
Umiddelbart er jeg dog lidt i tvivl om hvordan jeg får adgang
til funktionerne. Der står f.eks. om Message-konstruktoren at
den ikke tager en parameter, og så er jeg lidt på bar bund.
Hvis man har en fil, msg.txt, der indeholder en normal email eller
usenet-indlæg, kan man fx

import email

msg = email.message_from_file(file('msg.txt'))

og headerne vil derefter være tilgængelige som en liste af tuples i

msg._headers

eller

msg.items()

Hvis man har indlægget som en buffer, kan man bruge .message_from_string()
i stedet.

Der kan evt. kombineres med noget StringIO, hvis det er mere bekvemt.
Post by Bertel Lund Hansen
Jeg kunne også teste i hver løkke om der forekommer et kolon
i strengen. Fidusen med at springe over er at det kun udføres når
der er brug for det. Så af generel interesse er jeg stadig
nysgerrig efter hvordan man kan skippe nogle linjer midt i en
løkke (der læser en linje ad gangen).
I det konkrete tilfælde er du vel interesseret i at tilføje nuværende
linje [i løkken] til forrige linje hvis nuværende linje starter med
et blanktegn (dvs. line[0].isspace()). Det kunne klares ved at blive
ved med at putte linjestykker på en stack så længe linjerne starter
med et blanktegn, og så poppe stacken når en linje starter med noget
andet end et blanktegn. Og når stacken er poppet, kan der splittes
ved første kolon (hvis det findes).

Mvh,
--
Klaus Alexander Seistrup
http://klaus.seistrup.dk/
Bertel Lund Hansen
2008-10-22 10:43:05 UTC
Permalink
Post by Klaus Alexander Seistrup
Hvis man har en fil, msg.txt, der indeholder en normal email eller
usenet-indlæg, kan man fx
Tak, det lyder jo nemt nok. Det vil jeg prøve.
Post by Klaus Alexander Seistrup
I det konkrete tilfælde er du vel interesseret i at tilføje nuværende
linje [i løkken] til forrige linje hvis nuværende linje starter med
et blanktegn (dvs. line[0].isspace()). Det kunne klares ved at blive
ved med at putte linjestykker på en stack så længe linjerne starter
med et blanktegn, og så poppe stacken når en linje starter med noget
andet end et blanktegn. Og når stacken er poppet, kan der splittes
ved første kolon (hvis det findes).
Du forklarer hvordan jeg samler sammen på linjerne. Det kan gøres
på mange måder. Men det løser ikke det problem jeg spurgte om.

Her er noget pseudokode:

for nr in range(len(headerlines)):
line=headerlines[nr]
do_routine_that_fails_on(whitespace)
if line.contains('Content'):
stack_following_lines_until(no_whitespace)
pop_again_as(needed)
continue
if line.contains('Date'):
do_something(else)
osv...

Efter en vellykket opsamling af sammenhørende linjer fortsætter
løkken med den første opsamlede linje som begynder med blanktegn
og derfor crasher programmet.


Men du mente måske at jeg skulle stakke hele molesjasen og så
poppe i en løkke sålænge stakken ikke er tom? Det er jo en
mulighed.


Følgende program giver samme udskrift uanset om man medtager den
tredje linje:

for n in range(20):
print n
n+=1
exit()

Jeg ved ikke om jeg synes at det er smart, men jeg kan godt se
logikken i det.
--
Bertel
http://bertel.lundhansen.dk/ FIDUSO: http://fiduso.dk/
Klaus Alexander Seistrup
2008-10-22 11:22:58 UTC
Permalink
Post by Bertel Lund Hansen
line=headerlines[nr]
do_routine_that_fails_on(whitespace)
stack_following_lines_until(no_whitespace)
pop_again_as(needed)
continue
do_something(else)
osv...
Efter en vellykket opsamling af sammenhørende linjer fortsætter
løkken med den første opsamlede linje som begynder med blanktegn
og derfor crasher programmet.
Jeg ville nok vælge at reparere headerne, altså at have én fuld header-
linje på hver linje, inden jeg begyndte at regne på resultatet. Flg.
kan måske tjene som inspiration:

#!/usr/bin/python
# -*- coding: utf-8 -*-

def fix_headers(lines):
header = []
for line in lines:
if line[0].isspace():
# Burde kun ske hvis header != []
# men lav selv sikkerhedstjek
header.append(line.lstrip())
else:
# Stacken er ikke tom
if header:
yield ' '.join(header)
header = []
header = [line]
if header:
yield ' '.join(header)

HEADERS = '''Tag-One: First line
Tag-Two: Second line
\tspans
\tseveral
\tlines (i.e., originally)
Tag-Three: Third line
Tag-Four: Also
\ta
\tmulti-line
\theader...'''

if __name__ == '__main__':
print HEADERS
print '---'
for line in fix_headers(HEADERS.split('\n')):
print line

# eof

Ved at bruge 'yield' i stedet for 'return' i fix_headers(), opnår
man at kunne regne videre på et mellemresultat inden man returnerer
det, idet 'yield' giver et iterator-objekt.

Mvh,
--
Klaus Alexander Seistrup
http://klaus.seistrup.dk/
Torben Simonsen
2008-10-22 12:51:21 UTC
Permalink
Post by Klaus Alexander Seistrup
Jeg ville nok vælge at reparere headerne, altså at have én fuld header-
linje på hver linje, inden jeg begyndte at regne på resultatet.
Netop. Det er også sådan, RFC2822 (i afsnit 2.2.3) specificerer,
at man skal gøre for at lave "unfolding" af sådanne multi-linje headers.
Det må være langt det enkleste.
--
-- Torben.
Brian Elmegaard
2008-10-22 12:30:55 UTC
Permalink
Post by Bertel Lund Hansen
Jeg bakser med et problem med at læse headers i usenetindlæg.
headtag,headvalue=headline.split(':',1)
Kan du ikke bare springe de irrelevante linjer over?

if ':' in headline:
headtag,headvalue=headline.split(':',1)
mvh.
--
Brian (remove the sport for mail)
http://www.et.web.mek.dtu.dk/Staff/be/be.html
http://www.rugbyklubben-speed.dk
Bertel Lund Hansen
2008-10-22 12:48:01 UTC
Permalink
Post by Brian Elmegaard
Kan du ikke bare springe de irrelevante linjer over?
Jeg skal bruge deres indhold.

Mit spørgsmål går på hvordan jeg kan samle dem op og samtidig få
den omgivende løkke til ikke at behandle dem.
--
Bertel
http://bertel.lundhansen.dk/ FIDUSO: http://fiduso.dk/
Brian Elmegaard
2008-10-22 14:13:33 UTC
Permalink
Post by Bertel Lund Hansen
Post by Brian Elmegaard
Kan du ikke bare springe de irrelevante linjer over?
Jeg skal bruge deres indhold.
Nåh.

Løsningerne du har fået er sikkert bedst, men ellers kan re måske
bruges hvis du ikke læser hver linje for sig?


import re
print re.split('([a-z0-9])\n',
''' Content-Type: text/plain;
format=flowed;
charset="iso-8859-1";
reply-type=original
Content-Transfer-Encoding: 8bit''')
--
Brian (remove the sport for mail)
http://www.et.web.mek.dtu.dk/Staff/be/be.html
http://www.rugbyklubben-speed.dk
Bertel Lund Hansen
2008-10-22 15:13:46 UTC
Permalink
Post by Brian Elmegaard
Løsningerne du har fået er sikkert bedst, men ellers kan re måske
bruges hvis du ikke læser hver linje for sig?
Det nemmeste til den selvbyggede parser er at løbe headerlinjerne
igennem og samle sammenhørende linjer til én sådan som Klaus
foreslår. Derefter kan man lave en parsning uden at få behov for
at springe rundt inden i en løkke.

Og det må så være det generelle princip når man arbejder med
Python: at forberede data så løkker altid kan køre 'uforstyrret'.
--
Bertel
http://bertel.lundhansen.dk/ FIDUSO: http://fiduso.dk/
Torben Simonsen
2008-10-22 17:16:44 UTC
Permalink
Post by Bertel Lund Hansen
Det nemmeste til den selvbyggede parser er at løbe headerlinjerne
igennem og samle sammenhørende linjer til én sådan som Klaus
foreslår. Derefter kan man lave en parsning uden at få behov for
at springe rundt inden i en løkke.
På den måde sikrer du også, at din parser vil fortokle enhver
RFC-compliant meddelelse korrekt. I henhold til RFC2822 er det
faktisk lovligt at lave ting i stil med:

Content-type: text/plain; charset=
iso-8859-1

Jeg siger ikke, at der findes mail- eller news-klienter, der rent
faktisk gør den slags, men du kan jo lige så godt lave din parser,
så den æder enhver meddelelse, der er RFC-korrekt.

Det er dog stadig mit bedste bud, at du vil spare dig selv for
en masse hovedbrud ved at bruge Pythons email-modul og dets
Message-klasse. Jeg kan ikke se, at du i dit statistik-program
skulle kunne løbe ind i noget, som ikke kan klares med dette
modul.
--
-- Torben.
Klaus Alexander Seistrup
2008-10-22 22:40:20 UTC
Permalink
Post by Torben Simonsen
Det er dog stadig mit bedste bud, at du vil spare dig
selv for en masse hovedbrud ved at bruge Pythons email-
modul og dets Message-klasse.
Enig, det er osse min anbefaling.

Mvh,
--
Klaus Alexander Seistrup
http://klaus.seistrup.dk/
Bertel Lund Hansen
2008-10-23 09:29:41 UTC
Permalink
Post by Torben Simonsen
Det er dog stadig mit bedste bud, at du vil spare dig selv for
en masse hovedbrud ved at bruge Pythons email-modul og dets
Message-klasse. Jeg kan ikke se, at du i dit statistik-program
skulle kunne løbe ind i noget, som ikke kan klares med dette
modul.
Jeg er skam også ved at udforske mulighederne, men jeg løb ind i
noget sært.

Hvis jeg bruger email.utils.parsedate(), virker det fint, og jeg
får de seks tidsværdier fint ud i en liste.

Hvis jeg derimod bruger email.utils.parsedate_tz(), så får jeg
ganske vist også de samme seks værdier ud, men tidszonen virker
ikke. De fleste steder står der +7200, enkelte steder -7200 og
mindst et sted noget med over 25000. Jeg har ikke fundet system i
det (de fleste har +0200 i tidszonen).

Pythonsystemet er lige opdateret til version 2.6.
--
Bertel
http://bertel.lundhansen.dk/ FIDUSO: http://fiduso.dk/
Torben Simonsen
2008-10-23 23:06:39 UTC
Permalink
Post by Bertel Lund Hansen
Hvis jeg derimod bruger email.utils.parsedate_tz(), så får jeg
ganske vist også de samme seks værdier ud, men tidszonen virker
ikke. De fleste steder står der +7200, enkelte steder -7200 og
mindst et sted noget med over 25000. Jeg har ikke fundet system i
det (de fleste har +0200 i tidszonen).
Dokumentationen siger, at parsedate_tz returnerer tidszonen
som et offset i _sekunder_ fra UTC. Så det ser da vist rigtigt
nok ud. 7200 sekunder = 2 timer.
--
-- Torben.
Bertel Lund Hansen
2008-10-24 11:36:50 UTC
Permalink
Post by Torben Simonsen
Dokumentationen siger, at parsedate_tz returnerer tidszonen
som et offset i _sekunder_ fra UTC. Så det ser da vist rigtigt
nok ud. 7200 sekunder = 2 timer.
Åh ...
--
Bertel
http://bertel.lundhansen.dk/ FIDUSO: http://fiduso.dk/
Loading...