几个有趣的python技巧

2019 年第 82 篇文章,总第 106 篇文章

标题 | python-is-cool

作者 | chiphuyen

原文 | https://github.com/chiphuyen/python-is-cool

译者 | kbsc13("算法猿的成长"公众号作者)

声明 | 翻译是出于交流学习的目的,欢迎转载,但请保留本文出于,请勿用作商业或者非法用途

导读

这篇文章主要是介绍一些 python 的技巧。

采用的 python 版本是 3.6+

本文的目录如下:

  • Lambda, map, filter, reduce

  • 列表操作

  • 类和魔法方法

  • 本地命名空间和对象的属性

  • 疯狂的导入

1. Lambda, map, filter, reduce

lambda 是创建匿名函数,下面是一个使用的例子,其中 square_fnsquare_ld 这两个都是相同作用的函数:

def square_fn(x):return x * xsquare_ld = lambda x: x * xfor 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

需要注意的是,mapfilter 返回的对象都是迭代器,也就是说它们的数值并没有被存储下来,只是在需要的时候生成,所以如果调用了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 定义的有名字函数相比,会稍微慢一些,因此更建议使用带名字的函数。


2. 列表操作

python 中的列表也是有很多特别的技巧。

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] 表示在一个列表中,从索引 xy 中取出元素,步长是 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]
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)# This line of code is from
# https://github.com/sahands/python-by-example/blob/master/python-by-example.rst#flattening-lists
2.5 List vs generator

为了解释列表和生成器的区别,这里用一个创建一个列表的所有字符串的 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 gramsprint(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 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__ 了,下面是实现一个 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 对象,不过输出结果并非很好解释:

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

接着,我们可能想进一步实现两个节点的比较数值的功能,这里通过 __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 = 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

当然也可以查看官方文档的说明,不过阅读起来会有些难度:

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

其中推荐以下这些方法:

  • __len__ :重写 len() 方法

  • __str__:重写str() 方法

  • __iter__:如果想让对象可以迭代,可以继承这个方法,并且还可以调用 next() 方法

对于类似 Node 这样的类,即我们确定其支持的所有属性(比如对于 Node ,这里就是指 value, left, right 着三个属性),可以采用 __slots__ 来表示这些数值,这有利于提升性能和节省内存空间。想更详细了解 __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 = right

4. 本地命名空间,对象的属性

locals() 函数会返回一个字典,它包含了所有定义在本地命名空间的变量,例子如下所示:

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}

注意手动将所有参数分配到对应的属性会非常麻烦,特别是在参数列表比较大的时候。为了避免这种情况,可以利用对象的 __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 来进行初始化,会更加的方便,不过**kwargs 应该尽量少使用:

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. 疯狂的导入

通常会陷入这种疯狂的导入操作* 的例子如下所示:

file.py  文件中

from parts import *

这个写法非常不负责任,它是将另一个模块的一切都导入到当前的模块,包括那个模块的导入的内容,比如说,parts.py 模块可能是这样的:

import numpy
import tensorflowclass Encoder:...class Decoder:...class Loss:...def helper(*args, **kwargs):...def utils(*args, **kwargs):...

由于 parts.py 没有指定 __all__ ,所以 file.py 会导入 Encoder, Decoder, Loss, utils, helper,以及 numpytensorflow

如果我们只想让 Encoder, Decoder, Loss 被导入到另一个模块中使用,那么就需要指定 __all__ 参数:

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

通过上述代码,当有另一个文件也是直接采用 from part import * 的做法,那么只会导入给定的  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


欢迎关注我的微信公众号--算法猿的成长,或者扫描下方的二维码,大家一起交流,学习和进步!

如果觉得不错,在看、转发就是对小编的一个支持!

推荐阅读

  • 数据科学家令人惊叹的排序技巧

  • 一文了解类别型特征的编码方法

  • 快速入门 Jupyter notebook

  • Jupyter 进阶教程

  • 只需几行代码,即可实现多线程和多进程操作

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/408461.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

20191215周学习总结

最近会打算每周总结一下学习的内容&#xff0c;主要内容可能是看过的书的一些学习笔记、论文阅读、学习的知识点以及推荐一些文章。这周的学习包括&#xff1a;推荐系统的知识点整理机器学习的技巧学习linux下两台机器的ssh免密登陆方式书籍阅读效率方法推荐系统因为工作方向的…

玩转12306之系统登录

【申明&#xff1a;本文所涉及的技术和分析的目的都是为了学习和交流&#xff0c;任何人使用文中所提的技术或成果做出的违法事情与我无关&#xff0c;大家购买火车票还是去12306官网上去购买。】 从今天起&#xff0c;我开始分析12306网站的Http请求&#xff0c;以及编写一个客…

Nginx快速搭建和基本使用

2019年第 83 篇文章&#xff0c;总第 107 篇文章最近在工作中项目需要上线&#xff0c;所以也了解到关于一些部署上线的知识内容&#xff0c;Nginx 就是其中一个知识点&#xff0c;主要是可以用它来进行负载均衡&#xff0c;本文的目录如下&#xff1a;简介安装配置基本使用简介…

第二期周总结

第二期的周总结&#xff0c;这次学习的内容可能没有上次那么广泛&#xff0c;主要是因为这周我负责的模块需要测试并进行上线&#xff0c;所以主要学习了解的就是工程开发方面的内容&#xff0c;准确说是部署上线的内容&#xff0c;所以本周主要简单总结这次上线过程的一些内容…

