python --- kivételkezelés. IndexError és egyebek

Sziasztok.

Mark Summerfield Python3 c. remekműve előtt görnyedek és csak bámulok, hogy ezt a nyelvet miért nem a ZXSpektrumra találták ki régebben. Ezen kellett volna szocializálódnom.

Ha egy vagy több vezérlőszerkezetben semmiféle errorral nem akarom elszállítani a scriptemet, miket tehetek?

Jelenleg van egy while: -szerkezetem, amely indexhibával már nem száll el, mert beleraktam egy sort:


except IndexError:
 print('indexhiba, tovabb...')

Amint ezt beraktam a kódba, azóta ha egy sorban nem találja meg monjuk a 6. oszlopot, nem száll el, nem ír ki semmi hülyeséget csak a hibaüzenetet és veszi a következő sort.

-------
A kérdésem rövid.

Van még néhány error-lehetőség, mellyel még nem szállt el a python-script, de talán elszállhat.

EOFError
ValueError
KeyError
OSError
IOError

Ezek között talán a legutolsó érdekelhet még, ha esetleg már nem tud hova írni az adathordozóra a script.

Tehát ha abszolút paranoiásan nézem a dolgokat, hányféle errorkezelést érdemes beépíteni egy mezei scriptbe, amit nem akarok leállítani semmiképp se, hiszen folyamatosan kapja a feldolgozandó sorokat?

Elég az indexhiba és az IO-hiba?

Hozzászólások

Ezek szerint: https://docs.python.org/3.5/library/exceptions.html

Van egy

BaseException

osztály, ami minden beépített exception őse. Lásd.:
(a teljes fa, az öröklődésről, a doksi végén van)


try:
    raise IndexError()
except BaseException:
    print("elkaptam")

Ettől függetlenül ez nem ajánlott. Két oldalról megközelítve:
- az a kód, ami a tömbök, listák végét így ellenőrzi, igencsak pocsékoló. Az exception kezelés alapból egy költséges művelet. Továbbá, ha én leülnék egy ilyen kód elé, ami a tömbök végét így ellenőrzi (elkapja a túlindexelésről szóló exceptiont), elsőre valószínűleg vakargatnám a fejemet, hogy mire gondolt a költő. Az Exceptionjeid első felét (

EOFError

,

ValueError

,

KeyError

) nem illik így kezelni - ha számítassz a fájl végére, akkor ellenőrizd le. Ha nem számítassz rá, hanem tényleg hiba, akkor hagyd elszállni a programot. Sokkal jobb, ha elhal egyből, mintha napokkal később kiugrik egy bug - s aztán gondolkozhatsz, hogy melyik lenyelt exception miatt is.

- Tényleg azt akarod, hogy IO error után menjen tovább a program? Az exceptionök másik része meg olyan (

OSError

,

IOError

), amivel sokmindent nem tudsz kezdeni. Ha borul az OS, elfogyott a hely, nincs jogod oda írni, azt megintcsak nem szokás lenyelni, mert te nem tudsz vele sokmindent kezdeni.
--
blogom

Az utolsó bekezdésed falhozvágott :)
Nem akarom hogy tovább fusson..

Amúgy sorhiba csak ritkán jelentkezik úgy, hogy nincs index.

