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("*") # 删除首尾的*

迭代对象

基础

  1. for ... in .. 原理:会使用iter()方法获取可迭代对象的迭代器,该对象应该有__iter__方法
  2. 可以将迭代器传入next方法中,每次调用next的时候,都会去除一个元素,如果没有元素了,则抛出StopIteration异常
  3. next()方法就是调用了迭代器的__next__方法,迭代器本身就是一个可迭代对象
  4. 迭代器的__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())

二进制文件

  1. 使用b打开二进制文件
  2. 二进制文件可以使用readinto方法写入缓冲区中
  3. 解析二进制文件可以使用struct模块的unpack方法

文件的缓冲

将文件内容写入到硬件时,使用系统调用,这类IO操作时间很长,为了减少IO次数,文件通常使用缓冲区。在缓冲区中有足够的数据时,才会进行系统调用,写入磁盘中。
缓冲行为分为全缓冲 行缓冲 无缓冲 文本模式下打开是基于二进制模式打开,包装了一层而已

三层模型


Created with Raphaël 2.2.0 TextIO (负责编解码) B (负责缓冲区) Raw (直接写入)


  1. 全缓冲 : 缓冲区大小固定,当缓冲区满了就使用系统调用 ,写入硬盘中(二进制写默认)
  2. 行缓冲 : 遇到换行或者到了buffer size就写入(只能用于文本写入
  3. 无缓冲 : 直接写入硬盘中

设置文件的缓冲大小

# buffering:
# > 0 => 设置缓冲区
# = 0 => 使用无缓冲区
open('test.x', 'wb', buffering=6666)

# 除此之外 无缓冲区还可
f.raw.write(b'hello')

# 设置行缓冲只能针对 文本模式 打开
# buffering == 1 时就是行缓冲
open('test.x', 'w', buffering=1)

文件映射到内存

  1. 在访问某些二进制文件时,希望把文件映射到内存当中去,用于实现随机访问(framebuffer文件)
  2. 某些嵌入设备,寄存器被编址到内存地址空间,可以映射/dev/mem某范围,去访问这些寄存器
  3. 多个进程同时映射同一个文件,也可实现进程间通信

类与对象

  1. 类实例化的时候,会调用两个方法,先调用__new__方法,再调用 __init__方法
  2. 实例对象是由__new__(cls)方法所返回的
  3. 所有类的实例都是由 object.__new__(cls) 方法创建
  4. 当实例创建完成以后,该实例就会作为__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语句工作流程:

  1. 创建实例 file = open('abc.txt', 'w')
  2. 调用内置函数 f = file.__enter__()
  3. 执行f.write('abcdef')
  4. 退出语句块的时候 调用 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)

比较运算符重载

运算符

重载方法

<

__lt__(self)

>=

__ge__(self)

>

__gt__(self)

使用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

存在循环引用的情况
在程序执行结束之前ab

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对象

  1. 使用Eventwait方法可以暂时阻塞当前线程
  2. 在其它线程调用Eventset方法,可以让被阻塞的线程继续执行
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

使用线程池执行方法,返回一个future对象

map

使用多个线程池,执行方法,返回一个generator对象

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.Queuequeue.Queue的区别

  1. m.Queue用于进程之间的通信,q.Queue用于线程间的通信
  2. q.Queue存在的是通信线程双方所在进程的地址空间内,m.Queue创建在操作系统内核上的一个管道(pipe),不属于通信双方

使用Pipe
创建一个管道pipe会得到两个端,一个进程拿一个端,可读可写
使用管道的recvsend方法进行数据的交互

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))