Python3(9) Python 錯(cuò)誤断国、調(diào)試和測(cè)試

本系列主要學(xué)習(xí)Python的基本使用和語(yǔ)法知識(shí)娱仔,后續(xù)可能會(huì)圍繞著AI學(xué)習(xí)展開(kāi)。
Python3 (1) Python語(yǔ)言的簡(jiǎn)介
Python3 (2) Python語(yǔ)法基礎(chǔ)
Python3 (3) Python函數(shù)
Python3 (4) Python高級(jí)特性
Python3(5) Python 函數(shù)式編程
Python3(6) Python 模塊
Python3(7) Python 面向?qū)ο缶幊?/a>
Python3(8) Python 面向?qū)ο蟾呒?jí)編程
對(duì)于編程來(lái)說(shuō)匿乃,調(diào)試與處理bug占用的時(shí)間遠(yuǎn)遠(yuǎn)高于開(kāi)發(fā)的時(shí)間。所以學(xué)會(huì)調(diào)試bug豌汇,分析bug , 解決bug 是編程的一個(gè)重要能力幢炸,想當(dāng)初自己剛學(xué)java的時(shí)候,沒(méi)人教過(guò)自己如何調(diào)試拒贱,在工作中摸索了小半年才真正的學(xué)會(huì)如何斷點(diǎn)調(diào)試阳懂,如何分析、解決bug,調(diào)試其實(shí)各種語(yǔ)言大同小異岩调,但是我覺(jué)得有必要專門來(lái)寫(xiě)巷燥,因?yàn)樗浅V匾I弦黄捎跁r(shí)間和篇幅原因号枕,遺留下一個(gè)問(wèn)題缰揪,如何設(shè)計(jì)ORM框架?現(xiàn)在我們從這里說(shuō)起葱淳。

設(shè)計(jì) ORM 框架

上一篇介紹了python 面向?qū)ο蟾呒?jí)編程钝腺,涉及到的內(nèi)容非常的燒腦,講到metaclass元類時(shí)赞厕,腦細(xì)胞已經(jīng)不太夠用了艳狐,為了少死點(diǎn)腦細(xì)胞,就把這個(gè)元類的應(yīng)用:ORM框架擱置了皿桑,我們從這里講起毫目,元類其實(shí)字面意思就是類的元老,有資格創(chuàng)建和修改類诲侮。

ORM全稱“Object Relational Mapping”镀虐,即對(duì)象-關(guān)系映射,就是把關(guān)系數(shù)據(jù)庫(kù)庫(kù)的字段映射為一個(gè)對(duì)象沟绪,也就是一個(gè)類對(duì)應(yīng)一個(gè)表刮便。這樣我們不必直接使用SQL語(yǔ)句,通過(guò)ORM框架來(lái)進(jìn)行映射绽慈,ORM框架定義的類必須是動(dòng)態(tài)的恨旱,使用者根據(jù)自己的表結(jié)構(gòu)來(lái)定義自己需要的類。

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

# 定義Field類坝疼,它負(fù)責(zé)保存數(shù)據(jù)庫(kù)表的字段名和字段類型
class Field(object):

    def __init__(self, name, column_type):
        self.name = name
        self.column_type = column_type

    def __str__(self):
        return '<%s:%s>' % (self.__class__.__name__, self.name)

# 在Field基礎(chǔ)上定義 各種類型的xxxField
class StringField(Field):

    def __init__(self, name):
        super(StringField, self).__init__(name, 'varchar(100)')

class IntegerField(Field):

    def __init__(self, name):
        super(IntegerField, self).__init__(name, 'bigint')

