Shanraq.org Shanraq.org
Қателер мен ерекше жағдайлар: файл жоқ па, әлде ішінде қоқыс па
IT

Python: деректен өз есебіңізге дейін 10-сабақ (барлығы 56)

Қателер мен ерекше жағдайлар: файл жоқ па, әлде ішінде қоқыс па

Python курсының оныншы сабағы. Басқа жүйеден экспортталған деректің бес жолынан үшеуі ғана талданды: үтір — нүкте, ал «дерек жоқ» пен бос жол сан емес. `try/except/else/finally`, бір жолмен жасалатын өз қате атауы және басты білік — traceback-ті соңғы жолынан бастап, төменнен жоғары оқу.

Не үшін керек

Өткен сабақта assert есеп туралы өз болжамдарымызды тексерді. Бүгін басқа нәрсе басталады: біреу жіберген дерек.

Сіздің кодыңыз бітіп, бөтен файл басталған жерде қате нашар бағдарламашының белгісі болуын қояды да, кәдімгі оқиғаға айналады. Файл жоқ. Файлда санның орнына сөз. Диск толы. Желі ортасында үзілді.

Әр осындай жолда құлайтын бағдарлама пайдасыз. Бәрін үнсіз аттап өтетін бағдарлама қауіпті. Екеуінің арасындағы айырма — қатені атай білуде және онымен не істеу керегін шешуде.

Бірден тұтас

qate.py файлы. Іске қосу: ортадан python qate.py.

Міндеттісі — бірінші блок: try, қатенің аты жазылған except, else және жинақтауыш. Файл мен finally туралы екінші блок төменде талданады; файлдармен шындап келесі сабақта айналысамыз.

"""10-сабақ: қате — бағдарламаның соңы емес, қабылдау керек шешім.

Бөтен файлдан дерек қалай болса, солай келеді: нүктенің орнына үтірмен, бос
жолмен, санның орнына сөзбен. Бағдарлама әрқайсысымен не істеу керегін білуге тиіс.
"""


class BadRow(ValueError):
    """Талдауға келмеген жол. Өз қатеңізге өз атауы.

    Бұл — класс, әрі бүгін ол тек атау ретінде керек: class сөзінің артында не
    тұрғаны жиырма бірінші сабақта талданған.
    """


# Бөтен кестеден түскен деректер экспорты осылай көрінеді: бес жол, үшеуі жарамды.
rows = ["8.0", "15,0", "дерек жоқ", "", "11.4"]


def to_number(text):
    """Мәтінді санға: үтір нүкте деп саналады, қалғаны — BadRow."""
    clean = text.strip().replace(",", ".")
    try:
        return float(clean)
    except ValueError:
        raise BadRow(f"сан емес: {text!r}")


print("== жолдарды талдаймыз")
total = 0.0
count = 0
for text in rows:
    try:
        value = to_number(text)
    except BadRow as error:
        print(f"аттап өтілді — {error}")
    else:
        total += value
        count += 1
        print(f"алынды: {value}")
print(f"{len(rows)} жолдың {count} талданды, сомасы {total:.1f}")

print()
print("== файлдың жоғы мен оқылмауы — бөлек қателер")
try:
    with open("dannye.csv", encoding="utf-8") as source:
        print(source.readline())
except FileNotFoundError:
    print("файл жоқ: деректі желіден аламыз")
except OSError as error:
    print("файл бар, бірақ оқылмайды:", error)
finally:
    print("finally кез келген жағдайда орындалады")

Шығатыны:

== жолдарды талдаймыз
алынды: 8.0
алынды: 15.0
аттап өтілді — сан емес: 'дерек жоқ'
аттап өтілді — сан емес: ''
алынды: 11.4
5 жолдың 3 талданды, сомасы 34.4

== файлдың жоғы мен оқылмауы — бөлек қателер
файл жоқ: деректі желіден аламыз
finally кез келген жағдайда орындалады

Талдау

Қатенің аты бар, әрі аттар бір әулетке тізілген

ValueError, FileNotFoundError, KeyError, ZeroDivisionError — бұлар да типтер, int пен str сияқты. Олар шашылып жатқан жоқ, ағаш болып тұр: FileNotFoundErrorOSError-дың жеке жағдайы, ал OSErrorException-ның жеке жағдайы.

