深入理解 Python 特性(讀書(shū)筆記)

點(diǎn)擊上方“Python學(xué)習(xí)開(kāi)發(fā)”,選擇“加為星標(biāo)”
第一時(shí)間關(guān)注Python技術(shù)干貨!
1.斷言
Python 的斷言語(yǔ)句是一種調(diào)試輔助功能,不是用來(lái)處理運(yùn)行時(shí)錯(cuò)誤的機(jī)制。
assert 在條件為 False 的時(shí)候觸發(fā),后面的內(nèi)容是報(bào)錯(cuò)信息。
import?sys
assert?sys.version_info?>=?(3,?7),?"請(qǐng)?jiān)赑ython3.7及以上環(huán)境執(zhí)行"
如果這個(gè)項(xiàng)目要求最低是 Python3.7 的環(huán)境,那么如果使用 Python3.6 來(lái)運(yùn)行這個(gè)項(xiàng)目,就會(huì)出現(xiàn)這個(gè)錯(cuò)誤信息。
Traceback?(most?recent?call?last):
??File?"/Users/chennan/pythonproject/demo/nyandemo.py",?line?3,?in?<module>
????assert?sys.version_info?>?(3,?7),?"請(qǐng)?jiān)赑ython3.7以上環(huán)境執(zhí)行"
AssertionError:?請(qǐng)?jiān)赑ython3.7以上環(huán)境執(zhí)行
提前中止項(xiàng)目
2.巧妙的放置逗號(hào)
合理的格式化列表里面的元素,更容易維護(hù)
一般我們?cè)趯?xiě)列表的時(shí)候會(huì)這樣
l?=?["apple",?"banana",?"orange"]
使用下面的方式可以更加清晰的區(qū)分每一個(gè)項(xiàng)目,習(xí)慣性的在末尾加個(gè)逗號(hào),防止下次添加元素遺漏了逗號(hào),看著也更 Pythonic
l?=?[
????"apple",?
????"banana",?
????"orange",
]
3.下劃線(xiàn)、雙下劃線(xiàn)以及其他
前置單下劃線(xiàn) : _var
1.是一種約定,前置單下劃線(xiàn)的方法和變量只在內(nèi)部使用
2.在使用通配符導(dǎo)包的使用 from xx import * 這種,不用導(dǎo)入前置單下劃線(xiàn)的變量,除非定義了 __all__ 覆蓋了這個(gè)行為。PEP8 一般不建議通過(guò)這種方式導(dǎo)包。
后置單下劃線(xiàn): ?var_
如果使用的變量名被 Python 中的關(guān)鍵字占用,比如要聲明 class 這個(gè)變量,我們這時(shí)候可以在其后面加個(gè)單下劃線(xiàn) class_
這個(gè)也是 PEP8 里約定的
前置雙下劃線(xiàn): __var
?????前置雙下劃線(xiàn)會(huì)讓 Python 解釋器重寫(xiě)屬性名稱(chēng),防止被子類(lèi)中的命名覆蓋。
class?Test:
????def?__init__(self):
????????self.foo?=?11
????????self.__bar?=?2
t?=?Test()
print(dir(t))
查看類(lèi)的屬性可以發(fā)現(xiàn) self.__bar 變?yōu)榱?_Test__bar,這也稱(chēng)之為名稱(chēng)改寫(xiě) (name mangling),解釋器會(huì)更改變量的名稱(chēng),防止拓展這個(gè)類(lèi)型時(shí)命名沖突。
['_Test__bar',?'__class__',?'__delattr__',?'__dict__',?'__dir__',?'__doc__',?'__eq__',?'__format__',?'__ge__',?'__getattribute__',?'__gt__',?'__hash__',?'__init__',?'__init_subclass__',?'__le__',?'__lt__',?'__module__',?'__ne__',?'__new__',?'__reduce__',?'__reduce_ex__',?'__repr__',?'__setattr__',?'__sizeof__',?'__str__',?'__subclasshook__',?'__weakref__',?'foo']
這個(gè)時(shí)候如果想訪(fǎng)問(wèn) __bar, 怎么辦呢,我們可以通過(guò) t._Test__bar進(jìn)行訪(fǎng)問(wèn)。
如果我們繼承一下Test然后重寫(xiě) __bar會(huì)咋樣呢
class?ExtendTest(Test):
????def?__init__(self):
????????super().__init__()
????????self.foo?=?"overridden"
????????self.__bar?=?"overridden"
et?=?ExtendTest()
print(et.foo)
print(et.__bar)
發(fā)現(xiàn)出現(xiàn)了錯(cuò)誤
AttributeError: 'ExtendTest' object has no attribute '__bar'
原因就是前面的一樣,因?yàn)榻忉屍靼?__bar的名字給改了防止父類(lèi)的這個(gè)變量被改寫(xiě)了。
我們可以分別訪(fǎng)問(wèn)這兩個(gè)類(lèi)的 __bar發(fā)現(xiàn)他們是同時(shí)存在的,確實(shí)沒(méi)有被覆蓋。
print(et._Test__bar)
print(et._ExtendTest__bar)
得到結(jié)果
2
overridden
順便說(shuō)下 __bar 在英語(yǔ)中一般都是叫做 dunderbar。
除了雙下劃線(xiàn)的變量,雙下劃線(xiàn)的方法名也可以被解釋器名稱(chēng)改寫(xiě)。
class?ManglingMethod:
????def?__mangled(self):
????????return?42
????def?call_it(self):
????????return?self.__mangled()
md?=?ManglingMethod()
md.call_it()
md.__mangled()
運(yùn)行之后得到出錯(cuò)信息
AttributeError:?'ManglingMethod'?object?has?no?attribute?'__mangled'
前后雙下劃線(xiàn): __var__
所謂的魔法方法,它的名稱(chēng)不會(huì)被解釋器所改變,但是就命名約定而言最好避免使用這種形式變量和方法名
單下劃線(xiàn): _
1._ 可以表示變量是臨時(shí)的或者是無(wú)關(guān)緊要的
for?_?in?rang(5):
????print("hello")
2.在數(shù)字之前使用還可以當(dāng)作是千位分隔符
for?i?in?range(1000_000):
????print(i)
3.在解包元組的時(shí)候可以當(dāng)作是占位符。
car?=?("red",?"auto",?12,?332.4?)
color,_,_,mileage?=?car
print(color)
print(_mileage)
4.如果使用命令行模式的話(huà),_ 可以獲取先前計(jì)算的結(jié)果
>>>??20+5
25
>>>?_
25
>>>?print(_)
25
4.自定義異常類(lèi)
我們有以下代碼
def?validate(name):
????if?len(name)?10:
????????raise?ValueError
如果在其他文件中調(diào)用這個(gè)方法,
validate("lisa")
在不理解這個(gè)方法的作用的時(shí)候,如果名字驗(yàn)證失敗時(shí),調(diào)用棧會(huì)打印出以下信息
Traceback?(most?recent?call?last):
??File?"/Users/chennan/pythonproject/demo/nyandemo.py",?line?57,?in?<module>
????validate("lisa")
??File?"/Users/chennan/pythonproject/demo/nyandemo.py",?line?55,?in?validate
????raise?ValueError
ValueError
這個(gè)棧調(diào)試回溯中的信息指出了,出現(xiàn)了錯(cuò)誤的值,但是并不知道為什么出錯(cuò)了,所以這個(gè)時(shí)候就需要跟進(jìn)這個(gè) validate 一探究竟,
這個(gè)時(shí)候我們就可以自己定義一個(gè)異常類(lèi)
class?NameTooShortException(ValueError):
???def?__str__(self):
???????return?"輸入的名字長(zhǎng)度必須大于等于10"
def?validate(name):
????if?len(name)?10:
????????raise?NameTooShortException(name)
validate("lisa")
這樣如果再出現(xiàn)錯(cuò)誤,就可以知道為什么錯(cuò)了,同時(shí)調(diào)用法也方便捕獲指定的異常,不用再使用 ValueError。
try:
????validate("lisa")
except?NameTooShortException?as?e:
????print(e)
5.Python字節(jié)碼
Cpython 解釋器執(zhí)行時(shí),首先將其翻譯成一系列的字節(jié)碼指令。字節(jié)碼是 Python 虛擬機(jī)的中間語(yǔ)言,可以提高程序的執(zhí)行效率
Cpython 不直接執(zhí)行人類(lèi)可讀的源碼,而是執(zhí)行由編譯器解析和語(yǔ)法語(yǔ)義分析產(chǎn)生的緊湊的數(shù)、變量和引用。
這樣,再次執(zhí)行相同程序時(shí)能節(jié)省時(shí)間和內(nèi)存。因?yàn)榫幾g步驟產(chǎn)生的字節(jié)碼會(huì)以 .pyc 和 .pyo 文件的形式緩存在硬盤(pán)上,所以執(zhí)行字節(jié)碼比再次執(zhí)行相同的Python文件速度更快。
def?greet(name):
????return?'hello,?'?+?name?+?'!'
#__code__可以獲取greet函數(shù)用到的虛擬機(jī)指令,常量和變量
gc?=?greet.__code__
print(gc.co_code)??#?指令流
print(gc.co_consts)??#?常量
print(gc.co_varnames)??#?傳過(guò)來(lái)的參數(shù)
dis.dis(greet)
結(jié)果
b'd\x01|\x00\x17\x00d\x02\x17\x00S\x00'
(None,?'hello,?',?'!')
('name',)
?70???????????0?LOAD_CONST???????????????1?('hello,?')
??????????????2?LOAD_FAST????????????????0?(name)
??????????????4?BINARY_ADD
??????????????6?LOAD_CONST???????????????2?('!')
??????????????8?BINARY_ADD
?????????????10?RETURN_VALUE
解釋器在索引1處('hello, ')查找常量,并放入棧中,然后將 name 的變量?jī)?nèi)容放入棧
Cpython 虛擬機(jī)是基于棧式虛擬機(jī),棧就是虛擬機(jī)的內(nèi)部數(shù)據(jù)結(jié)構(gòu)。
棧只支持兩種動(dòng)作:入棧和出棧
入棧:將一個(gè)值添加到棧頂
出棧:刪除并返回棧頂?shù)闹怠?/p>
假設(shè)棧初始為空,在執(zhí)行前兩個(gè)操作碼(opcode)之后,虛擬的內(nèi)容(0是最上面的元素)
比如我們傳入的name為lisa.
0:?'lisa'
1:?'hello,?'
BINARY_ADD 指令從棧中彈出兩個(gè)字符串值,并將他們連接起來(lái)
然后再次將結(jié)果壓入棧中。
0:'hello,?lisa'
然后由下一個(gè) LOAD_CONST 將'!'壓入棧。
此時(shí)的結(jié)果
0:'!'
1:'hello,?lisa'
下一個(gè) BINARY_ADD 操作碼再次將這兩個(gè)字符串從棧中彈出并連接之后壓入棧,生成最終結(jié)果
0:'hello,?lisa!'
最后字節(jié)碼 RETURN_VALUE ,告訴虛擬機(jī)當(dāng)前位于棧頂?shù)氖窃摵瘮?shù)的返回值。
推薦閱讀
