6個Python酷技巧,原來還能這樣用!
不管學(xué)什么,我個人是非常喜歡小技巧(tricks)的,Python 也不例外。著名 Python 技巧大師 Dan Bader 是這樣定義 Python Tricks 的。
A?Python?Trick?either?teaches?an?aspect?of?Python?with?a?simple?illustration,?or?it?serves?as?a?motivating?example,?enabling?you?to?dig?deeper?and?develop?an?intuitive?understanding.
Dan Bader
Python Trick 表現(xiàn)簡單而直觀,但可以激發(fā)你繼續(xù)深挖的興趣,你會說“原來還可以這樣做啊”。
今天就來介紹6個我最喜歡的 Trick,使用它們可以讓你的代碼更 Pythonic:
下劃線占位符
枚舉
打包
解包
動態(tài)屬性
密碼函數(shù)
它們都非常直觀而簡單,相信讀完之后,肯定有幾個技巧會讓你驚嘆到,原來 Python 還可以這么用。
有時候數(shù)字一大,數(shù)起來會犯迷糊,看下例。
apple_mktcap?=?1084000000000facebook_mktcap?=?458870000000total = apple_mktcap + facebook_mktcapprint(total)
1542870000000.0這個蘋果和臉書的市值之和有多少個零???數(shù)不清楚是吧,在 Python 中,我們可以用下劃線占位符(underscore placeholder)來將大數(shù)每三位數(shù)分段。請注意,多加了下劃線,數(shù)字還是數(shù)值型變量,只是讓我們?nèi)菀妆嬲J(rèn)大數(shù)。
apple_mktcap = 1_084_000_000_000facebook_mktcap = 458_870_000_000total = apple_mktcap + facebook_mktcapprint(total)
1542870000000你看,加個下劃線的數(shù)字還是可以相加,但是結(jié)果還是不好認(rèn)。還記得 f string 格式化字符串嗎?用 :, 來每三位數(shù)分段。
print(f'Total is {total:,} USD')Total is?1,542,870,000,000?USD“下劃線占位符”解決痛點(diǎn):容易辨認(rèn)大數(shù)的位數(shù)。
給定一列表,包含四種計算機(jī)語言的元素。
languages = ['Python', 'R', 'Matlab', 'Julia']如果我們想把每中語言附加對應(yīng)的索引一來打印出來,怎么寫代碼呢?最直接的想法就是初始化 index 為 0,然后在運(yùn)行每個 for 循環(huán)后將 index 的值加 1,代碼如下。?
index = 0for lang in languages:print(index, lang)index +=1
0 Python
1 R
2 Matlab
3 Julia結(jié)果是對的,但是這代碼你不覺得很丑嗎?很不 Pythonic 嗎?
Python 有 enumerate() 函數(shù)可以一次性返回列表(任意迭代器)的元素以及其對應(yīng)的索引,代碼如下,優(yōu)雅嗎?
for index, lang in enumerate(languages):print(index, lang)
0 Python
1 R
2 Matlab
3 Julia除此之外,你還可以自定義索引的初始值。在實(shí)際生活中,一般索引從 1 開始更自然,那么將參數(shù) start 設(shè)置為 1 就好了。
for index, lang in enumerate(languages, start=1):print(index, lang)
1 Python
2 R
3 Matlab
4 Julia“枚舉函數(shù) enumerate()”解決痛點(diǎn):不需要顯性創(chuàng)建索引。
給定一串名字(names)和演員角色(actors),用兩個列表存儲。
names = ['小羅伯特唐尼', '托比·馬奎爾', '克里斯蒂安·貝爾', '杰森·莫瑪']actors = ['鋼鐵俠', '蜘蛛俠', '蝙蝠俠', '水行俠']
如果我們想把每個名字和角色一一對應(yīng)起來,可以用上節(jié)學(xué)到的?enumerate() 函數(shù)。我們可以返回 names 里的元素和索引,再用索引來獲取 actors 里的元素,代碼如下。
for index, name in enumerate(names):print(f'{name}是{actors[index]}')
小羅伯特唐尼是鋼鐵俠
托比·馬奎爾是蜘蛛俠
克里斯蒂安·貝爾是蝙蝠俠
杰森·莫瑪是水行俠結(jié)果是對的,但是代碼不夠優(yōu)雅。來,zip() 函數(shù)了解一下?
for name, actor in zip(names, actors):print(f'{name}是{actor}')
小羅伯特唐尼是鋼鐵俠
托比·馬奎爾是蜘蛛俠
克里斯蒂安·貝爾是蝙蝠俠
杰森·莫瑪是水行俠zip()?函數(shù)將列表(迭代器)中對應(yīng)的元素打包成一個個元組,然后返回由這些元組組成的列表。上面代碼是不是漂亮多了。
再加一個列表如何?zip() 函數(shù)表示毫無壓力。
universes = ['漫威', '漫威', 'DC', 'DC']for name, actor, universe in zip(names, actors, universes):print(f'{name}是來自{universe}的{actor}')
小羅伯特唐尼是來自漫威的鋼鐵俠
托比·馬奎爾是來自漫威的蜘蛛俠
克里斯蒂安·貝爾是來自DC的蝙蝠俠
杰森·莫瑪是來自DC的水行俠讓我們再看一次?zip() 函數(shù)的用法,其 3 個參數(shù) names, actors 和 universes 列表中都有 4 個元素,那么在對應(yīng)的位置 i(從 0 到 3)一個個獲取 names[i], actors[i] 和 universes[i],并打包成新列表,因此輸出是 4 個列表,每個列表有 3 個元素。
a = zip(names, actors, universes)print(*a)
('小羅伯特唐尼', '鋼鐵俠', '漫威')
('托比·馬奎爾', '蜘蛛俠', '漫威')
('克里斯蒂安·貝爾', '蝙蝠俠', 'DC')
('杰森·莫瑪', '水行俠', 'DC')結(jié)果沒問題。需要注意的是 a 實(shí)際上是個對象,要看它里面的內(nèi)容,需要在 a 前面加個?* 字符。
你們現(xiàn)在肯定會想,有了?zip(),那有沒有其反向操作的?unzip() 呢?答案是沒有,zip() 的反向操作還是 .... zip()!!!
你品,你細(xì)品。
a = zip(names, actors, universes)names, actors, universes = zip(*a)print(names, actors, universes)
('小羅伯特唐尼', '托比·馬奎爾', '克里斯蒂安·貝爾', '杰森·莫瑪')
('鋼鐵俠', '蜘蛛俠', '蝙蝠俠', '水行俠')
('漫威', '漫威', 'DC', 'DC')“打包函數(shù) zip()”解決痛點(diǎn):能同時遍歷多個迭代器。
一個簡單例子,將 1 和 2 分別賦給 a 和 b,這種操作稱為解包(unpack)。
a, b = 1, 2print(a)print(b)
1
2如果你不想要 b 的話,用下劃線代替就行了。
a, _ = 1, 2print(a)
1但如果等號左右兩邊元素和變量個數(shù)不一樣。程序會報錯。
a, b, c = 1, 2---------------------------------------------------------------------------
ValueError Traceback (most recent call?last)
input-77-9dbc59cfd6c6> in?<module>
----> 1 a, b, c = 1, 2
ValueError: not?enough values?to?unpack (expected 3, got 2) 用 * 字符可以解決這個問題。將右邊的 1 和 2 分別解包給 a 和 b,那么什么都不剩了,因此 c 得到的是個空集 []。
a, b, *c = 1, 2print(a)print(b)print(c)
1
2
[]如果右邊元素多過左邊變量呢?從頭開始一一解包,再把多余的全部賦給 c。
a, b, *c = 1, 2, 3, 4, 5print(a)print(b)print(c)
1
2
[3, 4, 5]更進(jìn)一步,我們還可以從頭和尾開始一一解包,再把多余的全部賦給 c。
a, b, *c, d = 1, 2, 3, 4, 5print(a)print(b)print(c)print(d)
1
2
[3, 4]
5不想要 c 的話,用 *_??將其代替即可。
a, b, *_, d = 1, 2, 3, 4, 5print(a)print(b)print(d)
1
2
5“解包”解決痛點(diǎn):將值賦給正確的變量。
這個技巧是我覺得最有用的。首先定一個金融產(chǎn)品的類 Instrument,并創(chuàng)建一個對象 inst。
class Instrument():passinst = Instrument()
定義 inst 的兩個屬性并賦值,本金(notional)和到期日(maturity)。
inst.notional = 100_000_000inst.maturity = '2025-03-25'
print(inst.notional)print(inst.maturity)
100000000
2025-03-25現(xiàn)在將屬性 notional 和其屬性值 10000000 存儲在變量 first_key 和 first_val 中。
first_key = 'notional'first_val = 100_000_000
我們想用 first_key 的值 notional(而不是 first_key 這個字符)來作為屬性。
inst = Instrument()inst.first_key = first_val
打印 inst.notional 會報錯,錯誤是 Instrument 對象中沒有 notional 這樣的屬性名。
print(inst.notional)---------------------------------------------------------------------------
AttributeError Traceback (most recent call?last)
input-99-50eeb1451324> in?<module>
----> 1 print(inst.notional)
AttributeError: 'Instrument'?object?has no?attribute?'notional' 原因是 inst 把 first_key 這個字符串當(dāng)成屬性名,驗(yàn)證如下。
print(inst.first_key)100000000怎么解決這個動態(tài)屬性的問題呢?即我們要變量的值為屬性名,而不是變量本身名稱當(dāng)屬性名。用 setattr() 函數(shù),它有三個參數(shù):
參數(shù) 1 - 對象
參數(shù) 2 - 屬性名的變量名
參數(shù) 3?- 屬性值的變量名
代碼如下,這時用 inst.notional 不會報錯了。
inst = Instrument()setattr(inst, first_key, first_val)print(inst.notional)
100000000和 setattr() 相對應(yīng),你可以用 getattr() 函數(shù)來獲取屬性值,它有兩個參數(shù):
參數(shù) 1 - 對象
參數(shù) 2 - 屬性名的變量名
代碼如下:
getattr(inst, first_key)100000000和靜態(tài)屬性相比,動態(tài)屬性到底好在哪里呢?以讀取歐式期權(quán)的特征舉例,通常信息以字典(也有其他格式)存儲,具體內(nèi)容如下:
inst_info = {'ID':'9001001',?????????????'Effective?Date':'2020-03-20',??????????????'Maturity?Date':'2020-06-20',?????????????'Notional':10_000_000,?????????????'Domestic?Currency':'USD',?????????????'Foreign?Currency':'EUR',?????????????'Flavor':'Put',?????????????'Strike':1.08,?????????????'Display':'domestic?pips',?????????????'Asset?Class':'FX',?????????????'Ins trument?Type':'European?Option','Model':'Heston'}
那么當(dāng)我們創(chuàng)建 inst 對象時,把上面字典的鍵(key)作為屬性名。每種產(chǎn)品具體的特征都不一樣,如果用靜態(tài)屬性的將字典轉(zhuǎn)成對象的話,代碼會非常亂而且無法管理,但如果用動態(tài)屬性的話,下面三行代碼就能搞定(用 setattr())。
inst = Instrument()for key, val in inst_info.items():setattr(inst, key, val)
用 getattr()?函數(shù)來打印出來看結(jié)果對不對,兩行代碼搞定。
for key in inst_info.keys():print( key, '|', getattr(inst, key))
ID | 9001001
Effective Date |?2020-03-20
Maturity Date | 2020-06-20
Notional |?10000000
Domestic Currency | USD
Foreign Currency |?EUR
Flavor | Put
Strike |?1.08
Display | domestic pips
Asset Class |?FX
Instrument Type | European Option
Model |?Heston結(jié)果是對的,但也是丑的,用 f string 來添加若干個空白,將每個屬性值的起始位置對齊。
for key in inst_info.keys():print( f'{key:18s}|', getattr(inst, key))
ID | 9001001
Effective Date |?2020-03-20
Maturity Date | 2020-06-20
Notional |?10000000
Domestic Currency | USD
Foreign Currency |?EUR
Flavor | Put
Strike |?1.08
Display | domestic pips
Asset Class |?FX
Instrument Type | European Option
Model |?Heston“動態(tài)屬性 setattr()”解決痛點(diǎn):用盡可能少的代碼快速創(chuàng)建對象。
當(dāng)?shù)卿洉r,你需要輸入你的用戶名和密碼,用 input() 函數(shù)可以做到要求用戶主動輸入,但是輸入的密碼任何人都可見,這還是密碼嗎?
username = input('Username: ')password = input('Password: ')print('Logging In...')

Username: Steven
Password: 1031
Logging In ...用 getpass() 函數(shù)即可,不解釋,自己看下圖。
from getpass import getpassusername = input('Username: ')password = getpass('Password: ')print('Logging In...')

Username: Steven
Password: ········
Logging In ...“密碼函數(shù) getpass()”解決痛點(diǎn):讓輸入的密碼不可見。
六個小技巧,簡單直觀,但是超級有用。有時候就是用這樣的一個函數(shù),你不知道,寫出來的代碼不優(yōu)雅;你知道了,寫出來的代碼真好看。
六個技巧總結(jié)如下:
下劃線占位符:容易辨認(rèn)大數(shù)的位數(shù)
枚舉函數(shù)?enumerate():不需要顯性創(chuàng)建索引
打包函數(shù)?zip():能同時遍歷多個迭代器
解包:將值賦給正確的變量
動態(tài)屬性?setattr():用盡可能少的代碼快速創(chuàng)建對象
密碼函數(shù)?getpass():讓輸入的密碼不可見
用起來,酷起來。
_往期文章推薦_