Példa:
$GPRMC,135938.000,A,4729.7200,N,01902.3363,E,0.00,43.98,051215,,,A*5D
$GPRMC,135943.000,A,4729.7200,N,01902.3363,E,0.00,43.98,051215,,,A*51
$GNRMC,135948.000,A,4729.7200,N,01902.3363,E,0.00,43.98,051215,,,A*44
$GNGLL,4729.7200,N,01902.3363,E,13$GPRMC,135953.000,A,4729.7200,N,01902.3363,E,0.00,43.98,051215,,,A*50
$GLGSV,3,2,09,83,34,288,$GPRMC,135958.000,A,4729.7200,N,01902.3363,E,0.00,43.98,051215,,,A*5B
$GLGSV,3,2,09,83,34,288,$GPRMC,140003.000,A,4729.7200,N,01902.3363,E,0.00,43.98,051215,,,A*5E
$GPRMC,140004.000,A,4729.7200,N,01902.3363,E,0.00,43.98,051215,,,A*59
$GPVTG,43.98,T,,M,0.00,N,0.00,$GPRMC,140008.000,A,4729.7200,N,01902.3363,E,0.00,43.98,051215,,,A*55
$GPRMC,140009.000,A,4729.7200,N,01902.3363,E,0.00,43.98,051215,,,A*54
$GPRMC,140024.000,A,4729.7078,N,01902.3299,E,0.90,188.56,051215,,,A*6F
$GPRMC,140029.000,A,4729.7065,N,01902.3293,E,0.81,194.91,051215,,,A*62
$GPRMC,140030.000,A,4729.7063,N,01902.3291,E,0.80,194.51,051215,,,A*63
$GPRMC,140034.000,A,4729.7065,N,01902.3287,E,0.00,187.78,051215,,,A*67
$GPRMC,140039.000,A,4729.7065,N,01902.3287,E,0.00,187.78,051215,,,A*6A
$GPRMC,140044.000,V,,,,,0.17,257.05,051215,,,E*42
$GPRMC,140045.000,A,4729.7102,N,$GNRMC,140049.000,A,4729.7106,N,01902.3292,E,0.00,279.31,051215,,,A*7C
$GLGSV,3,1,09,67,67,029,,77,45,122,,68,36,$GNRMC,140054.000,A,4729.7106,N,01902.3292,E,0.00,279.31,051215,,,A*70
$GLGSV,3,1,09,67,67,029,,77,45,122,,68,36,$GNRMC,140059.000,A,4729.7106,N,01902.3292,E,0.00,279.31,051215,,,A*7D
$GNRMC,140100.000,A,4729.7106,N,01902.3292,E,0.00,279$GNRMC,140104.000,A,4729.7106,N,01902.3292,E,0.00,279.31,051215,,,A*74
$GNRMC,140125.000,A,4729.7133,N,01902.3378,E,0.00,76.52,051215,,,A*4C
$GNRMC,140130.000,A,4729.7133,N,01902.3378,E,0.00,76.52,051215,,,A*48
$GNRMC,140131.000,A,4729.7133,N,01902.3378,E,0.00,76.52,051215,,,A*49
$GNRMC,140135.000,A,4729.7133,N,01902.3378,E,0.00,76.52,051215,,,A*4D
$GNRMC,140140.000,A,4729.7133,N,01902.3378,E,0.00,76.52,051215,,,A*4F
$GNRMC,140145.000,A,4729.7133,N,01902.3378,E,0.00,76.52,051215,,,A*4A
$GNRMC,140150.000,A,4729.7133,N,01902.3378,E,0.00,76.52,051215,,,A*4E
$GNRMC,140155.000,A,4729.7133,N,01902.3378,E,0.00,76.52,051215,,,A*4B
$GNRMC,140200.000,A,4729.7133,N,01902.3378,E,0.00,76.52,051215,,,A*48

Vannak hosszabb sorok is, melyeknek eleje használható, de eddig a rövideknél elszállt az egész.

Különleges gond még az, hogy reguláris kifejezést szeretnék a sor elejére illeszkedésre, de a ^ jellel sehogy sem boldogulok, mert közben ott a sor elején a $ jel, ami meg a sor végére illeszkedik.

---
--- A gond akkor van, ha látszólag minden működik. ---
---

Nem akartalak falhoz vágni, ne haragudj :-).

A dollár jelet ki kéne tudnod escapelni. (ki kéne tudnod = szerintem a Pythonos regexpben is ki lehet)
A szövegnek meg van hossza.
Vagy a felbontod, mondjuk a vesszőknél, és tömb lesz belőle, annak is van hossza.

