Python编程技巧

分享一下python编程的技巧,在阅读此内容时,请确保你已经掌握了python的基本用法,

筛选列表,字典

列表筛选

一般都是用for循环加if 来判断如:

1
2
3
4
5
6
7
8
9
from random import randint
# 模拟数据
data = [randint(-10,10) for _ in range(10)]
result=[]
for i in data:
if i >=0:
result.append(i)
result
out:[4, 4, 1, 6, 2, 9, 6]

我们可以用filter函数

filter() 函数用于过滤序列,过滤掉不符合条件的元素,返回一个迭代器对象

该接收两个参数,第一个为函数,第二个为序列,序列的每个元素作为参数传递给函数进行判,然后返回 True 或 False,最后将返回 True 的元素放到新列表中。

1
2
3
4
result = filter(lambda x:x>=0,data)
# 如果要转换为列表,可以使用 list() 来转换。
list(result)
out:[4, 4, 1, 6, 2, 9, 6]

我们也可以使用列表生成式

1
[x for x in data if x>=0]

那到底哪个更快呢?我们可以用%timeit来测一下,发现是filter函数更快

image-20200703232926291

字典筛选

1
2
3
4
5
6
7
# 模拟一个{学号:分数}的字典
data={k:randint(60,100) for k in range(1,11)}
data
{1: 66, 2: 100, 3: 67, 4: 60, 5: 70, 6: 99, 7: 71, 8: 95, 9: 88, 10: 69}
# 筛选出大于90的
{k:v for k,v in data.items() if v>=90}
{2: 100, 6: 99, 8: 95}

命名元组

我们有一个固定格式的数据,如:

学生信息:(姓名,年龄,性别,邮箱),而数据量很大,为了减少开销,我们可以用元组表示

1
2
3
4
5
6
7
('小明',15,'男','xiaoming@qq.com')
('小里',11,'男','xiaolig@qq.com')
('小话',13,'女','xiaohuag@qq.com')
('小图',14,'男','xiaotug@qq.com')
data= ('小明',15,'男','xiaoming@qq.com')
我们需要访问的话会使用索引index来访问,但是如果用
姓名:data[0],年龄data[1]这种的话大大降低了可读性

1.我们可以用全局变量

1
2
3
4
5
6
7
8
NAME = 0
AGE = 1
GENDER = 2
EMAIL = 3
# 我们也可以用拆包的形式
# NAME,AGE,GENDER,EMAIL = range(4)
data = ('小明', 15, '男', 'xiaoming@qq.com')
data[NAME]

2.也可以用namedtuple来替换元组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from collections import namedtuple
# 定义一个namedtuple类型Student,并包含name,age,gender,email属性。
Student = namedtuple('Student',['name','age','gemder','email'])
# 创建一个Student对象
# student = Student('小明', 15, '男', 'xiaoming@qq.com')
# 我也可以使用关键字传参
# student=Student(name='小明', age=15, gemder='男', email='xiaoming@qq.com')
# 也可以传入一个列表,这里注意需要使用"_make"方法
student = Student._make(['小明', 15, '男', 'xiaoming@qq.com'])
student
out:Student(name='小明', age=15, gemder='男', email='xiaoming@qq.com')

# 这样的好处是,我们可以直接用属性来访问
student.name
out:'小明'

统计列表或字典出现的次数

我们希望统计一下列表中,每个元素出现的个数,

1
2
3
4
5
from random import randint
# 用randint模拟
data = [randint(0,20) for _ in range(20)]
data
out:[13, 5, 1, 2, 14, 13, 6, 5, 13, 11, 19, 3, 13, 5, 13, 11, 10, 6, 17, 18]

第一种做法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
result = {}
for i in data:
if i in result:
result[i]+=1
else:
result[i]=1
result
out:{13: 5, 5: 3, 1: 1, 2: 1, 14: 1, 6: 2, 11: 2, 19: 1, 3: 1, 10: 1, 17: 1, 18: 1}
# 如果我们要统计前3个的话,就要对字典进行排序,我们可以使用内置函数sorted,将字典转化为元组
new_tuple = [(v,k)for k,v in result.items()]
# 然后用sorted
sorted(new_tuple)
[(1, 1),
(1, 2),
(1, 3),
(1, 10),
(1, 14),
(1, 17),
(1, 18),
(1, 19),
(2, 6),
(2, 11),
(3, 5),
(5, 13)]
# 我们也可以用zip拼接
new_tuple_1 = zip(result.values(),result.keys())
sorted(new_tuple_1)
[(1, 1),
(1, 2),
(1, 3),
(1, 10),
(1, 14),
(1, 17),
(1, 18),
(1, 19),
(2, 6),
(2, 11),
(3, 5),
(5, 13)]
# 也可以用sorted函数内置的key来指定比较
# 第一个参数为传入的数据,第二个key来指定用哪个值作为比较
sorted(result.items(),key=lambda x:x[1])
[(1, 1),
(2, 1),
(14, 1),
(19, 1),
(3, 1),
(10, 1),
(17, 1),
(18, 1),
(6, 2),
(11, 2),
(5, 3),
(13, 5)]