# 類的模板定義  ModelMetaclass
class ModelMetaclass(type):

    def __new__(cls, name, bases, attrs):
        # 排除掉對(duì)Model類的修改,Model類中有metaclass是具體數(shù)據(jù)表類的父類,不能自己實(shí)現(xiàn)只提供繼承
        if name=='Model':
            return type.__new__(cls, name, bases, attrs)
        print('Found model: %s' % name)
        # 保存Field 屬性到mappings中
        mappings = dict()
        for k, v in attrs.items():
            if isinstance(v, Field):
                print('Found mapping: %s ==> %s' % (k, v))
                mappings[k] = v
        # 刪除attrs中Field屬性窖杀,防止運(yùn)行時(shí),實(shí)例的屬性遮蓋類的同名屬性出現(xiàn)異常
        for k in mappings.keys():
            attrs.pop(k)
        # 保存屬性和列的映射關(guān)系
        attrs['__mappings__'] = mappings
        # 假設(shè)表名和類名一致
        attrs['__table__'] = name
        return type.__new__(cls, name, bases, attrs)

# 創(chuàng)建具體數(shù)據(jù)表的基類
class Model(dict, metaclass=ModelMetaclass):

    def __init__(self, **kw):
        super(Model, self).__init__(**kw)

    def __getattr__(self, key):
        try:
            return self[key]
        except KeyError:
            raise AttributeError(r"'Model' object has no attribute '%s'" % key)

    def __setattr__(self, key, value):
        self[key] = value
    # 定義一個(gè)操作數(shù)據(jù)庫(kù)的方法
    def save(self):
        fields = []
        params = []
        args = []
        for k, v in self.__mappings__.items():
            fields.append(v.name)
            params.append('?')
            args.append(getattr(self, k, None))
        # 拼接sql語(yǔ)句
        sql = 'insert into %s (%s) values (%s)' % (self.__table__, ','.join(fields), ','.join(params))
        print('SQL: %s' % sql)
        print('ARGS: %s' % str(args))

# 定義一個(gè)具體的數(shù)據(jù)表類裙士,繼承Model
class User(Model):
    # 定義類的屬性到列的映射:
    id = IntegerField('id')
    name = StringField('username')
    email = StringField('email')
    password = StringField('password')

# 創(chuàng)建一個(gè)實(shí)例:
u = User(id=12345, name='Michael', email='test@orm.org', password='my-pwd')
# 保存到數(shù)據(jù)庫(kù):
u.save()

輸出結(jié)果:

Found model: User
Found mapping: id ==> <IntegerField:id>
Found mapping: name ==> <StringField:username>
Found mapping: email ==> <StringField:email>
Found mapping: password ==> <StringField:password>
SQL: insert into User (id,username,email,password) values (?,?,?,?)
ARGS: [12345, 'Michael', 'test@orm.org', 'my-pwd']

這就是一個(gè)ORM框架的基本原理代碼入客,我們實(shí)現(xiàn)了一個(gè)插入的操作,執(zhí)行流程:用戶根據(jù)表的結(jié)構(gòu)定義了一個(gè)User類腿椎,Python解釋器在創(chuàng)建類時(shí)會(huì)查找是否存在metaclass元類桌硫,在Model類中找到ModelMetaclass元類,之后就去創(chuàng)建元類啃炸,元類會(huì)根據(jù)User類的屬性生成一個(gè)__mappings字典铆隘,在Model中根據(jù)__mappings__字典創(chuàng)建各種方法去執(zhí)行與數(shù)據(jù)庫(kù)交互。最后就在User類完全不知情的情況下生成了各種與數(shù)據(jù)庫(kù)交互的方法南用。不必去執(zhí)行各種 sql 語(yǔ)句膀钠,這就是一個(gè)典型的ORM框架原理掏湾。

錯(cuò)誤產(chǎn)生的情景

  • 程序編寫(xiě)導(dǎo)致的問(wèn)題,這個(gè)是程序員邏輯考慮不周全造成的肿嘲。
  • 用戶交互導(dǎo)致的問(wèn)題融击,沒(méi)有按設(shè)計(jì)的規(guī)則進(jìn)行交互。
  • 程序運(yùn)行時(shí)產(chǎn)生的問(wèn)題雳窟,這個(gè)異常是硬件尊浪、網(wǎng)路等外在因素產(chǎn)生的問(wèn)題。
    Python中內(nèi)置了一系列的處理錯(cuò)誤的機(jī)制封救。是程序永遠(yuǎn)處于可控的狀態(tài)拇涤。

錯(cuò)誤處理

try...except...finally...是python內(nèi)置的錯(cuò)誤處理機(jī)制。

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

try:
    print('try...')
    r = 10 / 0
    print('result:', r)
except ValueError as e:
    print('ValueError:', e)
except ZeroDivisionError as e:
    print('ZeroDivisionError:', e)
else:
    print('no error!')
finally:
    print('finally...')

輸出結(jié)果:

try...
ZeroDivisionError: division by zero
finally...
# ----r = 10 / 1 沒(méi)有發(fā)生異常-----
try...
result: 10.0
no error!
finally...

python中try...except...finally...與java中的try...catch...finally...使用規(guī)則是相同的誉结。

  • try...except包含的代碼塊如果發(fā)生異常鹅士,根據(jù)異常類型執(zhí)行對(duì)應(yīng)的except代碼,只要執(zhí)行了except語(yǔ)句惩坑,代碼塊就會(huì)被截住掉盅,不論下面是否還有異常,執(zhí)行完except語(yǔ)句旭贬,如果有finally就會(huì)執(zhí)行finallyfinally不是必須的搪泳。
  • try...except含的代碼塊沒(méi)有發(fā)生異常稀轨,如果有else會(huì)執(zhí)行else語(yǔ)句,else不是必須的岸军,接著如果有finally就會(huì)執(zhí)行finally奋刽,finally不是必須的。
  • try...except...finally...可以進(jìn)行嵌套艰赞。

錯(cuò)誤類型及繼承關(guān)系

BaseException
 +-- SystemExit
 +-- KeyboardInterrupt
 +-- GeneratorExit
 +-- Exception
      +-- StopIteration
      +-- StopAsyncIteration
      +-- ArithmeticError
      |    +-- FloatingPointError
      |    +-- OverflowError
      |    +-- ZeroDivisionError
      +-- AssertionError
      +-- AttributeError
      +-- BufferError
      +-- EOFError
      +-- ImportError
      |    +-- ModuleNotFoundError
      +-- LookupError
      |    +-- IndexError
      |    +-- KeyError
      +-- MemoryError
      +-- NameError
      |    +-- UnboundLocalError
      +-- OSError
      |    +-- BlockingIOError
      |    +-- ChildProcessError
      |    +-- ConnectionError
      |    |    +-- BrokenPipeError
      |    |    +-- ConnectionAbortedError
      |    |    +-- ConnectionRefusedError
      |    |    +-- ConnectionResetError
      |    +-- FileExistsError
      |    +-- FileNotFoundError
      |    +-- InterruptedError
      |    +-- IsADirectoryError
      |    +-- NotADirectoryError
      |    +-- PermissionError
      |    +-- ProcessLookupError
      |    +-- TimeoutError
      +-- ReferenceError
      +-- RuntimeError
      |    +-- NotImplementedError
      |    +-- RecursionError
      +-- SyntaxError
      |    +-- IndentationError
      |         +-- TabError
      +-- SystemError
      +-- TypeError
      +-- ValueError
      |    +-- UnicodeError
      |         +-- UnicodeDecodeError
      |         +-- UnicodeEncodeError
      |         +-- UnicodeTranslateError
      +-- Warning
           +-- DeprecationWarning
           +-- PendingDeprecationWarning
           +-- RuntimeWarning
           +-- SyntaxWarning
           +-- UserWarning
           +-- FutureWarning
           +-- ImportWarning
           +-- UnicodeWarning
           +-- BytesWarning
           +-- ResourceWarning

錯(cuò)誤棧

python 中錯(cuò)誤也會(huì)一層層像上拋出佣谐,并且不需要像java 等語(yǔ)言通過(guò)throw來(lái)標(biāo)識(shí),它會(huì)自動(dòng)向上拋出方妖。如果代碼層面沒(méi)有處理狭魂,最后被Python解釋器捕獲,打印一個(gè)錯(cuò)誤信息党觅,然后程序退出雌澄。

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import logging

def foo(s):
    return 10 / s

def bar(s):
    return foo(s) * 2

def main():
    try:
        bar('0')
    except Exception as e:
      #  logging.exception(e)
        print('Error:', e)
    finally:
        print('finally...')