Ha gép elé kerülök, írok példát.

--
blogom

Eleve felbontásra szánom a sorokat, lásd itt: http://hup.hu/node/144355#comment-1935428
De előbb a sorok között egy szűrést végzek, amit bashban valahogy így fogalmaznék meg:


cat /dev/ttyAMA0 | grep "^$GNRMC" | awk 'BEGIN {FS=","}{print $5}'#etc..

---
--- A gond akkor van, ha látszólag minden működik. ---
---

valami ilyesmi:


import re

def regexp(s):
    m = re.search('^\\$GNRMC', s)
    if (m is not None):
        print("találat, " + s + " egyezik")
    else:
        print("nem találtam egyezést")

def tordeles(s):
   sArray = s.split(",")
   i = 0;
   for sPart in sArray:
       print(str(i) + ". rész: " + sPart)

s = "$GNRMC,140200.000,A,4729.7133,N,01902.3378,E,0.00,76.52,051215,,,A*48"
regexp(s);
regexp("árvíztűrő tükördúrógép")

tordeles(s);

a regexet ezek szerint escapelni kell, a speciális karaketereket \\-rel tudod.
A tördelést én így oldanám meg - s akkor nincs túlindexelés.

--
blogom

Működik!! :)))


  def regexp(x):
   m = re.search('^\\$GNRMC', x)
   if (m is not None):
    print('talalat, ' + x + ' egyezik')
   else:
    print('nem RMC')

  regexp(x);

Még csiszolok rajta, aztán holnap kint hagyom egész nap és figyelem megmurál-e.

---
--- A gond akkor van, ha látszólag minden működik. ---
---

OT

>> cat /dev/ttyAMA0 | grep "^$GNRMC" | awk 'BEGIN {FS=","}{print $5}'#etc..

Jelzem, ez shellben is hibás, ugyanis a grep paramétere "idézőjelek" között van, amin belül a shell elvégzi a változóhelyettesítést. Azaz a shell előbb kicserélni a GNRMC nevű változó értékét ( $GNRMC ), majd ezt odaragasztja a ^ mögé, és erre a sztringre fog keresni. (Ami azért izgis, mert kb 100% eséllyel nincs ilyen nevű shell vagy környezeti változód, tehát üres sztring lesz, azaz a minta "^" - ami viszont mindig igaz.) Azaz vagy 'aposztrófok' közé kéne írni azt a paramétert (így: grep '^$GNRMC'), vagy az idézőjelen belüli $-t el kell fedni a shell elől ( "^\$GNRMC" formában ).
/OT

"reguláris kifejezést szeretnék a sor elejére illeszkedésre, de a ^ jellel sehogy sem boldogulok, mert közben ott a sor elején a $ jel, ami meg a sor végére illeszkedik."

Csak hogy tisztázzuk. RE-ben a ^ csak akkor jelent sor elejét, ha az egész kifejezésben ő van az első pozícióban. Tehát ^ab esetén igen, de a^b esetén nem. Ugyanígy, a $ csak akkor jelent sor végét, ha ő áll a minta végén. (és el se takarod). Szóval ^$a: a sor elején álló "$", amit egy "a" követ.

>>> Szóval ^$a: a sor elején álló "$", amit egy "a" követ.

Igen, pont ezt próbáltam egy ideig, de nem ment.

Ez a kritikus sorom:
if 'GNRMC' in x: # GNRMC: glonass, GPRMC: gps

