目前一共有5个版块,专注机器学习,日后还会持续更新:

  1、Lambda、Map、Filter、Reduce函数

  lambda 关键字,是用来创建内联函数 (Inline Functions) 的。square_fn 和 square_ld 函数,在这里是一样的。

  1def square_fn(x):

  2 return x * x

  3

  4square_ld=lambda x : x * x

  5

  6for i in range(10):

  7 assert square_fn(i)==square_ld(i)

  lambda 函数可以快速声明,所以拿来当回调 (Callbacks) 函数是非常理想的:就是作为参数 (Arguments) 传递给其他函数用的,那种函数。

  和 map、filter 和 reduce 这样的函数搭配使用,尤其有效。

  map(fn,iterable) 会把 fn 应用在 iterable 的所有元素上,返回一个map object。

  1nums=[1/3, 333/7, 2323/2230, 40/34, 2/3]

  2nums_squared=[num * num for num in nums]

  3print(nums_squared)

  4

  5==> [0.1111111, 2263.04081632, 1.085147, 1.384083, 0.44444444]

  这样调用,跟用有回调函数的 map 来调用,是一样的。

  1nums_squared_1=map(square_fn, nums)

  2nums_squared_2=map(lambda x : x * x, nums)

  3print(list(nums_squared_1))

  4

  5==> [0.1111111, 2263.04081632, 1.085147, 1.384083, 0.44444444]

  map 也可以有不止一个 iterable。

  比如,你要想计算一个简单线性函数 f(x)=ax+b 的均方误差 (MSE) ,两种方法就是等同的。

  1a, b=3, -0.5

  2xs=[2, 3, 4, 5]

  3labels=[6.4, 8.9, 10.9, 15.3]

  4

  5# Method 1: using a loop

  6errors=[]

  7for i, x in enumerate(xs):

  8 errors.append((a * x + b - labels[i]) ** 2)

  9result1=sum(errors) ** 0.5 / len(xs)

  10

  11# Method 2: using map

  12diffs=map(lambda x, y: (a * x + b - y) ** 2, xs, labels)

  13result2=sum(diffs) ** 0.5 / len(xs)

  14

  15print(result1, result2)

  16

  17==> 0.35089172119045514 0.35089172119045514

  要注意的是,map 和 filter 返回的是迭代器 (Iterator) ,这就是说它们的值不是存储的,是按需生成的。

  当你调用了sum(diffs) 之后,diffs 就空了。如果你想要保留 diffs 里面所有的元素,就用 list(diffs) 把它转换成一个列表。

  filter(fn,iterable) 也是和 map 一样道理,只不过 fn 返回的是一个布尔值,filter 返回的是,iterable 里面所有 fn 返回True的元素。

  1bad_preds=filter(lambda x: x > 0.5, errors)

  2print(list(bad_preds))

  3

  4==> [0.8100000000000006, 0.6400000000000011]

  reduce(fn,iterable,initializer) 是用来给列表里的所有元素,迭代地应用某一个算子。比如,想要算出列表里所有元素的乘积:

  1product=1

  2for num in nums:

  3 product *=num

  4print(product)

  5

  6==> 12.95564683272412

  上面这串代码,和下面这串代码是等同的:

  1from functools import reduce

  2product=reduce(lambda x, y: x * y, nums)

  3print(product)

  4

  5==> 12.95564683272412

  2、列表操作

  小姐姐说,Python的列表太炫酷了。

  2.1、解包 (Unpacking)

  想把一个列表解包成一个一个元素,就这样:

  1elems=[1, 2, 3, 4]

  2a, b, c, d=elems

  3print(a, b, c, d)

  4

  5==> 1 2 3 4

  也可以这样:

  1elems=[1, 2, 3, 4]

  2a, b, c, d=elems

  3print(a, b, c, d)

  4

  5==> 1 2 3 4

  2.2、切片 (Slicing)

  大家可能知道,如果想把一个列表反过来排,就用 [::-1] 。

  1elems=list(range(10))

  2print(elems)

  3

  4==> [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

  5

  6print(elems[::-1])

  7

  8==> [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]

  而 [x:y:z] 这种语法的意思是,从索引x到索引y,每z个元素取一个。

  如果z是负数,就是反向取了。

  如果x不特别指定,就默认是在遍历列表的方向上,遇到的第一个元素。

  如果y不特别指定,就默认是列表最后一个元素。

  所以,我们要从一个列表里面,每两个取一个的话,就是 [::2] 。

  1evens=elems[::2]

  2print(evens)

  3

  4reversed_evens=elems[-2::-2]

  5print(reversed_evens)

  6

  7==> [0, 2, 4, 6, 8]

  8 [8, 6, 4, 2, 0]

  也可以用这种方法,把一个列表里的偶数都删掉,只留奇数:

  1del elems[::2]

  2print(elems)

  3

  4==> [1, 3, 5, 7, 9]

  2.3、插入 (Insertion)

  把列表里的其中一个元素的值,换成另一个值。

  1elems=list(range(10))

  2elems[1]=10

  3print(elems)

  4

  5==> [0, 10, 2, 3, 4, 5, 6, 7, 8, 9]

  如果想把某个索引处的一个元素,替换成多个元素,比如把 1 换成 20, 30, 40 :

  1elems=list(range(10))

  2elems[1:2]=[20, 30, 40]

  3print(elems)

  4

  5==> [0, 20, 30, 40, 2, 3, 4, 5, 6, 7, 8, 9]

  如果想把3个值 0.2, 0.3, 0.5 插在索引0和索引1之间:

  1elems=list(range(10))

  2elems[1:1]=[0.2, 0.3, 0.5]

  3print(elems)

  4

  5==> [0, 0.2, 0.3, 0.5, 1, 2, 3, 4, 5, 6, 7, 8, 9]

  2.4、拉平 (Flattening)

  如果,一个列表里的每个元素都是个列表,可以用sum把它拉平:

  1list_of_lists=[[1], [2, 3], [4, 5, 6]]

  2sum(list_of_lists, [])

  3

  4==> [1, 2, 3, 4, 5, 6]

  如果是嵌套列表 (Nested List) 的话,就可以用递归的方法把它拉平。这也是lambda函数又一种优美的使用方法:在创建函数的同一行,就能用上这个函数。

  1nested_lists=[[1, 2], [[3, 4], [5, 6], [[7, 8], [9, 10], [[11, [12, 13]]]]]]

  2flatten=lambda x: [y for l in x for y in flatten(l)] if type(x) is list else [x]

  3flatten(nested_lists)

  4

  5# This line of code is from

  6# github/sahands/python-by-example/blob/master/python-by-example.rst#flattening-lists

  2.5、列表vs生成器

  要想知道列表和生成器的区别在哪,看个例子:从token列表里面创建n-grams。

  一种方法是用滑窗来创建:

  1tokens=['i', 'want', 'to', 'go', 'to', 'school']

  2

  3def ngrams(tokens, n):

  4 length=len(tokens)

  5 grams=[]

  6 for i in range(length - n + 1):

  7 grams.append(tokens[i:i+n])

  8 return grams

  9

  10print(ngrams(tokens, 3))

  11

  12==> [['i', 'want', 'to'],

  13 ['want', 'to', 'go'],

  14 ['to', 'go', 'to'],

  15 ['go', 'to', 'school']]

  上面这个例子,是需要把所有n-gram同时储存起来的。如果文本里有m个token,内存需求就是 O(nm) 。m值太大的话,存储就可能成问题。

  所以,不一定要用一个列表储存所有n-gram。可以用一个生成器,在收到指令的时候,生成下一个n-gram,这叫做惰性计算 (Lazy Evaluation) 。

  只要让 ngrams 函数,用 yield 关键字返回一个生成器,然后内存需求就变成 O(n) 了。

  1def ngrams(tokens, n):

  2 length=len(tokens)

  3 for i in range(length - n + 1):

  4 yield tokens[i:i+n]

  5

  6ngrams_generator=ngrams(tokens, 3)

  7print(ngrams_generator)

  8

  9==>

  10

  11for ngram in ngrams_generator:

  12 print(ngram)

  13

  14==> ['i', 'want', 'to']

  15 ['want', 'to', 'go']

  16 ['to', 'go', 'to']

  17 ['go', 'to', 'school']

  还有一种生成n-grams的方法,是用切片来创建列表:[0, 1, …, -n], [1, 2, …, -n+1], …, [n-1, n, …, -1],然后把它们zip到一起。

  1def ngrams(tokens, n):

  2 length=len(tokens)

  3 slices=(tokens[i:length-n+i+1] for i in range(n))

  4 return zip(*slices)

  5

  6ngrams_generator=ngrams(tokens, 3)

  7print(ngrams_generator)

  8

  9==> # zip objects are generators

  10

  11for ngram in ngrams_generator:

  12 print(ngram)

  13

  14==> ('i', 'want', 'to')

  15 ('want', 'to', 'go')

  16 ('to', 'go', 'to')

  17 ('go', 'to', 'school')

  注意,创建切片用的是 (tokens[…] for i in range(n)) ,不是 [tokens[…] for i in range(n)] 。

  [] 返回的是列表,() 返回的是生成器。

  3、类,以及魔术方法

  在Python里面,魔术方法 (Magic Methods) 是用双下划线,作为前缀后缀的。

  其中,最知名的可能就是 _init_ 了。

  1class Node:

  2 """ A struct to denote the node of a binary tree.

  3 It contains a value and pointers to left and right children.

  4 """

  5 def __init__(self, value, left=None, right=None):

  6 self.value=value

  7 self.left=left

  8 self.right=right

  不过,如果想输出 (Print) 一个节点 (Node) ,就不是很容易了。

  1root=Node(5)

  2print(root) # <__main__.Node object at 0x1069c4518>

  理想情况,应该是输出它的值,如果它有子节点的话,也输出子节点的值。

  所以,要用魔术方法 _repr_ ,它必须返回一个可输出的object,如字符串。

  1class Node:

  2 """ A struct to denote the node of a binary tree.

  3 It contains a value and pointers to left and right children.

  4 """

  5 def __init__(self, value, left=None, right=None):

  6 self.value=value

  7 self.left=left

  8 self.right=right

  9

  10 def __repr__(self):

  11 strings=[f'value: {self.value}']

  12 strings.append(f'left: {self.left.value}' if self.left else 'left: None')

  13 strings.append(f'right: {self.right.value}' if self.right else 'right: None')

  14 return ', '.join(strings)

  15

  16left=Node(4)

  17root=Node(5, left)

  18print(root) # value: 5, left: 4, right: None

  如果想对比两个节点 (的各种值) ,就用 _eq_ 来重载==运算符,用 _lt_ 来重载 < 运算符,用 _ge_ 来重载 >=。

  1class Node:

  2 """ A struct to denote the node of a binary tree.

  3 It contains a value and pointers to left and right children.

  4 """

  5 def __init__(self, value, left=None, right=None):

  6 self.value=value

  7 self.left=left

  8 self.right=right

  9

  10 def __eq__(self, other):

  11 return self.value==other.value

  12

  13 def __lt__(self, other):

  14 return self.value < other.value

  15

  16 def __ge__(self, other):

  17 return self.value >=other.value

  18

  19

  20left=Node(4)

  21root=Node(5, left)

  22print(left==root) # False

  23print(left < root) # True

  24print(left >=root) # False

  想要了解更多魔术方法,请前往:

  tutorialsteacher/python/magic-methods-in-python

  或者使用官方文档,只是有一点点难读:

  docs.python/3/reference/datamodel.html#special-method-names

  这里,还要重点安利几种魔术方法:

  一是 _len_ :重载 len() 函数用的。

  二是 _str_:重载 str() 函数用的。

  三是 _iter_:想让object变成迭代器,就用这个。有了它,还可以在object上调用 next() 函数。

  对于像节点这样的类,我们已经知道了它支持的所有属性 (Attributes) :value、left和right,那就可以用 _slots_ 来表示这些值。这样有助于提升性能,节省内存。

  1class Node:

  2 """ A struct to denote the node of a binary tree.

  3 It contains a value and pointers to left and right children.

  4 """

  5 __slots__=('value', 'left', 'right')

  6 def __init__(self, value, left=None, right=None):

  7 self.value=value

  8 self.left=left

  9 self.right=right

  想要全面了解 _slots_ 的优点和缺点,可以看看Aaron Hall的精彩回答:

  stackoverflow/a/28059785/5029595

  4、局部命名空间,对象的属性

  locals() 函数,返回的是一个字典 (Dictionary) ,它包含了局部命名空间 (Local Namespace) 里定义的变量。

  1class Model1:

  2 def __init__(self, hidden_size=100, num_layers=3, learning_rate=3e-4):

  3 print(locals())

  4 self.hidden_size=hidden_size

  5 self.num_layers=num_layers

  6 self.learning_rate=learning_rate

  7

  8model1=Model1()

  9

  10==> {'learning_rate': 0.0003, 'num_layers': 3, 'hidden_size': 100, 'self': <__main__.Model1 object at 0x1069b1470>}

  一个object的所有属性,都存在 _dict_ 里面。

  1print(model1.__dict__)

  2

  3==> {'hidden_size': 100, 'num_layers': 3, 'learning_rate': 0.0003}

  注意,当参数列表 (List of Arguments) 很大的时候,手动把每个参数值分配给一个属性会很累。

  想简单一点的话,可以直接把整个参数列表分配给 _dict_ 。

  1class Model2:

  2 def __init__(self, hidden_size=100, num_layers=3, learning_rate=3e-4):

  3 params=locals()

  4 del params['self']

  5 self.__dict__=params

  6

  7model2=Model2()

  8print(model2.__dict__)

  9

  10==> {'learning_rate': 0.0003, 'num_layers': 3, 'hidden_size': 100}

  当object是用 kwargs** 初始化的时候,这种做法尤其方便 (虽然 kwargs** 还是尽量少用为好) :

  1class Model3:

  2 def __init__(self, **kwargs):

  3 self.__dict__=kwargs

  4

  5model3=Model3(hidden_size=100, num_layers=3, learning_rate=3e-4)

  6print(model3.__dict__)

  7

  8==> {'hidden_size': 100, 'num_layers': 3, 'learning_rate': 0.0003}

  前4个版块就到这里了,至于第5个版块传授了怎样的技巧,先不介绍