main()

輸出結(jié)果:

Error: unsupported operand type(s) for /: 'int' and 'str'
finally...
# ----如果沒(méi)有進(jìn)行異常捕獲-----
Traceback (most recent call last):
  File "F:/python/HelloWord/def_func.py", line 19, in <module>
    main()
  File "F:/python/HelloWord/def_func.py", line 11, in main
    bar('0')
  File "F:/python/HelloWord/def_func.py", line 8, in bar
    return foo(s) * 2
  File "F:/python/HelloWord/def_func.py", line 5, in foo
    return 10 / s
TypeError: unsupported operand type(s) for /: 'int' and 'str'
#----通過(guò)logging.exception(e)打印錯(cuò)誤信息----
ERROR:root:unsupported operand type(s) for /: 'int' and 'str'
Traceback (most recent call last):
  File "F:/python/HelloWord/def_func.py", line 14, in main
    bar('0')
  File "F:/python/HelloWord/def_func.py", line 10, in bar
    return foo(s) * 2
  File "F:/python/HelloWord/def_func.py", line 7, in foo
    return 10 / s
TypeError: unsupported operand type(s) for /: 'int' and 'str'

如果我們?cè)诖a中進(jìn)行錯(cuò)誤捕獲,我們會(huì)看到錯(cuò)誤在foo()中產(chǎn)生杯瞻,但是在main()中捕獲镐牺,錯(cuò)誤進(jìn)行向上的拋出,如果沒(méi)有錯(cuò)誤捕獲魁莉,系統(tǒng)會(huì)打印出錯(cuò)誤棧信息睬涧。并退出執(zhí)行募胃。如果使用logging.exception(e)我們也會(huì)打印出全部的錯(cuò)誤棧信息。

自定義錯(cuò)誤

python中的錯(cuò)誤是一個(gè)個(gè)類的實(shí)例畦浓,創(chuàng)建并拋出的痹束,所以我們也可以自定義自己的錯(cuò)誤類型,并且手動(dòng)拋出 raise關(guān)鍵字就是手動(dòng)拋出異常宅粥。

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

class FooError(ValueError):
    pass

def foo(s):
    n = int(s)
    if n==0:
        raise FooError('invalid value: %s' % s)
    return 10 / n

foo('0')

輸出結(jié)果:

Traceback (most recent call last):
  File "F:/python/HelloWord/def_func.py", line 13, in <module>
    foo('0')
  File "F:/python/HelloWord/def_func.py", line 10, in foo
    raise FooError('invalid value: %s' % s)
__main__.FooError: invalid value: 0
  • 根據(jù)自定義的錯(cuò)誤参袱,我們可以根據(jù)具體的業(yè)務(wù)邏輯,拋出自定義的錯(cuò)誤信息秽梅。方便用戶或者調(diào)用你的代碼的人理解抹蚀。
  • raise 還可以將錯(cuò)誤繼續(xù)拋出,在except中企垦,使用raise 可以將錯(cuò)誤原樣拋出环壤,也可以改變錯(cuò)誤類型進(jìn)行拋出。

調(diào)試

print

通過(guò)print 輸出可能出錯(cuò)的變量钞诡,我們可以通過(guò)錯(cuò)誤信息和print 輸出的內(nèi)容來(lái)進(jìn)行修改bug郑现,但是print在測(cè)試完成后還需要?jiǎng)h除。

assert

斷言assert的使用荧降,就是我們?cè)谶壿媽用孀龅囊粋€(gè)if判斷相當(dāng)于raise-if-not接箫,如果assert n != 0, 'n is zero!'中的n != 0True代碼繼續(xù)執(zhí)行,如果是False會(huì)打印出AssertionError: n is zero!錯(cuò)誤朵诫。斷言可以通過(guò)python -O err.py來(lái)屏蔽辛友。

logging

logging 的使用與 android中的log的使用時(shí)大同小異的,分為幾種級(jí)別剪返,方便過(guò)濾信息废累。logging可以輸出到文件 如:logging.basicConfig(filename='example.log',level=logging.ERROR) 輸出到example.log文件中。

