IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Python知识库 -> 【Python】小技巧、性能优化 -> 正文阅读

[Python知识库]【Python】小技巧、性能优化

一、高质量python

参考资料:
《EffectivePython:编写高质量python代码的90个有效方法》

1.使用f-string替代format

- ------------ format
name = '张三'
age = 12
n = 5
content = '{}今年{}岁'.format(name,age)
print(content)  # 张三今年12岁
content = '{0}今年{1}岁'.format(name,age)
print(content)  # 张三今年12岁
content = '{name}今年{age}岁'.format(name=name,age=age)
print(content)  # 张三今年12岁
content = '{0}年后,{1}将是{2}岁'.format(n,name,age+n)
print(content)  # 5年后,张三将是17岁

# - ----------- f-string
name = '张三'
age = 12
n = 5
content = f'{name}今年{age}岁'
print(content)  # 张三今年12岁
# 花括号内支持运算

# f-string也可以处理数值长度和精度,{}还可以套娃
num = 1.2455
int_num = 3
n = 5
print(f'{num}保留2位小数:{num:.2f}')	# 1.2455保留2位小数:1.25
print(f'{num}保留{n}位小数:{num:.{n}f}')	# 1.2455保留5位小数:1.24550
print(f'{num}转为int:{int(num)}')	# 1.2455转为int:1
print(f'整数{int_num},以0填充至{n}位:{int_num:0{n}d}')	# 整数3,以0填充至5位:00003

2.尽量用enumerate取代range

 示例:需要将a非str的值打印出来,并记录下标
# 使用range
a = ['s',3,'sf','6',0]
tmp = []
for i in range(len(a)):
    if not isinstance(a[i],str):
        print(a[i])
        tmp.append(i)

# 使用enumrate更优雅
a = ['s',3,'sf','6',0]
tmp = []
for idx,item in enumerate(a):
    if not isinstance(item,str):
        print(item)
        tmp.append(idx)

# enumerate输出的每一组数据,可以拆包到for语句的变量里
a = [('张三',22),('李四',18)]
for idx,(name,age) in enumerate(a,start=1): # start控制idx的初始值
    print(f'第{idx}个人信息:\n姓名:{name}\n年龄:{age}')

3.使用zip函数同时遍历两个迭代器

names = ['张三','李四']
ages = [22,12]
# names和ages一一对应,现在需要处理成{'张三': {'name': '张三', 'age': 22}, '李四': {'name': '李四', 'age': 12}}

# 不用zip
info = {}
for i in range(len(names)):
    info[names[i]] = {'name': names[i], 'age': ages[i]} # 若names长度大些,按下标取age会报错

# 使用zip,更优雅且清晰明了
info = {}
for name,age in zip(names,ages):
    info[name] = {'name':name,'age':age}    # 会以较小的迭代对象的长度取

4.不要在for与while循环后写else块

a = [1,2,3]
# for+else,将在执行for循环后执行else(while+else也是一样)

for i in a:
    print(i)
else:
    print('else')

for i in a:
    if i >3:
        print(i)
        break   # 这里的break 将跳出for循环和else,如果满足跳出条件,将不会执行else(while+else也是一样)
else:
    print('else')
print(a)

5.使用赋值表达式(海象表达式:=)减少重复代码

python 3.8及之后才支持

# ------------- 海象表达式(:=)进行赋值
info = [{'name': '张三', 'age': 22}, {'name': '李四', 'age': 12}]

# 不使用海象表达式
for person in info:
    name = person.get('name')
    age = person.get('age')
    if name == '张三':
        if age >= 18:
            print(name,'成年了,年龄:',age)

# 使用海象表达式
for person in info:
    if (name := person.get('name')) == '张三':    # 把:=后的表达式person.get('name')的值赋值给:=前的变量name,然后使用这个变量name与'张三'进行判断
        if (age := person.get('age')) >= 18:
            print(name,'成年了,年龄:',age)

6.用sort的key来表示复杂排序逻辑

info = [{'name': '张三', 'age': 22,'weight':55},
        {'name': '李四', 'age': 12,'weight':55},
        {'name': '王五', 'age': 22,'weight':70},
        {'name': '赵六', 'age': 7}]
# 按照weight、age升序,nulls last
info.sort(key = lambda item:(item.get('weight',999
),item.get('age',999)),reverse=True)
print(info) 

# 实现按照weight降序,age升序排序,nulls last
# sort的key有个特性:如果key函数认定的两个值相等,那么这两个值在排序结果中的顺序与排序前一致;要实现按照weight降序,age升序排序,就需要先对age排序 再对weight排序
info.sort(key = lambda item:item.get('age',999),reverse=False)
info.sort(key = lambda item:item.get('weight',0),reverse=True)
print(info) # [{'name': '王五', 'age': 22, 'weight': 70}, {'name': '李四', 'age': 12, 'weight': 55}, {'name': '张三', 'age': 22, 'weight': 55}, {'name': '赵六', 'age': 7}]

7.尽量使用参数名进行可选参数传递

def func(a,b,c=1):
        pass

func(1,2,3)

# 使用参数名传递,更便于他人理解;对被调用的函数进行参数修改,不会影响到对该函数的调用
def func(a,b,d=0,c=1):
        pass
func(1,2,3)
func(1,2,c=3)
func(1,2,c=3,d=4)

8.使用/和*指定参数传递方式

# /和*可以单独使用,也可以混合使用
# /不占参数位,起分隔作用,左边只能以位置参数形式传递
def func(a,b,/,c,d):
    print(a,b,c,d)

func(a=1,b=2,c=3,d=4)  # TypeError: a() got some positional-only arguments passed as keyword arguments: 'a, b'
func(1,2,3,4)  # 1 2 3 4
func(1,2,c=3,d=4)      # 1 2 3 4

# *不占参数位,起分隔作用,*左边是位置参数,右边是关键字参数
def func1(a,b,*,x,y):
    print(a,b,x,y)

# func1(1,2,3,4)   # TypeError: func() takes 2 positional arguments but 4 were given
func1(1,2,x=3,y=4)       # 1 2 3 4

9.用None和docstring来描述默认值会变的参数

# 参数的默认值只会计算一次,函数加载时参数值就固定了
import time
def func(t=time.time()):
    print(t)

func()  # 1649778416.5511928
time.sleep(5)
func()  # 1649778416.5511928

def func(t=None):
	"""t传入None,将通过time.time()获取当前时间"""
    if t:
        print(t)
    else:
        t=time.time()
        print(t)

func()  # 1649778598.119803

10.使用cProfile和pstats进行性能分析

from cProfile import Profile
from pstats import Stats

def a():
    for i in range(3):
        time.sleep(1)

def b():
    for i in range(2):
        time.sleep(1)

def t():
    a()
    b()
    print(1,2,3)

profiler = Profile()
profiler.runcall(t)
stats = Stats(profiler)
stats.strip_dirs()
stats.sort_stats('cumulative')
stats.print_stats()

ncalls tottime percall cumtime percall filename:lineno(function)
1 0.000 0.000 5.031 5.031 Python_练习.py:1643(t)
5 5.031 1.006 5.031 1.006 {built-in method time.sleep}
1 0.000 0.000 3.007 3.007 Python_练习.py:1635(a)
1 0.000 0.000 2.024 2.024 Python_练习.py:1639(b)
1 0.000 0.000 0.000 0.000 {built-in method builtins.print}
1 0.000 0.000 0.000 0.000 {method ‘disable’ of ‘_lsprof.Profiler’ objects}

ncalls:函数在测评期间的调用次数
tottime:执行函数本身所花时间(不含调用其他函数时间)
tottimepercall:执行这个函数平均所花时间(不含调用其他函数时间)
cumtime:执行这个函数及它调用其他函数所花时间
cumtimepercal:执行这个函数它调用其他函数平均所花时间

二、python性能优化

参考资料:
微信公众号 - 菜鸟学python

1和2都是相同的原理:局部变量存放在栈区,全局变量和对象存放在堆区,从栈区查找数据的速度会更快

1.避免全局变量

a = 1
t1 = time.time()
for i in range(10000000):
    a + 1
t2 = time.time()
print(t2-t1)    # 0.6302330493927002

def func():
    a = 1   # 在函数内定义局部变量
    t1 = time.time()
    for i in range(10000000):
        a + 1
    t2 = time.time()
    print(t2 - t1)
func()  # 0.37824249267578125

2.避免使用.访问属性

对于频繁访问的属性,可通过赋值给一个局部变量以提高性能

class A():
    pass

def func1():
    a = []
    t1 = time.time()
    for i in range(1000000):
        a.append(A.__name__)
    t2 = time.time()
    print(t2-t1)