Осыдан пайдалы қорытынды шығады: OSError-ды ұстасаңыз, «файл жоқты» да, «рұқсат жоқты» да, «диск ажырағанды» да ұстайсыз. Ал Exception-ды ұстасаңыз — бәрін, соның ішінде айнымалы атындағы өз қатеңізді де.

Елестетіп көріңіз. Қабылдау бөлмесіндегі дәрігер. «Ішім ауырады» мен «қолым сынды» дегенді әр адам емдейді, әрі алдымен не болғанын атайды. «Жаман» деген диагноз емделмейді.

Атауы жоқ except — дерлік әрқашан қате

try:
    ...
except:            ← былай жазбайды
    pass

Жалаң except бәрін ұстайды: деректегі қатені де, қате терілген аттан шыққан NameError-ды да, бағдарламаны тоқтатқыңыз келгендегі Ctrl-C-ні де. Қатемен бірге ол себепті де жұтады, ал ішіндегі pass «не болғаны маған бәрібір» дегенді білдіреді.

Ереже қарапайым: өзіңіз өңдей алатынды ғана ұстаңыз. Қалғаны құласын — түсінікті хабармен құлау есептегі үнсіз бос сөзден шыншыл.

try-дағы else: қорғауда тек сынуы мүмкін нәрсе тұрады

try:
    value = to_number(text)
except BadRow as error:
    print(f"аттап өтілді — {error}")
else:
    total += value
    count += 1

try ішінде бір жол тұр — шықпай қалуы мүмкін жол. Ал сәттіліктен кейін істелетіннің бәрі else-ке кетті.

Бұл әдемілік үшін емес. try ішінде дәл сол шықпай қалуы мүмкін жол тұрады: сонда бәленің қай жерден күтілгені кодтан көрініп тұрады. total += value дегенді ішіне қойсақ, ешкім қорғамаған жер қорғалған болып көрінер еді: қосудың өз қате түрі бар, TypeError, ал except BadRow оны ұстамайды.

Кеңірек ұстағанда одан жаман. except ValueError деп жазыңыз — азғырық бар, BadRow содан шыққан — да, try ішіне екінші тәуекелді жолды, айталық year = int(year_text) дегенді қойыңыз. Нашар жыл сол ValueError-мен келеді, «сан емес» деп жарияланады да, үнсіз аттап өтілгендерге кетеді, ал сіз оны кодтан емес, деректен іздейсіз. else шекараны ұстайды: try ішінде — тәуекелді, else ішінде — тәуекел ақталмағанда істелетін.

except-тердің реті маңызды

except FileNotFoundError:
    ...
except OSError as error:
    ...

Python тармақтарды жоғарыдан төмен қарайды да, алғаш келгенін алады. FileNotFoundErrorOSError-дың жеке жағдайы, сондықтан жекесі жалпысынан жоғары тұрады. Орнын ауыстырыңыз — бірінші тармақ бәрін алады, екіншісі ешқашан орындалмайды.

finally әрқашан орындалады

finally сәттіліктен кейін де, қатеден кейін де, тіпті try-дан return-мен шығып кеткенде де істейді. Онда кез келген жағдайда жабылуға тиіс нәрсені жабады.

Біздің мысалда файлды жабудың қажеті жоқ — оны with істейді: ішінде не болса да, файлды өзі жабады. Файлдарды дерлік әрқашан with арқылы ашатыны да сондықтан, ал finally with істей алмайтын нәрсеге қалады.

raise және өзіңіздің қате атауыңыз

class BadRow(ValueError):
    """Талдауға келмеген жол."""

Бір жол — әрі бізде өз қате атауымыз бар. ValueError-дан мұралау «бұл — дұрыс емес мәннің жеке жағдайы» дегенді білдіреді: ValueError-ды ұстайтын код біздікін де ұстайды.

Дайын ValueError тұрғанда ол не үшін керек? Деректер экспортын талдағанда ValueError кез келген жерден ұшып келуі мүмкін — float-тан да, int-тен де, бөтен кітапханадан да. Ал BadRow: дәл біздің жол талданбады дейді, әрі оны дәл ұстауға болады.

