社区所有版块导航
Python
python开源   Django   Python   DjangoApp   pycharm  
DATA
docker   Elasticsearch  
aigc
aigc   chatgpt  
WEB开发
linux   MongoDB   Redis   DATABASE   NGINX   其他Web框架   web工具   zookeeper   tornado   NoSql   Bootstrap   js   peewee   Git   bottle   IE   MQ   Jquery  
机器学习
机器学习算法  
Python88.com
反馈   公告   社区推广  
产品
短视频  
印度
印度  
Py学习  »  Python

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

王的机器 • 5 年前 • 490 次点击  




本文含 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
下划线占位符


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

apple_mktcap = 1084000000000facebook_mktcap = 458870000000total = apple_mktcap + facebook_mktcapprint(total)
1542870000000.0


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

apple_mktcap = 1_084_000_000_000facebook_mktcap = 458_870_000_000total = apple_mktcap + facebook_mktcapprint(total)
1542870000000


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

print(f'Total is {total:,} USD')
Total is 1,542,870,000,000 USD



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





2
枚举


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

languages = ['Python', 'R', 'Matlab', 'Julia']


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

index = 0for lang in languages:    print(index, lang)    index +=1
0 Python
1 R
2 Matlab
3 Julia


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


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

for index, lang in enumerate(languages):    print(index, lang)
0 Python
1 R
2 Matlab
3 Julia


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

for index, lang in enumerate(languages, start=1):    print(index, lang)
1 Python
2 R
3 Matlab
4 Julia



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





3
打包


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

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


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

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


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

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


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


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

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



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

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


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


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


你品,你细品。

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



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





4
解包


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

a, b = 1, 2print(a)print(b)
1
2



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

a, _ = 1, 2print(a)
1



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

a, b, c = 1, 2
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
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 得到的是个空集 []。

a, b, *c = 1, 2print(a)print(b)print(c)
1
2
[]



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

a, b, *c = 1, 2, 3, 4, 5print(a)print(b)print(c)
1
2
[3, 4, 5]



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

a, b, *c, d = 1, 2, 3, 4, 5print(a)print(b)print(c)print(d)
1
2
[3, 4]
5



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

a, b, *_, d = 1, 2, 3, 4, 5print(a)print(b)print(d)



    
1
2
5


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





5
动态属性


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

class Instrument():    pass
inst = Instrument()


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

inst.notional = 100_000_000inst.maturity = '2025-03-25'
print(inst.notional)print(inst.maturity)
100000000
2025-03-25


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

first_key = 'notional'first_val = 100_000_000


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

inst = Instrument()inst.first_key = first_val


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

print(inst.notional)
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
input-99-50eeb1451324> in <module>
----> 1 print(inst.notional)

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



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

print(inst.first_key)
100000000



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


  • 参数 1 - 对象

  • 参数 2 - 属性名的变量名

  • 参数 3 - 属性值的变量名


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

inst = Instrument()setattr(inst, first_key, first_val)print(inst.notional)
100000000



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


  • 参数 1 - 对象

  • 参数 2 - 属性名的变量名


代码如下:

getattr(inst, first_key)
100000000



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

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())。

inst = Instrument()for key, val in inst_info.items():    setattr(inst, key, val)


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

for key in inst_info.keys():    print( key, '|', getattr(inst, key))
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 来添加若干个空白,将每个属性值的起始位置对齐。

for key in inst_info.keys():    print( f'{key:18


    
s}|', getattr(inst, key))
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() 函数可以做到要求用户主动输入,但是输入的密码任何人都可见,这还是密码吗?

username = input('Username: ')password = input('Password: ')print('Logging In...')

Username: Steven
Password: 1031
Logging In ...



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

from getpass import getpassusername = input('Username: ')password = getpass('Password: ')print('Logging In...')

Username: Steven
Password: ········
Logging In ...



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





7
总结


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


六个技巧总结如下:


  • 下划线占位符:容易辨认大数的位数

  • 枚举函数 enumerate()不需要显性创建索引

  • 打包函数 zip():能同时遍历多个迭代器

  • 解包将值赋给正确的变量

  • 动态属性 setattr():用尽可能少的代码快速创建对象

  • 密码函数 getpass():让输入的密码不可见


用起来,酷起来。


Stay Tuned!


Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/56773
 
490 次点击