英伟达小姐姐开源Python隐藏技巧合集,推出不足半日,获赞2400+

英伟达小姐姐开源Python隐藏技巧合集,推出不足半日,获赞2400+

五大隐藏技巧到底是什么?什么样的特性使得这个小姐姐以前也不敢使用。却在推出后引得各路大神前来围观,更是一跃登上GitHub热榜。

英伟达小姐姐开源Python隐藏技巧合集,推出不足半日,获赞2400+

01 Lambda, map, filter, reduce

1.1 Lambda

lambda关键词用于创建内联函数,下面的square和square是相同的。

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非常适合在回调中或在将函数作为参数传递给其他函数时使用。

They are especially useful when used in conjunction with functions like map, filter, and reduce.

这里需要提醒小伙伴,lambda函数是一次性使用的。每次调用lambda x:dosomething(x)时,都必须创建该函数,如果多次调用lambda x:dosomething(x)(例如,当你在reduce中传递它时),则会影响性能。尽管小姐姐个人也认为lambdas很酷炫,但为了清晰起见,建议尽量使用命名函数。

1.2 Map

map(fn,iterable)将fn应用于iterable的所有元素(例如list、set、dictionary、tuple、string),并返回一个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]

你也可以将map与多个iterable一起使用。例如,如果要计算具有真标签的简单线性函数f(x)=ax+b的均方误差时,这两种方法是等效的:

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(diff)之后,diff变为空。如果要保留diff中的所有元素,请使用list(diff)将其转换为列表。

1.3 Filter

filter(fn,iterable)的工作方式与map相同,只是fn返回布尔值,filter返回iterable中fn返回true的所有元素。

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

print(list(bad_preds))

==> [0.8100000000000006, 0.6400000000000011]

1.4 Reduce

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

02 List manipulation

Python lists are super cool.

2.1 Unpacking

我们可以按以下元素展开列表:

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

2.2 Slicing

我们可以使用[::-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获取列表的每个zth元素”。当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]

2.3 Insertion

我们可以将列表中某个元素的值更改为另一个值。

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]

2.4 Flattening

我们可以使用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)

……

2.5 List vs generator

为了说明列表和生成器之间的区别,让我们看一个从标记列表中创建n-gram的示例。

创建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 grams

print(ngrams(tokens, 3))

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

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

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

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

在上面的示例中,我们必须同时存储所有n-gram。 如果文本具有m个标记,则内存要求为O(nm),当m大时可能会出现问题。

我们可以使用生成器在需要时生成下一个n-gram,而不是使用列表来存储所有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元语法的另一种方法是使用切片来创建列表:[0、1,...,-n],[1、2,...,-n + 1],...,[n- 1,n,...,-1],然后将它们压缩在一起。

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')

注意,要创建切片,使用(范围(n)中的i使用标记[…],而不是范围(n)中的i使用标记[…]。[]是返回列表的常规列表理解。()返回生成器。

03 Classes and magic methods

在Python中,魔术方法以双下划线__(也称为dunder)作为前缀和后缀。 最著名的魔术方法可能是__init__。

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

有关此处支持魔术方法的完整列表,可参见此处的官方Python文档(只是有点难读)。

强烈推荐的一些方法:

__len__:重载len()函数。

__str__:重载str()函数。

__iter__:如果你希望对象成为迭代器。 这也允许你在对象上调用next()。

像Node这样的类,我们知道它们可以支持的所有属性(像value,left和right),也能想到使用__slots__来表示这些值,以提高性能并节省内存。 要全面了解__slots__的优缺点,可以参阅Aaron Hall在StackOverflow上给出的答案。

04 local namespace, object's attributes

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}

05 Wild import

大家都会遇到这样的导入*,看起来像这样:

file.py

另一行代码

from parts import *

这是不负责任的,因为它将导入模块中的所有内容,甚至导入该模块。 例如,如果parts.py看起来像这样:

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将与numpy和tensorflow一起导入Encoder,Decoder,Loss,utils,helper。

如果我们只导入Encoder,Decoder和Loss并在另一个模块中使用,则应使用__all__关键字在parts.py中进行指定。

parts.py

新一行代码

__all__ = ['Encoder', 'Decoder', 'Loss']

import numpy

import tensorflow

class Encoder:

...

如果用户不负责任地对零件野蛮式导入,则他们只能导入编码器,解码器和损耗。 就个人而言,我认为__all__很有帮助,因为它使我对该模块有了一个总体的了解。