def func2():
    a = []
    b = A.__name__
    t1 = time.time()
    for i in range(1000000):
        a.append(b)
    t2 = time.time()
    print(t2-t1)

func1() # 0.0781545639038086
func2() # 0.062497854232788086

3.使用join替代+进行字符串拼接

因为str不可变,每次相加都会申请内存空间,n-1次
使用join会一次性的申请所需的内存空间

lis = [str(i) for i in range(100000)]

def func1():
    res = ''
    t1 = time.time()
    for s in lis:
        res += s
    t2 = time.time()
    print(t2-t1)

def func2():
    t1 = time.time()
    res = ''.join(lis)
    t2 = time.time()
    print(t2-t1)

func1() # 0.025266647338867188
func2() # 0.0009961128234863281

4.利用好if的短路特性

当多个or条件时:遇到为True的就不会再进行后面的判断,将为True概率大的表达式排在前面,能够有效提升性能
当多个and条件时:遇到为False的就不会再进行后面的判断,将为False概率大的表达式排在前面,能够有效提升性能

def s():
    time.sleep(3)
    return False

def func1():
    if 1==1 or s():
        return 0

def func2():
    if s() or 1==1:
        return 0
t1 = time.time()
func1()
t2 = time.time()
print(t2-t1)    # 0

t1 = time.time()
func2()
t2 = time.time()
print(t2-t1)    # 3

5.减少内层for循环的计算

def func1(x,y):
    for i in range(x):
        for j in range(y):
            res = (x**2) +(y**2)

# 优化
def func1(x,y):
    for i in range(x):
        tmp = x**2
        for j in range(y):
            res = tmp +(y**2)

6.数据复制

copy:浅复制,只复制第一层,下层还是指向原对象的下层,对下层的数据操作是在原对象上进行的,所以会对被复制的对象造成影响
deepcopy:深复制,完整的复制一个新对象

1)对于list和dict,优先使用list和dict类实现的copy/deepcopy方法,会比copy模块的快很多
2)copy比deepcopy耗时更少(性能相差几十倍),对于不是必须使用deepcopy的场景,优先使用copy

三、一些模块介绍

1.collections

这个模块实现了特定目标的容器,以提供Python标准内建容器 dict、list、set、tuple 的替代选择。

Counter:字典的子类,提供了可哈希对象的计数功能
defaultdict:字典的子类,提供了一个工厂函数,为字典查询提供了默认值
OrderedDict:字典的子类,保留了他们被添加的顺序(python 3.6之后dict是有序的
了)
namedtuple:创建命名元组子类的工厂函数
deque:类似列表容器,实现了在两端快速添加(append)和弹出(pop)
ChainMap:类似字典的容器类,将多个映射集合到一个视图里面

1)Counter

 #主要功能:可以支持方便、快速的计数,将元素数量统计,然后计数并返回一个字典,键为元素,值为元素个数。
_list = [1,2,3,4,1,12,3,4,23,4,123,12,'a','b','a']
counter = collections.Counter(_list)
print(counter)  # Counter({4: 3, 1: 2, 3: 2, 12: 2, 'a': 2, 2: 1, 23: 1, 123: 1, 'b': 1})
print(counter[1])   # 2
print(counter.values()) # dict_values([2, 1, 2, 3, 2, 1, 1, 2, 1])
print(counter.get(1))   # 2
print(counter.copy())   # Counter({4: 3, 1: 2, 3: 2, 12: 2, 'a': 2, 2: 1, 23: 1, 123: 1, 'b': 1})
print(counter.keys())   # dict_keys([1, 2, 3, 4, 12, 23, 123, 'a', 'b'])
print(counter.items())  # dict_items([(1, 2), (2, 1), (3, 2), (4, 3), (12, 2), (23, 1), (123, 1), ('a', 2), ('b', 1)])

# Counter().update(dict)和dict.update不同,Counter的不是替换键值,而是将对应的key的计数相加
counter.update({'a':2})
print(counter)  # Counter({'a': 4, 4: 3, 1: 2, 3: 2, 12: 2, 2: 1, 23: 1, 123: 1, 'b': 1})

# Counter().subtract(dict)是计数相减
counter.subtract({'a':1})
print(counter)  # Counter({4: 3, 'a': 3, 1: 2, 3: 2, 12: 2, 2: 1, 23: 1, 123: 1, 'b': 1})

# counter.clear()   # 清空对象
# print(counter)  # Counter()

print(counter.elements())   # <itertools.chain object at 0x0000020E5AEE5F60>
print(next(counter.elements())) # 1
print(list(counter.elements())) # [1, 1, 2, 3, 3, 4, 4, 4, 12, 12, 23, 123, 'a', 'a', 'a', 'a', 'b']
# print(counter.fromkeys([1,2,3]))    # NotImplementedError: Counter.fromkeys() is undefined. 静态方法,继承的时候自定义

# most_common(n=None),返回最常出现的n个元素和数量;如果n=None,返回所有的元素(按出现次数倒序,元素正序排序)
print(counter.most_common())    # [(4, 3), (1, 2), (3, 2), (12, 2), ('a', 2), (2, 1), (23, 1), (123, 1), ('b', 1)]
print(counter.most_common(2))   # [(4, 3), (1, 2)]

# Counter().pop(key) 移出某个元素的计数
counter.pop('a')
print(counter)  # Counter({4: 3, 1: 2, 3: 2, 12: 2, 2: 1, 23: 1, 123: 1, 'b': 1})

# Counter().popitem() 从末尾移出1个元素的计数
counter.popitem()
print(counter)  # Counter({4: 3, 1: 2, 3: 2, 12: 2, 2: 1, 23: 1, 123: 1})
counter.popitem()
print(counter)  # Counter({4: 3, 1: 2, 3: 2, 12: 2, 2: 1, 23: 1})

# Counter().setdefault(key,defaultValue),跟dict一样,如果key存在则返回key的值,否则设置key=defaultValue 并返回defaultValue
print(counter.setdefault('a',0))    # 2('a'存在,直接返回a的值)
print(counter)  # Counter({4: 3, 1: 2, 3: 2, 12: 2, 'a': 2, 2: 1, 23: 1, 123: 1, 'b': 1})
print(counter.setdefault('d',0))    # 0('d'不存在,设置d=0,并返回0)
print(counter)  # Counter({4: 3, 1: 2, 3: 2, 12: 2, 'a': 2, 2: 1, 23: 1, 123: 1, 'b': 1, 'd': 0})

_tuple = (1,2,3,4,1,12,3,4,23,4,123,12,'a','b','a')
counter = collections.Counter(_tuple)
print(counter)  # Counter({4: 3, 1: 2, 3: 2, 12: 2, 'a': 2, 2: 1, 23: 1, 123: 1, 'b': 1})
print(counter[1])   # 2

_dict = {'a':1,'b':2,'c':3}
counter = collections.Counter(_dict)  # 只是将dict转为Counter对象
print(counter)  # -> Counter({'c': 3, 'b': 2, 'a': 1})

2.typing-类型提示支持

python3.6加入的特性
不符合类型要求时,会有个提示,但不按类型提示输入也不会报错

from typing import List,Dict,Tuple,Callable,NewType,TypeVar,Generic



def greeting(name: str) -> str:
    return 'Hello ' + name

greeting(123)
greeting('123')


ConnectionOptions = Dict[str, str]  # str类型的key,str类型的value
Address = Tuple[str, int]   # 长度为2,值分别为str和int的tuple

Server = Tuple[Address, ConnectionOptions]
def func1(a:ConnectionOptions) -> str:
    pass
a = {'a':1}
func1(a)
b = {'b':'1'}
func1(b)

def func2(a:Address) -> str:
    pass
a = {'a':1}
func2(a)
b = [1,2,3]
func2(b)
c = (1,2,3)
func2(c)
d = (1,'2')
func2(d)
e = ('1',2)
func2(e)

Conn = List[str or int]
def func3(a:Conn) -> str:
    pass
a = [1,2,3]
func3(a)
b = ['s','d',1]
func3(b)


# typing.Callable
# 可调类型; Callable[[int], str] 是把(int)转为 str 的函数
# 预期特定签名回调函数的框架可以用 Callable[[Arg1Type, Arg2Type], ReturnType] 实现类型提示
def xx(a:str)->int:
    return 0
def yy(a:str)->str:
    return '1'
def zz(a:str,b:str)->int:
    return 0

def func(x:Callable[[str],int]) -> list:
    return []
func(xx)
func(yy)
func(zz)


T = TypeVar('T')  # Can be anything
S = TypeVar('S', bound=str)  # Can be any subtype of str
A = TypeVar('A', str, bytes)  # Must be exactly str or bytes

# 用户定义的类可以定义为泛型类。
from typing import TypeVar, Generic

T = TypeVar('T')
class LoggedVar(Generic[T]):
    def __init__(self, value: T, name: str) -> None:
        self.name = name
        self.value = value

    def set(self, new: T) -> None:
        self.value = new

    def get(self) -> T:
        return self.value



from typing import Any
def func(a:Any) -> str:
    try:
        return str(a)
    except:
        return 'error'


from typing import Optional
# typing.Optional可选类型。
# Optional[X] is equivalent to X | None (or Union[X, None]).
# 注意,可选类型与含默认值的可选参数不同。
# 含默认值的可选参数不需要在类型注解上添加 Optional 限定符,因为它仅是可选的。例如:
def foo(arg: int = 0) -> None:
    ...
# 另一方面,显式应用 None 值时,不管该参数是否可选, Optional 都适用。例如:
def foo(arg: Optional[int] = None) -> None:
    ...



# 3.10新功能
from typing import Type
a = 3
b = Type(a)
def func(a:str) -> b:
    return 0



from typing import AnyStr
def func(a:str) -> int:
    return 0
func('s')
func(u's')
func(b's')  #x
def func1(a:AnyStr) -> int:
    return 0
func1('s')
func1(u's')
func1(b's')

3.pydantic-数据类型验证

pydantic 可以在代码运行时强制执行类型提示

from pydantic import BaseModel
from enum import Enum
from typing import List

class SEX(Enum):
    male = '男'
    female = '女'


class User(BaseModel):
    userNm : str
    age : int
    sex : SEX
    hobbies : List[str] = []

    def getUserNm(self):
        return self.userNm

    def getAge(self):
        return self.age

    def getSex(self):
        return self.sex

    def getHobbies(self):
        return self.hobbies

zhangSan = {'userNm':'zhangSan',
            'age':18,
            'sex':'男',
            'hobbies':['游泳']}

user = User(**zhangSan)
# print(user.__fields_set__)  # __fields_set__,该变量返回用户初始化对象时提供了什么字段

# ----------------- BaseModels 属性/方法
# dict()返回模型字段和值,字典格式
print(user.dict())
# json()返回模型字段和值,json 字符串格式
print(user.json())
# schema()以 JSON Schema 形式返回模型,字典格式
print(user.schema())
# schema_json()以 JSON Schema 形式返回模型,json 字符串格式
print(user.schema_json())
# copy()浅拷贝模型对象

4.traceback-异常和栈轨迹

使用try except捕获到异常后,Excepption.args只能拿到顶层的错误信息,如果相要拿到完整的异常信息,可以使用此模块

记住traceback.print_exc()和traceback.format_exc()就够了

traceback.print_exc():异常信息重定向,file参数默认None 输出到控制台,也可传入一个打开的文件 将信息写入文件
traceback.format_exc():返回str,和print_exc()看到的信息一样

mport traceback
import sys

def add():
    return 1+None

def sub():
    return 1-None

try:
    add()
    sub()
except:
    with open('./trace.txt','w',encoding='utf8') as file:
        traceback.print_exc(limit=1,file=sys.stdout,chain=True)   # file参数:数据输出的位置,需要是一个打开的文件或者实现了write方法的object

    errInfo = traceback.format_exc(limit=1,chain=True)
    print(errInfo)

5.chardet-编码格式检查

import chardet
with open(file=filePath,mode='rb') as file:
    data = file.read()
    encodingInfo = chardet.detect(data)	# 对byte类型的数据进行检查
encoding = encodingInfo['encoding'] if encodingInfo['encoding'] == 'utf-8' else 'gbk'
data = str(data,encoding=encoding)	# 将byte转为str
  Python知识库 最新文章
Python中String模块
【Python】 14-CVS文件操作
python的panda库读写文件
使用Nordic的nrf52840实现蓝牙DFU过程
【Python学习记录】numpy数组用法整理
Python学习笔记
python字符串和列表
python如何从txt文件中解析出有效的数据
Python编程从入门到实践自学/3.1-3.2
python变量
上一篇文章      下一篇文章      查看所有文章
加:2022-04-18 17:36:49  更:2022-04-18 17:42:41 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/15 17:26:37-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码