標題 | python-is-coolhtml
做者 | chiphuyennode
原文 | github.com/chiphuyen/p…python
譯者 | kbsc13("算法猿的成長"公衆號做者)git
聲明 | 翻譯是出於交流學習的目的,歡迎轉載,但請保留本文出於,請勿用做商業或者非法用途github
這篇文章主要是介紹一些 python 的技巧。算法
採用的python版本是 3.6+
微信
本文的目錄以下:多線程
lambda
是建立匿名函數,下面是一個使用的例子,其中 square_fn
和 square_ld
這兩個都是相同做用的函數:app
def square_fn(x):
return x * x
square_ld = lambda x: x * x
for i in range(10):
assert square_fn(i) == square_ld(i)
複製代碼
由於快速聲明的特色使得 lambda
很是適合用於回調函數以及做爲一個參數傳入其餘函數中。此外,它還能夠很好的和 map
, filter
, reduce
這幾個函數一塊兒使用。函數
map(fn, iterable)
是將 iterable
參數的全部元素都傳給函數fn
,這裏能夠做爲iterable
參數的有列表、集合、字典、元祖和字符串,返回的是一個 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
函數做爲回調函數,則代碼爲:
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]
複製代碼
還可使用多個迭代對象,例如,若是想計算一個簡單的線性函數f(x)=ax+b
和真實標籤 labels
的均方差,下面有兩個相同做用的實現方法:
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
返回的對象都是迭代器,也就是說它們的數值並無被存儲下來,只是在須要的時候生成,因此若是調用了sum(diffs)
,diffs
將變爲空,若是想保存全部diffs
的元素,須要轉爲列表的類型--list(diffs)
。
filter(fn, iterable)
的使用方式和 map
同樣,不一樣的是 fn
返回的是布爾類型的數值,而後 filter
函數返回的就是 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
函數的運算時間並非很好,和用 def
定義的有名字函數相比,會稍微慢一些,所以更建議使用帶名字的函數。
python
中的列表也是有不少特別的技巧。
對於展開列表的每一個元素,能夠這麼實現:
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]
4
複製代碼
反轉一個列表能夠經過切片方式實現--[::-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
。當 z
是負數,它表示從後往前,x
沒有指定的時候,默認從第一個元素開始遍歷列表。若是沒有指定 y
,則默認採用最後一個元素。所以,若是咱們但願每隔2個元素進行採樣,能夠採用 [::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]
複製代碼
改變列表中一個元素的代碼實現以下所示:
elems = list(range(10))
elems[1] = 10
print(elems)
==> [0, 10, 2, 3, 4, 5, 6, 7, 8, 9]
複製代碼
而若是但願修改特定範圍內的多個元素,好比用 3 個數值 20,30,40
來替換數值 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 個數值 [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]
複製代碼
經過採用 sum
方法來碾平一個嵌套列表的對象:
list_of_lists = [[1], [2, 3], [4, 5, 6]]
sum(list_of_lists, [])
==> [1, 2, 3, 4, 5, 6]
複製代碼
但若是嵌套的層次太多,就須要遞歸的操做,這裏介紹另外一個經過 lambda
實現的方法:
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-lists
複製代碼
爲了解釋列表和生成器的區別,這裏用一個建立一個列表的全部字符串的 n-grams 做爲例子:
其中一個實現方法是採用滑動窗口:
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 grams
print(ngrams(tokens, 3))
==> [['i', 'want', 'to'],
['want', 'to', 'go'],
['to', 'go', 'to'],
['go', 'to', 'school']]
複製代碼
在上述例子中,咱們須要同時存儲全部的 n-grams
,若是文本是有 m
個字符,那麼內存大小就是 O(nm)
,這在 m
很大的時候問題會很大。
所以,能夠考慮經過生成器在須要的時候才生成新的 n-gram
,因此咱們能夠建立一個函數 ngrams
經過關鍵詞 yield
返回一個生成器,此內存只須要 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 generators
for 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)]
,由於 []
是列表生成式,而 ()
會返回一個生成器。
在 python 中,魔法方法是前綴和後綴都帶有兩個下劃線的 __
,最有名的一個魔法方法可能就是 __init__
了,下面是實現一個 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 = value
self.left = left
self.right = right
複製代碼
若是咱們要打印一個 Node
對象,不過輸出結果並不是很好解釋:
root = Node(5)
print(root) # <__main__.Node object at 0x1069c4518>
複製代碼
理想的狀況是,能夠打印一個節點的數值以及其包含的全部子節點,要實現這個功能,能夠採用 __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 = value
self.left = left
self.right = right
def __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
複製代碼
接着,咱們可能想進一步實現兩個節點的比較數值的功能,這裏經過 __eq__
實現相等 ==
,__lt__
實現小於 <
,__ge__
實現 大於等於 >=
。
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 = value
self.left = left
self.right = right
def __eq__(self, other):
return self.value == other.value
def __lt__(self, other):
return self.value < other.value
def __ge__(self, other):
return self.value >= other.value
left = Node(4)
root = Node(5, left)
print(left == root) # False
print(left < root) # True
print(left >= root) # False
複製代碼
在下面這篇文章給出了全部的魔法方法列表:
www.tutorialsteacher.com/python/magi…
固然也能夠查看官方文檔的說明,不過閱讀起來會有些難度:
其中推薦如下這些方法:
__len__
:重寫 len()
方法__str__
:重寫str()
方法__iter__
:若是想讓對象能夠迭代,能夠繼承這個方法,而且還能夠調用 next()
方法對於相似 Node
這樣的類,即咱們肯定其支持的全部屬性(好比對於 Node
,這裏就是指 value, left, right
着三個屬性),能夠採用 __slots__
來表示這些數值,這有利於提高性能和節省內存空間。想更詳細瞭解 __slots__
,能夠看看這篇 Stackoverflow 上的回答:
stackoverflow.com/questions/4…
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 = value
self.left = left
self.right = right
複製代碼
locals()
函數會返回一個字典,它包含了全部定義在本地命名空間的變量,例子以下所示:
class Model1:
def __init__(self, hidden_size=100, num_layers=3, learning_rate=3e-4):
print(locals())
self.hidden_size = hidden_size
self.num_layers = num_layers
self.learning_rate = learning_rate
model1 = 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}
複製代碼
注意手動將全部參數分配到對應的屬性會很是麻煩,特別是在參數列表比較大的時候。爲了不這種狀況,能夠利用對象的 __dict__
:
class Model2:
def __init__(self, hidden_size=100, num_layers=3, learning_rate=3e-4):
params = locals()
del params['self']
self.__dict__ = params
model2 = Model2()
print(model2.__dict__)
==> {'learning_rate': 0.0003, 'num_layers': 3, 'hidden_size': 100}
複製代碼
若是對象是經過 **kwargs
來進行初始化,會更加的方便,不過**kwargs
應該儘可能少使用:
class Model3:
def __init__(self, **kwargs):
self.__dict__ = kwargs
model3 = Model3(hidden_size=100, num_layers=3, learning_rate=3e-4)
print(model3.__dict__)
==> {'hidden_size': 100, 'num_layers': 3, 'learning_rate': 0.0003}
複製代碼
一般會陷入這種瘋狂的導入操做*
的例子以下所示:
在 file.py
文件中
from parts import *
複製代碼
這個寫法很是不負責任,它是將另外一個模塊的一切都導入到當前的模塊,包括那個模塊的導入的內容,好比說,parts.py
模塊多是這樣的:
import numpy
import tensorflow
class Encoder:
...
class Decoder:
...
class Loss:
...
def helper(*args, **kwargs):
...
def utils(*args, **kwargs):
...
複製代碼
因爲 parts.py
沒有指定 __all__
,因此 file.py
會導入 Encoder, Decoder, Loss, utils, helper
,以及 numpy
和 tensorflow
。
若是咱們只想讓 Encoder, Decoder, Loss
被導入到另外一個模塊中使用,那麼就須要指定 __all__
參數:
__all__ = ['Encoder', 'Decoder', 'Loss']
import numpy
import tensorflow
class Encoder:
...
複製代碼
經過上述代碼,當有另外一個文件也是直接採用 from part import *
的作法,那麼只會導入給定的 Encoder, Decoder, Loss
,同時 __all__
也是對一個模塊的一個概覽。
歡迎關注個人微信公衆號--算法猿的成長,或者掃描下方的二維碼,你們一塊兒交流,學習和進步!
推薦閱讀: