提示:python提高代码运行效率的一些小技巧或者一些细节
文章目录
1、小技巧
1.1、内存分配问题
"""我用的是3.8版本的python,与视频中讲述所用python版本不一样"""
import sys
sys.getsizeof([0]*3) # 内存大小:80字节,慎用,一般可用于不可变对象
sys.getsizeof([0, 0, 0]) # 内存大小:80字节,与视频中讲的,具体跟python版本相关
sys.getsizeof([0 for _ in range(3)]) # 内存大小,一般开辟空间会大于实际所需要的数目,主要思想是使用空间换时间。
import dis
dis.dis()方法可以查看代码的字节码,我的理解是python代码运行后,会先将代码翻译成字节码再使用Python解释器对字节码进行运行得出代码运行结果
1.2、for与else组合
参考视频
除了if和else组合,python中for和else也可以进行组合。
# 当第二层循环没有提前退出,就会执行else里面内容;若提前退出,就不会执行else里面内容,直接执行else下面的break
if __name__ == "__main__":
for n in range(100):
for m in range(10):
print(n,m)
if n == m == 3:
break
else:
continue
break
1.3、深浅拷贝
from copy import deepcopy
if __name__ == "__main__":
a = [1, 2, 3, 4, 5]
""" a和b均会改变 """
b = a
b.append(999)
print(a, b)
a = [1, 2, 3, 4, 5]
"""只有在a中元素都为不可变对象时,这种才为可行的深拷贝, 否则就会出错"""
b = a[:] # 或者 b = a[::]或b = a.copy()
b.append(999)
print(a, b)
a = [[1, 2, 3], 4, 5]
"""若此时使用其他几种方式进行拷贝,则a和b会同时改变"""
b = deepcopy(a) # 最保险的深拷贝
b[0].append(999)
print(a, b)
2、Python函数注解
参考视频
注意:“注解”不是“注释”哦。函数注解能够让Python像Java、C语言等静态类型语言一样严格,起规范代码作用。
# 导入库typing
from typing import *
# a为列表,里面元素为int类型或者float类型
def fun1(a: List[int or float]):
pass
# a为元组,里面元素为int类型或者float类型
def fun2(a: Tuple[int]):
...
# a为字典,字典的键为字符串类型
def fun3(a: Dict[str]):
...
# a为集合,里面的元素为字符串
def fun4(a: Set[str]):
pass
# 自己定义一个类TreeNode
class TreeNode:
def __init__(self):
self.left = None
self.right = None
self.val = 1
# 函数参数a的类型为TreeNode对象
def fun1(a: TreeNode):
...
# 函数参数x为int类型,返回值类型为int类型
def fun1(x: int) -> int:
return x ** 2
# 函数fun2的参数为函数,其参数类型为int类型,返回值类型为int类型
def fun2(fun: Callable[[int], int]):
print(fun(1))
3、装饰器
3.1、global和nonlocal问题
3.2、函数装饰器
3.2.1、为何使用装饰器
网上找的一段解释,如下:
\hspace*{0.6cm}
软件的设计应该遵循开放封闭原则,即对扩展是开放的,而对修改是封闭的。对扩展开放,意味着有新的需求或变化时,可以对现有代码进行扩展,以适应新的情况。对修改封闭,意味着对象一旦设计完成,就可以独立完成其工作,而不要对其进行修改。
\hspace*{0.6cm}
软件包含的所有功能的源代码以及调用方式,都应该避免修改,否则一旦改错,则极有可能产生连锁反应,最终导致程序崩溃,而对于上线后的软件,新需求或者变化又层出不穷,我们必须为程序提供扩展的可能性,这就用到了装饰器。
总结如下:
1、微程序扩展提供可能性;
2、开放封闭原则。禁止修改原代码,但是可以增加新功能;也不能修改调用方式
3.2.2、什么时候使用装饰器
装饰器最大的优势是用于解决重复性的操作,其主要使用场景有如下所列:
- 计算函数运行时间
- 给函数打日志
- 类型检查
如果遇到其他重复操作的场景也可以类比使用装饰器
3.2.3、使用举例
如统计多个函数运行时间,不使用装饰器的程序如下:
import time
def fun1():
time.sleep(0.5)
print("fun1 is running!")
def fun2():
time.sleep(1)
print("fun2 is running!")
def fun3():
time.sleep(1.5)
print("fun3 is running!")
if __name__ == "__main__":
t1_start = time.time()
fun1()
t1_end = time.time()
print(f"The running time of fun1 is {t1_end - t1_start}")
t2_start = time.time()
fun2()
t2_end = time.time()
print(f"The running time of fun2 is {t2_end - t2_start}")
t3_start = time.time()
fun3()
t3_end = time.time()
print(f"The running time of fun3 is {t3_end - t3_start}")
若使用装饰器程序如下:
import time
from functools import wraps
def timer(func):
"""
这里使用wraps装饰器,加或者不加,大家可以看下输出异同(fun2和fun3同理),前面加@wraps(func)保留了函数的源信息
print(fun1.__name__)
"""
@wraps(func)
def wrapper(*args, **kwargs):
start_time = time.time()
res = func(*args, **kwargs)
stop_time = time.time()
print(f'{func.__name__} time is {stop_time-start_time}')
return res
return wrapper
"""
这里加的是没有参数的装饰器,等价执行内容如下:
fun1 = timer(fun1)
以C语言角度看,在加了装饰器之后,fun1现在内容并不是原本fun1函数的地址,存储是返回的wrapper函数地址
"""
@timer
def fun1():
time.sleep(0.5)
print("fun1 is running!")
@timer
def fun2():
time.sleep(1)
print("fun2 is running!")
@timer
def fun3():
time.sleep(1.5)
print("fun3 is running!")
if __name__ == "__main__":
fun1()
fun2()
fun3()
还可以使用带参数的装饰器,程序如下:
import time
from functools import wraps
"""
使用 @wraps 装饰器后,fun1、fun2、fun3 函数的元信息得到了保留,包括函数名和文档字符串。
即输出fun1.__name__,fun2.__name__, fun3.__name__保持不变,若不用@wraps装饰后,则经过其他装饰器装饰后,
fun1.__name__、fun2.__name__、fun3.__name__输出与其原本输出不一样了。
"""
def decorate(choice="fun"):
if(choice == "fun"):
def timer(func):
@wraps(func)
def wrapper(*args, **kwargs):
start_time = time.time()
res = func(*args, **kwargs)
stop_time = time.time()
print(f'{func.__name__} time is {stop_time - start_time}')
return res
return wrapper
else:
def timer(func):
@wraps(func)
def wrapper(*args, **kwargs):
print("None fun is running!")
return wrapper
return timer
"""
这里加的是有参数的装饰器,等价执行内容如下:
fun1 = decorate(choice="None")(fun1)
以C语言角度看,在加了装饰器之后,fun1现在内容并不是原本fun1函数的地址,存储是返回的wrapper函数地址
"""
@decorate(choice="None")
def fun1():
time.sleep(0.5)
print("fun1 is running!")
@decorate()
def fun2():
time.sleep(1)
print("fun2 is running!")
@decorate(choice="str")
def fun3():
time.sleep(1.5)
print("fun3 is running!")
if __name__ == "__main__":
fun1()
fun2()
fun3()
接下来看一个日志打印问题,代码如下
def wrapper_property(obj, func=None):
if func is None:
return partial(wrapper_property, obj)
# 为对象obj添加属性func.__name__,其值为func
setattr(obj, func.__name__, func)
return func
def logger_info(level, name=None, message=None):
def decorate(func):
logmsg = message if message else func.__name__
def wrapper(*args, **kwargs):
logging.log(level, logmsg)
return func(*args, **kwargs)
"""
其等价执行内容如下:
set_level = wrapper_property(wrapper)(set_level)
"""
@wrapper_property(wrapper)
def set_level(newlevel):
nonlocal level
level = newlevel
@wrapper_property(wrapper)
def set_message(newmsg):
nonlocal logmsg
logmsg = newmsg
return wrapper
return decorate
@logger_info(logging.WARNING)
def main(x, y):
return x + y
if __name__ == "__main__":
print(main(0, 1))
main.set_message("Hello word!") # 改变输出的消息
print(main(0, 2))
main.set_level(logging.ERROR) # 改变输出等级
print(main(0, 3))
这里面最重要的是wrapper_property这个函数,它的功能是把一个函数func编程一个对象obj的属性,然后通过调用wrapper_property,给装饰器添加了两个属性set_message和set_level,分别用于改变输出日志的内容和改变输出日志的等级。
这里要注意一下partial,参考文章1和参考文章2
def add1(x, y=None):
print(x, y)
if __name__ == "__main__":
# 可行
f1 = partial(add1, 1)
f1(23)
# 不可行。。。。会报错,这两点在参考文章2里有示意参数
f1 = partial(add1, x=1)
f1(23) # 这一步报错。只能改成f1(x=23, y=1)或者f1(y=1)才行
3.3、类装饰器
参考博客
参考视频1、参考视频2、参考视频3
整体与函数装饰器很像,可参考这篇博客。
3.3.1、@classmethod
from random import choice
class Person:
_FirstName = ["岳", "王", "李", "张", "赵"] # 类属性
_LastName = ["三", "建国", "宇航", "超", "子航", "鹏"] # 类属性
def __init__(self, name):
self.name = name
def say(self):
print(f"大家好我叫{self.name}")
@classmethod
def create_man(cls):
return cls(choice(cls._FirstName) + choice(cls._LastName))
def __str__(self):
return f"str:【{self.name}】"
def __repr__(self):
return f"{self.__class__.__name__}(\"{self.name}\")"
if __name__ == "__main__":
p = Person.create_man()
print(p) # 由__str__方法实现
print([Person.create_man() for _ in range(10)]) # 由魔方方法__repr__实现
3.3.2、@staticmethod
该装饰器一般用于当方法不用调用类内属性或者方法时使用
参考视频link
参考程序
class MyPrinter:
def __init__(self):
pass
@staticmethod
def print_xxx():
print("my name is print_xxx")
def print_obj(self):
print(self.__class__.__name__)
if __name__ == "__main__":
MyPrinter().print_xxx() # 可以调用,不会报错
MyPrinter().print_obj() # 可以调用, 不会报错
MyPrinter.print_xxx() # 可以调用,不会报错
MyPrinter.print_obj() # 报错。。。。。。。。。。
4、极速递归
from functools import lru_cache
# 该装饰器用于加快递归速度
@lru_cache(None)
def f(n):
if n == 1 or n == 2:
return 1
else:
return f(n-1) + f(n-2)
if __name__ == "__main__":
print(f(100))
5、collections库
5.1、Couter
Couter常用于列表、字符串、元组等可迭代对象中每个元素出现的次数,并返回一个字典,并按元素出现次数从大到小排列。示例程序如下:
from collections import Counter
if __name__ == "__main__":
a = Counter([1, 2, 34, 34, 34, 1, 2, 2, 2, 2])
print(a)
输出:
Counter({2: 5, 34: 3, 1: 2})
5.2、deque
可当做队列使用
from collections import deque
if __name__ == "__main__":
que = deque([1, 2, 3])
print(que)
que.append(4) # 在队列尾添加一个元素
print(que)
que.pop() # 从队列尾弹出元素
print(que)
que.appendleft(5) # 从队列首添加元素
print(que)
que.popleft() # 从队列首弹出元素
print(que)
应用示范:
class TreeNode:
def __init__(self, val):
self.val = val
self.left = None
self.right = None
root = TreeNode(1)
n1 = TreeNode(2)
n2 = TreeNode(3)
n3 = TreeNode(4)
n4 = TreeNode(5)
root.left = n1
root.right = n2
n1.left = n3
n1.right = n4
def browse(node):
if node is None:
return
# 前序遍历
print(node.val)
browse(node.left)
browse(node.right)
# 中序遍历
browse(node.left)
print(node.val)
browse(node.right)
# 后续遍历
browse(node.left)
browse(node.right)
print(node.val)
# 层序遍历
def level_traversal(root):
que = deque([root])
while len(que) > 0:
node = que.popleft()
if node is None:
continue
else:
print(node.val)
que.append(node.left)
que.append(node.right)
if __name__ == "__main__":
level_traversal(root)
# browse(root)
6、python魔法方法小结
6.1、 __str__
返回对象描写:
class My:
def __init__(self):
pass
def __str__(self):
return "aaa"
if __name__ == "__main__":
a = My()
print(str(a)) # 使用str转换后,输出aaa
6.2、 __getitem__
6.3、__repr__
参考博客link
6.4、__class__和__name__
参考视频link
6.5、__dir__和__dict__
参考视频link
from random import choice
"""
python运算符重载:
https://www.nhooo.com/note/qa0xld.html
"""
class Person:
_FirstName = ["岳", "王", "李", "张", "赵"] # 类属性
_LastName = ["三", "建国", "宇航", "超", "子航", "鹏"] # 类属性
def __init__(self, name, name2):
self.name = name + name2
def say(self):
print(f"大家好我叫{self.name}")
@classmethod
def create_man(cls):
return cls(choice(cls._FirstName) + choice(cls._LastName), "12")
def __str__(self):
return f"str:【{self.name}】"
def __repr__(self):
return f"{self.__class__.__name__}(\"{self.name}\")"
if __name__ == "__main__":
p = Person.create_man()
print(p.__dir__()) # 输出实例p的属性、方法等
print(dir(p)) # 可以直接使用dir函数返回实例p的属性、方法等
'''
getattr输出属性的值或者类属方法的地址
hasattr检查类是否有某属性
'''
for item in dir(p):
print(item, "\t\t\t", getattr(p, item))
print(hasattr(p, "create_man")) # 检查实例p是否有"create_man"属性
print(p.__dict__) # 只会输出属于实例p的属性(以字典形式输出),不会输出属于类的属性,以及实例方法
print(Person.__dict__) # 都会输出,使用print打印在终端显示的时候跟字典很像,但其实不是字典,是一种映射,不可修改
6.6、__slots__
用于对类做出限制,防止动态添加属性等
参考视频link
参考博客link
7、高阶函数
7.1、reversed函数
用于反序操作
if __name__ == "__main__":
a = [1, 2, 3]
for ele in reversed(a):
print(ele)
print(list(reversed(a))) # 不会改变a本身
print(a)
a.reverse()
print(a) # 改变a本身
7.2、sorted函数
"""多条件判断"""
def cmp(a, b):
# return 1 表示 a 应该比 b 大
# return -1 表示 b 比 a 大
# return 0 表示 一样大
if a[0] > b[0]:
return 1
elif a[0] < b[0]:
return -1
else:
if a[1] > b[1]:
return 1
elif a[1] < b[1]:
return -1
else:
return 0
if __name__ == "__main__":
a = [(randint(1, 100), randint(1, 100)) for _ in range(50)]
print(a)
"""使用sorted判断"""
# b = sorted(a, reverse=False, key=lambda x: x[0]) # a中每个元素为元组,按照a中每个元素的第一个值进行从小到大排列,不改变自身
# print(b)
# print(a)
"""使用自带的sort判断,进行多条件判断, 多条件判断必须使用cmp_to_key进行包装在传递给key参数"""
a.sort(reverse=False, key=cmp_to_key(cmp)) # a中每个元素为元组,按照a中每个元素的第一个值进行从小到大排列,改变自身
print(a)
8、列举所有组合
from itertools import combinatio
if __name__ == "__main__":
all_element = ["星期一", "星期二", "星期三", "星期四"]
for ele in combinations(all_element, 2): # 返回all_element中元素的两两组合
print(ele)
9、any和all方法
"""any相当于 或操作, all相当于 与操作"""
any([True, False, False]) # 输出Ture
all([False, True, True]) # 输出False
list1 = [11, 22, 33, 44]
any(ele == 44 for ele in list1) # 输出True
all(ele == 44 for ele in list1) # 输出False
10、python高精度运算
import decimal
from decimal import Decimal
import fractions
if __name__ == "__main__":
decimal.getcontext().prec = 10 # 设置小数精度,保留10位小数
print(Decimal(3)/Decimal(7))
a = fractions.Fraction(1, 5) # 1/5
print(a)
print(a + fractions.Fraction(3, 4)) # 3/4+1/5
11、降低数据可变化,提高可读性
参考视频link
from types import MappingProxyType
if __name__ == "__main__":
temp = {1, 2, 3, 4} # 集合是可对数据进行增删改查的
print(temp)
temp.add(5)
print(temp)
temp1 = frozenset(temp) # 使用该函数进行冻结
temp1.add(2) # 报错.......
dic = {"1": 1, "2": 2, "3": 3}
print(dic)
dic["4"] = 4
print(dic)
dic1 = MappingProxyType(dic) # 使用该函数进行冻结
print(dic1)
dic1["5"] = 5 # 报错.......
12、枚举 enum
示例:
from enum import Enum, unique
@unique # 该装饰器是当枚举中有相同的值时,程序运行会报错
class Block(Enum):
AIR = 0
STONE = 1
GRASS = 2
DIRT = 3
if __name__ == "__main__":
print("Block.AIR.value = ", Block.AIR.value) # 访问枚举成员变量的值
print("\"Block(1)\" is ", Block(1)) # 通过值访问枚举成员对象
for k, v in Block.__members__.items():
print(k, "\t", v, "\t", v.value)
终端输出:
示例:
import enum
from typing import Set
class AutoName(enum.Enum):
@classmethod
def values(cls) -> Set[str]:
"""返回枚举成员变量的值"""
return {member.value for _, member in cls.__members__.items()}
def _generate_next_value_(self, start, count, last_values) -> str:
"""重写该方法,可以定义枚举成员的值"""
del start, count, last_values
return self.lower() # 返回小写
class link(AutoName):
BITCOIN = enum.auto() # 其值为"bitcoin",下同
EOS = enum.auto()
ETHEREUM = enum.auto()
HYPERLEDGER = enum.auto()
IOTA = enum.auto()
MULTICHAIN = enum.auto()
STELLAR = enum.auto()
if __name__ == "__main__":
for k, v in link.__members__.items():
print(v.value)
终端输出如下
13、datetime库
import datetime
now_time = datetime.datetime.now() # 获取当前时间
print("当前时间: ", now_time)
'''
可选参数
datetime.timedelta(days=0, seconds=0, microseconds=0, milliseconds=0, minutes=0, hours=0, weeks=0)
'''
print("一小时后时间: ", (now_time+datetime.timedelta(hours=+1)).strftime("%Y-%m-%d %H:%M:%S")) # 获取后一小时
"""看官方手册写的一个用于时间的一个类,以UTC(世界统一时间)为基准进行统一加减,北京时间要在此基础上加8"""
class TzInfo(datetime.tzinfo):
def __init__(self, delay: int): # delay位于-24到+24之间
super(TzInfo, self).__init__()
self.delay = delay
def utcoffset(self, dt):
return datetime.timedelta(hours=self.delay)
def dst(self, dt):
return datetime.timedelta(0)
print("当前时间(另一种写法):", datetime.datetime.now(tz=TzInfo(8)).strftime("%H-%M"))
最后输出如下: