前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >听说你会玩 Python 系列 1 - 六酷技巧

听说你会玩 Python 系列 1 - 六酷技巧

作者头像
用户5753894
发布2020-03-27 16:49:18
9850
发布2020-03-27 16:49:18
举报
文章被收录于专栏:王的机器王的机器

本文含 6318 字,4 图表截屏

建议阅读 32 分钟

本文是听说你会玩 Python 系列的第一篇

  • 1 - 六酷技巧

0

引言

不管学什么,我个人是非常喜欢小技巧(tricks)的,Python 也不例外。著名 Python 技巧大师 Dan Bader 是这样定义 Python Tricks 的。

A Python Trick either teaches an aspect of Python with a simple illustration, or it serves as a motivating example, enabling you to dig deeper and develop an intuitive understanding. Dan Bader

Python Trick 表明简单而直观,但可以激发你继续深挖的兴趣,你会说“原来还可以这样做啊”。本帖就介绍几个我最喜欢的 Trick,使用它们可以让你的代码更 Pythonic。

本帖介绍六种技巧:

  1. 下划线占位符
  2. 枚举
  3. 打包
  4. 解包
  5. 动态属性
  6. 密码函数

它们都非常直观而简单,相信读完之后,肯定有几个技巧会让你惊叹到,原来 Python 还可以这么用。

1

下划线占位符

有时候数字一大,数起来会犯迷糊,看下例。

代码语言:javascript
复制
apple_mktcap = 1084000000000
facebook_mktcap = 458870000000
total = apple_mktcap + facebook_mktcap
代码语言:javascript
复制
1542870000000.0

这个苹果和脸书的市值之和有多少个零啊?数不清楚是吧,在 Python 中,我们可以用下划线占位符(underscore placeholder)来将大数每三位数分段。请注意,多加了下划线,数字还是数值型变量,只是让我们容易辨认大数。

代码语言:javascript
复制
apple_mktcap = 1_084_000_000_000
facebook_mktcap = 458_870_000_000
total = apple_mktcap + facebook_mktcap
print(total)
代码语言:javascript
复制
1542870000000

你看,加个下划线的数字还是可以相加,但是结果还是不好认。还记得 f string 格式化字符串吗?用 :, 来每三位数分段。

代码语言:javascript
复制
print(f'Total is {total:,} USD')
代码语言:javascript
复制
Total is 1,542,870,000,000 USD

“下划线占位符”解决痛点:容易辨认大数的位数。

2

枚举

给定一列表,包含四种计算机语言的元素。

代码语言:javascript
复制
languages = ['Python', 'R', 'Matlab', 'Julia']

如果我们想把每中语言附加对应的索引一来打印出来,怎么写代码呢?最直接的想法就是初始化 index 为 0,然后在运行每个 for 循环后将 index 的值加 1,代码如下。

代码语言:javascript
复制
index = 0
for lang in languages:
    print(index, lang)
    index +=1
代码语言:javascript
复制
0 Python
1 R
2 Matlab
3 Julia

结果是对的,但是这代码你不觉得很丑吗?很不 Pythonic 吗?

Python 有 enumerate() 函数可以一次性返回列表(任意迭代器)的元素以及其对应的索引,代码如下,优雅吗?

代码语言:javascript
复制
for index, lang in enumerate(languages):
    print(index, lang)
代码语言:javascript
复制
0 Python
1 R
2 Matlab
3 Julia

除此之外,你还可以自定义索引的初始值。在实际生活中,一般索引从 1 开始更自然,那么将参数 start 设置为 1 就好了。

代码语言:javascript
复制
for index, lang in enumerate(languages, start=1):
    print(index, lang)
代码语言:javascript
复制
1 Python
2 R
3 Matlab
4 Julia

“枚举函数 enumerate()”解决痛点:不需要显性创建索引。

3

打包

给定一串名字(names)和演员角色(actors),用两个列表存储。

代码语言:javascript
复制
names = ['小罗伯特唐尼', '托比·马奎尔', '克里斯蒂安·贝尔', '杰森·莫玛']
actors = ['钢铁侠', '蜘蛛侠', '蝙蝠侠', '水行侠']

如果我们想把每个名字和角色一一对应起来,可以用上节学到的 enumerate() 函数。我们可以返回 names 里的元素和索引,再用索引来获取 actors 里的元素,代码如下。

代码语言:javascript
复制
for index, name in enumerate(names):
    print(f'{name}是{actors[index]}')
代码语言:javascript
复制
小罗伯特唐尼是钢铁侠
托比·马奎尔是蜘蛛侠
克里斯蒂安·贝尔是蝙蝠侠
杰森·莫玛是水行侠

结果是对的,但是代码不够优雅。来,zip() 函数了解一下?

代码语言:javascript
复制
for name, actor in zip(names, actors):
    print(f'{name}是{actor}')