AI知识点(1)--激活函数

2019年第 84 篇文章&#xff0c;总第 108 篇文章本文大约 5000 字&#xff0c;阅读大约需要 15 分钟AI知识点&#xff08;AI Knowledge&#xff09;系列第一篇文章--激活函数。本文主要的目录如下&#xff1a;激活函数的定义为什么需要激活函数常见的激活函数1. 激活函数的定义…

Linux 定时执行shell 脚本

2019年第 85 篇文章&#xff0c;总第 109 篇文章本文大约2000字&#xff0c;阅读大约需要6分钟crontab 可以在指定的时间执行一个shell脚本以及执行一系列 Linux 命令。定时执行shell 脚本简单给出执行 shell 脚本的步骤。首先是编写一个测试脚本--test.sh# 创建脚本 $ vim tes…

RS(1)--10分钟了解什么是推荐系统

总第 110 篇文章&#xff0c;本文大约 3200 字&#xff0c;阅读大约需要 10 分钟2020 年第一篇技术文章&#xff0c;以一个新的系列开始--推荐系统&#xff08;Recommend System&#xff09;&#xff0c;第一篇文章会简单介绍推荐系统的定义和应用&#xff0c;目录如下&#xf…

当搭配遇上个性化推荐

总第 111 篇文章&#xff0c;本文大约 3000 字&#xff0c;阅读大约需要 10 分钟今天介绍的是一篇个性化搭配推荐的论文&#xff0c;是 2017 年时候的论文&#xff0c;这也是比较早的开始结合搭配和个性化推荐的一个工作&#xff0c;基于度量学习和排序学习的方法。论文题目&am…

2020年周记(1/50)

总第 112 篇文章&#xff0c;本文大约 1200 字&#xff0c;阅读大约需要 3 分钟正如标题所言&#xff0c;希望 2020 年能写满 50 篇周记吧&#xff0c;刚好前两周没有发&#xff0c;所以希望接下来每周完成一篇。周记的内容主要是这几方面的内容&#xff1a;工作学习阅读&…

python版代码整洁之道

总第 113 篇文章&#xff0c;本文大约 8000 字&#xff0c;阅读大约需要 20 分钟原文&#xff1a;https://github.com/zedr/clean-code-pythonpython 版的代码整洁之道。目录如下所示&#xff1a;介绍变量函数1. 介绍软件工程的原则&#xff0c;来自 Robert C. Martins 的书--《…

MVC 3.0错误 HTTP 404您正在查找的资源(或者它的一个依赖项)可能已被移除,或其名称已更改,或暂时不可用。请检查以下 URL 并确保其拼写正确。...

MVC3.0框架开发项目&#xff1a; 有时在程序运行的时候会出现“HTTP 404。您正在查找的资源(或者它的一个依赖项)可能已被移除&#xff0c;或其名称已更改&#xff0c;或暂时不可用。请检查以下 URL 并确保其拼写正确。”的错误提示。 在这里我们以运行时打开登录页面&#xff…

2020年1月总结

总第 114 篇文章&#xff0c;本文大约 1300 字&#xff0c;阅读大约需要 4 分钟这是 2020 年的第一篇月总结&#xff0c;总结的内容和周记差不多&#xff0c;也还是从这几个方面进行总结&#xff1a;工作学习阅读&写作2月计划工作这个月的工作时间大概是2周多一点&#xff…

python技巧(1)--如何转换itertools.chain对象为数组

总第 115 篇文章&#xff0c;本文大约 900 字&#xff0c;阅读大约需要 3 分钟之前做1月总结的时候说过希望每天或者每2天开始的更新一些学习笔记&#xff0c;这是开始的第一篇。这篇介绍的是如何把一个 itertools.chain 对象转换为一个数组。参考 stackoverflow 上的一个回答&…

python技巧(2)--碾平列表和列表去重

总第 116 篇文章&#xff0c;本文大约 1000 字&#xff0c;阅读大约需要 3 分钟今天介绍和列表相关的两个小技巧&#xff1a;碾平列表&#xff08;flatten list&#xff09;&#xff0c;也就是列表里的元素也带有列表的情况&#xff1b;列表去重&#xff0c;保留原始顺序和不保…

原来电脑并不需要重装系统才能恢复出厂设置,这个操作学起来!

前言 小伙伴们应该都知道手机上有恢复出厂设置的功能&#xff0c;如果想要把手机送给朋友或者卖给别人&#xff0c;就会先恢复出厂设置。 但换到Windows电脑上之后&#xff0c;如果出现同样的情况&#xff0c;就会第一时间想到重装系统。就好像Windows电脑上不存在恢复出厂设…

2020年周记(2/50)

总第 117 篇文章&#xff0c;本文大约 1400 字&#xff0c;阅读大约需要 5 分钟因为春节假期的延长&#xff0c;中间还是休息过长&#xff0c;少了两周的周记了&#xff0c;这是2020年的第二篇周记&#xff0c;内容还是这几个方面&#xff1a;工作学习阅读&写作其他下周计划…

一日一学:返回排序好的列表的索引顺序

总第 118 篇文章&#xff0c;本文大约 700 字&#xff0c;阅读大约需要 2 分钟今天介绍的是对列表排序后&#xff0c;返回排序好的索引顺序。问题描述&#xff1a;给定一个列表 [2, 3, 1, 4, 5] &#xff0c;怎么返回排序后的索引顺序&#xff0c;即 [2,0,1,3,4] ?解决方法&a…