本文首发于“生信补给站”公众号 https://mp.weixin.qq.com/s/-x2V_41lJlQX4xp8GXPKLA
正则表达式
正则表达式 (regular expression) 主要用于识别字符串中符合某种模式的部分,什么叫模式呢?用下面一个具体例子来讲解。
input = """'06/18/2019 13:00:00', 100, '1st';'06/18/2019 13:30:00', 110, '2nd';'06/18/2019 14:00:00', 120, '3rd'"""input
"\n'06/18/2019 13:00:00', 100, '1st'; \n'06/18/2019 13:30:00', 110, '2nd'; \n'06/18/2019 14:00:00', 120, '3rd'\n"
假如你想把上面字符串中的「时间」的模式来抽象的表示出来,对照着具体表达式 '06/18/201913:00:00' 来看,我们发现该字符串有以下规则:
- 开头和结束都有个单引号 '
- 里面有多个 0-9数字
- 里面有多个正斜线 / 和分号 :
- 还有一个空格
因此我们用下面这样的模式
pattern = re.compile("'[0-9/:\s]+'")
再看这个抽象模式表达式 '[0-9/:\s]+',里面符号的意思如下:
- 最外面的两个单引号 ' 代表该模式以它们开始和结束
- 中括号 [] 用来概括该模式涵盖的所有类型的字节
- 0-9 代表数字类的字节
- / 代表正斜线
- : 代表分号
- \s 代表空格
- [] 外面的加号 + 代表 [] 里面的字节出现至少 1 次
有了模式 pattern,我们来看看是否能把字符串中所有符合 pattern 的日期表达式都找出来。
pattern.findall(input)
["'06/18/2019 13:00:00'", "'06/18/2019 13:30:00'", "'06/18/2019 14:00:00'"]
结果是对的,之后你想怎么盘它就是你自己的事了,比如把 / 换成 -,比如用 datetime 里面的 striptime() 把日期里年、月、日、小时、分钟和秒都获取出来。
2.2 元组
创建元组
「元组」定义语法为
(元素1, 元素2, ..., 元素n)
关键点是「小括号 ()」和「逗号 ,」
- 小括号把所有元素绑在一起
- 逗号将每个元素一一分开
创建元组的例子如下:
t1 = (1, 10.31, 'python')t2 = 1, 10.31, 'python'print( t1, type(t1) )print( t2, type(t2) )
(1, 10.31, 'python') <class 'tuple'> (1, 10.31, 'python') <class 'tuple'>
知识点
创建元组可以用小括号 (),也可以什么都不用,为了可读性,建议还是用 ()。此外对于含单个元素的元组,务必记住要多加一个逗号,举例如下:
print( type( ('OK') ) ) # 没有逗号 , print( type( ('OK',) ) ) # 有逗号 ,
<class 'str'> <class 'tuple'>
看看,没加逗号来创建含单元素的元组,Python 认为它是字符。
当然也可以创建二维元组:
nested = (1, 10.31, 'python'), ('data', 11)nested
((1, 10.31, 'python'), ('data', 11))
索引和切片
元组中可以用整数来对它进行索引 (indexing) 和切片 (slicing),不严谨的讲,前者是获取单个元素,后者是获取一组元素。接着上面二维元组的例子,先看看索引的代码:
nested[0]print( nested[0][0], nested[0][1], nested[0][2] )
(1, 10.31, 'python') 1 10.31 python
再看看切片的代码:
nested[0][0:2]
(1, 10.31)
不可更改
元组有不可更改 (immutable) 的性质,因此不能直接给元组的元素赋值,例子如下 (注意「元组不支持元素赋值」的报错提示)。
t = ('OK', [1, 2], True)t[2] = False
TypeError: 'tuple' object does not support item assignment
但是只要元组中的元素可更改 (mutable),那么我们可以直接更改其元素,注意这跟赋值其元素不同。如下例 t[1] 是列表,其内容可以更改,因此用 append 在列表后加一个值没问题。
t[1].append(3)
('OK', [1, 2, 3], True)
内置方法
元组大小和内容都不可更改,因此只有 count 和 index 两种方法。
t = (1, 10.31, 'python')print( t.count('python') )print( t.index(10.31) )
1 1
这两个方法返回值都是 1,但意思完全不同
- count('python') 是记录在元组 t 中该元素出现几次,显然是 1 次
- index(10.31) 是找到该元素在元组 t 的索引,显然是 1
元组拼接
元组拼接 (concatenate) 有两种方式,用「加号 +」和「乘号 *」,前者首尾拼接,后者复制拼接。
(1, 10.31, 'python') + ('data', 11) + ('OK',)(1, 10.31, 'python') * 2
(1, 10.31, 'python', 'data', 11, 'OK') (1, 10.31, 'python', 1, 10.31, 'python')
解压元组
解压 (unpack) 一维元组 (有几个元素左边括号定义几个变量)
t = (1, 10.31, 'python')(a, b, c) = tprint( a, b, c )
1 10.31 python
解压二维元组 (按照元组里的元组结构来定义变量)
t = (1, 10.31, ('OK','python'))(a, b, (c,d)) = tprint( a, b, c, d )
1 10.31 OK python
如果你只想要元组其中几个元素,用通配符「*」,英文叫 wildcard,在计算机语言中代表一个或多个元素。下例就是把多个元素丢给了 rest 变量。
t = 1, 2, 3, 4, 5a, b, *rest, c = tprint( a, b, c )print( rest )
1 2 5 [3, 4]
如果你根本不在乎 rest 变量,那么就用通配符「*」加上下划线「_」,刘例子如下:
a, b, *_ = tprint( a, b )
1 2
优点缺点
优点:占内存小,安全,创建遍历速度比列表快,可一赋多值。
缺点:不能添加和更改元素。
等等等,这里有点矛盾,元组的不可更改性即使优点 (安全) 有时缺点?确实是这样的,安全就没那么灵活,灵活就没那么安全。看看大佬廖雪峰怎么评价「不可更改性」吧
immutable 的好处实在是太多了:性能优化,多线程安全,不需要锁,不担心被恶意修改或者不小心修改。
后面那些安全性的东西我也不大懂,性能优化这个我可以来测试一下列表和元组。列表虽然没介绍,但是非常简单,把元组的「小括号 ()」该成「中括号 []」就是列表了。我们从创建、遍历和占空间三方面比较。
创建
%timeit [1, 2, 3, 4, 5]%timeit (1, 2, 3, 4, 5)
62 ns ± 13.2 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each) 12.9 ns ± 1.94 ns per loop (mean ± std. dev. of 7 runs, 100000000 loops each)
创建速度,元组 (12.9ns) 碾压列表 (62ns)。
遍历
lst = [i for i in range(65535)]tup = tuple(i for i in range(65535))%timeit for each in lst: pass%timeit for each in tup: pass
507 µs ± 61.1 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each) 498 µs ± 18.7 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
遍历速度两者相当,元组 (498 µs) 险胜列表 (507 µs)。
占空间
from sys import getsizeofprint( getsizeof(lst) )print( getsizeof(tup) )
578936 524328
列表比元组稍微废点内存空间。
2.3 列表
创建列表
「列表」定义语法为
[元素1, 元素2, ..., 元素n]
关键点是「中括号 []」和「逗号 ,」
- 中括号把所有元素绑在一起
- 逗号将每个元素一一分开
创建列表的例子如下:
l = [1, 10.31,'python']print(l, type(l))
[1, 10.31, 'python'] <class 'list'>
内置方法
不像元组,列表内容可更改 (mutable),因此附加 (append, extend)、插入 (insert)、删除 (remove, pop) 这些操作都可以用在它身上。
附加
l.append([4, 3])print( l )l.extend([1.5, 2.0, 'OK'])print( l )
[1, 10.31, 'python', [4, 3]] [1, 10.31, 'python', [4, 3], 1.5, 2.0, 'OK']
严格来说 append 是追加,把一个东西整体添加在列表后,而 extend 是扩展,把一个东西里的所有元素添加在列表后。对着上面结果感受一下区别。
插入
l.insert(1, 'abc') # insert object before the index positionl
[1, 'abc', 10.31, 'python', [4, 3], 1.5, 2.0, 'OK']
insert(i, x) 在编号 i 位置前插入 x。对着上面结果感受一下。
删除
l.remove('python') # remove first occurrence of objectl
[1, 'abc', 10.31, [4, 3], 1.5, 2.0, 'OK']
p = l.pop(3) # removes and returns object at index. Only only pop 1 index position at any time.print( p )print( l )
[4, 3] [1, 'abc', 10.31, 1.5, 2.0, 'OK']
remove 和 pop 都可以删除元素
- 前者是指定具体要删除的元素,比如 'python'
- 后者是指定一个编号位置,比如 3,删除 l[3] 并返回出来
对着上面结果感受一下,具体用哪个看你需求。
切片索引
索引 (indexing) 和切片 (slicing) 语法在元组那节都讲了,而且怎么判断切片出来的元素在字符那节也讲了,规则如下图:
对照上图看下面两个例子 (顺着数和倒着数编号):
l = [7, 2, 9, 10, 1, 3, 7, 2, 0, 1]l[1:5]
[2, 9, 10, 1]
l[-4:]
[7, 2, 0, 1]
列表可更改,因此可以用切片来赋值。
l[2:4] = [999, 1000]l
[7, 2, 999, 1000, 1, 3, 7, 2, 0, 1]
切片的通用写法是
start : stop : step