本系列主要學(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í)行finally
,finally
不是必須的搪泳。 -
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)試
通過(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 != 0
是True
代碼繼續(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 级乐。