第二种,使用collectionsCounter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from collections import Counter
c = Counter(data)
Counter({13: 5,
5: 3,
1: 1,
2: 1,
14: 1,
6: 2,
11: 2,
19: 1,
3: 1,
10: 1,
17: 1,
18: 1})
# 我们如果要取最多的话只需要使用most_common
# 表示取出现次数最多的前三个
c.most_common(3)
[(13, 5), (5, 3), (6, 2)]

查找公共键

1
2
3
4
5
6
7
8
9
10
11
12
13
'''
需求:
现有3个字典,需要查找出在三个字典中公共出现的键
'''
dict_1 = {k:randint(60,100) for k in sample('abcdefg',randint(1,7))}
dict_2 = {k:randint(60,100) for k in sample('abcdefg',randint(1,7))}
dict_3 = {k:randint(60,100) for k in sample('abcdefg',randint(1,7))}
dict_1
{'e': 68, 'c': 73, 'b': 68, 'f': 80}
dict_2
{'e': 99, 'g': 74, 'c': 96, 'b': 78}
dict_3
{'b': 90, 'g': 93}

第一种方法:

1
2
3
4
5
6
result = []
for k in dict_1.keys():
if k in dict_2 and k in dict_3:
result.append(k)
result
['b']

这种的话 如果我有很多个字典,我就要一个一个判断

第二种:我们可以用集合的交集

1
2
3
4
5
6
7
8
9
10
dict_1.keys()& dict_2.keys() &dict_3.keys()
# 如果有多个 我们可以用
'''
reduce() 函数会对参数序列中元素进行累积。
函数将一个数据集合(链表,元组等)中的所有数据进行下列操作:用传给 reduce 中的函数 function(有两个参数)先对集合中的第 1、2 个元素进行操作,得到的结果再与第三个数据用 function 函数运算,最后得到一个结果。
'''
# 在python3中已将reduce移除,我们需要在functools中导入
from functools import reduce
reduce(lambda a,b:a&b,map(dict.keys,[dict_1,dict_2,dict_3]))
{'b'}

如何实现历史记录功能

需求:记录用户的每次输入

1
2
3
4
5
6
7
8
9
10
11
12
13
from collections import deque
# 定义一个deque 他是一个双端循环队列
# deque(可迭代对象, 长度)
q = deque([], 5)
q.append(1)
q.append(2)
q.append(3)
q.append(4)
q.append(5)
q.append(6)
q
deque([2, 3, 4, 5, 6])
# 其实内部实现了一个判断列表长度,判断如果长度太长就把左边先进的剔除掉

迭代多个可迭代对象

需求:

  1. 有一个班的,希望得到语文,数学,英语的总分
1
2
3
4
5
6
7
8
9
from random import randint
chinese = [randint(60, 100) for _ in range(30)]
math = [randint(60, 100) for _ in range(30)]
enlish = [randint(60, 100) for _ in range(30)]
# 我们可以使用zip函数
total = []
for i in zip(chinese, math, enlish):
total.append(sum(i))
total
  1. 多个班,希望得到分数超过90的人数
1
2
3
4
5
6
7
8
9
10
11
12
13
from random import randint
c1 = [randint(60, 100) for _ in range(30)]
c2 = [randint(60, 100) for _ in range(33)]
c3 = [randint(60, 100) for _ in range(32)]
c4 = [randint(60, 100) for _ in range(31)]
count = 0
from itertools import chain
# 也可以直接相加,两个效率是一样的
# for i in c1+c2+c3+c4:
for i in chain(c1 ,c2 , c3 , c4):
if i >= 90:
count += 1
count

拆分多个分隔符的字符串

需求:

1
2
有一个字符串包含多个分隔符,如:
s = 'ads;cd|efesada,fjasd,fasd\tfasd;feas,asdas:fefa\tffgg'
  1. 使用split方法,进行多次
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
s = 'ads;cd|efesada,fjasd,fasd\tfasd;feas,asdas:fefa\tffgg'
# 我们可以对s进行split()
res = s.split(';')
res
['ads', 'cd|efesada,fjasd,fasd\tfasd', 'feas,asdas:fefa\tffgg']
# 然后再对res进行一次split()
[i.split('|') for i in res]
[['ads'], ['cd', 'efesada,fjasd,fasd\tfasd'], ['feas,asdas:fefa\tffgg']]
# 这并不是我们想要的结果,我们可以用变量t来接收
t=[]
[t.extend(i.split('|')) for i in res]
t
['ads', 'cd', 'efesada,fjasd,fasd\tfasd', 'feas,asdas:fefa\tffgg']
# 然后再对其进行split()
# 所以我们可以定义一个函数
def mySplit(s,separators):
# 这里需要把传进来的字符串变成列表
res=[s]
for separator in separators:
t = []
[t.extend(i.split(separator)) for i in res]
res =t
return res
s = 'ads;cd|efesada,fjasd,fasd\tfasd;feas,asdas:fefa\tffgg'
result = mySplit(s,';,|\t')
result
['ads', 'cd', 'efesada', 'fjasd', 'fasd', 'fasd', 'feas', 'asdas:fefa', 'ffgg']
  1. 使用re.split