代码语言:javascript
复制
小罗伯特唐尼是钢铁侠
托比·马奎尔是蜘蛛侠
克里斯蒂安·贝尔是蝙蝠侠
杰森·莫玛是水行侠

zip() 函数将列表(迭代器)中对应的元素打包成一个个元组,然后返回由这些元组组成的列表。上面代码是不是漂亮多了。

再加一个列表如何?zip() 函数表示毫无压力。

代码语言:javascript
复制
universes = ['漫威', '漫威', 'DC', 'DC']
代码语言:javascript
复制
for name, actor, universe in zip(names, actors, universes):
    print(f'{name}是来自{universe}的{actor}')
代码语言:javascript
复制
小罗伯特唐尼是来自漫威的钢铁侠
托比·马奎尔是来自漫威的蜘蛛侠
克里斯蒂安·贝尔是来自DC的蝙蝠侠
杰森·莫玛是来自DC的水行侠

让我们再看一次 zip() 函数的用法,其 3 个参数 names, actors 和 universes 列表中都有 4 个元素,那么在对应的位置 i(从 0 到 3)一个个获取 names[i], actors[i] 和 universes[i],并打包成新列表,因此输出是 4 个列表,每个列表有 3 个元素。

代码语言:javascript
复制
a = zip(names, actors, universes)
print(*a)
代码语言:javascript
复制
('小罗伯特唐尼', '钢铁侠', '漫威')
('托比·马奎尔', '蜘蛛侠', '漫威')
('克里斯蒂安·贝尔', '蝙蝠侠', 'DC')
('杰森·莫玛', '水行侠', 'DC')

结果没问题。需要注意的是 a 实际上是个对象,要看它里面的内容,需要在 a 前面加个 * 字符。

你们现在肯定会想,有了 zip(),那有没有其反向操作的 unzip() 呢?答案是没有,zip() 的反向操作还是 .... zip()!!!

你品,你细品。

代码语言:javascript
复制
a = zip(names, actors, universes)
names, actors, universes = zip(*a)
print(names, actors, universes)
代码语言:javascript
复制
('小罗伯特唐尼', '托比·马奎尔', '克里斯蒂安·贝尔', '杰森·莫玛')
('钢铁侠', '蜘蛛侠', '蝙蝠侠', '水行侠')
('漫威', '漫威', 'DC', 'DC')

“打包函数 zip()”解决痛点:能同时遍历多个迭代器

4

解包

一个简单例子,将 1 和 2 分别赋给 a 和 b,这种操作称为解包(unpack)。

代码语言:javascript
复制
a, b = 1, 2
print(a)
print(b)
代码语言:javascript
复制
1
2

如果你不想要 b 的话,用下划线代替就行了。

代码语言:javascript
复制
a, _ = 1, 2
print(a)
代码语言:javascript
复制
1

但如果等号左右两边元素和变量个数不一样。程序会报错。

代码语言:javascript
复制
a, b, c = 1, 2
代码语言:javascript
复制
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
<ipython-input-77-9dbc59cfd6c6> in <module>
----> 1 a, b, c = 1, 2

ValueError: not enough values to unpack (expected 3, got 2)

用 * 字符可以解决这个问题。将右边的 1 和 2 分别解包给 a 和 b,那么什么都不剩了,因此 c 得到的是个空集 []。

代码语言:javascript
复制
a, b, *c = 1, 2
print(a)
print(b)
print(c)
代码语言:javascript
复制
1
2
[]

如果右边元素多过左边变量呢?从头开始一一解包,再把多余的全部赋给 c。

代码语言:javascript
复制
a, b, *c = 1, 2, 3, 4, 5
print(a)
print(b)
print(c)
代码语言:javascript
复制
1
2
[3, 4, 5]

更进一步,我们还可以从头和尾开始一一解包,再把多余的全部赋给 c。

代码语言:javascript
复制
a, b, *c, d = 1, 2, 3, 4, 5
print(a)
print(b)
print(c)
print(d)
代码语言:javascript
复制
1
2
[3, 4]
5

不想要 c 的话,用 *_ 将其代替即可。

代码语言:javascript
复制
a, b, *_, d = 1, 2, 3, 4, 5
print(a)
print(b)
print(d)
代码语言:javascript
复制
1
2
5

“解包”解决痛点:将值赋给正确的变量。

5

动态属性

这个技巧是我觉得最有用的。首先定一个金融产品的类 Instrument,并创建一个对象 inst。

代码语言:javascript
复制
class Instrument():
    pass

inst = Instrument()

定义 inst 的两个属性并赋值,本金(notional)和到期日(maturity)。

代码语言:javascript
复制
inst.notional = 100_000_000
inst.maturity = '2025-03-25'
代码语言:javascript
复制
print(inst.notional)
print(inst.maturity)
代码语言:javascript
复制
100000000
2025-03-25

现在将属性 notional 和其属性值 10000000 存储在变量 first_key 和 first_val 中。

代码语言:javascript
复制
first_key = 'notional'
first_val = 100_000_000

