英伟达小姐姐的Python7大技巧合集,Github点赞量高达2500+

对于程序员来说,GitHub 绝对是一个卧虎藏龙的地方,作为一个全球最大的代码托管平台!我相信大部分开发者都在上面找过轮子、分享代码。在 GitHub 上你总会发现别人更优秀,对代码的理解与使用更是出神入化。

今天我就给大家分享一位英伟达小姐姐Chip Huyen的代码,一个集合为python-is-cool,截止目前她已分享了7个 Python 的魔法技能,在分享之后不到半天的时间,已经收获了2500+赞。更详细代码,在下方公众号后台回复:魔法代码。

那么,这份如此收到大众喜爱的代码,到底长什么样子?

1、 Lambda, map, filter, reduce

lambda关键字用于创建内联函数。下面的函数square_fn和square_ld具有相同的作用。

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) 会把 fn 应用在 iterable 的所有元素上,然后返回结果

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]

上面使用为一个 iterable,map 也可以有不止使用一个 iterable。

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

a, b = 3, -0.5
xs = [2, 3, 4, 5]
labels = [6.4, 8.9, 10.9, 15.3]

# Method 1: 循环
errors = []
for i, x in enumerate(xs):
    errors.append((a * x + b - labels[i]) ** 2)
result1 = sum(errors) ** 0.5 / len(xs)

# Method 2: 使用 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

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]

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

2、列表操作

拉平 (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)
插入 (Insertion)

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

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]
列表vs生成器

要想知道列表和生成器的区别在哪,看个例子:从token列表里面创建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-gram同时储存起来的。如果文本里有m个token,内存需求就是 O(nm) 。m值太大的话,存储就可能成问题。

只要让 ngrams 函数,用 yield 关键字返回一个生成器,然后内存需求就变成 O(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']

3、魔术方法

这里,还要重点介绍几种魔术方法:

  • len :重载 len() 函数用的。
  • str:重载 str() 函数用的。
  • iter:想让object变成迭代器,就用这个

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

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

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

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>}

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

print(model1.__dict__)

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

当参数列表 (List of Arguments) 很大的时候,手动把每个参数值分配给一个属性会很累。想简单一点的话,可以直接把整个参数列表分配给 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}

当object是用 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}

5、import *

在使用引入数据包的时候,是否这样使用过?

from parts import *

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

import numpy
import tensorflow
class Encoder:
    ...

class Decoder:
    ...

class Loss:
    ...

def helper(*args, **kwargs):
    ...

def utils(*args, **kwargs):
    ...

如果我们不全用使用,比如我们使用Encoder, Decoder, Loss,我们只需使用关键字 all 就可以了


 __all__ = ['Encoder', 'Decoder', 'Loss']
import numpy
import tensorflow
class Encoder:
    ...

6、计时函数

当我们需要比较两个程序的运行效率时,我们通常使用的为 time.time() 然后print时差,比如下面的斐波纳切函数:

def fib_helper(n):
    if n < 2:
        return n
    return fib_helper(n - 1) + fib_helper(n - 2)

def fib(n):
    """ fib is a wrapper function so that later we can change its behavior
    at the top level without affecting the behavior at every recursion step.
    """
    return fib_helper(n)

def fib_m_helper(n, computed):
    if n in computed:
        return computed[n]
    computed[n] = fib_m_helper(n - 1, computed) + fib_m_helper(n - 2, computed)
    return computed[n]

def fib_m(n):
    return fib_m_helper(n, {
    
    0: 0, 1: 1})

计算两个函数的运行时间

import time

start = time.time()
fib(30)
print(f'Without memoization, it takes {time.time() - start:7f} seconds.')

==> Without memoization, it takes 0.267569 seconds.

start = time.time()
fib_m(30)
print(f'With memoization, it takes {time.time() - start:.7f} seconds.')

==> With memoization, it takes 0.0000713 seconds.

如果您想计时多个功能,可能不得不一遍又一遍地写相同的代码,我相信这不是一种明智的选择。那么这个时候装饰器派上用场了

def timeit(fn): 
    # *args and **kwargs are to support positional and named arguments of fn
    def get_time(*args, **kwargs): 
        start = time.time() 
        output = fn(*args, **kwargs)
        print(f"Time taken in {fn.__name__}: {time.time() - start:.7f}")
        return output  # make sure that the decorator returns the output of fn
    return get_time 

此时我们只需要将上述函数按照如下方法使用:

@timeit
def fib(n):
    return fib_helper(n)

@timeit
def fib_m(n):
    return fib_m_helper(n, {
    
    0: 0, 1: 1})

fib(30)
fib_m(30)

==> Time taken in fib: 0.2787242
==> Time taken in fib_m: 0.0000138
7、@functools.lru_cache缓存

缓存是一项非常重要的技术,Python提供了一个内置的装饰器来为您的函数提供缓存能力。如果要之前使用的fib_helper计算的斐波那契数,则只需在functools种添加lru_cache

import functools

@functools.lru_cache()
def fib_helper(n):
    if n < 2:
        return n
    return fib_helper(n - 1) + fib_helper(n - 2)

@timeit
def fib(n):
    """ fib is a wrapper function so that later we can change its behavior
    at the top level without affecting the behavior at every recursion step.
    """
    return fib_helper(n)

fib(50)
fib_m(50)

==> Time taken in fib: 0.0000412
==> Time taken in fib_m: 0.0000281
推荐阅读

更多精彩内容,关注微信公众号『Python学习与数据挖掘』

为方便技术交流,本号开通了技术交流群,有问题请添加小助手微信号:connect_we,备注:加群来自CSDN,欢迎转载,收藏,码字不易,喜欢文章就点赞一下!谢啦
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_38037405/article/details/107141008