...és ha ezt írom:
if '^$GNRMC' in x: # GNRMC: glonass, GPRMC: gps
akkor semmi sem érkezik és nemtommér. :(

---
--- A gond akkor van, ha látszólag minden működik. ---
---

Jó ronda nagy lett a kód is:


#!/usr/bin/env python
# coding: utf8

from __future__ import print_function
import serial, io, re
import pcd8544.lcd as lcd
import time, os, sys, getopt
import textwrap


# RTC orabol a datum kiszedese
from datetime import datetime
d = datetime.now()
rtctime = "{:%Y-%m-%d_%H:%M:%S}".format(d)
#rtctime = "at {:%H:%M} UTC".format(d)
print (rtctime+" fajlba iras kezdete...")

lcd.init()
if ( lcd.LED != 1 ):
  sys.exit('LED pin should be GPIO1 (12)')
# Backlight PWM testing -- off -> 25% -> off
for i in range(0,1023,16):
  lcd.set_brightness(i)
  time.sleep(0.025)

import RPi.GPIO as GPIO  
if GPIO.RPI_REVISION == 1:  
    ports = [0, 1, 21]  
elif GPIO.RPI_REVISION == 2:  
    ports = [2, 3, 27]  
else:
    ports = ["whatever the new changes will be"]
#print "Your Pi is a Revision %s, so your ports are: %s" % (GPIO.RPI_REVISION, ports)





if not os.geteuid() == 0:
    sys.exit('Script must be run as root')

addr  = '/dev/ttyAMA0'  # serial port
baud  = 9600            # baud rate
fname = '/home/pi/gpslogger/gps-log/'+rtctime   # log file rtctime szerint
fmode = 'a'             
with serial.Serial(addr,9600) as pt, open(fname,fmode) as outf:
 spb = io.TextIOWrapper(io.BufferedRWPair(pt,pt,1),
  encoding='ascii', errors='ignore', newline='\r',line_buffering=True)

 while (1):
  x = spb.readline()  # read one line of text from serial port
  if 'GNRMC' in x:      # GNRMC: glonass, GPRMC: gps

   outf.write(x)       # write line of text to file
   outf.flush()        # make sure it actually gets written out


   print (x)
   sorhossz = 14
   balra = x.ljust(sorhossz)
   kiirni = balra
   print (textwrap.fill(kiirni, 14))
   
   cucc = kiirni.split(',')  # ez szeletel
   print ("{[0]}".format(cucc))


   try:
    longitude="L: ""{[3]}".format(cucc)+"{[4]}"
    print (longitude.format(cucc))
   except IndexError:
    print ('nincs longitude')


   try:
    latitude="G:""{[5]}".format(cucc)+"{[6]}"
    print (latitude.format(cucc))
   except IndexError:
    print ('nincs latiitude')

   idokiiras="{[1]}".format(cucc)
#   print ('at '+idokiiras[:6]+' UTC')
   print ('at '+idokiiras[:2]+':'+idokiiras[2:4]+' UTC')


   try:
    cog_korlat='0.3'   # hogy ne ugraljon nekem a szam itt
    brg="{[7]}"
    if ( brg > cog_korlat ):
     brg = "0"
     print ('BRG:'+brg+'kn')
    else:
     print ('BRG:',brg.format(cucc))
   except IndexError:
    print ('nincs ')


   try:
    cog_korlat='0.3'
    brg="{[7]}"
    cog="{[8]}"
    if ( brg > cog_korlat ):
     cog = "-"
     print ('COG:'+cog)
    else:
     print ('COG:',cog.format(cucc))
   except IndexError:
    print ('nincs ')


   try:
    datum="{[9]}"
    print (datum.format(cucc))
   except IndexError:
    print ('nincs ')

   try:
    v4="{[10]}"
    print (v4.format(cucc))
   except IndexError:
    print ('nincs ')


   try:
    v5="{[11]}"
    print (v5.format(cucc))
   except IndexError:
    print ('nincs ')


   print (kiirni)
   lcd.text(kiirni)
   time.sleep(5)
#   lcd.cls()
   #print ("1-----------",sorhossz)


---
--- A gond akkor van, ha látszólag minden működik. ---
---

Ez így gányolás. Pontosan azért van struktúrált kivételkezelés, hogy ne kelljen ilyen c-szerű kódot írni; hogy ne kelljen ennyiszer ellenőrizni, hogy történt-e hiba.

Legyen a while(1) ciklusban egyetlen try/except (pl.: except Exception as ex). Ha a try lefut (nincs exception), akkor a try blokk végén, a konverziók elvégzése után kiírhatod az LCD-re az értéket. Exception esetén az except blokkban pedig eldöntheted, hogy kiírsz-e hibaüzenetet az LCD-re, vagy "lenyeled" a hibát.

szerk.: pontosítás.

Ezen vagyok most, mivel gyakorlatilag a kísérletezések véére értem, minden működik. Szóval most jönnek a try: while: except: megy egyéb újdonságok.
Na meg a parancssori paraméterek megtervezése, mert az is szebb, mint amiket eddig műveltem más nyelvben

---
--- A gond akkor van, ha látszólag minden működik. ---
---

"és csak bámulok, hogy ezt a nyelvet miért nem a ZXSpektrumra találták ki régebben. Ezen kellett volna szocializálódnom."

Egy hátránya azért van: futási sebesség. Ez ZX Spektrumon erőteljesen kijött volna. Miért?

ZX Spectrum: 3,54 MHz 8 bites Zilog Z80 (de jobb tempüt adott, mint az akkor menő 0,98 MHz-es C64 proci!)
Raspberry2: 900 MHz 32 bites ARM Cortex A7 proci, 4 maggal
Intel Core i3: ...

Mértem egy alap tesztet minap:

#!/usr/bin/python3

k=0;
for i in range(1000):
    for j in range(1000*1000):
        k+=i+1

print(k)

Hát, a Python3 értelmező néhány százszor (!!) lassabban futtatta le, mint az ezzel megegyező C-ben írt programot.

64 bites architektúrán futtattam és shellből a "time" paranccsal mértem. Mérd meg légyszíves te is az időket, és kérlek írd meg.
Én is keresem, hogy netán tévedtem-e? Ezt is ellenőrizd légyszíves.

#include <stdio.h>

int main() {
   int i, j;
   unsigned long long k=0;
   for (i=0; i<1000; i++) {
	for (j=0; j<1000*1000; j++) {
	    k+=i+1;
	    asm volatile ("nop");  // "gcc -O2"-nél nélküle a ciklus kiszámításra kerül, és le se fut.
	}
   }
   printf("%Lu\n", k);
   return 0;
}

Mielőtt tesztelnék: első blikkre azért lehet ekkora különbség, mert a Pythonban az int típus immutable (minden változtatásnál új példány jön létre). Ráadásul a range generátor függvényt használ, ami lehet, hogy nem a leggyorsabb (nem ágyaznám egymásba a két ciklust). A Python inkább a rugalmasságáról, mintsem a sebességéről híres. Van olyan szkriptnyelv, ami viszonylag gyors is: a lua, bár a szintaxisa eléggé érdekes.

Átírhatod optimálisabbra is a Python kódot. Tényleg néhány százszor lassabb a mellékelt C-ben írt lefordítottjánál, amit kivárni ZX spektrumon "örökkévalóság" lett volna.

LUA-ban is írtam programot már. Egyébként ott is csak a luajit-tel gyors ez, a sima lua környezet szintén elvérzett, bár ez utóbbi is 3-szor gyorsabban hajtotta ezt a tesztemet végre a Python3 környezethez képest. A luajit viszont 20-szoros tempóval repesztett a sima lua értelmezőhöz képest.

Csak egy dolgogban tudtam gyorsítani a Python szkriptet: egy függvénybe tettem a kódot. Így a változók lokálisak lettek, nem pedig a globals-ban kaptak helyet. Sok hozzáférés esetén ez számít. A map függvény használata nem gyorsított ezen konkrét esetben.

A tesztekhez egy 64-bites Windows 8-at futtató gépet használtam, 3.4.1-es, 64-bites Python interpreterrel, és 32-bites mingw GCC-vel.

Futási idők:
* C kód: ~2.1 mp
* eredeti Python kód: 213 mp (kb. 100x lassabb, mint a C kód)
* optimalizált Python kód: 161,5 mp (kb. 70x lassabb, mint a C kód)

Mellékszál:
A C kód nem ugyanazt a kimenetet adta, mint a Python szkript. Először azt hittem, hogy túlcsordult a 64-bites long, és a Python ezért át kellett álljon tetszőleges hosszúságú egész aritmetikára, és ez lassít. Ezért megnéztem, hogy a végeredmény ábrázolásához hány bitre van szükség:

import math
math.log2(500500000000)
38.864579112822256

Mivel ez kevesebb, mint az unsigned long long 64 bitje, ezért csak a formázási sztringgel van a gond, amit lecseréltem "%lu"-ra, így már a megfelelő értéket írta ki.

Módosított változat:

#!/usr/bin/python3
def szamol():
    k=0
    for i in range(1000):
        for j in range(1000 * 1000):
            k += i + 1
    print(k)

szamol()

Tanultam ismét (lokális névtér). Köszi.
Nálam megfeleződött az ideje az általad módosítottal.

Ez most 91 másodperc. A "gcc -O1" optimalizációval a C-ben írt 0,43 másodperc, az asm volatile "nop") miatt -O2 esetén is alig tér el ettől.
A -O0 esetben (optimalizáció tiltásával) pedig 2,58 másodperc.