raise қатені көтереді. Оның қай жерде тұрғанына назар аударыңыз: except ішінде. Python мұны есте сақтайды да, құлағанда екеуін де көрсетеді:

ValueError: could not convert string to float: 'дерек жоқ'

During handling of the above exception, another exception occurred:
...
BadRow: сан емес: 'дерек жоқ'

Бұл ыңғайлы: не болғаны да, оны қалай атағанымыз да көрінеді. Екінші жартысы кедергі болса, raise BadRow(...) from None деп жазады.

Traceback-ті қалай оқу керек

Нағыз қате былай көрінеді (файл жолдары қысқартылған):

Traceback (most recent call last):
  File "tb.py", line 12, in <module>
    print(average(["8.0", "дерек жоқ"]))
          ~~~~~~~^^^^^^^^^^^^^^^^^^^^^^
  File "tb.py", line 8, in average
    total += to_number(text)
             ~~~~~~~~~^^^^^^
  File "tb.py", line 2, in to_number
    return float(text)
ValueError: could not convert string to float: 'дерек жоқ'

Оны төменнен жоғары оқиды, әрі бүгінгі сабақтың басты білігі осы.

Соңғы жол — не болғаны: қатенің түрі мен хабары. Мұнда float санға айналдыра алмайтын жол алды да, оны тікелей атады.

Одан жоғарғы жолдар — бағдарламаның сол жерге келген жолы: ең төменгі кадр — сынған орын, ең жоғарғысы — бәрі басталған жер. Жол астындағы ~~~^^^ көрсеткіштері бір жолда бірнешеу болса, қай өрнек кінәлі екенін көрсетеді.

Осыдан сағаттарды үнемдейтін әдет шығады: ұзындығынан қорықпаңыз. Соңғы жолды қараңыз, содан кейін өз файлыңыз тұрған ең жақын кадрды — қате дерлік әрқашан сонда, бөтен кітапхананың тереңінде емес.

Сабақ картасы

Сабақ картасы: қатенің аты, тармақ және traceback

Өз сөзіңізбен айтыңыз

Қарамай, дауыстап немесе қағазға жауап беріңіз. Жауаптары — сабақтың соңында.

  1. total += value неге try ішінде емес, else ішінде тұр?
  2. except OSError-ды except FileNotFoundError-дан жоғары қойсақ не болады?
  3. Traceback қай жолдан бастап оқылады, әрі ол не айтады?

Жаттығу

Тапсырмаға дейінгі үш қысқа қадам: болжау, толықтыру, жөндеу. Жауаптары — сабақтың соңында, бірақ алдымен өзіңіз жауап беріңіз.

1. Болжаңыз. Бұл бағдарлама не басып шығарады?

def to_number(text):
    return float(text.replace(",", "."))


for text in ["8,0", "дерек жоқ"]:
    try:
        print(to_number(text))
    except ValueError:
        print("сан емес:", text)

2. Бос орынды толтырыңыз. ... орнына мұнда ұстау керек қатенің атын қойыңыз.

prices = {"нан": 260}
try:
    print(prices["сүт"])
except ...:
    print("мұндай тауар жоқ")

3. Жөндеңіз. Сан екеу болса да, бағдарлама 8.0 басып шығарады. Екіншісі қайда кеткенін тауып, аттап өтілген жол туралы дауыстап айтылатындай етіңіз.

values = ["8.0", "15,0"]
total = 0.0
for text in values:
    try:
        total += float(text)
    except:
        pass
print(total)

Тапсырма

Міндетті. Берілгені:

rows = ["520", "546,5", "", "бағасы жоқ", "498.0"]

Өз қатеңіз BadRow-ды және to_number(text) функциясын жасаңыз: үтір нүкте деп саналады, қалғанының бәрі жолдың өзі көрінетін мәтінмен BadRow көтереді. Тізімді аралаңыз: жарамды жолды алынды деп, жарамсызын себебімен бірге аттап өтілді деп басып шығарыңыз. Соңында нешеуінің нешеуі талданғанын және екі таңбалы орташаны басыңыз.

Күтілетін шығу:

