Python常见特性
数据结构
集合中筛选数据
eg : 将数组、字典、集合中的小于0
的数去除掉
数组:
a = [9, 5, -2, -3, 6, 1, -5, -10, 3, 4]
# 数组推导式
b = [for i in a if i > 0]
# filter 返回一个生成器
b = filter(lambda x:x>0, a)
字典
k = {
"a": 23,
"b": 9,
"c": -34,
"d": 13,
"e": -2,
}
# 字典推导式
q = {k:v for k, v in di.items() if v > 0}
# filter 函数
q = dict(filter(lambda x:x[1]>0, di.items()))
集合
s = {2, 10, -3, -45, 9, -2}
s1 = {i for i in s if i > 0}
s2 = set(filter(lambda x:x>0, s))
命名元祖
from collections import namedtuple
# 传入名称和字段列表
Person = namedtuple('Person', ['name', 'age'])
p = Person(name="Mao", age=10)
# 可以和元祖一样访问
p[0]
# 也可直接访问属性
p.name
字典值排序
有以下字典数据 :
d = {
'a': 56,
'b': 79,
'c': 34,
'd': 43
}
根据值进行排序
将字典的每个键值对
转换为元祖
,放在一个列表
中,再使用sorted
函数进行排序:
res = sorted(d.items(), key=lambda e:e[1], reverse=True)
# 得到
# [('b', 79), ('a', 56), ('d', 43), ('c', 34)]
创建一个带有排名信息的字典:
t = {k:(i, v) for i, (k, v) in enumerate(res, 1)}
{'b': (1, 79), 'a': (2, 56), 'd': (3, 43), 'c': (4, 34)}
统计元素出现频率
在序列中出现次数最高的几个数字
a = [2,3,5,6,3,4,54,7,3,4,3,3,2,1,2,33,4]
方法1:
# 创建一个 key为a中数 初始值为 0 记录频率的字典
d = dict.fromkeys(a, 0)
# 对字典d进行排序,获取一个列表
res = sorted(((v, k) for k, v in d.items()), reverse=True)
# [(5, 3), (3, 4), (3, 2), (1, 54), (1, 33), (1, 7), (1, 6), (1, 5), (1, 1)]
方法2:
使用Counter
from collections import Counter
c = Counter(a)
print(c)
# Counter({3: 5, 2: 3, 4: 3, 5: 1, 6: 1, 54: 1, 7: 1, 1: 1, 33: 1})
c.most_common(3) # 这样就得到出现频率最高的三个元素
# [(3, 5), (2, 3), (4, 3)]
多个字典的公共key
有字典列表信息如下:
a = [
{'a': 1, 'b': 2, 'c': 3, 'd': 4},
{'a': 1 'c': 3, 'd': 4},
{'a': 1, 'b': 2, 'c': 3, 'e': 4}
]
此时公共key应该为['a', 'c']
from functiontools import reduce
# 1.获取每个字典的keys
# 2.两两进行&操作
print(reduce(lambda a,b:a&b, map(dict.keys, a)))
有序字典
默认的dict
是无需的,可以用collections
中的OrderedDict
from collections import OrderedDict
d = OrderedDict()
d['c'] = 1
d['b'] = 2
d['a'] = 3
# 此时遍历d时,就会按照插入顺序访问
配合islice
使用,获取部分排名
from itertools import islice
from collections import OrderedDict
class Con:
def __init__(self):
self.d = OrderedDict()
def add(self, k, v):
self.d[k] = v
def get(self, k):
# 通过名字获取排名
return self.d[k]
def get_range(self, a, b=None):
# 获取排名为a -> b的人
a -= 1
if b is None:
b = a + 1
return list(islice(self.d, a, b))
c = Con()
p = ['a', 'c' ,'d', 'b', 'g', 'f']
for i, v in enumerate(p, 1):
c.add(v, i)
# 获取部分排名信息
c.get_range(2, 6)
常用数据结构
队列
# 线程安全的队列 有可能阻塞
from queue import Queue
q = Queue()
q.put(1)
q.put([1, 2, 3])
q.put('a')
q.get()
print(q)
# get_nowait() 不会阻塞
# put_nowait()
字符串处理
文本格式
将时间格式调整
import re
p = '(\d{4})-(\d{2})-(\d{2})'
r = r'\2-\3-\1'
s = '2020-05-12 : qwewqe'
# p: 模式
# r: 被替换成的字符串
# s: 原始字符串
print(re.sub(p, r, s))
拼接字符串
在一个列表
中有多个字符串,将所有字符串拼接起来
a = ['a', 'b', 'c', 'd', 'e', 'f']
# 1.连续加
res = ""
for s in a:
res += s
# 2.reduce
from functools import reduce
reduce(str.__add__, a)
# 3.一次性拼接
# 以""为连接符 连接可迭代对象a中的元素
"".join(a)
字符串输出对齐
a = 'abc'
# 左对齐
a.ljust(10)
format(a, '<10') # 实际是调用实例的__format__方法
# 'abc '
# 右对齐
a.rjust(10)
format(a, '>10')
' abc'
# 居中对齐
a.center(10)
format(a, '^10')
' abc '
# 添加填充物
format(a, '*^10')
a.center(10, "*")
'***abc****'
# 定宽数字带符号,填充输出
format(-123, '0=+10')
'-000000123'
去除首尾
a = ' maomaomao '
# 空白字符 : " " "\t"
a.strip() # 去掉首尾空白字符
a.lstrip() # 去掉左侧空白字符
a.rstrip() # 去掉右侧空白字符
a.strip("*") # 删除首尾的*
迭代对象
基础
-
for ... in ..
原理:会使用iter()
方法获取可迭代对象
的迭代器,该对象应该有__iter__
方法 - 可以将
迭代器
传入next
方法中,每次调用next
的时候,都会去除一个元素,如果没有元素了,则抛出StopIteration
异常 -
next()
方法就是调用了迭代器的__next__
方法,迭代器本身就是一个可迭代对象 - 迭代器的
__iter__
方法会返回自身
实现一个迭代类
1 class StudentIterator:
2 def __init__(self, names):
3 self.names = names
4 self.index = 0
5
6 def __next__(self):
7 if self.index == len(self.names):
8 raise StopIteration()
9 res = self.names[self.index]
10 self.index += 1
11 return res
12
13 class Students:
14 def __init__(self, names):
15 self.names = names
16
17 def __iter__(self):
18 return StudentIterator(names)
19
20 names = ['a', 'b', 'c', 'd', 'e']
21 stus = Students(names)
22 for s in stus:
23 print(s)
生成器实现可迭代对象
1 class OddNumbers:
2 def __init__(self, s, e):
3 self.s = s
4 self.e = e
5 self.cur = s
6
7 def __iter__(self):
8 for i in range(self.s, self.e + 1):
9 if i % 2 == 1:
10 yield i
11
12 nums = OddNumbers(1, 50)
13 for n in nums:
14 print(n)
反向迭代
使用reversed()
函数获取到一个反向迭代器
传入的参数必须实现__reversed__
函数,就是反向迭代的接口
1 l = [1, 2, 3, 4, 5, 6]
2 for n in reversed(l):
3 print(n)
实现一个可以反向迭代的类
1 from decimal import Decimal
2
3 class Ranger:
4 def __init__(self, a, b, s):
5 self.a = Decimal(str(a))
6 self.b = Decimal(str(b))
7 self.s = Decimal(str(s))
8
9 def __iter__(self):
10 t = self.a
11 while t < self.b:
12 yield float(t)
13 t += self.s
14
15 def __reversed__(self):
16 t = self.b
17 while t > self.a:
18 yield float(t)
19 t -= self.s
20
21 r = Ranger(3.0, 4.0, 0.2)
22 for n in r:
23 print(n)
24 print("*"*20)
25 for n in reversed(r):
26 print(n)
快乐切片
实际上调用的是对象的__getitem__(slice(2, 5, 1))
方法
使用itertools.islice
可以返回一个迭代对线切片
的生成器
# 返回islice object
islice(iterable, stop)
islice(iterable, start, stop[, step])
一个for迭代多个对象
并行迭代
,不同的数据存在不同的列表当中:
可使用zip
对象将多个可迭代转换为一个可迭代对象
1 a = [1, 2, 3, 4, 5]
2 b = [6, 7, 8, 9, 10]
3 c = [11, 12, 13, 14, 15]
4
5 for q, w, e in zip(a, b, c):
6 print(q, w, e)
串行迭代
,相同的数据存在不同的列表当中
可使用itertools.chain
将多个可迭代的对象串成一个
1 from itertools import chain
2 for n in chain([1, 2, 3], [4, 5, 6], [7]):
3 print(n)
文件IO正确姿势
读取文本文件
1 with open('test.txt', 'rt', encoding='utf-8' ) as f:
2 print(f.read())
二进制文件
- 使用
b
打开二进制文件 - 二进制文件可以使用
readinto
方法写入缓冲区中 - 解析二进制文件可以使用
struct
模块的unpack
方法
文件的缓冲
将文件内容写入到硬件时,使用系统调用,这类IO操作时间很长,为了减少IO次数,文件通常使用缓冲区。在缓冲区中有足够的数据时,才会进行系统调用,写入磁盘中。
缓冲行为分为全缓冲 行缓冲 无缓冲
文本模式下打开是基于二进制模式打开,包装了一层而已
三层模型
Created with Raphaël 2.2.0 TextIO (负责编解码) B (负责缓冲区) Raw (直接写入)
- 全缓冲 : 缓冲区大小固定,当缓冲区满了就使用
系统调用
,写入硬盘中(二进制写默认
) - 行缓冲 : 遇到换行或者到了buffer size就写入(
只能用于文本写入
) - 无缓冲 : 直接写入硬盘中
设置文件的缓冲大小
# buffering:
# > 0 => 设置缓冲区
# = 0 => 使用无缓冲区
open('test.x', 'wb', buffering=6666)
# 除此之外 无缓冲区还可
f.raw.write(b'hello')
# 设置行缓冲只能针对 文本模式 打开
# buffering == 1 时就是行缓冲
open('test.x', 'w', buffering=1)
文件映射到内存
- 在访问某些二进制文件时,希望把文件映射到内存当中去,用于实现随机访问(
framebuffer文件
) - 某些嵌入设备,寄存器被编址到内存地址空间,可以映射
/dev/mem
某范围,去访问这些寄存器 - 多个进程同时映射同一个文件,也可实现进程间通信
类与对象
- 类实例化的时候,会调用两个方法,先调用
__new__
方法,再调用__init__
方法 - 实例对象是由
__new__(cls)
方法所返回的 - 所有类的实例都是由
object.__new__(cls)
方法创建 - 当实例创建完成以后,该实例就会作为
__init__
函数的第一个参数
派生内置不可变对象 修改实例化行为
eg : 定义一个新的类型IntTuple
, 只能包含有整数元素,tuple
没有实现自己的__init__
方法
1 class IntTuple(tuple):
2 def __new__(cls, it):
3 items = [e for e in it if isinstance (e, int)]
4 return super().__new__(cls, items)
5
6 t = IntTuple([2, 4, 'sd', '8', 6])
7 print(t)
创建大量实例节省内存
实例个数太多了 如何减少开销
使用__slots__
属性声明实例有哪些属性,(关闭动态绑定)
class Student:
# 可以少 __dict__ 和 __weakref__ 属性
# __dict__ 用于维护动态绑定的属性
__slots__ = ['name', 'age']
def __init__(self, name, age):
self.name = self.name
self.age = self.age
上下文管理
with open('abc.txt', 'w') as f:
f.write("abcdef")
with
语句工作流程:
- 创建实例
file = open('abc.txt', 'w')
- 调用内置函数
f = file.__enter__()
- 执行
f.write('abcdef')
- 退出语句块的时候 调用
file.__exit__(self, exc_type, exc_value, exc_tb)
方法,关闭文件
可管理的对象属性
不直接访问属性,而是使用一些getter/setter
来获取或设置值
o.getA()
o.setA(1)
o.a
o.a = 10
在python中可以在直接访问属性的时候,调用一些方法
方法一:
在类当中定义一个property
1 class Student:
2 def getAge(self):
3 return self._age
4 def setAge(self, age):
5 if not isinstance(age, int) :
6 raise Exception('age错误!')
7 self._age = age
8 # property 有三个参数:
9 # 属性访问函数
10 # 属性设置函数
11 # 属性删除函数
12 age = property(getAge, setAge)
13 s = Student()
14 s.age = '10'
15 print(s.age)
方法二:
使用装饰器:
1 class Student:
2 @property
3 def age(self):
4 return self._age
5 @age.setter
6 def age(self, age):
7 if not isinstance(age, int) :
8 raise Exception('age错误!')
9 self._age = age
10 s = Student()
11 s.age = 10
12 print(s.age)
比较运算符重载
运算符 | 重载方法 |
< |
|
>= |
|
> |
|
使用functools.total_ordering
这个类修饰器的话,只需要实现一个__eq__
方法,加上其它一个不等比较的任意方法即可
1 from functools import total_ordering
2
3 @total_ordering
4 class Student:
5 def __init__(self, a, b):
6 self.a = a
7 self.b = b
8 # 实现eq
9 def __eq__(self, o):
10 print('__eq__')
11 return self.a + self.b < o.a + o.b
12 # 其它任意
13 def __lt__(self, o):
14 print('__lt__')
15 return self.a + self.b < o.a + o.b
16
17 q = Student(11, 20)
18 w = Student(10, 20)
19 print(q > w)
对属性类型进行检查
由于是动态语言,就应该在代码运行的时候,进行检查。
使用描述符
可以进行实现,只要包含__set__(self, instance, value)
,__get__(self, instance, cls)
,__delete__(self, instance)
就是一个描述符
1 class Description:
2 def __init__(self, key, t):
3 self.key = key
4 self.t = t
5 def __set__(self, instance, value):
6 print('__set__')
7 # 进行类型检测
8 if not isinstance(value, self.t):
9 raise TypeError(f'{self.key} must be {self.t}' )
10 instance.__dict__[self.key] = value
11 def __get__(self, instance, cls):
12 print('__get__')
13 return instance.__dict__[self.key]
14 def __delete__(self, instance):
15 print('__delete__')
16 del instance.__dict__[self.key]
17
18 class A:
19 x = Description('qq', str)
20 y = Description(12, int)
21
22 a = A()
23 a.x = 'asd'
24 a.y = 3
25 print(a.x)
26 print(a.y)
环状数据管理内存
python中使用引用计数来进行垃圾回收
但在一些存在循环引用
的数据结构,比如双向链表的时候,就不能正常的进行垃圾回收了。
不存在循环引用的情况
1 class A:
2 def __del__(self):
3 # 被回收的时候调用
4 print("__del__")
5 a = A()
6 a2 = a
7 # 此时引用计数为2
8
9 a2 = None
10 a = 2
11 # 此时引用计数为0
存在循环引用的情况
在程序执行结束之前a
和 b
1 import time
2 class A:
3 def __init__(self):
4 self.k = None
5 def __del__(self):
6 print("__del__")
7
8 a = A()
9 b = A()
10 a.k = b
11 b.k = a
12
13 a = None
14 b = None
15
16 time.sleep(10)
使用弱引用解决
弱引用可不增加引用计数
1 import time
2 import weakref
3 class A:
4 def __init__(self):
5 self.k = None
6 def __del__(self):
7 print("__del__")
8
9 a = A()
10 b = A()
11
12 # 该对象的引用, 如果对象不存在的则返回None
13 a2 = weakref.ref(a)
14 b2 = weakref.ref(a)
15
16 a.k = b2
17 b.k = a2
18
19 a, b = None, None
20
21 time.sleep(10)
通过方法名来调用方法
方法1
获取方法对象,使用()
调用
1 class A:
2 def do_get(self):
3 print('do_get')
4
5 a = A()
6
7 # 从实例中获取函数对象
8 # 第三个参数None是默认值
9 # 如果方法不存在且没有默认值,则会抛出异常
10 method = getattr(a, 'do_get', None)
11
12 # 调用函数
13 method()
方法2
使用oprator.methodcaller
参数1 : 方法名
其它参数 : 方法调用的参数
1 from operator import methodcaller
2 class A:
3 def do_get(self, a, b):
4 print(f'do_get : {a + b}')
5
6 a = A()
7 # 获取一个caller对象
8 caller = methodcaller('do_get', 1, 4)
9 # 将实例传入caller中执行
10 caller(a)
线程
python存在一个GIL(全局解析器锁)
属于进程,多线程进行CPU密集型操作不能提高效率
一个python中的多个线程不能同时
在不同的
CPU上执行
当一个要线程运行的时候,需要获取GIL锁
。
创建线程
方法1
1 from threading import Thread
2
3 def func(n):
4 print(n)
5
6 for n in range(10):
7 t = Thread(target=func, args=(n,))
8 t.start()
方法2
1 from threading import Thread
2
3 class Task(Thread):
4
5 def __init__(self, n):
6 self.n = n
7 super().__init__(daemon=False)
8
9 def run(self):
10 print(n)
11
12 for n in range(10):
13 t = Task(n)
14 t.start()
线程间事件通知
可以使用threading.Event
对象
- 使用
Event
的wait
方法可以暂时阻塞当前线程 - 在其它线程调用
Event
的set
方法,可以让被阻塞的线程继续执行
1 from threading import Thread, Event
2
3 def func(e):
4 print(1)
5 e.wait()
6 print(2)
7
8 e = Event()
9 t = Thread(target=func, args=(e,))
10 t.start()
11
12 e.set()
但是如果使用一次的Event
实例想要再次使用,必须调用clear()
方法
线程本地数据
使用threading.local
创建线程本地数据
from threading import local
loc = local()
loc.y = 2
其中local
实例的属性,就属于当前线程的数据,在其它线程中则访问不到loc.y
这个数据
每个线程都是独立的,每个线程里loc
中的属性互不影响
线程池
使用concurrent.futures.ThreadPoolExecutor
来创建线程池
Executor方法:
方法 | 作用 |
submit | 使用线程池执行方法,返回一个 |
map | 使用多个线程池,执行方法,返回一个 |
1 from threading import Thread
2 from concurrent.futures import ThreadPoolExecutor
3
4 def func(a, b):
5 print(a, b)
6 return a + b
7
8 # 线程数量为3
9 executor = ThreadPoolExecutor(3)
10
11 # 使用线程池执行
12 future = executor.submit(func, 6, 7)
13
14 # 返回一个生成器
15 # 返回每一个调用f记录的结果
16 generator = executor.map(func, [1,2,3,4], [5,6 ,7,8])
17 print(list(generator))
18
19 # 有个返回值future
20 # 获取函数调用结果
21 # result是阻塞函数
22 print(future.result())
多进程操作
由于GIL
是线程锁,多进程不收影响,可以处理CPU密集型任务
使用multiprocessing.Process
可进行多进程操作,和Thread
的操作差不多
多进程使用
1 from multiprocessing import Process
2
3 def func(n):
4 print(f'child..{n}')
5 import time
6 time.sleep(5)
7
8 # 创建进程
9 p = Process(target=func, args=(2,))
10
11 # 启动子进程
12 p.start()
13
14 # 等待子进程结束
15 p.join()
和多进程的不同点
最大区别在于虚拟地址空间,多个线程
是共享
一个虚拟地址空间的
而进程
之间的虚拟地址空间是独立
的
进程间通信
使用multiprocessing
中的Queue, Pipe
进行通信
使用Queue:
1 from multiprocessing import Process, Queue, Pi pe
2
3 def func(q):
4 print('child...')
5 print(q.get())
6
7
8 q = Queue()
9 p = Process(target=func, args=(q,))
10 p.start()
11
12 q.put('mao')
multiprocessing.Queue 和 queue.Queue的区别
-
m.Queue
用于进程之间的通信,q.Queue
用于线程间的通信 -
q.Queue
存在的是通信线程双方所在进程的地址空间内,m.Queue
创建在操作系统内核上的一个管道(pipe),不属于通信双方
使用Pipe
创建一个管道pipe
会得到两个端,一个进程拿一个端,可读可写
使用管道的recv
和 send
方法进行数据的交互
1 from multiprocessing import Process, Queue, Pi pe
2
3 def func(c):
4 print('child...')
5 data = c.recv()
6 print(data)
7 c.send(data**2)
8
9
10 c1, c2 = Pipe()
11 p = Process(target=func, args=(c2,))
12 p.start()
13
14 # 主进程传值
15 c1.send(6)
16
17 # 主进程接受
18 print(c1.recv())
19
装饰器
基本使用
在某种场景下,需要给多个
函数添加同一种功能(计时,日志,缓存)
一下是计算斐波那契数列,使用装饰器给原始暴力解法添加缓存
class Solution:
def memo(self, func):
cache = {}
def wrap(*args):
res = cache.get(args)
if not res:
res = cache[args] = func(*args)
return res
return wrap
def fib(self, N: int) -> int:
@self.memo
def func(n):
if n <= 1:
return n
return func(n - 1) + func(n - 2)
return func(N)
保存被装饰的函数的元数据
函数元数据:
def func(a:int, b:int)->int:
''' func n '''
return a + b
元数据 | 值 |
__name__ | func |
__doc__ | func n |
__annotations__ | {‘a’: <class ‘int’>, ‘b’: <class ‘int’>, ‘return’: <class ‘int’>} |
__module__ | __main__ |
使用装饰器
以后的:
1 def f(func):
2 def wrap(*args):
3 return func(*args)
4 return wrap
5
6 @f
7 def func(a:int, b:int)->int:
8 ''' func n '''
9 return a + b
元数据 | 值 |
__name__ | wrap |
__doc__ | None |
__annotations__ | {} |
__module__ | __main__ |
解决方案1:
1 from functools import update_wrapper
2 def f(func):
3 def wrap(*args):
4 return func(*args)
5 update_wrapper(wrap, func)
6 return wrap
解决方案2
使用注解
1 from functools import wraps
2 def f(func):
3 @wraps(func)
4 def wrap(*args):
5 return func(*args)
6 return wrap
定义带参数的装饰器
定义一个带参数的装饰器,使得被wrap的函数返回值不超过8
在创建wrap
的方法中添加一层decorator
函数
1 def lt(n):
2 def decorator(func):
3 def wrap(*args):
4 res = sum(args)
5 if res < n:
6 return res
7 raise ValueError(f"参数和大于{n}")
8 return wrap
9 return decorator
10
11 @lt(8)
12 def func(a, b)->int:
13 ''' func n '''
14 return a + b
15
16 print(func(2,6))
属性可修改的装饰器
思路,给wrap
添加一个修改属性的方法,修改时,直接调用即可
1 def lt(n):
2 def decorator(func):
3 def wrap(*args):
4 res = sum(args)
5 if res < n:
6 return res
7 raise ValueError(f"参数和大于{n}")
8 def set_n(new_n):
9 nonlocal n
10 n = new_n
11 wrap.set_n = set_n
12 return wrap
13 return decorator
14
15 @lt(8)
16 def func(a, b)->int:
17 ''' func n '''
18 return a + b
19
20 print(func(2,5))
21 func.set_n(7)
22 print(func(2,5))