Python使用小技巧总结

提示: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、函数装饰器

参考视频1参考视频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

对该装饰器的解释可参考该博客link
参考视频link

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函数

参考视频
python运算符重载博客

"""多条件判断"""
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"))

最后输出如下:
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值