алынды: 520.0
алынды: 546.5
аттап өтілді — сан емес: ''
аттап өтілді — сан емес: 'бағасы жоқ'
алынды: 498.0
5 жолдың 3-і талданды, орташа 521.50

Дайын болғаны: шығу жол-жолмен сәйкес келеді; try ішінде бір жол тұр, ал жинау — else ішінде; бағдарламада атауы жоқ except жоқ.

Өз деректеріңізде. Деректер экспортынан келгендей жолдар тізімін алыңыз: сан, үтірі бар сан, бос жол, сөз. Талдауға келмеген жолда өз ерекше жағдайыңызды көтеретін to_number(text) жазыңыз, әрі жарамдыны қосып, жарамсызды себебімен басып шығаратын цикл жасаңыз. Соңында — нешеуінің нешеуі талданғаны.

Қалауыңызша.

  • Орташаны есептеуге except ZeroDivisionError тармағын қосып, оны бос тізімде тексеріңіз.
  • raise BadRow(...) from None жасап, traceback-ті бұрынғысымен салыстырыңыз.
  • Жоқ файлды try-сыз ашып, traceback-ті дауыстап оқыңыз: не болды, қай жерде, қайдан келді.

Жобада бұл қайда тұрады

Шолу бір жаман жолдан құлауын қояды. Деректер экспортын талдау аяғына дейін жүреді, жарамсыз жолдар бір-бірлеп аталады, ал есепке сан түседі: сонша талданды, сонша аттап өтілді — әрі бұл сан өзі дереккөз сапасының көрсеткіші.

Қарыздар. Аттап өтілген жолдарды әзірге экранға басып отырмыз. Олардың орны — есептің қасындағы журналда, ертең кеше не талданбағаны көрінсін деп.

Жауаптар

Жауаптарды көрсету

Сұрақтарға

  1. Өйткені try ішінде тек сынуы мүмкін нәрсе ұсталады — сонда бәленің қай жерден күтілгені көрініп тұрады. Әйтпесе келесі жолдың қатесі не қорғалған болып көрінетін жерде бағдарламаны құлатады, не — except кеңірек болса — дұрыс емес себеппен түсіндіріліп, үнсіз аттап өтілгендерге кетеді.
  2. FileNotFoundError тармағы ешқашан орындалмайды: OSError — жалпы жағдай, ал Python жоғарыдан алғаш келген тармақты алады. Жекесі әрқашан жалпысынан жоғары тұрады.
  3. Соңғысынан: онда қатенің түрі мен хабары — не болғаны. Одан жоғары — шақырулар жолы, әрі онда өз файлы бар ең жақын кадр ізделеді.

Жаттығуға

  1. Алдымен 8.0, содан кейін хабар: float("8,0".replace(",", ".")) — бұл 8.0, ал «дерек жоқ» санға айналмайды, сондықтан ValueError except-ке барады.
8.0
сан емес: дерек жоқ
  1. KeyError — жоқ кілттің қатесі. ValueError мұнда жүрмейді: мән бұзылған жоқ, ол жай ғана жоқ.
prices = {"нан": 260}
try:
    print(prices["сүт"])
except KeyError:
    print("мұндай тауар жоқ")
мұндай тауар жоқ
  1. Атауы жоқ except пен pass 15,0-ді себебімен қоса жұтып қойды: float("15,0") талданбайды, қате ұсталып, үнсіз лақтырылды. Қатені атымен атап, ол туралы дауыстап айтады:
values = ["8.0", "15,0"]
total = 0.0
for text in values:
    try:
        total += float(text)
    except ValueError:
        print("аттап өтілді:", text)
print(total)
аттап өтілді: 15,0
8.0

Дереккөздер

Мәтінде қате не теру қатесі кездессе, бізге айтыңыз

Тапсырманы тексеру

Алдымен VS Code-та шешіп, іске қосыңыз — редактор қатені сол жерде көрсетеді. Дайын шешімді осында қойыңыз. Тексеретін — модель: ол қатені атап көрсетеді, бірақ дайын жауапты бермейді.

Тексеру үшін кіру керек. Кіру

Пікірлер (0)

Әзірге пікір жоқ. Бірінші болыңыз.