Python编程技巧.[环境:Python3.6.0]
字符串处理
字符串最主要的处理之一就是分割.
最基础简单的分割方式就是使用split
函数.
1 | "Hello".split("l") |
哎?出现了一个空字符串!, 这并不是我们想要的, 再讲如何消去它之前, 我们先来说一下为什么会产生.
可以做做这样的小实验:
1 | "aaaa".split("a") |
产生了五个空字符串, 然而我们只传了四个呀. 那么就开始猜测, 会不会是两个字符串的叠加效果呢?(此时可以试试"a".split("a")
)
所以说, 这个情况是在寻找符合条件的字符串的两边时,发现要么没有了, 要么还是原来的条件.
现在提供一个可以快速消去空字符串的方法,不仅仅在这里适用,只要是为了剔除空数据, 都是可以的!
1 | [x for x in ["","","Only",""] if x] |
这种基本方法还是有一点的笨拙的, 如果遇到字符串中含有多个split点时, 就懵逼了.
所以,还是要请出主角–正则表达式re模块
当你遇到了一个问题的时候, 为了解决它, 你想到了使用正则表达式, 这时, 你就有了两个问题. :)
正则模块需要引入.
1 | import re |
小结一下:当字符串不是很复杂的时候,仍然建议使用默认的字符串的split
函数, 因为他更快速.如果遇到较为棘手的切割, 最好使用正则的切割.
想象这么一种场景: 你需要获得文件的后缀名/你想知道你个网址使用的协议是什么.
抽象出来就是判断字符串的开头或者结尾.
就如同方法名一样简单, 两个方法分别用来检测头和尾. startswith
和endswith
.
举个例子:
1 | import os, stat |
方法内可以传入元组进行多适配, 但一定不接受列表.
接下来就可以使用这样的方法找到一堆数据中所有的ftp://
的服务器地址.(略 :) )
下面说一个超实用的正则魔法!
re.sub
可以进行替换, 如果继续利用正则的捕获组就能进行格式的变换,(比如把04/05/2017
变成2017-05-04
).
sub
接受三个参数, 分别是['正则表达式', '替换后的字符串', '需要替换的字符串']
.
接下来再说一下什么是正则中的捕获组.
捕获组简单的说就是一个小整体,还是用实例来说明下:
1 | import re |
一个一个来说明, access.log
的文件类似
5?.6?.2?.?1? - - [03/May/2017:23:19:25 +0800] "GET /??/ HTTP/1.1" 304 0 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.98 Safari/537.36"
这个实例是将所有的时间改为2017-May-03
的形式.
接着来看一下捕获组, 就是那个用括号抱起来的东西. 被抱起来的从0开始计数, 在后面使用反斜线加上组号就可以进行引用.
另外, 正则的捕获组可以添加名字的, 像这样使用: ?P<名字>
, 引用时这样: \g<名字>
.
将上面的例子进行一下更改就变成这样: re.sub(r'(?P<day>\d{2})/(?P<month>[a-zA-Z]{3})/(?P<year>\d{4})', r'\g<day>-\g<month>-\g<year>', log)
.
说完了分割, 接下来再来看另一个重要的操作–拼接.
传统的拼接方法上面也展示过了:
1 | l = ["Hel", "lo,", "Worl", "d"] |
这样拼接还是有一点麻烦的, 更好地方法是使用str.join
方法.
这个方法接受一个可迭代对象作为参数, 调用者(self)就是分割符, 比如:
1 | ",".join(['Hello', 'world']) |
但是,我们遗漏了一种情况, 如果传入的可迭代对象包含多个类型的数据怎么办呢?
先试一试吧:
1 | "".join(['123', 456, '789']) |
呀!报错了! 看样子还是要处理这个问题的.
一种解决方法是使用列表解析:
1 | example = ['123', 456, '789'] |
这样虽然可以处理, 但如果数据很大的话, 会占用很多资源.
所以, 使用Generator
是更优的选择.
因为他的开销更小.
1 | example = ['123', 456, '789'] |
接着再来说说文本的排版.
我们经常遇到的一种情况是, 对一个字典进行输出.
1 | { |
我们期望的输出是:
1 | name : 'J' |
由于键的长度并不一样, 所以直接输出是做不到的.
str
有几个方法可以帮助我们达成这个目标.
准确的说, 这些方法只是填充字符串而已.
1 | s = 'abc' |
另外一个类似的方法是使用format
函数, format
接受一个字符串和格式字符串, 使用起来的效果就像是这样:
1 | format(s, '>10') |
那么现在来完成开头提出的问题, 排版输出键值长度不一致的字典:
1 | kv = { |
除了分割和拼接, 字符串另一个重要的处理就是删除(叫剔除应该更好),
比如剔除两端的空格, 剔除指定字符,等等..
str类仍然封装了很多丰富的方法.
*strip一类方法
1 | s = "! ++Hello== ?" |
*strip不能搞定中间的字符, 我们可以通过切片加拼接的方式完成.
比如:
1 | s = "Hel:lo" |
但是切片的方法略显笨重,来看一下字符串的replace
方法(别忘了正则的sub
方法呀.)
1 | s = "123\n456\n789" |
但是, 你现在也注意到了, replace
方法只能匹配一个字符, 如果遇到多个就不行了.
比如剔除字符串s = "123.456,789;"
中所有的特殊字符.
这个时候还是需要靠强大的正则了, 也就是之前说的sub
函数.
1 | re.sub("[.,;]", "",s) |
就可以了.
最后再来说一下translate
方法, str
和re
, 他们的作用稍有不同, 我们分开来说.
首先, translate
方法可以实现 伪加密(更换顺序)和剔除指定字符的效果. 一个联动的函数是str.maketrans
.
其中, translate
接受一个参数, 一个映射表(需要实现__getitem__
接口), 这个映射表一般有str.maketrans
产生.
str.maketrans
最多接受三个参数, 前两个为映射字符串, 最后一个是将选定的字符串映射成None
, 被映射成None
的字符串会在translate
的过程中被删除.
1 | s = "123\n456\t789\r" |
到此, 字符串处理的基本就结束了.
函数
Python有个特别好用的函数特性, 叫做装饰器.
装饰器可以帮助我们减少大量代码的冗余, 使得函数的利用更加便利.
一个非常的经典的例子就是这样:
1 | def fib(n): |
这是一个非常经典的斐波那契数列递归,但如果使用这个函数进行fib(50)
的话, 也许你可以先去泡杯咖啡.
一种优化方案是:
1 | def fib(n, cache=None): |
在这里,我们建立了一个缓存池(原函数缓慢的原因就是因为大量的重复计算, 计算得到的值都没有得到保存.), 如果能从缓存池中读取, 就不再计算而直接读取.
对于这个问题,事实上好多递归函数都存在这个问题.
比如爬楼梯问题:
一个共有10个台阶的楼梯, 从下面走到上面, 一次只能迈1-3的阶梯, 在不能后退的情况下, 走完楼梯一共有多少种方法?
传统的递归解法是:
1 | def climb(n, steps): |
计算climb(50, (1,2,3))
试试, 你的咖啡已经凉了.
我们仍然可以考虑加上缓存的方法, 但是那就意味着还要再重新书写一遍近乎是一样的代码.
这时引入装饰器, 装饰器也是个函数, 接受一个函数, 在他的内部生产一个符合要求的函数(比如生成缓存池, 记录日志等等), 再调用参数(也就是传进来的函数), 接着将这个包装的函数返回出去.就成为了一个满足要求的函数.
本来的过程是这样的:
1 | func = wraaper(func) |
这样有点麻烦, 所以Python提供了这样的语法糖:
1 |
|
为上述递归问题写一个装饰器就像是这样:
1 | def caching(func): |
这样再调用fib
和climb
, 就可秒出结果.
再比如说一个很常用的功能, 我想知道我的函数执行了多长时间.我就可以也写个装饰器:
1 | import time |
但是这个装饰器不是太实用, 我们可以添加让他在超过一定时间就记录日志的功能.
1 | import time, logging, random |
接着运行的结果就像是这样:
1 | Test |
但是这个装饰器仍然不够灵活, 他的时间阈值是个固定值,如果是个可调节的值就更好了.
为了达成装饰器同样可以接受参数, 我们就需要写个三层嵌套的装饰器.
也就是说: 一个返回装饰器的函数.
1 | def log(text): |
这就是一个最简单的展示接受参数的装饰器.
使用中你会发现这样的情况.
1 | @log("execute") |
看起来很正常是吗.但是如果你试试查看now.__name__
的时候就会发现, now
已经被wrapper
污染了.
所以返回的是wrapper
.这并不是我们所希望的.
Python
的functools
包内的wraps
提供了我们需要的功能.
由于wraps
也是一个装饰器, 所以使用起来也非常方便, 在原基础上加上这样的一行代码即可解决问题 :
1 | import functools |