Proci: i5-3337U CPU @ 1.80GHz laptop.
Python 3.4.3
gcc 5.2.1

Kiegészítés: most tudtam csak lefordítani O2-vel, meg ASM betéttel. A mingw nem fogadta el az asm-et, ezt kellett __asm-re cseréljem. Így ~0,369 mp alatt fut, ami ~18%-a az eddigi futási időnek.

Proci: Xeon E3-v1230 V2 @ 3.3 GHz.

Pontosítás: a format string nem %lu, hanem %llu kell, hogy legyen.

Ha jol ertem, a kivetelkezeles a sorok vegenek hianya miatt kell.

Nem vagyok a kivetelekezeles ellen, de itt nem lenne szebb megoldas, ha az NMEA mondatok checksum-jat nezned? Elvegre ezert van. Kiszamolni is eleg egyszeru es szepen jelzi a nalad jelentkezo hibat. Amelyik sornak nem jo a checksum-ja, azt egyszeruen eldobod. Igy elo sem fordul a kivetel, amelyet kezelni probalsz.

Raadasul jobb megoldas is, mert ahogy lattam, hogy hogyan volt problemas egy-egy sor nalad, meg az is elofordulhat, hogy a kivant helyen levo adat megvan ugyan, csak hulyeseg - szerintem erdemes lenne atgondolnod a checksum hasznalatat.

/sza2

Ne, kerlek, hogy ezt a viselkedest addig felejtsd el, amig meg nem csontosodik. Ha exception tortenik, az azert van, mert nem figyeltel oda valamire, amire oda kellett volna figyelni. Sose nyelj be exceptiont, csak es kizarolag akkor, ha mar semmi mas modon nem tudod ezt megoldani. Ez leginkabb akkor szokott tortenni, amikor nem nalad van a bug, hanem mondjuk a frameworkben, es workaroundolsz. Minden mas esetben tessek elengedni az exceptiont, tudja meg orszag-vilag, hogy itt nagy baj van.

Ami az EOFError, ValueError, KeyError uzeneteket illeti, azokat el is lehet kerulni, a kollegak fentebb emlitettek, a rendszer szintu exception pedig azert van, hogy ertesulj arrol, hogy a scritpet futtato rendszerben gixer van.

--
Blog | @hron84
Üzemeltető macik