1
2
3
4
import re
s = 'ads;cd|efesada,fjasd,fasd\tfasd;feas,asdas:fefa\tffgg'
re.split('[;|,\t]+',s)
['ads', 'cd', 'efesada', 'fjasd', 'fasd', 'fasd', 'feas', 'asdas:fefa', 'ffgg']

进行字符串对齐

  1. 字符串方法
1
2
3
4
5
6
7
8
9
10
s='aaa'
# ljust 左对齐,第二个参数为填充
s.ljust(20,'#')
'aaa#################'
# 右对齐
s.rjust(20)
' aaa'
# 居中对齐
s.center(20)
' aaa '
  1. format方法
1
2
3
4
5
6
7
8
9
#左对齐
format(s,'<20')
'aaa '
#右对齐
format(s,'>20')
' aaa'
#居中对齐
format(s,'^20')
' aaa '

如何设置文件缓冲

  • 全缓冲:open函数的bufering设置为大于1的整数n,n为缓冲区大小
  • 行缓冲:open函数的buffering设置为1,当有换行时写入
  • 无缓冲:open函数的buffering设置为0,实时写入

如何使用临时文件

你需要在程序执行时创建一个临时文件或目录,并希望使用完之后可以自动销毁掉。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
from tempfile import TemporaryFile

f = TemporaryFile(mode='w+t')
f.write('abcdef' * 1000)
f.seek(0)
data = f.read(100)
data
f.close()
from tempfile import TemporaryFile

with TemporaryFile('w+t') as f:
# Read/write to the file
f.write('Hello World\n')
f.write('Testing\n')
# Seek back to beginning and read the data
f.seek(0)
data = f.read()
TemporaryFile`是匿名的文件,如果你需要有名字的文件,可以使用`NamedTemporaryFile
from tempfile import NamedTemporaryFile
with NamedTemporaryFile('w+t') as f:
print('filename is:', f.name)

filename is: C:\Users\Tiger\AppData\Local\Temp\tmphjupdmz8
'''
这里,被打开文件的 f.name 属性包含了该临时文件的文件名。 当你需要将文件名传递给其他代码来打开这个文件的时候,这个就很有用了。 和 TemporaryFile() 一样,结果文件关闭时会被自动删除掉。 如果你不想这么做,可以传递一个关键字参数 delete=False 即可
'''

如何在一个现有类中,添加自己的逻辑

1
2
3
4
比如,我已经有一个tuple类,这个类可以把列表变成元组
tuple([[1, 2, 3, -1, 'abc', ['x', 'u'], 3]])
([1, 2, 3, -1, 'abc', ['x', 'u'], 3],)
而我们希望得到过滤掉字符串已经小于0的数

我们可以重写new方法

1
2
3
4
5
6
7
class intTuple(tuple):
def __new__(cls, iterable):
g = [i for i in iterable if isinstance(i, int) and i > 0]
return super().__new__(cls,g)
t = intTuple([1, 2, 3, -1, 'abc', ['x', 'u'], 3])
print(t)
(1, 2, 3, 3)

上下文管理

实现一个新的上下文管理器的最简单的方法就是使用 contexlib 模块中的 @contextmanager 装饰器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from contextlib import contextmanager
import sqlite3
@contextmanager
def conn_sqlite3(db):
conn = sqlite3.connect(db)
print('start')
yield conn
print('close')
conn.close()

with conn_sqlite3('db.sqlite3') as conn:
cur = conn.cursor()
cur.execute('select * from main.api_userunfo')
result = cur.fetchall()
print(result)

在函数 conn_sqlite3 中,yield 之前的代码会在上下文管理器中作为 __enter__() 方法执行, 所有在 yield 之后的代码会作为 __exit__() 方法执行。 如果出现了异常,异常会在yield语句那里抛出。

当然也可以在类中用__enter__()方法 跟 __exit__()方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import sqlite3
class connSqlite3(object):
def __init__(self,db):
self.db =db
self.conn=None

def __enter__(self):
print('start')
self.conn = sqlite3.connect(self.db)
return self.conn

def __exit__(self, exc_type, exc_val, exc_tb):
print('close')
self.conn.close()

with connSqlite3('db.sqlite3') as conn:
cur = conn.cursor()
cur.execute('select * from main.api_userunfo')
result = cur.fetchall()
print(result)