pdb

啟動(dòng)Python的單步調(diào)試器pdb脱盲,程序一行行執(zhí)行邑滨。可以采用pdb.set_trace()在代碼中設(shè)置斷點(diǎn)钱反。

IDE斷點(diǎn)調(diào)試

例如 PyCharm 我們可以打斷點(diǎn)進(jìn)行Debug模式下運(yùn)行掖看。

單元測(cè)試

單元測(cè)試就是將某一個(gè)模塊 通過(guò)編寫(xiě)一個(gè)測(cè)試用例在測(cè)試。

文檔測(cè)試

根據(jù)文檔中注釋的測(cè)試代碼來(lái)進(jìn)行測(cè)試面哥。

最后總結(jié)一下 Python中錯(cuò)誤乙各、調(diào)試、測(cè)試 幢竹,首先我們要清楚產(chǎn)生bug 的3種情景 程序邏輯問(wèn)題耳峦,用戶操作問(wèn)題,硬件 焕毫、網(wǎng)絡(luò)等外在因素問(wèn)題蹲坷,其次要知道Python 中有內(nèi)置的錯(cuò)誤處理機(jī)制 驶乾,錯(cuò)誤類型都繼承自BaseException,接著我們可以通過(guò)幾種方式來(lái)打印錯(cuò)誤信息循签,在IDE 上可以通過(guò)斷點(diǎn)來(lái)調(diào)試bug 级乐。

參考

https://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000/001431913726557e5e43e1ee8d54ee486bddc3f607afb75000

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市县匠,隨后出現(xiàn)的幾起案子风科,更是在濱河造成了極大的恐慌,老刑警劉巖乞旦,帶你破解...
    沈念sama閱讀 221,695評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件贼穆,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡兰粉,警方通過(guò)查閱死者的電腦和手機(jī)故痊,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,569評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)玖姑,“玉大人愕秫,你說(shuō)我怎么就攤上這事⊙媛纾” “怎么了戴甩?”我有些...
    開(kāi)封第一講書(shū)人閱讀 168,130評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)闪彼。 經(jīng)常有香客問(wèn)我甜孤,道長(zhǎng),這世上最難降的妖魔是什么备蚓? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,648評(píng)論 1 297
  • 正文 為了忘掉前任课蔬,我火速辦了婚禮囱稽,結(jié)果婚禮上郊尝,老公的妹妹穿的比我還像新娘。我一直安慰自己战惊,他們只是感情好流昏,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,655評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著吞获,像睡著了一般况凉。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上各拷,一...
    開(kāi)封第一講書(shū)人閱讀 52,268評(píng)論 1 309
  • 那天刁绒,我揣著相機(jī)與錄音,去河邊找鬼烤黍。 笑死知市,一個(gè)胖子當(dāng)著我的面吹牛傻盟,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播嫂丙,決...
    沈念sama閱讀 40,835評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼娘赴,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了跟啤?” 一聲冷哼從身側(cè)響起诽表,我...
    開(kāi)封第一講書(shū)人閱讀 39,740評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎隅肥,沒(méi)想到半個(gè)月后竿奏,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,286評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡武福,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,375評(píng)論 3 340
  • 正文 我和宋清朗相戀三年议双,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片捉片。...
    茶點(diǎn)故事閱讀 40,505評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡平痰,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出伍纫,到底是詐尸還是另有隱情宗雇,我是刑警寧澤,帶...
    沈念sama閱讀 36,185評(píng)論 5 350
  • 正文 年R本政府宣布莹规,位于F島的核電站赔蒲,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏良漱。R本人自食惡果不足惜舞虱,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,873評(píng)論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望母市。 院中可真熱鬧矾兜,春花似錦、人聲如沸患久。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,357評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)蒋失。三九已至返帕,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間篙挽,已是汗流浹背荆萤。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,466評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留铣卡,地道東北人链韭。 一個(gè)月前我還...
    沈念sama閱讀 48,921評(píng)論 3 376
  • 正文 我出身青樓邑闲,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親梧油。 傳聞我的和親對(duì)象是個(gè)殘疾皇子苫耸,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,515評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容