Python近几年来热度不断增加,依赖于开发者社区的无私贡献,Python从程序员圈子迅速火到了自动化办公、机器学习、数据分析、运维等领域,作为从脚本早期就关注其发展的使用者,也欣慰自己点亮了一个道路正确的"技能树"。可以说在我遇到Python之之后才开始真正将我的技能带入到了日常生活中,切实的提高了工作效率也改变了我的生活方式。Python就像是一个多年老友,知心而且可靠,遇到的各类问题只要是计算机能做的,我知道它都能帮我高效解决。在这里我想写一个很长的故事,和大家分享Python的各种特点以及我日常工作生活中是如何使用它的。
Python的一些特点
如果你没有装过Python,建议你使用Anaconda一次性搞定各种常用的Python依赖库,Anaconda是一个科学计算、数据处理开发套件,集成了常用的相关Python库并且自带包管理工具,能一站式提供绝大多数常用包。
Anaconda最新版自带的解释器是Python3,Python2和Python3并不兼容,如果你对Python版本没有历史包袱的话,推荐使用Python3,本文所有代码基于Python3演示。
下面正式为大家介绍,可爱的Python。
Python之禅
Python有一个彩蛋,在脚本中import this
的话可以打印出”The Zen of Python“,Python之禅,可以认为这是Python的设计思想,也是给各位(不只是Python)程序员的忠告,译文不再完整粘贴,摘录几句——
大家可以自己尝试翻译一下,也请尽量记住这些忠告,在写代码的时候保持理性和克制。
Jupyter Notebook以及帮助信息
Python的编辑器非常多,PyCharm、Wing、vscode甚至记事本都不影响我们发挥,但是在需要快速验证想法,或者希望快速了解某些库的话个人更推荐Jupyter Notebook,它是一个网页版的IDE,有拼写提示,能快捷的显示各库的帮助信息;网页写代码,服务端执行。对于数据分析、脚本验证再合适不过了。Jupyter Notebook集成在Anaconda中,在终端上输入jupyter notebook
,将输出的URL复制到浏览器中即可使用。如果没有已经编写好的脚本,需要新建一个Python3脚本,如图。
在需要查看某个库/函数的帮助信息时,在要查询的主体后面加'?'即可——
想知道库中有哪些类型/函数,使用dir(lib)
预览(也可以预览类型的属性和方法)——
希望详细了解语言特性或者库函数的话,还是首选官方网站(https://docs.python.org/3/)
想直接执行dos命令或shell,在语句前加'!'即可——
按tab
键开始拼写提示。其他还有各种快捷操作和插件,不再详细介绍,感兴趣的同学详细研究吧。
常用数据结构
Python中一切皆对象,甚至包括函数、包、类,都是对象,可以用下面方法验证——
因此请了解下面列举的各种类型变量,本质上都是对象。
数值类型
Python的数值类型有int/float/bool/complex
几种,需要注意数值类型在Python中是不可变类型,意思是该变量的值不能被改变。
我上面那句没有写错你也没看错,数值类型在Python中的确无法改变,那如下代码发生了什么?
a的类型是int,初始值是1。在执行a+=1
后,a变量指向的就不再是之前的值1
了,而是一个新的对象,也就是说加法执行完毕后a指向的对象变了。具体可以用id
来验证,这个函数用来返回变量指向的对象"地址"——
可以看出执行前后a指向的对象地址已经变了。
这个结论有点颠覆三观,但为了提高执行效率Python的确就是这么实现的。数值类型都是不可变的,其他几种类型同理,不再具体举例了。
字符串
字符串在Python中同样是不可变的——
字符串可以用单引号、双引号和三个单/双引号包括,其中单引号和双引号没有区别,可以在字符串也包含引号时用不同的方式区分,例如
三个引号包括的内容被完整引用,包括缩进、换行等,例如
除此之外,Python的字符串有大量非常方便的函数,下面举几个简单的例子。
格式化
Python的字符串格式化操作非常简洁,例如想将各种类型变量格式化到一个字符串中,可以
上面代码混合了字符串、数值、字典的格式化,一个format
就能搞定。更多的控制细节大家可以自行查询。
字符串重复打印
如何重复打印100次字符串?其他语言可能需要用循环来控制,Python则使用“乘法”来实现——
字符串分割
如果有日志字符串格式类似2020-9-1 19:49:06 [INFO] aaa|bbb|ccc
,如果希望抽取时间,可以按空格分割并把的第二个空格前面的字符串合并,例如
如果想把日志级别抽取出来,除了正则表达式之外,还可以按[ ]
分割,例如
提取bbb一样的道理,例如
字符串查找in
关键字可以用来判断是否包含子串
find
函数可以用来查找子串出现的位置
大小写转换upper/lower
函数用来转换字符串的大小写
去掉空白字符
在文本处理时经常要忽略掉空白字符,例如换行、回车、空格,可以使用strip/lstrip/rstrip
清除,例如
元组
从这里开始就是Python内置的常用容器了,元组(tuple)是一个不可修改的序列,可以随机访问,但无法修改元组本身,也就是说元组创建之后不能增加或删除里面的元素。元组创建方式是t=tuple()
或t=()
——
所谓的元组不能修改,指的是元组本身无法修改,元组中的数据其实是可以改变的——
元组可以转为其他容器、可以切片也可以迭代、可以随机访问、计算长度等——
值得注意的是如上面第一个例子所示,元组(和其他所有容器)可以保存任意类型对象,类型无需相同。
列表
列表是Python中最常用的容器,它与元组不同之处是可以被修改。创建方式是l=list()
或l=[]
。向列表中追加元素——
删除列表中元素——
合并两个列表——
对列表排序——
其他元组支持的操作列表也都支持,不再重复。
集合
集合用来无序保存唯一的元素,创建方式是s=set()
或s={1}
,注意{}
创建的是字典,因此空集合必须用set()
创建。集合可以任意添加和删除元素,常见操作有——
更常用的操作是set之间的操作,例如判断集合的交、并、补——
字典
字典(dict)提供了key-value的映射关系,创建字典的方式是d=dict()
或d={k:v,k2:v2}
。可以通过'[]'设置或获取值,例如——
可以使用del
删除映射关系:del d[key]
。
可以通过key()
返回所有的键,values()
返回所有值,items()
返回所有键值对——
不存在映射关系时直接访问会抛出异常,如果希望key不存在时能返回默认值,可以使用setdefault()
或者get()
推导式
推导式是Python的一个特色语法糖,能让我们用简洁的代码快速生成容器,上述所有容器均支持推导式,例如——
字典一样可以用推导式生成,例如希望将两个列表合并,分别当做字典的key和value——
推导式写起来就和用自然语言描述一样,非常方便。
注意上面的例子中,生成列表和字典时用的是[]
和{}
,但生成元组并未直接用()
,是因为直接用()
的话创建的是生成器对象而非元组。生成器是Python为了节省空间而设计的一种特殊类,它的特点是几乎不占内存但可以和容器一样被遍历,例如——
上面的例子中Python解释器并未生成0~10000的数字,也没有相应的内存占用,但在遍历操作时每一轮循环生成器对象都会返回下一个数字,因此节省了内存占用(尤其是容器保存较为复杂的对象时),但由于容器并未实际生成,不允许对生成器对象进行随机访问或切片,需要此类操作前可以先转为列表或元组。
for/with
for是Python中循环常用关键字,可以用来遍历容器或生成器对象,例如需要循环10次,可以用如下代码——
需要遍历容器或生成器对象,可以用如下代码——
有时需要遍历容器的前几项,可以用切片——
但生成器对象无法切片就不能使用该方法了,粗暴一点可以用一个变量计数——
更符合Python风格(pythonic,我下次打算说说这个)做法是用enumerate
函数修饰可迭代对象(比如容器或者生成器),该函数返回一个迭代次数、迭代内容构成的元组——
with关键字用来实现上下文管理。在C++中一个常用技巧RAII,即在对象的构造和析构中进行资源申请和释放,用语言特性保证资源在合适时机被释放;Python的with关键字即实现类似效果。最常用的场景是打开文件——
with关键字保证了文件在离开with语句块后被关闭,无论do_sth
函数是正常返回还是抛出异常。如果想手写出此效果,需要写类似下面的代码——
相比之下非常臃肿。
我们也可以自己编写符合条件的类,实现其他资源的自动管理,只需要实现__enter__
和__exit__
方法即可——
C++程序员的疑惑
作为一名传统的C++程序员,Python带来的灵活性和语言特性总是让人是不是的收获意外惊喜(或惊吓),下面列举一些我平时发现的Python有违C++常识的内容。
任意添加属性
解释型语言就可以为所欲为,我可以定义一个空类,在对象中给它增加各种属性——
有人说设计模式是编程语言支持度不足的情况下产生的变通方案,至少在Python这个特性下策略模式不需要复杂设计就能轻松实现——
事物总是分两面,有时太灵活的技巧会导致代码维护难度加大,当我们想回归传统程序员,限制对象不能随意增加属性时,可以将支持的属性名放在__solts__
中,例如——
神奇的函数参数
函数默认值
这可以说是Python的一个隐藏的坑了,函数的默认参数只会被计算一次。比如——
上面代码中第二次调用测试函数,竟然返回了[1,1],似乎第二次调用时并没有创建新的list。原因在于函数的默认参数只在函数定义时计算一次,以后多次调用不会再创建新的参数。
函数内改变参数
C++中如果一个函数接收的是对象,在函数内对这个对象的任何修改都是针对函数内临时创建的对象,而非本函数被调用时传入的对象。在Python中,这个原则变的并不十分明显,例如函数接收可变类型的话,函数内修改是可以反映到传入参数的——
但是如果传入的是不可变类型(你一定还记得数值类型、字符串类型是不可变类型),那么Python函数的行为和C++类似,函数内的修改无法反映到传入参数——
这也是可以理解的,因为不可变类型是无法修改的,这个原则即使作为参数传给函数时也需要保持。只是我们在编写和阅读代码时,需要保持这样的认识——函数内修改参数的行为与参数的类型是有关系的。
伪多线程
Python的一个遗憾是由于全局锁的存在(GIL,其实GIL并不是Python语言的特性,而是使用范围很广的CPython实现引入的一个机制,这里为了简略描述,就叫Python了请注意辨别)并不支持完整的多线程,在同一个进程内的多个线程是无法并发运行的。好在如果我们的程序是I/O密集型的,依然可以考虑使用多线程来加快运行速度;但如果程序是CPU密集型的话,就需要用多进程来实现真正的并发了。好消息是Python的多进程库提供了和多线程库几乎一致的接口,几乎可以像编写多线程程序一样开发多进程。
限于篇幅具体不再举例子了,感兴趣的同学可以自行查阅官方手册了解接口细节。