几个有趣的python技巧
2019 年第 82 篇文章,總第 106 篇文章
標(biāo)題 | python-is-cool
作者 | chiphuyen
原文 | https://github.com/chiphuyen/python-is-cool
譯者 | kbsc13("算法猿的成長"公眾號作者)
聲明 | 翻譯是出于交流學(xué)習(xí)的目的,歡迎轉(zhuǎn)載,但請保留本文出于,請勿用作商業(yè)或者非法用途
導(dǎo)讀
這篇文章主要是介紹一些 python 的技巧。
采用的 python 版本是 3.6+
本文的目錄如下:
Lambda, map, filter, reduce
列表操作
類和魔法方法
本地命名空間和對象的屬性
瘋狂的導(dǎo)入
1. Lambda, map, filter, reduce
lambda 是創(chuàng)建匿名函數(shù),下面是一個使用的例子,其中 square_fn 和 square_ld 這兩個都是相同作用的函數(shù):
def square_fn(x):return x * xsquare_ld = lambda x: x * xfor i in range(10):assert square_fn(i) == square_ld(i)因為快速聲明的特點使得 lambda 非常適合用于回調(diào)函數(shù)以及作為一個參數(shù)傳入其他函數(shù)中。此外,它還可以很好的和 map, filter , reduce 這幾個函數(shù)一起使用。
map(fn, iterable) 是將 iterable 參數(shù)的所有元素都傳給函數(shù)fn ,這里可以作為iterable參數(shù)的有列表、集合、字典、元祖和字符串,返回的是一個 map 對象,例子如下所示:
nums = [1/3, 333/7, 2323/2230, 40/34, 2/3] nums_squared = [num * num for num in nums] print(nums_squared)==> [0.1111111, 2263.04081632, 1.085147, 1.384083, 0.44444444]如果用 map 函數(shù)作為回調(diào)函數(shù),則代碼為:
nums_squared_1 = map(square_fn, nums) nums_squared_2 = map(lambda x: x * x, nums) print(list(nums_squared_1))==> [0.1111111, 2263.04081632, 1.085147, 1.384083, 0.44444444]還可以使用多個迭代對象,例如,如果想計算一個簡單的線性函數(shù)f(x)=ax+b 和真實標(biāo)簽 labels 的均方差,下面有兩個相同作用的實現(xiàn)方法:
a, b = 3, -0.5 xs = [2, 3, 4, 5] labels = [6.4, 8.9, 10.9, 15.3]# Method 1: using a loop errors = [] for i, x in enumerate(xs):errors.append((a * x + b - labels[i]) ** 2) result1 = sum(errors) ** 0.5 / len(xs)# Method 2: using map diffs = map(lambda x, y: (a * x + b - y) ** 2, xs, labels) result2 = sum(diffs) ** 0.5 / len(xs)print(result1, result2)==> 0.35089172119045514 0.35089172119045514需要注意的是,map 和 filter 返回的對象都是迭代器,也就是說它們的數(shù)值并沒有被存儲下來,只是在需要的時候生成,所以如果調(diào)用了sum(diffs) ,diffs 將變?yōu)榭?#xff0c;如果想保存所有diffs的元素,需要轉(zhuǎn)為列表的類型--list(diffs)。
filter(fn, iterable) 的使用方式和 map 一樣,不同的是 fn 返回的是布爾類型的數(shù)值,然后 filter 函數(shù)返回的就是 fn 會返回 True 的元素,一個例子如下所示:
bad_preds = filter(lambda x: x > 0.5, errors) print(list(bad_preds))==> [0.8100000000000006, 0.6400000000000011]reduce(fn, iterable, initializer) 是在我們想對一個列表的元素都迭代地采用一個操作器的使用。比如,我們想計算一個列表的所有元素的乘積:
product = 1 for num in nums:product *= num print(product)==> 12.95564683272412這等價于:
from functools import reduce product = reduce(lambda x, y: x * y, nums) print(product)==> 12.95564683272412注意:
lambda 函數(shù)的運算時間并不是很好,和用 def 定義的有名字函數(shù)相比,會稍微慢一些,因此更建議使用帶名字的函數(shù)。
2. 列表操作
python 中的列表也是有很多特別的技巧。
2.1 Unpacking
對于展開列表的每個元素,可以這么實現(xiàn):
elems = [1, 2, 3, 4] a, b, c, d = elems print(a, b, c, d)==> 1 2 3 4也可以這么做:
a, *new_elems, d = elems print(a) print(new_elems) print(d)==> 1[2, 3]42.2 Slicing
反轉(zhuǎn)一個列表可以通過切片方式實現(xiàn)--[::-1]:
elems = list(range(10)) print(elems)==> [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]print(elems[::-1])==> [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]這個語法 [x:y:z] 表示在一個列表中,從索引 x 到 y 中取出元素,步長是 z 。當(dāng) z 是負(fù)數(shù),它表示從后往前,x 沒有指定的時候,默認(rèn)從第一個元素開始遍歷列表。如果沒有指定 y ,則默認(rèn)采用最后一個元素。因此,如果我們希望每隔2個元素進(jìn)行采樣,可以采用 [::2]:
evens = elems[::2] print(evens)reversed_evens = elems[-2::-2] print(reversed_evens)==> [0, 2, 4, 6, 8][8, 6, 4, 2, 0]也可以通過切片的方式來刪除列表的元素:
del elems[::2] print(elems)==> [1, 3, 5, 7, 9]2.3 Insertion
改變列表中一個元素的代碼實現(xiàn)如下所示:
elems = list(range(10)) elems[1] = 10 print(elems)==> [0, 10, 2, 3, 4, 5, 6, 7, 8, 9]而如果希望修改特定范圍內(nèi)的多個元素,比如用 3 個數(shù)值 20,30,40 來替換數(shù)值 1 ,代碼如下所示:
elems = list(range(10)) elems[1:2] = [20, 30, 40] print(elems)==> [0, 20, 30, 40, 2, 3, 4, 5, 6, 7, 8, 9]還可以在索引為0和索引為1之間插入 3 個數(shù)值 [0.2, 0.3, 0.5]:
elems = list(range(10)) elems[1:1] = [0.2, 0.3, 0.5] print(elems)==> [0, 0.2, 0.3, 0.5, 1, 2, 3, 4, 5, 6, 7, 8, 9]2.4 Flattening
通過采用 sum 方法來碾平一個嵌套列表的對象:
list_of_lists = [[1], [2, 3], [4, 5, 6]] sum(list_of_lists, [])==> [1, 2, 3, 4, 5, 6]但如果嵌套的層次太多,就需要遞歸的操作,這里介紹另一個通過 lambda 實現(xiàn)的方法:
nested_lists = [[1, 2], [[3, 4], [5, 6], [[7, 8], [9, 10], [[11, [12, 13]]]]]] flatten = lambda x: [y for l in x for y in flatten(l)] if type(x) is list else [x] flatten(nested_lists)# This line of code is from # https://github.com/sahands/python-by-example/blob/master/python-by-example.rst#flattening-lists2.5 List vs generator
為了解釋列表和生成器的區(qū)別,這里用一個創(chuàng)建一個列表的所有字符串的 n-grams 作為例子:
其中一個實現(xiàn)方法是采用滑動窗口:
tokens = ['i', 'want', 'to', 'go', 'to', 'school']def ngrams(tokens, n):length = len(tokens)grams = []for i in range(length - n + 1):grams.append(tokens[i:i+n])return gramsprint(ngrams(tokens, 3))==> [['i', 'want', 'to'],['want', 'to', 'go'],['to', 'go', 'to'],['go', 'to', 'school']]在上述例子中,我們需要同時存儲所有的 n-grams,如果文本是有 m 個字符,那么內(nèi)存大小就是 O(nm) ,這在 m 很大的時候問題會很大。
因此,可以考慮通過生成器在需要的時候才生成新的 n-gram ,所以我們可以創(chuàng)建一個函數(shù) ngrams 通過關(guān)鍵詞 yield 返回一個生成器,此內(nèi)存只需要 O(m+n):
def ngrams(tokens, n):length = len(tokens)for i in range(length - n + 1):yield tokens[i:i+n]ngrams_generator = ngrams(tokens, 3) print(ngrams_generator)==> <generator object ngrams at 0x1069b26d0>for ngram in ngrams_generator:print(ngram)==> ['i', 'want', 'to']['want', 'to', 'go']['to', 'go', 'to']['go', 'to', 'school']另外一種方式生成 n-grams 是通過切片方式來生成列表 [0, 1, ..., -n], [1, 2, ..., -n+1], ..., [n-1, n, ..., -1] ,然后通過 zip 來包裝到一起:
def ngrams(tokens, n):length = len(tokens)slices = (tokens[i:length-n+i+1] for i in range(n))return zip(*slices)ngrams_generator = ngrams(tokens, 3) print(ngrams_generator)==> <zip object at 0x1069a7dc8> # zip objects are generatorsfor ngram in ngrams_generator:print(ngram)==> ('i', 'want', 'to')('want', 'to', 'go')('to', 'go', 'to')('go', 'to', 'school')注意,這里生成切片的方法是 (tokens[...] for i in range(n)) ,而不是 [tokens[...] for i in range(n)],因為 [] 是列表生成式,而 () 會返回一個生成器。
3. 類和魔法方法
在 python 中,魔法方法是前綴和后綴都帶有兩個下劃線的 __,最有名的一個魔法方法可能就是 __init__ 了,下面是實現(xiàn)一個 Node 類,表示一個二叉樹:
class Node:""" A struct to denote the node of a binary tree.It contains a value and pointers to left and right children."""def __init__(self, value, left=None, right=None):self.value = valueself.left = leftself.right = right如果我們要打印一個 Node 對象,不過輸出結(jié)果并非很好解釋:
root = Node(5) print(root) # <__main__.Node object at 0x1069c4518>理想的情況是,可以打印一個節(jié)點的數(shù)值以及其包含的所有子節(jié)點,要實現(xiàn)這個功能,可以采用 __repr__ 方法,它會返回一個可解釋的對象,比如字符串。
class Node:""" A struct to denote the node of a binary tree.It contains a value and pointers to left and right children."""def __init__(self, value, left=None, right=None):self.value = valueself.left = leftself.right = rightdef __repr__(self):strings = [f'value: {self.value}']strings.append(f'left: {self.left.value}' if self.left else 'left: None')strings.append(f'right: {self.right.value}' if self.right else 'right: None')return ', '.join(strings)left = Node(4) root = Node(5, left) print(root) # value: 5, left: 4, right: None接著,我們可能想進(jìn)一步實現(xiàn)兩個節(jié)點的比較數(shù)值的功能,這里通過 __eq__ 實現(xiàn)相等 == ,__lt__實現(xiàn)小于 < ,__ge__ 實現(xiàn) 大于等于 >= 。
class Node:""" A struct to denote the node of a binary tree.It contains a value and pointers to left and right children."""def __init__(self, value, left=None, right=None):self.value = valueself.left = leftself.right = rightdef __eq__(self, other):return self.value == other.valuedef __lt__(self, other):return self.value < other.valuedef __ge__(self, other):return self.value >= other.valueleft = Node(4) root = Node(5, left) print(left == root) # False print(left < root) # True print(left >= root) # False在下面這篇文章給出了所有的魔法方法列表:
https://www.tutorialsteacher.com/python/magic-methods-in-python
當(dāng)然也可以查看官方文檔的說明,不過閱讀起來會有些難度:
https://docs.python.org/3/reference/datamodel.html#special-method-names
其中推薦以下這些方法:
__len__ :重寫 len() 方法
__str__:重寫str() 方法
__iter__:如果想讓對象可以迭代,可以繼承這個方法,并且還可以調(diào)用 next() 方法
對于類似 Node 這樣的類,即我們確定其支持的所有屬性(比如對于 Node ,這里就是指 value, left, right 著三個屬性),可以采用 __slots__ 來表示這些數(shù)值,這有利于提升性能和節(jié)省內(nèi)存空間。想更詳細(xì)了解 __slots__ ,可以看看這篇 Stackoverflow 上的回答:
https://stackoverflow.com/questions/472000/usage-of-slots/28059785#28059785
class Node:""" A struct to denote the node of a binary tree.It contains a value and pointers to left and right children."""__slots__ = ('value', 'left', 'right')def __init__(self, value, left=None, right=None):self.value = valueself.left = leftself.right = right4. 本地命名空間,對象的屬性
locals() 函數(shù)會返回一個字典,它包含了所有定義在本地命名空間的變量,例子如下所示:
class Model1:def __init__(self, hidden_size=100, num_layers=3, learning_rate=3e-4):print(locals())self.hidden_size = hidden_sizeself.num_layers = num_layersself.learning_rate = learning_ratemodel1 = Model1()==> {'learning_rate': 0.0003, 'num_layers': 3, 'hidden_size': 100, 'self': <__main__.Model1 object at 0x1069b1470>}一個對象的所有屬性都保存在它的 __dict__ :
print(model1.__dict__)==> {'hidden_size': 100, 'num_layers': 3, 'learning_rate': 0.0003}注意手動將所有參數(shù)分配到對應(yīng)的屬性會非常麻煩,特別是在參數(shù)列表比較大的時候。為了避免這種情況,可以利用對象的 __dict__:
class Model2:def __init__(self, hidden_size=100, num_layers=3, learning_rate=3e-4):params = locals()del params['self']self.__dict__ = paramsmodel2 = Model2() print(model2.__dict__)==> {'learning_rate': 0.0003, 'num_layers': 3, 'hidden_size': 100}如果對象是通過 **kwargs 來進(jìn)行初始化,會更加的方便,不過**kwargs 應(yīng)該盡量少使用:
class Model3:def __init__(self, **kwargs):self.__dict__ = kwargsmodel3 = Model3(hidden_size=100, num_layers=3, learning_rate=3e-4) print(model3.__dict__)==> {'hidden_size': 100, 'num_layers': 3, 'learning_rate': 0.0003}5. 瘋狂的導(dǎo)入
通常會陷入這種瘋狂的導(dǎo)入操作* 的例子如下所示:
在 file.py ?文件中
from parts import *這個寫法非常不負(fù)責(zé)任,它是將另一個模塊的一切都導(dǎo)入到當(dāng)前的模塊,包括那個模塊的導(dǎo)入的內(nèi)容,比如說,parts.py 模塊可能是這樣的:
import numpy import tensorflowclass Encoder:...class Decoder:...class Loss:...def helper(*args, **kwargs):...def utils(*args, **kwargs):...由于 parts.py 沒有指定 __all__ ,所以 file.py 會導(dǎo)入 Encoder, Decoder, Loss, utils, helper,以及 numpy 和 tensorflow 。
如果我們只想讓 Encoder, Decoder, Loss 被導(dǎo)入到另一個模塊中使用,那么就需要指定 __all__ 參數(shù):
__all__ = ['Encoder', 'Decoder', 'Loss'] import numpy import tensorflowclass Encoder:...通過上述代碼,當(dāng)有另一個文件也是直接采用 from part import * 的做法,那么只會導(dǎo)入給定的 ?Encoder, Decoder, Loss ,同時 __all__ 也是對一個模塊的一個概覽。
參考
https://www.tutorialsteacher.com/python/magic-methods-in-python
https://docs.python.org/3/reference/datamodel.html#special-method-names
https://stackoverflow.com/questions/472000/usage-of-slots/28059785#28059785
歡迎關(guān)注我的微信公眾號--算法猿的成長,或者掃描下方的二維碼,大家一起交流,學(xué)習(xí)和進(jìn)步!
如果覺得不錯,在看、轉(zhuǎn)發(fā)就是對小編的一個支持!
推薦閱讀:
數(shù)據(jù)科學(xué)家令人驚嘆的排序技巧
一文了解類別型特征的編碼方法
快速入門 Jupyter notebook
Jupyter 進(jìn)階教程
只需幾行代碼,即可實現(xiàn)多線程和多進(jìn)程操作
總結(jié)
以上是生活随笔為你收集整理的几个有趣的python技巧的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: JS 中 call()、apply()、
- 下一篇: python 问题