我们想用 first_key 的值 notional(而不是 first_key 这个字符)来作为属性。

代码语言:javascript
复制
inst = Instrument()
inst.first_key = first_val

打印 inst.notional 会报错,错误是 Instrument 对象中没有 notional 这样的属性名。

代码语言:javascript
复制
print(inst.notional)
代码语言:javascript
复制
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-99-50eeb1451324> in <module>
----> 1 print(inst.notional)

AttributeError: 'Instrument' object has no attribute 'notional'

原因是 inst 把 first_key 这个字符串当成属性名,验证如下。

代码语言:javascript
复制
print(inst.first_key)
代码语言:javascript
复制
100000000

怎么解决这个动态属性的问题呢?即我们要变量的值为属性名,而不是变量本身名称当属性名。用 setattr() 函数,它有三个参数:

  • 参数 1 - 对象
  • 参数 2 - 属性名的变量名
  • 参数 3 - 属性值的变量名

代码如下,这时用 inst.notional 不会报错了。

代码语言:javascript
复制
inst = Instrument()
setattr(inst, first_key, first_val)
print(inst.notional)
代码语言:javascript
复制
100000000

和 setattr() 相对应,你可以用 getattr() 函数来获取属性值,它有两个参数:

  • 参数 1 - 对象
  • 参数 2 - 属性名的变量名

代码如下:

代码语言:javascript
复制
getattr(inst, first_key)
代码语言:javascript
复制
100000000

和静态属性相比,动态属性到底好在哪里呢?以读取欧式期权的特征举例,通常信息以字典(也有其他格式)存储,具体内容如下:

代码语言:javascript
复制
inst_info = {'ID':'9001001', 
             'Effective Date':'2020-03-20', 
             'Maturity Date':'2020-06-20',
             'Notional':10_000_000,
             'Domestic Currency':'USD',
             'Foreign Currency':'EUR',
             'Flavor':'Put',
             'Strike':1.08,
             'Display':'domestic pips',
             'Asset Class':'FX',
             'Ins trument Type':'European Option',
             'Model':'Heston'}

那么当我们创建 inst 对象时,把上面字典的键(key)作为属性名。每种产品具体的特征都不一样,如果用静态属性的将字典转成对象的话,代码会非常乱而且无法管理,但如果用动态属性的话,下面三行代码就能搞定(用 setattr())。

代码语言:javascript
复制
inst = Instrument()
for key, val in inst_info.items():
    setattr(inst, key, val)

用 getattr() 函数来打印出来看结果对不对,两行代码搞定。

代码语言:javascript
复制
for key in inst_info.keys():
    print( key, '|', getattr(inst, key))
代码语言:javascript
复制
ID | 9001001
Effective Date | 2020-03-20
Maturity Date | 2020-06-20
Notional | 10000000
Domestic Currency | USD
Foreign Currency | EUR
Flavor | Put
Strike | 1.08
Display | domestic pips
Asset Class | FX
Instrument Type | European Option
Model | Heston

结果是对的,但也是丑的,用 f string 来添加若干个空白,将每个属性值的起始位置对齐。

代码语言:javascript
复制
for key in inst_info.keys():
    print( f'{key:18s}|', getattr(inst, key))
代码语言:javascript
复制
ID | 9001001
Effective Date | 2020-03-20
Maturity Date | 2020-06-20
Notional | 10000000
Domestic Currency | USD
Foreign Currency | EUR
Flavor | Put
Strike | 1.08
Display | domestic pips
Asset Class | FX
Instrument Type | European Option
Model | Heston

“动态属性 setattr()”解决痛点:用尽可能少的代码快速创建对象。

6

密码函数

当登录时,你需要输入你的用户名和密码,用 input() 函数可以做到要求用户主动输入,但是输入的密码任何人都可见,这还是密码吗?

代码语言:javascript
复制
username = input('Username: ')
password = input('Password: ')
print('Logging In...')
代码语言:javascript
复制
Username: Steven
Password: 1031
Logging In ...

用 getpass() 函数即可,不解释,自己看下图。

代码语言:javascript
复制
from getpass import getpass
username = input('Username: ')
password = getpass('Password: ')
print('Logging In...')
代码语言:javascript
复制
Username: Steven
Password: ········
Logging In ...

“密码函数 getpass()”解决痛点:让输入的密码不可见。

7

总结

六个小技巧,简单直观,但是超级有用。有时候就是用这样的一个函数,你不知道,写出来的代码不优雅,你知道了,写出来的代码真好看。

六个技巧总结如下:

  • 下划线占位符:容易辨认大数的位数
  • 枚举函数 enumerate():不需要显性创建索引
  • 打包函数 zip():能同时遍历多个迭代器
  • 解包:将值赋给正确的变量
  • 动态属性 setattr():用尽可能少的代码快速创建对象
  • 密码函数 getpass():让输入的密码不可见

用起来,酷起来。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-03-25,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 王的机器 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档